Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

[Script] Music Control for Tokens

1454626442

Edited 1457918532
Matt
Pro
This script allows me to create macros or place a token at specific points on a map that allow me to trigger a sound effect or start a background track or playlist without having to navigate to or scroll through the jukebox. It allows me to distinguish between a sound effect that should layer with the other sounds, and a background track that should stop other background tracks when played. For example, let's assume I have three sounds named "amb-combat", "amb-noncombat", and "effect-rain". The track "amb-noncombat" is started on a loop. Later, it begins to rain so I start the track "effect-rain" on a loop as well. Both the rain and the noncombat tracks play simultaneously. When combat starts, the track "amb-combat" is looped as unique. Because the track is flagged as unique, any other track with the same prefix (in this case "amb") will be stopped; all other tracks are unaffected. So when track "amb-combat" is started, the track "amb-noncombat" is stopped, and the track "effect-rain" continues to play. When combat is over, the track "amb-noncombat" is looped as unique, which again stops all other tracks with the same prefix ("amb"). I can also associate the commands with tokens, and use the token attributes as the parameters (see below). I can now place tokens on my map at specific points of interest that allow me to quickly play a track or change the mood. You can start and stop a playlist with this script; however, the playlist will adhere to the volume and repeat settings as they are set within the jukebox. Command !sfx Parameters Parameters are defined as a keyword and a value separated by a colon. The order of parameters is not important. Parameters are not case sensitive, with the exception of the track name. action - (play, loop, stop) defines whether to play a track, loop a track, or stop a track from playing. unique - (true, false) defines if other tracks currently playing that have a matching prefix should be stopped. song - (text, case-sensitive) the track name exactly as it appears in the Jukebox list - (text, case-sensitive) the playlist name exactly as it appears in the Jukebox volume - (integer 0-100) the volume of the track You cannot use the song  and list  parameter in the same command. Examples !sfx action:loop unique:true song:Combat Music volume:30 !sfx volume:75 song:Screaming action:play !sfx song:Fast Heartbeat action:stop  For playlists, only the action and list parameters are used. !sfx list:Tavern Scene action:play !sfx action:stop list:Dungeon Crawl Character Sheet Abilities I created a new character entry, and use the following two ability macros. In my setup, the name of the token is the name of the track. "Bar1" is the action parameter and is set to either "play' or 'loop'. "Bar2" is the unique parameter, and is set to either "true" or "false".  "Bar3" is the volume the track should play. PLAY: !sfx song:@{selected|token_name} action:@{selected|bar1} unique:@{selected|bar2} volume:@{selected|bar3} STOP: !sfx song:@{selected|token_name} action:stop Customize There are a few lines near the top of the code that can be easily changed. SfxCtrl.Command - this is the command that calls the script, and should be listed in code as all caps. SfxCtrl.OptionDelimiter - this is a single character that separates the parameter keys from their values. SfxCtrl.PrefixDelimiter - this is a single character that separates a track prefix from the rest of the track name. SfxCtrl.SendAs - the name displayed when the script whispers a response. SfxCtrl.VolumeBar - the field to monitor for dynamic volume changes. SfxCtrl.AllowPlayerControl - this determines if players can give commands Commands as Players When AllowPlayerControl is set to true, non-GM players can use the !SFX command.  This will allow your players to use the !SFX command within their own macros.  However, there are some limitations.  When the !SFX command is given by anyone other than the GM, that player: cannot start or stop a play list.   cannot loop a track; any command of action:loop given by a player will be interpreted as action:play instead.   cannot stop another track that was started by another player. Code state.SfxControl = state.SfxControl || {}; var SfxCtrl = {}; //customize options SfxCtrl.Command            = '!SFX'; SfxCtrl.OptionDelimiter    = ':'; SfxCtrl.PrefixDelimiter    = '-'; SfxCtrl.SendAs             = 'SFX'; SfxCtrl.VolumeBar          = 'bar3_value'; SfxCtrl.AllowPlayerControl = true; //do not edit below this line on('chat:message', function(msg) {          //validate command     var param = msg.content.split(' ');     var isGM = playerIsGM(msg.playerid);     if(msg.type != 'api' || param[0].toLowerCase() != SfxCtrl.Command.toLowerCase() || (!isGM  && !SfxCtrl.AllowPlayerControl)) return; var replyTo  = '/w "' + msg.who + '" ';           //can't have song: and list: parameters in the same command     var ucaseContent = msg.content.toUpperCase();     if(ucaseContent.indexOf(' SONG:') !== -1 && ucaseContent.indexOf(' LIST:') !== -1) {         sendChat(SfxCtrl.SendAs, replyTo + 'Error:  SONG and LIST are exclusive options.');         return;     }          //set default options     var play     = true;     var unique   = false;     var loop     = false;     var prefix   = '';     var song     = '';     var list     = '';     var lasttype = '';     var vol      = null;       //define lookup tables     function ReadParam(key) {         params = {             'ACTION': function ReadAction(key) {                 actions = {                     'PLAY': function() { play=true; loop=false; },                     'LOOP': function() { play=true; loop=true; },                     'STOP': function() { play=false; loop=false; },                     'DEFAULT': function() { errmsg = 'Unknown action: ' + key; },                 };                 (actions[key.toUpperCase()] || actions['DEFAULT'])();             },                    'UNIQUE': function(val) { unique = (val.toLowerCase() === 'true'); },             'SONG': function(val) { song = val; lasttype = 'SONG'; },             'LIST': function(val) { list = val; lasttype = 'LIST'; },             'VOLUME': function(val) { if (!isNaN(val)) { vol = parseInt(Math.max(0,Math.min(100,val))); } },             'DEFAULT': function() { errmsg = 'Unknown parameter: ' + key; },         };         return (params[key.toUpperCase()] || params['DEFAULT']);     }          //loop through each parameter     for (i=1; i<param.length; i++) {         var errmsg;         //split each parameter by the defined delimiter         var opt = param[i].split(SfxCtrl.OptionDelimiter);         switch (opt.length) {             case 1:                 //if no delimiter was found, append the parameter to the song or list title                  if (lasttype === 'LIST') {                     list += ' ' + opt[0];                    } else {                      song += ' ' + opt[0];                 }                 break;             case 2:                 //if one delimiter was found, filter through lookup tables                 ReadParam(opt[0])(opt[1]);                 break;             default:                 //if more than one delimiter was found, set the error message                 errmsg = 'Too many delimiters in parameter: ' + param[i];                 break;         }         //if an error msg was set, whisper and return         if (errmsg) {             sendChat(SfxCtrl.SendAs, replyTo + errmsg);             return;         }     }          //switch based on the the title entered     switch(lasttype) {         case 'LIST':             if (!isGM) return;             var ListID;             var folders = JSON.parse(Campaign().get('_jukeboxfolder'));             for (p in folders) {                 if(folders[p]['n'] && folders[p]['n'] === list) { ListID = folders[p]['id']; }             }             if (ListID) {                 if (play) { playJukeboxPlaylist(ListID); } else { stopJukeboxPlaylist(); }             } else {                 sendChat(SfxCtrl.SendAs, replyTo + 'Playlist Not Found: ' + list);             }             return;                     break;              default:             if (song == '' || !findObjs({ _type: 'jukeboxtrack', title: song })[0]) {                 sendChat(SfxCtrl.SendAs, replyTo + 'Song Not Found: ' + song);                  return;             };             //create list of all campaign tracks             var allsongs = findObjs({                 _type: 'jukeboxtrack',             });             //if unique, get the song prefix by delimiter as defined             if (unique) {                  prefix = song.split(SfxCtrl.PrefixDelimiter);                  if (prefix.length == 1) {                     prefix = '';                 } else {                     prefix = prefix[0];                 }             }             //loop through each track in campaign jukebox                          _.each(allsongs, function(track) {                                  //get the ID of the last person who played the song, if it's still playing                 var isPlaying = track.get('playing') && (!track.get('softstop') || track.get('loop'));                 var playedbyid = isPlaying ? (state.SfxControl[track.get('_id')] || '') : '';                                  //turn off an active track if unique and the prefix matches                 if (unique && play && track.get('title') != song && track.get('title').split(SfxCtrl.PrefixDelimiter)[0] == prefix && track.get('playing') === true && (isGM || playedbyid === msg.playerid )) {                     track.set({                         playing: false,                         softstop: false,                     });                     delete state.SfxControl[track.get('_id')];                 }                 //if the song titles match, apply the settings                 if (track.get('title') == song) {                     if (isGM || !isPlaying || (isPlaying && playedbyid === msg.playerid)) {                         track.set({                         'playing'    : play,                         'softstop'   : false,                         'loop'       : (isGM) ? loop : false,                         'volume'     : !isNaN(vol) ? vol : track.get('volume'),                         });                                                  if (play) {                             state.SfxControl[track.get('_id')] = msg.playerid;                         } else {                             delete state.SfxControl[track.get('_id')];                         }                     }                 }             });             break;     } }); on('change:graphic', function(obj, old) {     if(obj.get(SfxCtrl.VolumeBar) !== old[SfxCtrl.VolumeBar]  && !isNaN(obj.get(SfxCtrl.VolumeBar))) {         var song = findObjs({             _type: 'jukeboxtrack',             title: obj.get('name'),             playing: true,         })[0];         if (song) {             song.set({                 volume: parseInt(Math.min(Math.max(0,obj.get(SfxCtrl.VolumeBar)),100)),             });         }     } }); on('ready', function() {     log('Script loaded: Music Control for Tokens'); });
1454667211
Ziechael
Forum Champion
Sheet Author
API Scripter
Nice work Matt, I use a very basic script to play sounds currently, such as a 'creak' when a door (using your doors script of course :) ) is opened but the extra functions found here are already giving me ideas of other potential sound based fun i can have with my players :) Now to get to work on building a 'soundboard' token of sound effects for quick access on the table at all times!!!
1454734668
Alex C.
Plus
Marketplace Creator
Thanks for this! I've been waiting for an easy-to-use macro-enabled sound fx script.
Do you think this could be modified to play a folder/playlist of tracks? For example: !sfx playlist:Combat Music action:loop
Trigshot said: Do you think this could be modified to play a folder/playlist of tracks? For example: !sfx playlist:Combat Music action:loop This would be amazing.  I've tried figuring out how to call other music sheets, but without the tokens, it can't get the bars.
1454901173

