Understanding the Sound Constructor (AS2)

ActionScript 2.0

Generally speaking, once you “get” how objects work in ActionScript, you’re on good footing.  Even if you’ve never used, for example, the Date class, you’ll be able to see from a few ActionScript Language Reference examples that it’s not a static class (such as Math) — which means you must instantiate an object first, then work with the instance.  Once you’ve decided on an instance name, you’re good to go:  just invoke the desired property, method, or event on the instance.  That said, a few choice ActionScript classes are a bit different; the Sound class is one of them.

Connected at the clip

What do I mean by different?  Well, Sound instances are effectively Siamese twins.  A Sound object is always associated with a particular MovieClip instance, and it’s this relationship that can cause confusion until you understand what’s going on.  Let’s take a look at an example. 

Assuming a movie clip on the Stage with the instance name mcSound

var h:Sound = new Sound(mcSound);
var d:Sound = new Sound(mcSound);
h.attachSound("horse");
d.attachSound("drill");
h.start();
d.start();
h.stop();

… what do you think will happen here?  Let’s step through.

var h:Sound = new Sound(mcSound);
var d:Sound = new Sound(mcSound);

Two unique variables, h and d, are declared, each of which points to a unique instance of the Sound class.  In both cases, the mcSound movie clip is provided as the optional parameter in the constructor.

h.attachSound("horse");
d.attachSound("drill");

The Sound.attachSound() method is invoked on each Sound instance, h and d.  The parameter in this case is a string that refers to the Linkage id of a sound asset in the Library (two WAV files, as it happens).

h.start();
d.start();

Here, the Sound.start() method is invoked on each Sound instance.  Both should start to play.

h.stop();

Finally, the Sound.stop() method is invoked on one instance only, h.  So h (the horse whinny) should stop playing — before it ever really gets started — and d (the drill buzz) should continue.  Right?

What actually happens is that no sound plays at all.

When I first encountered this phenomenon, I was taken aback:  after all, each Sound instance is unique; each instance is an object unto itself, with all the rights and privileges pertaining to the Sound class.  If I tell one instance to stop, the other should keep playing, so what gives?  Then it occurred to me:  it’s the relationship between the Sound instances and the mcSound movie clip that flips my erroneous impression on its ear (heh, pun).

Effectively, when you tell h to stop, you’re also telling any other Sound instance associated with mcSound to do the same.

Using the association to your advantage

This can be a good thing.  For example, you may want to associate half a dozen Sound instances with a particular movie clip, then pan them all simultaneously (maybe you have six honking geese running from the left side of the Stage to the right).  To do that, give them each a “membership” to the “same country club,” so to speak.  As long as they’re all associated with the same movie clip, you may invoke Sound.setPan() on any one of them and the others will follow suit.

var goose1:Sound = new Sound(mcFlock);
var goose2:Sound = new Sound(mcFlock);
var goose3:Sound = new Sound(mcFlock);
var goose4:Sound = new Sound(mcFlock);
var goose5:Sound = new Sound(mcFlock);
var goose6:Sound = new Sound(mcFlock);

goose1.attachSound("honk1");
goose2.attachSound("honk2");
goose3.attachSound("honk3");
goose4.attachSound("honk4");
goose5.attachSound("honk5");
goose6.attachSound("honk6");

goose1.setPan(-100);

goose1.start();
goose2.start();
goose3.start();
goose4.start();
goose5.start();
goose6.start();

The above saves you from having to invoke Sound.setPan() on each instance, because each is associated with the same movie clip.  Now, to be truthful, what I’ve shown so far only pans the audio once hard left.  To make those geese seem waddle to the right gradually over time, we can make use of the Sound.duration and Sound.position properties.  I’ll drop the flock to three to make this easier to read.

var goose1:Sound = new Sound(mcFlock);
var goose2:Sound = new Sound(mcFlock);
var goose3:Sound = new Sound(mcFlock);

goose1.attachSound("honk1");
goose2.attachSound("honk2");
goose3.attachSound("honk3");

goose1.setPan(-100);

goose1.start();
goose2.start();
goose3.start();

function panRight(soundObject:Sound):Void {
  var ratio:Number = 200 / soundObject.duration;
  var id:Number = setInterval(function() {
    if (soundObject.position < soundObject.duration) {
      d.setPan(soundObject.position * ratio - 100);
    } else {
      clearInterval(id);
    }
  }, 50);
}
panRight(goose1);

Let’s break it down.

var goose1:Sound = new Sound(mcFlock);
var goose2:Sound = new Sound(mcFlock);
var goose3:Sound = new Sound(mcFlock);

This should be familiar now:  declare three variables, each of which points to its own instance of the Sound class associated with the same movie clip, mcFlock.

goose1.setPan(-100);

goose1.start();
goose2.start();
goose3.start();

Here, we arbitrarily choose one instance (happens to be goose1) and set its pan to -100.  Because these instances all share the same movie clip, they’re all shoved to the left.  Then each one is started.

Now comes the kicker.  The custom panRight() function pans a single Sound instance from left to right in synch with the sound’s duration.  At the beginning, the sound will come from the left speaker; at the end, it will come from the right.  Again, because all instances share the same movie clip, they’ll all get panned.  Here’s a break down.

