A very useful audio feature of Director 8.5 is the ability to queue sounds. This provides continuous audio between sound files. There are two main ways to play sounds in Lingo. The command:
sound(8).play(member(« mySound »))
plays the sound named mySound in channel 8 immediately with no audible delay. However, if you don’t want mySound to play until myOtherSound has finished, and you do not wish to hear any audible gap between the two sounds, you can use the command:
sound(8).queue(member(« mySound »))
to queue the sound (assuming myOtherSound is currently playing in channel 8) and then use:
sound(8).play()
to set the queue in motion. mySound will not play until you issue this command. However, in some circumstances, queuing sounds takes about 1.2 seconds (on a 400 MHz PC), and this duration is primarily independent of the length of the sound (unless, perhaps, if the sound is shorter than 1.5 seconds—the default preloadTime). It takes approximately 1.2 seconds to queue a sound if a queued sound is already playing in the channel. Sound files take considerably less time to queue if there aren’t any other sounds playing in the channel. We can expect that this latency duration will shorten in future versions of Director and as computers become faster. For now, if you are attempting to queue a sound while the queue is already in motion, be aware that this requires additional coding to ensure smooth playback. Although the 1.2 seconds of latency may shorten over time, it will most likely still exist. Any algorithms you implement now will still be useful in future projects, and may only need to be adjusted in the event that the 1.2 second duration is reduced. When a sound is queued, the whole file is not queued. Only the preloadTime is queued. The default preloadTime is 1.5 seconds. So, it currently takes 1.2 seconds to queue 1.5 seconds of sound if the queue is already active. Reducing the setting of the preloadTime to a value shorter than the 1.5 second default doesn’t seem to help this issue. This latency is an interesting consideration if you are building a highly interactive application that involves continuous audio. This issue is pronounced in situations that allow the user to randomly select a sound to play. In these cases, users will expect instant results when they take an action to play a specific sound. Nio deals with this problem rather gracefully. Here’s the strategy: Make sure to queue sounds well enough in advance (at least 1.2 seconds before the current sound ends) to avoid gaps in the audio playback. Setting the queue to begin as it encounters a well-placed cue point will do the trick. But what happens in the event that the user triggers an action while or after you are queuing sounds—indicating that they wish to hear a brand new sound that is not currently in the queue? Immediately stop all the sounds, use the setPlayList command to empty the queue of sounds completely, then requeue the sounds correctly, and finally set the queue in motion using the sound(channel).play() command. You can do this with all eight channels in the blink of an eye, although there is an audible interruption in the sound. But if you are clever about it, you can requeue the sounds using the startTime parameter, which determines how far into the sound the playback commences, to almost eliminate the audio gap. In other words, before you requeue, fetch the currentTime of the sound, (where it stopped playing). Requeue the sound to start just after that time—not immediately after, but a bit longer, perhaps 212 ms. This will result in hardly any interruption in the music’s rhythm. This is the strategy Nio uses in both Verse One and Verse Two. (Note: These examples require that the Shockwave player is installed. Click on the « ? » help button for playback instructions). The resulting playback performance compares acceptably with desktop sound-studio programs, such as Cakewalk. Next, we’ll discuss how to use cue points.
You can use SoundForge or SoundEdit 16 to embed cue points into sounds. You can also name the cue points with these tools. Director will detect cue points (they are called « markers » in SoundForge) and can execute code when the playback head passes a specified cue point. This is accomplished by writing an on cuepassed handler, which is documented in the Lingo Dictionary. Using this approach, you can also find the name of the cue point and the channel it is playing in, as well as other parameters within the handler. The ability to embed cue points into sounds, combined with Director’s response when a cue point is encountered, are crucial to developing applications—particularly when the application requires precise timing and synchronization between sound and visuals. Cue points make it possible for visual, computational, and sonic events to be triggered in response to the state of the audio, rather than in response to a specific frame in the score or when a timer expires. In practice, timers are not as accurate as the audio stream. Timers are not automatically in synch with the audio stream. Additionally, the frame rate of a movie is not always constant (because different frames require different amounts of processing). Regardless of whether the audio stream is perfectly constant or not, you’ll achieve better performance when developing audio-driven applications using cue points. Cue points make it possible to gather immediate data about the current state of the audio in the queue. Adding cue points to your project is not computationally intensive. Cue points are almost more valuable when queuing sounds, rather than playing sounds. Direct contact with the current, immediate state of the audio requires the use of cue points. Consider using cue points to trigger the queuing rather than playing sounds, because when a sound is queued, it often doesn’t start playing until some time later. Alternatives to cue points There are other, more computationally intensive ways to have direct, immediate access to the state of the audio. For instance, sound(1).currentTime returns the number of milliseconds that have elapsed since the sound in channel 1 played from its startTime. This can be set to either the beginning of the sound or some arbitrary time later in the sound file. sound(1).elapsedTime returns how many milliseconds the sound has played in total, which is especially useful if the sound is looping
But it would be easier to avoid having to request this data repeatedly in order to know very precisely when a new sound begins. Further, currentTime and elapsedTime are only updated four or five times a second, which makes it difficult to use for musically precise synchronization. You can probably get away with it, if you are synching visuals and audio. But if you are synching audio and audio together, you might find that you require more precise timing, depending on your project. By the way, there’s a considerable amount of data and methods available for playing sounds in Director. There are more than 50 commands available in Director to get and set sound data. Quite deluxe. My hat is off to the Sound Engineers working on Director. The two primary documents to consult when working with sound in Director are The Lingo Dictionary and Using Director 8.5. These are both available in PDF format on the Director Documentation download page. In the next section, we’ll discuss how to use the getPlayList command.
You can also take advantage of the getPlayList command to find the sounds that are in the queue. For example, sound(1).getPlayList() returns a list of the sounds currently queued to play in channel 1. The list does not include the sound currently playing in channel 1, if one exists. So when sound(1).getPlayList().count (which is the number of sounds queued to play in channel 1) equals 0, the final sound in the queue for channel 1 is about to begin playing in a period of time less than the preLoadTime associated with that sound (which is the duration of sound buffered before the sound begins playing). It is important to note that sounds do not leave the queue when they begin playing; they leave the queue when they are buffered, which is slightly before they begin playing. There is enough difference between the time when sound(channel).getPlayList().count = 0 and when a queued sound starts playing that you cannot effectively trigger playback based on this condition. If you wish to initiate events that will be synchronized with the start of a new sound, this doesn’t work successfully, (unless the initiation of these events takes as long as it takes to buffer queued sounds when something is already playing). However, you can use the fact that sound(channel).getPlayList().count = 0 to initiate requeuing of sounds if the resulting performance is acceptable to you. Triggering sounds from cue points is better if you do not wish to queue sounds until the last possible moment. This is the strategy in Verse Two, which involves not only arbitrary layering of sounds but also arbitrary sequencing of sounds. In Verse Two, at any point in time, any of the available sounds could be in any of the available layers or any of the available sequential positions. Verse One of Nio uses sound(channel).getPlayList().count = 0 to initiate re-queuing. There is a script attached to the frame in which Verse One runs. Verse One stays in one frame after the initial streaming has been completed. To clarify this, Verse One is a ‘one frame movie’ entirely controlled by Lingo after the streaming is finished (which extends over sixteen frames). The first thing the frame script does is check to see whether sound(2).getPlayList().count = 0. If this is not so, the frame script does nothing. I should also mention that the movie frame rate of Verse One is set at 64 fps. The frame rate is set to this speed so that the condition is checked rather frequently. The fast rate is useful in situations where the user clicks a sound icon that isn’t queued yet, initiating playback of the associated sound and animation. This allows requeuing to be performed immediately in the frame script. In the next section, we’ll discuss timers and synching. There are several timers in Nio. One timer is used during the streaming process to minimize the number of times Nio checks to see if the next frame has finished downloading. This is accomplished using the frameready() command. A timer was used for this process, because Nio has other things to do; including the significant task of processing sounds and animations. Also, if you are repeatedly queuing sounds, you can’t always rely on a cue point that is placed at the very end or beginning of a sound. To work around this, try adding a cue point about 1.3 seconds before the end of a 4 second sound. Then, queue the next sounds to be played, and set a timer to go off in 1.3 seconds. In Nio that works well, because that allowed processing to be done when each new sound begins, such as starting its associated animation. A second timers is used when a new sound starts. This timer goes off at the end of each beat of music. As the end of each beat of music is encountered, Nio adjusts the frame rates of the animations currently playing, so that the animations are kept in synch with the audio. You’ll notice that the more animations playing at once, the slower each of them plays, if left to their own devices. Changing the fixedRate property (basically, increasing the frame rate) of the imported Macromedia Flash animations helps increase their speed. Adjusting the frame rate is preferable to adjusting the frame, because if you change the frame that the animation is on, rather than the frame rate, the animation will display in a jerky fashion. In Verse One of Nio, there are times when the animations speed up dramatically and aren’t in synch with the sound. I could have fixed that, but I thought it looked neat. Verse Two is more constant in its synchronization of audio and visual elements (but not quite as alive as Verse One, for my money). Timers in Director In the code that follows, blue words are « keywords » in the Lingo language, green words are « custom » Lingo words, (they also have special meaning to the Lingo Interpreter), red words are comments, gray words are « literals » (strings and numbers), and black words are everything else. Lingo code in Director is color-coded much like the examples below. Many thanks to Chuck Neal of mediamacros.com for helping me figure out timer objects. 1 First, create an object, (in other words, an instantiation of a parent script). This is usually done before the movie starts, in the ‘on prepareMovie’ handler.
gResetAnimations = new(script « ResetAnimationsScript », param1, param2)
gResetAnimations is a global variable. It’s not the timer. It’s just handy to have an object for each timer you create if you have parameters (param1, param2) associated with the functionality of the timer that is specific to what the timer does in your application. There is a cast member in the movie. This is a parent script, named ResetAnimationsScript in the « cast » window, which contains the media elements of the movie. 2 In the next line of the ‘on prepareMovie’ handler, the timer is created:
timeOut (« ResetAnimations »).new(VOID, #ResetAnimations, gResetAnimations)
In the line above, the timer is named ResetAnimations. The ‘period’ of the timer, (in other words, the number of milliseconds it runs before timing out), is set to VOID because that prevents the timer from running out. You can assign a handler to a timer so that when the timer times out, the code in the handler executes. When ResetAnimations times out, it runs the ResetAnimations handler. The associated object of the timer is gResetAnimations; the timer will look in that object first for the ResetAnimations handler, though the handler can also be in any movie script. 3 Next, let’s look at the ResetAnimationsScript. This code resets an animation back to its first frame if it is playing when the timer times out—which occurs when new sounds begin playing.
property theParam1, theParam2 on new me, myParam1, myParam2 me.theParam1 = myParam1 me.theParam2 = myParam2 — This is the constructor of the object, ie, this code runs only when the object is created. theParam1 and theParam2 are not functional in the example code here but are included just to show how to pass and use such parameters when creating objects. This particular object has two pieces of data (theParam1, theParam2) and one method or handler (ResetAnimations). return me end on ResetAnimations me global gSoundIconsPlaying, gBeatLength, gFreezeSprite, gInVerse1 if gInVerse1 then –if we are in Verse One of Nio if sprite(gFreezeSprite).up then –if the Start/Stop button is up repeat with Channel= 3 to 8 –Director has 8 channels of stereo sound at once if gSoundIconsPlaying[Channel]<>0 then –if there is an animation playing in the Channel animToReset = sprite(gSoundIconsPlaying[Channel]).itsAnimation) –name the animation to be reset sprite(animToReset).frame=1 –reset the animation to frame 1 sprite(animToReset).play() –play the animation end if end repeat timeout(« ResetAnimations »).period = VOID –stop this timer (it will be restarted by the next cuepoint passed) timeout(« AdjustAnimationSpeeds »).period = gBeatLength –start the timer that goes off at the end of each beat end if end if end
Note that the timer turns itself off with the line:
timeout(« ResetAnimations »).period = VOID
and starts a different timer with the line:
timeout(« AdjustAnimationSpeeds »).period = gBeatLength
The latter timer is the one that adjusts the frame rate of the animations at the end of each (except the very last) beat of music for each sound. 4 In general, once you have created a timer, you start it by using the last line of code in step 3, and assign its period of duration based on some number which determines how long it will run before timing out. If you do not set its period to VOID, then the timer will run again. For more information on timer objects, see Chapter 11 of Using Director Shockwave Studio entitled Parent Scripts. Pay particular attention to the section on creating timeout objects. Also, consult the following terms in the Lingo Dictionary: · forget() · new · period · persistent · target · time (timeout object property) · timeout() · timeoutHandler · timeoutList Click the Next button to view links to additional online resources.