Edited 1455066178
Matt
Pro
I'm not entirely certain that's possible.  Someone better than I might be able to correct me here, but I've looked at the Campaign() for "_jukeboxfolder" and the properties do not show a "playing"-type property as do the individual tracks. Output of a Jukebox Folder with a single Track {"i":["-K9hk63jjXgQgpbXxHP7"],"n":"Test Folder","id":"-K9ye7WdrldfQUjnuGGU","s":"a"} If I find an option (or someone points it out to me) to allow playlists in the future, I will include in this script. Thanks. Update :  I've found the commands for the jukebox.  I'll add those in shortly.
1455114931
Lucian
Pro
API Scripter
Hey this is a great script - thanks! Here's a suggestion for a possible enhancement. I've done this very crudely, but perhaps you could work it in a bit more robustly to the main script: on('change:token:bar3_value', function(obj) {     var represents = obj.get('represents');     if (represents == '') return;     var character = getObj("character", represents);     if (!character) return;          if (character.get('name') == 'SoundController') {         var song = findObjs({             _type: 'jukeboxtrack',             title: obj.get('name'),             playing: true,         });                  if (song && song.length == 1) {             log(song[0]);             song[0].set('volume', obj.get('bar3_value'));         }     } }); All this does is to watch for changes to bar3 on any tokens that represent the "SoundController" character, and then update the relevant track if it's playing. Seems to work ok and makes in-game volume-adjustments even easier! Cheers, Lucian
Lucian - Thanks for the suggestions, I've added that functionality.  There is a new configurable option in the script. SfxCtrl.VolumeBar - this identifies which field you want to monitor for your volume.   When that field is changed, if the track is playing, the volume will be adjusted accordingly.
1455156088

