The javascript code is
here.
Based on
http://www.glorioustrainwrecks.com/node/5061 (written by Leon Arnott), but has more possibilities and some significant differences. Some macros added, some changed, and now it is possible to add an optional second parameter (where the first is the sound file name) which regulates the behavior of several macros.
Written and tested for Sugarcane format. Probably should work with the others, but I don't know
So, this is the full list of macros (square brackets means that the parameter is optional, it's not a part of syntax):
<<playsound "file">>
Plays "file" once. Nothing changed here.
<<loopsound "file" [times]>>
Plays "file" "times" times - or infinitely, if the parameter if omitted. IMPORTANT: now it is played only in the current passage; when you leave it, the sound stops. Be advised, however, that the event handler gets the control only after the file is played to the end, so if the file is long, you may hear it long enough after quitting the passage. This can be fixed either by stopping it manually in every passage where the user can get from the initial one (definitely not a nice solution!), or by using prerender function which inserts <<stopallsound>> macro (see at the bottom of the file). However, for some passages you may want to keep the music from the previous ones. So, mark the passages where you want to mute any previous sounds immediately with "stopsound" tag, and for them it will be done automatically.
<<arealoopsound "file" [parameter]>>
A new one. You probably has areas in your game which consist of several passages but represent the same place of the game world. So you want to play one music (or other sound effects) in all of these passages. Mark them all with the same tag and use this macro. The "file" will be played infinitely (and without restarting when you change the passage) while you are moving across the passages which have the same tag as the first tag of the passage in which the macro was called, but will the stopped when you move to a passage without such tag (see the notice about playing after leaving above).
The parameter defines what exactly happens when you exit - and what will happen when you return to a passage with the same tag. If "parameter" is omitted, the sound stops completely, and you will need to restart it manually. If "parameter" is any positive number, the sound is only muted and will play again automatically. This may be very convenient, but remember that playing too many sound files simultaneously, even if they are muted, may become a burden to your browser and CPU.
<<pausesound "file">>, <<stopsound "file">> and <<unloopsound "file">>
Not changed (at least from the user's point of view). All do pretty the same, only "pause" and "unloop" don't reset "file" to the beginning.
<<fadeoutsound/fadeinsound "file" [seconds]>>
Now you can specify how long fading should take (2 seconds by default). "seconds" is a number (no "s" at the end). If file is being played already, it continues, otherwise it starts. Notice that fadeoutsound will eventually stop the file, overriding any previous loop settings for it.
<<setvolumesound "file" [level]>>
A new one. Allows, you guessed it, to set the sound volume for the specific file (no difference if it is currently played of not). If "level" is within 0..1 (borders included), it is treated as a fraction; if 1<level<=100, it is treated as percentage (no % sign is needed or allowed). So <<setvolumesound "file" 0.23>> and <<setvolumesound "file" 23>> do the same. If omitted or has other values, it means 1 (100% volume).
<<stopallsound>>
Not changed.
Additionally, if you want to play a sound in Twine as a function call (may be useful, e.g., if you need a sound when the user clicks a certain link), you may add this code to your Start passage:
<<silently>>
<<set $playsound=
function(track)
{track=track.slice(0, track.lastIndexOf(".")); macros["playsound"].soundtracks[track].play()}
>>
<<endsilently>>\
It will allow you to do things like this:
Switch 1 is up. [[Turn down|controls][$playsound("switchdown.wav");$switches[0]=0]]
Comments
As a suggestion, in your InArray function, don't use the for…in loop to iterate arrays. Use a standard for loop, with the 3-part syntax, to iterate arrays. For example:
Reasoning:
Arrays in JavaScript are just objects (i.e. Array object) with some extra syntactical sugar added. What the for…in loop does is iterate over all of an object's enumerable properties (in arbitrary order). The basic enumerable properties on an array object are its indices, which is why using the for…in loop works at all. However, the for…in loop does not limit itself to the object's own properties and will iterate all enumerable properties from the property chain as well. You really do not want, or need, to be traversing an array's property chain just to iterate over its indices.
Now, you could guard against enumerable properties on the property chain by using the hasOwnProperty() method to ensure that an iterated property is actually one of the array's own, however, that's extra work your loop, generally, doesn't need to be doing.
Meanwhile, I changed the code and added domacro method to allow all those macros to be called as a function in Twine this way:
I am trying the following and get a ReferenceError: InArray is not defined) (Everything else works.)
The concept you're thinking of is known as a sparse array.
You are unlikely to see a sparse array come out of the vanilla story formats (or most anything, really), so unless you know for a fact that you'll be dealing with them it's, generally, best to assume that any array you interact with will not be sparse.
In later versions of the ECMAScript (JavaScript) standards, there are several more ways to deal with arrays, many of which handle sparse arrays better than for…in allows.
So you aren't having to lookup the length property within the array stored within the array variable every time the loop conditional is checked. Caching the value is just a bit of a micro optimization.
Normally, you shouldn't worry too much about accessing object properties—just go ahead do it. However, in situations where you're going to be reusing the same property value over and over again (e.g. within a loop), then, as long as the property isn't going to mutate on you, you may want to consider caching it to speed up access.
Since InArray() iterates arrays, which are not being mutated, to search for a particular value, then caching the length of the array isn't a bad idea.
I thought accessing a property (not a method calculating something) and a single variable doesn't differ much. But even if it does, how many tags may a passage have? Usually no more than a couple. Saving several nanoseconds while looping them it not worth
Again, how to call InArray() defined in my script passage from Twine code? It says "InArray is not defined", see an example above.
You would have to make your InArray() function global for it to be seen everywhere, one common method used to do this is to define the function on the global window object although you could use the above indexOf() function instead which is already available on any array.
The following is a copy of your InArray function defined on the window object.
As I noted above, the Twine 1 vanilla story formats don't include any polyfills, however, so you probably shouldn't use <Array>.indexOf() and are better off sticking with your custom InArray() function.
It does exactly what yun needs. However, <Array>.indexOf() doesn't exist in some of the older browsers supported by the Twine 1 vanilla story formats and the original version of these macros. If yun is trying to maintain compatibility, then using <Array>.indexOf() is a non-starter, unless they also include a polyfill for it.
Speaking personally, I would go ahead and add a polyfill, so I could use the standard library method. However, I'm getting the impression that yun wouldn't be in favor of that.
Thanks to both of you!