function panRight(soundObject:Sound):Void {

This line declares an arbitrarily named function, panRight(), and specifies that it may receive a parameter (a Sound instance), which will be referred to as soundObject in the context of the function.  This function has no return value, so the declaration is followed by :Void.

var ratio:Number = 200 / soundObject.duration;
var id:Number = setInterval(function() {

Here, two variables are declared, both numbers.  The first one holds a ratio between the number 200 and the duration of the Sound instance that was passed as a parameter.  Where does 200 come from?  Well, the panning range of ActionScript’s Sound class goes from -100 to 100, which is a span of 200.  It’s easier (for me) to figure out the math if my ratio goes from zero to 200 than from -100 to 100 (we’ll just subtract 100 again down the line).  The other variable holds the return value of setInterval() — this is just a number, an unique ID of the interval in question — which we use in cahoots with clearInterval() later to quit the looping.  (The looping occurs every 50 milliseconds, which is established after the function literal parameter.  You’ll see this in a moment.)

if (soundObject.position < soundObject.duration) {

If the current position of the passed-in Sound instance is less than its duration (meaning, if the sound is not yet concluded) …

soundObject.setPan(soundObject.position * ratio - 100);

… then set its pan to whatever its current position is, times the aforementioned ratio, minus 100.  This line is important, so let’s examine it in greater detail.

As the sound begins to play, the value of Sound.position for that object will be 1.  That is, one millisecond into however many milliseconds the audio contains (for a 3.5 second file, that would be 3,500 milliseconds).  If the file’s duration is 3,500 milliseconds, then the value of ratio is approximately 0.0571 (that is, 3500 divided by 200).  So in the above line, the parameter supplied to Sound.setPan() is approximately -99.94; that is, the sound object’s position, 1, times 0.0571, minus 100.  That’s close enough to -100, isn’t it?  ;)

When the sound is halfway through, its position is 1,750.  Running that math again, we would find approximately zero:  1750 * 0.0571 – 100 = -0.075.  When the sound is completely through, its position is 3,500:  3500 * 0.0571 – 100 = 99.85 (close enough to 100).

Of course, at the end, the instance’s Sound.position value will no longer be less than Sound.duration, which means the if statement’s else clause kicks in:

} else {
  clearInterval(id);

Here, clearInterval() uses the id variable set earlier to quit the looping of setInteval().  Then the function literal completes, and the remainder of the setInterval() parameters is provided (here, that’s 50; as in, “repeat the above function literal every 50 milliseconds”).

  }, 50);
}

Finally, the function is invoked with goose1 as its parameter.

panRight(goose1);

If all of these “honk” files are the same duration, each pan will start at the left and end at the right.  If some are shorter, they won’t make it across to the right before falling silent.  To adjust the pan of each sound based on its own duration, you’d have to associate each Sound instance with a unique MovieClip instance.

What about no association?

Remember, the Sound constructor’s parameter is optional.  What happens if it’s omitted?

var audio:Sound = new Sound();

In this case, the resultant object, audio, governs sound for the whole SWF.  I mentioned earlier that Sound instances are always associated with a particular MovieClip instance, and it still holds true.  The movie clip in this case is the main timeline (the SWF itself).

If you invoke Sound.setPan(), Sound.setVolume(), or any other method on this Sound instance, changes will occur across the whole movie, which we saw in the earlier article “How to Toggle Sound Globally.”

Individual attention, even when associated

Let’s bring this full circle.  Here’s our first example again.

var h:Sound = new Sound(mcSound);
var d:Sound = new Sound(mcSound);
h.attachSound("horse");
d.attachSound("drill");
h.start();
d.start();
h.stop();

If you only want the horse whinny to stop, you have to options, here.  Either associate each Sound instance with its own movie clip …

var h:Sound = new Sound(mcSound1);
var d:Sound = new Sound(mcSound2);

… or provide the optional parameter to Sound.stop() — which would be the Linkage id of the desired sound.

h.stop("horse");

Bear in mind that if you go by the latter approach, you could invoke Sound.stop() on either Sound instance, since both are associated with the same movie clip.

2 Responses to “Understanding the Sound Constructor (AS2)”

  1. Thum Says:

    To David

    My purpose to stop start background music is when I load the flv file I want the backgraound music to stop.After I stop the flv movie then I want to start the background again.

    I had put your sample code with the stop start flv action script.
    The problem is when the flv movie start the script stop the back ground music.

    But When I stop the flv movie .The Background music is not start.
    Please advice me

    Here is my sample code.

    var h:Sound = new Sound(mcSound);
    h.attachSound(”bg1″);

    function onInit(){
    if( visible )
    playVideo();
    }

    function onOpen(){
    playVideo();
    }

    function onClose(){
    stopVideo();
    }

    function playVideo(){
    media.play();
    h.stop();

    }

    function stopVideo(){
    media.stop();
    h.start();
    }

  2. David Stiller Says:

    Thum,

    Is mcSound the instance name of a valid movie clip? What happens if you use the trace() function to trace the value of h (your Sound instance) in the stopVideo() function?

    e.g.

    function stopVideo():Void {
      media.stop();
      trace(h);
      h.start();
    }

Leave a Reply