Edited 1455158716
This addition is great. I have one minor house keeping request and one visual request. Is it possible to add something that let's us know if the script is running in API Output Console? Is it possible to add something that will change the aura or tint color of the token as a visual cue of what SoundController is being played? or   3. Does anyone know of a simple macro that I can tack onto the play and stop macros to change the aura or tint color? Tint would be the best, but a low radius (0.2) would be good too. [Edit] Nevermind about 2 & 3.  I found  The Aaron's script .
Added the ability to start and stop PlayLists with the list  parameter.  Playlists will follow the volume and repeat action set in the jukebox. Also added a simple 'on ready' statement to log the script is running.
Where is the updated code?
1455295347

Edited 1455295424
Matt
Pro
The  first post contains the updated script. 
Matt said: The  first post contains the updated script.  I see, you are updating it here, thanks I will plug the latest code in, working well so far.putting a token in the neighborhood of where the sound should occur is a great boon, keeps me in focus. Thanks.
Hi! I can't seem to get the script to work, whenever I enable it I get the error: "SfxCtrl is not defined". Any idea on what I'm doing wrong?
Hey Peter.  Check the first line of the code for the following declaration.  Without it, I can generate the same error. var SfxCtrl = {}; If this is line is present and doesn't fix the error you're having, let me know.
PM sent. Thanks Matt. :) It works fine.
Now THIS is what I am talking about when I ask if there will be a macro button option for sounds. &nbsp; If it weren't for the fact that I know dammit about programming (and of course that I am broke) this would have been the tipping point for me to buy a Pro subscription. &nbsp;The Idea of tokens so you can have specific environmental sounds per map is inspired. &nbsp; Thanks for the great work Matt. &nbsp; Please allow me to add what little I can to the effort. &nbsp;My Soundcloud selection of music, ambient sounds, and special effects. I have also included the folks who put up these sounds in case you want to see what else they might have that you would like to use. &nbsp;&nbsp; <a href="https://docs.google.com/document/d/1wSbob-Z3dZWImd" rel="nofollow">https://docs.google.com/document/d/1wSbob-Z3dZWImd</a>...
1455803776
Ziechael
Forum Champion
Sheet Author
API Scripter
Razir said: Please allow me to add what little I can to the effort. &nbsp;My Soundcloud selection of music, ambient sounds, and special effects. I have also included the folks who put up these sounds in case you want to see what else they might have that you would like to use. &nbsp;&nbsp; <a href="https://docs.google.com/document/d/1wSbob-Z3dZWImd" rel="nofollow">https://docs.google.com/document/d/1wSbob-Z3dZWImd</a>... Great list and great supplement to Matt's script, thanks :)
Excellent list Razir. Thanks a lot.
I second that, Razir. &nbsp;Thank you!
This is an amazing script.&nbsp; Really cool.
While fooling around with this script, I discovered that adding the line below to any other macro, you can put sounds into normal player actions, like shooting a gun etc. So now when my players click on the macro to shoot someone, they will also get a sound. I even made a grenade macro, second below, that drop the graphics effect and the sound effect from one macro !sfx song:GUNSHOT action:play unique:true volume:20 !sfx song:Grenade action:play unique:true volume:50 [[2D8]] **Damage to all within blast area of 5 meters** /fx fireball
al e. said: While fooling around with this script, I discovered that adding the line below to any other macro, you can put sounds into normal player actions, like shooting a gun etc. So now when my players click on the macro to shoot someone, they will also get a sound. I even made a grenade macro, second below, that drop the graphics effect and the sound effect from one macro !sfx song:GUNSHOT action:play unique:true volume:20 !sfx song:Grenade action:play unique:true volume:50 [[2D8]] **Damage to all within blast area of 5 meters** /fx fireball Hot Damn!
That's awesome! &nbsp;The sound effects play for me in a macro, but not my players. &nbsp;Can anyone shed some light?
1456347548
The Aaron
Pro
API Scripter
In the script, it checks if the player executing the command is a GM: !playerIsGM(msg.playerid) and doesn't execute if that is the case.
I can add in an option to allow players to call certain functions.&nbsp;
I would like to strip the GM requirement completely from the script, I would love to have player actions create sound events, especially for my Scifi game. Gunshots and explosions echoing through the abandoned spaceport... I have found that players can be trusted with these things, perhaps I just have good players.
I have updated the script to include the following config option: SfxCtrl.AllowPlayerControl = true; When this is set to true, your players should be able to use the !SFX command within their macros. &nbsp;However, there are some limitations. When the !SFX command is given by anyone other than a GM, that player: cannot start or stop a play list. cannot loop a track; any command of action:loop given by a player will be interpreted as action:play instead. cannot stop or alter another track that was started by another player. GMs continue to retain control over tracks and playlists, regardless who initiated the command. Thoughts and feedback...?
Thanks Matt, that is perfect. Your Door script and this are great!
1456449007

