This creation of a Sound class instance and the use of one method to load a sound file are the only basic differences between using internal and external sounds.. To load a sound from a
Trang 1Internal and External Sounds
Once you’ve provided a linkage class name, you can create an instance of the
sound the same way you instantiate a movie clip:
var snd:Sound = new ClaireElmo();
Thereafter, you can manage the instance of this sound by referring to the
vari-able snd This creation of a Sound class instance and the use of one method
to load a sound file are the only basic differences between using internal and
external sounds All play, pause, stop, and transform operations are identical,
regardless of the sound source
If you prefer to use internal sounds, the using_internal_sound.fla source file
demonstrates the playing and stopping of a short imported sound using the
basic syntax explained in the “Playing, Stopping, and Pausing Sounds”
sec-tion later in this chapter However, we recommend using external sounds for
most uses
Loading External Sounds
Using internal sounds, creating an instance of the Sound class and populating
it with audio occur in one step To load a sound from an external MP3, we
need to use the load() method of the Sound class, so we must first explicitly
create an instance of the class This is shown in line 1 of the following code,
found in the loading_external_sound.fla source file
1 var snd:Sound = new Sound();
2
3 var req:URLRequest = new URLRequest( "song.mp3" );
4 snd.load(req);
As discussed in prior chapters, you also need to create an instance of the
URLRequest class (line 3) any time you load something Although it has
addi-tional purposes for advanced URL processing, the URLRequest class is also
used to standardize the loading process This allows you to load many asset
types in a consistent manner Finally, we load the sound in line 4
Once you’ve completed this process, you’re again ready to start manipulating
the snd instance When working with external sounds, however, there are
additional factors that warrant a little extra attention The steps we’ll describe
aren’t required, but they’re recommended You’ll find that they improve not
only your own development efforts (such as when listening for errors when
loading a sound), but also the user experience (such as providing feedback
during loading)
It’s not uncommon when loading external assets to encounter a missing file
problem The path to the file may be old or incorrect, or the loaded asset or
your SWF may have been moved In this case, a runtime error like this may
occur:
Error #2044: Unhandled IOErrorEvent:
text=Error #2032: Stream Error.
N OT E
Another difference between working with internal and external sounds is that you can buffer a loaded sound to improve playback experience Buffering
a sound means that your playback won’t begin until a specified amount of sound data has loaded This allows the back-ground loading of the rest of the sound
to stay ahead of your playback and is discussed in the “Buffering Sounds” sec-tion later in this chapter.
Trang 2So, it’s a good idea to plan for this possibility and listen for an IO_ERROR
(input/output error) If you do encounter an error, you can present it to the viewer as part of the user experience (through an alert or warning icon, for example), or just try to correct it during the authoring process by tracing to the Output panel The following listener function traces a message, as well as the descriptive text sent with the event
5 //listen for error
6 snd.addEventListener(IOErrorEvent.IO_ERROR, onIOError,
7 false, 0, true);
8 function onIOError(evt:IOErrorEvent):void {
9 trace( "Error occurred when loading sound:" , evt.text);
10 } The next enhancement to include when working with external sounds is
to provide feedback to the user during the loading process Again we add a listener to the sound instance, this time listening for a PROGRESS event, which
is dispatched whenever loading progress occurs When this happens, you can update the user by, for example, increasing the width of a sprite to create a progress bar
Lines 12 through 24 of the following code create the progress bar Line 12 passes a color to the drawBar() function (lines 17 through 24), which creates
a sprite, draws a rectangle that is 1 pixel wide and 10 pixels tall, and returns the sprite Lines 13 and 14 position the progress bar and line 15 adds it to the display list
Lines 26 through 30 contain the listener function The event captured by the listener function carries with it information, including the total number of bytes in the target object (in this scenario the sound being loaded), as well as the number of bytes loaded at the moment the event was fired By dividing the latter by the former, you end up with a fraction For example, if 500 bytes
of a total 1,000 bytes have loaded, the progress is 500/1,000 or 0.5, indicating that the object is 50-percent loaded By multiplying with a desired width of the progress bar, the bar will increase to the final desired size when the file is 100-percent loaded
11 //track loading progress
12 var loadBar:Sprite = drawBar(0x000099);
13 loadBar.x = 20;
14 loadBar.y = 15;
15 addChild(loadBar);
16
17 function drawBar(col:uint):Sprite {
18 var bar:Sprite = new Sprite();
19 var g:Graphics = bar.graphics;
20 g.beginFill(col, 1);
21 g.drawRect(0, 0, 1, 10);
22 g.endFill();
23 return bar;
24 }
25
26 snd.addEventListener(ProgressEvent.PROGRESS, onLoadingProgress,
27 false, 0, true);
N OT E
In this example, the progress bar will
reach 100 pixels wide when the process
finishes or will stay at its original 1-pixel
width if the function fails Keeping these
start and finish sizes in mind when
test-ing locally is useful because loadtest-ing
even very large files from a local hard
drive happens very quickly, and it’s
quite common not to see the progress
bar move at all with small sound files.
Trang 3Playing, Stopping, and Pausing Sounds
28 function onLoadingProgress(evt:ProgressEvent):void {
29 loadBar.width = 100 * (evt.bytesLoaded / evt.bytesTotal);
30 }
The last option we’ll introduce here is for responding to the completion of
the sound loading process The structure is similar to the prior two event
listener examples, this time using the Event.COMPLETE event to trigger the
listener function
31 //react to successful load
32 var sndLength:Number = 0;
33 snd.addEventListener(Event.COMPLETE, onCompleteLoad,
34 false, 0, true);
35 function onCompleteLoad(evt:Event):void {
36 sndLength = snd.length;
37 trace( "Sound length:" , sndLength);
38 }
After creating the variable in line 32, this example stores the length of the
sound in milliseconds in sndLength, and traces that value as a quick
indica-tion that the process is complete This code is the starting point for an audio
player exercise that runs throughout the chapter Soon, we’ll use the sound’s
length to update a progress bar during playback of the sound First, however,
let’s look closely at the playback syntax
Playing, Stopping, and Pausing Sounds
The simple syntax of the Sound class’s play() method can be a bit deceiving
because it’s only part of the picture To play a sound, all you need to do is call
the method from the Sound class instance However, all this does is start
play-back and, without additional infrastructure, you can’t do much else Instead,
you should play the sound into an instance of the SoundChannel class, which
allows you to stop, pan, or adjust the volume of the channel This is different
from prior versions of ActionScript, in which all sound control rested with
the Sound object.
To emphasize this idea, let’s think again about a recording studio To move a
sound from the left speaker to the right speaker in a mix, a sound engineer
would twist a knob at the mixing desk, not ask a musician to run from one
side of the studio to another Similarly, although musicians often handle
volume subtleties, fading a sound up or down is typically accomplished by
adjusting a sound channel’s volume slider In other words, the playback of
the sound is typically separated from the manipulation of that sound in the
mixing process The same is true in ActionScript 3.0
We’ll begin with simple examples and then we’ll add these features to our
ongoing audio player
Trang 4Playing a Sound
To place a sound into a channel, all you need to do is create the channel and then set it equal to the result of the sound’s play() method.
var channel:SoundChannel; = new SoundChannel();
channel = snd.play();
This associates the sound with the specified channel, the same way you would plug a guitar into a channel in our metaphorical recording studio’s mixing desk Once the sound is in the channel, you’ll be able to adjust its volume, set its pan, and stop its playback—all of which we’ll discuss in a moment
But how soon can you play the sound? If you’re using imported audio files, you can typically play the sound right away However, when working with external files, you must consider the loading process If you invoke the load()
method to start loading an external sound file and then immediately attempt
to play the sound, your attempt will likely fail because the sound will prob-ably still be loading However, we saw in the previous section that a COMPLETE
event is dispatched when a Sound instance’s MP3 is finished loading So, an
event listener listening for that event can play the sound without any problem The following snippet shows syntax for playing a sound immediately after it’s loaded This snippet assumes a Sound instance of snd, and a SoundChannel
instance of channel.
snd.addEventListener(Event.COMPLETE, onLoadComplete, false, 0, true);
function onLoadComplete(evt:Event):void { channel = snd.play();
} This approach is used to play a sound as soon after loading as possible This
is useful for things like background audio that begins playing right away Perhaps the most common way to play a sound, however, is by clicking a but-ton or through similar user interaction We’ll set up just such a butbut-ton when
we return to our ongoing sound player project
Stopping a Sound
Stopping a single sound in a channel requires only the stop() method
Unlike playing the sound, however, this method is invoked from the channel, not from the sound itself Again assuming you’ve previously created a new instance of the SoundChannel, named channel, the syntax looks like this:
channel.stop();
It’s also possible to stop all sounds using the SoundMixer class As in the real
world, multichannel playback funnels through a master sound mixer Just as you can kill that master channel in a studio, you can stop all sounds using the SoundMixer class and it’s stopAll() method.
Trang 5Playing, Stopping, and Pausing Sounds
Unlike the previous methods discussed, stopAll() is static This means an
instance of the SoundMixer class does not need to be created using the new
keyword Instead, the method is called directly from the class Therefore, to
stop playing the sounds in all channels, you need only write:
SoundMixer.stopAll();
Pausing Sounds and Resuming Playback
Pausing a sound is a bit different Currently, there is no dedicated pause
method in ActionScript 3.0 Instead, you must rely on an optional parameter
of the play() method that allows you to play the sound starting from a
par-ticular number of seconds offset from the beginning of the sound
To use this feature to pause playback, you must first store the current
posi-tion of a sound as it’s playing Having retrieved this value from the channel’s
aptly named position property, you can then stop playback in that channel
Later, you can resume playback by playing the sound from the stored
posi-tion Assuming the ongoing use of the snd and channel instance names, here
are the first and second steps of the process:
var pausePosition:Number = channel.position;
channel.stop();
Then, at some later point, you can resume playback from where you left off:
channel = snd.play(pausePosition);
Applying the Syntax
Now let’s put these concepts into practice and pick up from the source file we
started in the “Loading External Sounds” section The following code, added
to the previous example, forms the player_basic.fla source file.
If you want to look ahead, here’s a quick description of all the code related
to playing a sound, spanning lines 40 through 75, so we know what we’re
trying to accomplish Lines 44 through 54 contain an event listener that will
be triggered when a user clicks on the play button we’ll soon create Lines 56
through 65 include the code required to show a progress bar during playback
Finally, lines 67 through 75 contain the function triggered when the playback
is complete
Now let’s focus on the function that plays the sound and its accompanying
variables Line 40 creates a Boolean variable that we’ll use to check whether
the sound is already playing Because we’ll be using a button to play our
sound, it will now be possible to play the sound multiple times However,
this also means that rapid repeated clicks of the button will play the sound
over and over itself, layering the sound This is fine for simulating a musical
instrument or an echo, for which multiple simultaneous occurrences of the
sound are acceptable, but it’s less desirable for playing spoken dialog or other
sounds that typically would not be layered over themselves
N OT E
You can control the volume and pan of the master mix by using the
SoundMixer class, which we’ll dem-onstrate later on We’ll also use the
SoundMixer to visualize a sound during playback later in the chapter.
Trang 6Line 41 creates a variable that will store the most recent playback position of the song in its channel, when the pause button is pressed Remember that we’re adding extra functionality to the play process to support pausing play-back, so we’ll need to pass this value to the play() method Because we’re
using one play() method to play from a stop and from a pause, it’s very
important to initialize the value of this variable to zero so that the sound starts playing at the beginning when first played Similarly, when we code the stop behavior later on, we’ll need to reset this variable to zero after stop-ping the sound, to avoid restarting from any previous pause position when replaying
Lines 44 through 54 make up the onPlaySound() function, which will be
called by a play button that we’ll add later on Line 45 checks to see whether
the sound is not already playing If that test passes, the isPlaying variable
is set to true to prevent the sound from playing more than once simultane-ously Lines 47 and 48 add a listener to the main timeline (the scope of the script) that will fire upon every enter frame event We’ll use this listener to update the playback progress bar in just a moment Lines 49 through 51 add
a listener to the channel to trigger when the sound playback is complete We’ll use that to reset things so the sound can be played again Finally, line 52 plays the sound The first time it plays, it will play from the beginning because of the initial 0 value of the soundPosition variable in line 41.
39 //play
40 var isPlaying:Boolean = false;
41 var soundPosition:Number = 0;
42 var channel:SoundChannel = new SoundChannel();
43
44 function onPlaySound(evt:MouseEvent):void {
45 if (!isPlaying) {
46 isPlaying = true;
47 addEventListener(Event.ENTER_FRAME, onPlayProgress,
48 false, 0, true);
49 channel.addEventListener(Event.SOUND_COMPLETE,
50 onPlayComplete,
51 false, 0, true);
52 channel = snd.play(soundPosition);
53 }
54 } Next, we’ll setup the playback progress bar Lines 56 through 59 create the bar, position it and add it to the display list The drawBar() function is the
same function found earlier in the file, spanning lines 17 through 24 and discussed in the “Loading External Sounds” section earlier in this chapter It simply creates a sprite and draws a 1 × 10-pixel rectangle
The function in lines 61 through 65 updates the width of the progress bar It’s called every enter frame event because of the listener created in lines 47 and 48 Dividing the playback position of the sound in the channel by the total length
of the sound gives us a percentage For example, if the position is 5000 and the length of the sound clip is 10,000 milliseconds (10 seconds), the playback is
Trang 7Playing, Stopping, and Pausing Sounds
50-percent complete That percentage is then multiplied by the desired width of
the bar, 100 pixels, and the width of the bar is set to this value
Later on in the chapter, we’ll drop two function calls into lines 63 and 64
to control the volume and pan of the sound, and we’ll update peak meter
graphics on the stage that show the amplitude of the sound during playback
55 //play progress bar
56 var playBar:Sprite = drawBar(0x0000FF);
57 playBar.x = 20;
58 playBar.y = 15;
59 addChild(playBar);
60
61 function onPlayProgress(evt:Event):void {
62 playBar.width = 100 * (channel.position / sndLength);
63 //future home of volume and pan adjustment
64 //future home of amplitude meter adjustment;
65 }
The onPlayComplete() listener function (added in line 48) is triggered after
the sound has finished playing Lines 68 and 69 remove both listeners added
when playback began Once the sound is finished playing, there is no longer
a need to update its playback progress or listen for a SOUND_COMPLETE event
Removing the listeners is not only efficient, but also allows us to set the
play-back progress bar width to 0 If not removed, the enter frame event would
continue to set the bar’s width to 100 (The position of the sound is at the end
of the file when playback is complete.)
The remainder of the function stops the sound, resets the soundPosition
variable to 0, the width of the play progress bar to 0, and the isPlaying
vari-able to false All of this allows us to play the sound anew
66 //playback complete listener function
67 function onPlayComplete(evt:Event):void {
68 removeEventListener(Event.ENTER_FRAME, onPlayProgress);
69 channel.removeEventListener(Event.SOUND_COMPLETE,
70 onPlayComplete);
71 channel.stop();
72 soundPosition = 0;
73 playBar.width = 0;
74 isPlaying = false;
75 }
Now that we have the play functionality complete, we need a button to
trig-ger it We’ll be creating three buttons by the time we’re done, so rather than
repeating the button creation code three times, let’s set up a function to do
the work for us This takes less code but, more importantly, it means that if a
future edit is required, you have to edit the code in only one place, not three
We’re going to draw our buttons dynamically, using the RoundRectButton
class we created in Chapter 8, so line 77 imports the class Remember that
this material is presented in chunks for clarity Import statements are
typical-ly consolidated at the top of your script and you should feel free to reorganize
your code any way you see fit
N OT E
One idea behind using the
RoundRectButton class to draw but-tons dynamically is to give you contin-ued practice using packages and classes However, you’ll also find when we’re done that this entire file will contain
no imported assets As such, the entire audio player is less than 5 KB! This is
a best-case scenario because we kept the interface simple—both so you didn’t need to rely on library assets and so a more complex interface didn’t intrude on the sound tutorial The idea, however, is good You could use the Graphics class
to draw additional interface artwork, for example, and still keep the file size low.
Trang 8The createButton() function in lines 79 through 87 instantiates a button,
positions it on the stage, adds a mouse click event listener, and adds the but-ton to the display list This later lets us create butbut-tons with only one line of code, as seen with the first button in line 89 With three or more buttons, this approach can really be economical
The function takes three arguments: the y coordinate to place the button
on the stage, the label for the button, and the function that will be triggered when the user clicks on the button Although this book has shown how to pass numbers and strings into functions, this is the first time we’ve used a function as an argument This is a handy process that’s not only expedient, but also emphasizes the fact that functions are objects too, just like numbers and strings
As mentioned, line 89 creates the first button, passing a y-position of 40, a label of “Play” and the onPlaySound() function, created earlier, as the
func-tion to execute when the button is clicked
76 //playback complete listener function
77 import com.learningactionscript3.ui.RoundRectButton;
78
79 function createButton(yLoc:Number, labl:String,
80 func:Function):void {
81 var btn:RoundRectButton =
82 new RoundRectButton(100,20,10,2,0x000099,labl,0xFFFFFF);
83 btn.x = 20;
84 btn.y = yLoc;
85 btn.addEventListener(MouseEvent.CLICK, func, false, 0, true);
86 addChild(btn);
87 }
88
89 createButton(40, "Play" , onPlaySound);
The remainder of the script is dedicated to the pause and stop buttons Lines
91 through 98 create the pause button, setting its y location to 65 and adding the onPauseSound() function as its mouse click listener In line 93, the
func-tion checks to see whether the sound is playing and, if so, stores the current playback position in the soundPosition variable It then stops the sound and
sets the isPlaying variable to false so the sound can play again later.
Lines 99 through 102 follow the same process but are even simpler The stop button is created in line 99, which places the button below the previously cre-ated play and pause buttons, and adds the onStopSound() method as the
but-ton’s mouse click listener The functionality required by manually stopping the sound is the same as the functionality required when the sound stops on its own in the onPlayComplete() function (lines 67 through 75) Therefore,
all that’s required here is to call that function
However, because onPlayComplete() is a listener function, it expects an
event argument Calling the function without supplying the expected event argument will cause an argument count mismatch error We can get around
Trang 9Buffering Sounds
this by sending null to the function to stand in for the event The null value
will satisfy the type checking at the listener function because null is the
default value for all events As long as your listener function doesn’t rely on
specific information from the event, such as mouse coordinates or keyboard
key code values, this technique makes it possible to use listener functions not
only when an event occurs, but also manually
90 //playback complete listener function
91 createButton(65, "Pause" , onPauseSound);
92 function onPauseSound(evt:MouseEvent):void {
93 if (isPlaying) {
94 soundPosition = channel.position;
95 channel.stop();
96 isPlaying = false;
97 }
98 }
99 createButton(90, "Stop" , onStopSound);
100 function onStopSound(evt:MouseEvent):void {
101 onPlayComplete(null);
102 }
At this point, you should be able play, pause, and stop your sound, and both
the load and play progress bar should reach 100 pixels in width upon their
respective completions
Buffering Sounds
Waiting to play a sound until it’s fully loaded will prevent errors or stutters
that might otherwise occur during the loading process This method,
howev-er, does suffer from the drawback of having to wait An alternative approach
is to preload only a portion of the sound prior to playback, and then play
the sound while it continues to download progressively to Flash Player in the
background The principle behind this approach is to preload a buffer that
can stay ahead of playback during the time required to download the
remain-der of the sound You’ll still need to wait, but not as much
How much of the sound should you buffer? That depends on how you plan
to distribute your project Theoretically, if you have no load time, you need
no buffer time because the sound loads instantly This is usually true of local
files, when you are not loading the sound from the Internet For remote files,
connection speeds can dictate how much sound needs to be preloaded If you
know you’ll only encounter broadband connection speeds, you can buffer
less of the sound If you’re worried about slower connections, you may want
to buffer more of the sound to prevent the playback from catching up with
the loading process and stalling playback
To specify the buffer time, you must use the SoundLoaderContext class at
the time of sound loading The number of milliseconds of sound to buffer is
passed to the constructor when instantiating the class; otherwise, a default
value of 1000 is used After instantiating the class, you then pass the resulting
N OT E
For more information about listener functions and the argument count mismatch error, see the “Using Event Listeners” section of Chapter 3.
Trang 10instance into the sound load() method, as a second parameter following the URLRequest object
The following example adapts the start of our audio player by inserting line
2, and modifying line 4 It buffers 5 seconds of the loaded sound before the
play() method will execute This modification can be found in the player_
buffering.fla source file
1 var snd:Sound = new Sound();
2 var context: SoundLoaderContext = new SoundLoaderContext (5000);
3 var req:URLRequest = new URLRequest( "song.mp3" );
4 snd load (req, context);
Changing Sound Volume and Pan
During playback, it’s possible to manipulate the volume and pan of indi-vidual channels, as well as the global mixer containing all sounds Doing so requires the SoundTransform class.
The process involves starting with a SoundTransform instance (either by
creating a new instance or by storing a reference to the existing transform object of the channel or mixer), setting the volume and/or pan setting of that instance, and then applying the transformation to the channel For example, this snippet will set the volume of a SoundChannel instance called channel
to 50 percent
var trans:SoundTransform = new SoundTransform();
trans.volume = 0.5;
channel.soundTransform = trans;
This syntax will set the volume of a channel to half of what it currently is: var trans:SoundTransform = channel.soundTransform;
trans.volume *= 0.5;
channel.soundTransform = trans;
Notice that the first example sets the volume of a new SoundTransform
instance to 0.5, while the second example multiplies the volume of an exist-ing SoundTransform instance by 0.5 The first example will set the volume to
50 percent, regardless of its prior setting, but the second example will cut the current volume in half For example, if the second volume was originally 50 percent, it would then be 25 percent
Most ActionScript 3.0 settings that require percentage values use a unit range
of 0 to 1 For example, volume is expressed as a range of 0 (muted) to 1 (full volume) with any interim value expressed as a percentage of full volume
To determine a value that describes a pan setting between left and right stereo channels, both a percentage left and a percentage right are required Therefore, the units are expressed as a range of –1 (full left) through 0 (cen-tered) to 1 (full right) Negative interim values reflect some degree of pan left,
N OT E
Remember that buffering will have
little effect when testing locally because
the loading process will complete very
quickly You may wish to upload your
test files to a server, and perhaps even
use a very large sound file, to test your
efforts.