Edited 1456450476
Alex C.
Plus
Marketplace Creator
I've thanked you once, but I'll do it again - THANK YOU. I'm getting a lot of use out of this script. And now for a question! Is it possible for a script like this to be triggered by a token gaining initiative in the turn tracker ie. a specified sound is automatically played when the arrow is clicked and the tracker moves to the next token in the initiative order? /edit I guess an alternate way would be to have a macro set up to advance the turn tracker and play a sound using this script. Is there a way using API to do that? I saw somewhere that it's not possible with a macro alone. / editedit My guess is that it can. TrackerJacker can advance the turn order with the !eot command, which then sends a customized powercard to the chat window which is determined by whatever token is next in the initiative order. I would think that an !sfx command could be sent along with it?
Matt said: Character Sheet Abilities I created a new character entry, and use the following two ability macros. In my setup, the name of the token is the name of the track. "Bar1" is the action parameter and is set to either "play' or 'loop'. "Bar2" is the unique parameter, and is set to either "true" or "false". &nbsp;"Bar3" is the volume the track should play. PLAY: !sfx song:@{selected|token_name} action:@{selected|bar1} unique:@{selected|bar2} volume:@{selected|bar3} STOP: !sfx song:@{selected|token_name} action:stop I tried this but for some reason the Play macro doesn't work. If I type it into chat manually, it works fine, but not when I click on the Play token action. Any ideas?
Arthur B. said: I tried this but for some reason the Play macro doesn't work. If I type it into chat manually, it works fine, but not when I click on the Play token action. Any ideas? I've tried replicating this, but every situation I've tried generates an error in the chat window. &nbsp;If you're not receiving an error, then I honestly do not know. &nbsp;However, if you want to PM me a link to your game, I'll be happy to hop over and take a look.
Matt, is there a way to make the traps from Door Control execute something? I would love to have an /fx and the music control trigger from the traps. A loud boom and a flash of fire, followed by a damage roll would really make the rogues move up in status.
al e. said: Matt, is there a way to make the traps from Door Control execute something? I would love to have an /fx and the music control trigger from the traps. A loud boom and a flash of fire, followed by a damage roll would really make the rogues move up in status. I am actually rewriting the door script currently. &nbsp;It should include its own sound control as well as support for fx's on triggered traps.
Wow, thanks, love your work.
1457912949
Gold
Forum Champion
Studying up on the macro codes to try to implement this, similar to how Al did it. For a starting point I am just going to have the Minotaur's character play a ROAR sound from the jukebox when he rolls his regular attack macro. &nbsp;Thank you for developing this API script.
My SWN game has lots of sounds, doors opening and closing, gunshots, bursts... All in the macros to make the attack roll. It really seems to make the players keep focused and immersed more.
1461290192

Edited 1461290322
Great script! When I used this as the GM, I got a silent error only visible in the API Output Console because there was a mismatch between msg.who (which included " (GM)" at the end of my name) and what /w expects (my name without that suffix). I replaced: &nbsp; &nbsp;var replyTo = '/w "' + msg.who + '" '; With: &nbsp; &nbsp; var whisperTarget = msg.who.replace(" (GM)", "").trim(); &nbsp; &nbsp; &nbsp;var replyTo = '/w "' + whisperTarget + '" '; And that fixed it, in case that's helpful for anyone.
Any idea how to get this to work in chat buttons? I'm trying to make a menu flow for things and it just won't work for me.