Whew! Ready to run the course again? The ActionScript 3.0 version of SoundSync parallels the ActionScript 2.0
version very closely. There are inevitable differences, of course, but
migration needn't be a frightening endeavor.
Here you have two choices: either Flash CS3 or Flex Builder 2. If you're continuing on with Flash CS3, simply create a new SoundSync.as file—a text file, as before—inside the same nested folder structure (net/quip/sound) described at the beginning of the article. This time, put those nested folders inside a root folder set aside for ActionScript 3.0 classes. Use the Edit > Preferences > ActionScript category > ActionScript 3.0 Settings button to specify the location of this root folder. This lets Flash know how to set paths separately to the distinct folders for these two languages.
Alternatively, if you want to continue on with Flex, perform the following steps to set up the SoundSync.as file within the context of the Flex Builder 2 authoring environment. Once the class file is created, you may proceed under either platform. Flash developers may, at this point, safely skip to the "Starting with the class groundwork" section.
Here's the Flex detour. Launch Flex Builder 2 and select File > New > ActionScript Project to begin (see Figure 2).

Figure 2. Starting a new ActionScript project in Flex Builder 2
Name the project SoundSyncExample and then click the Finish
button. This creates a new project in Flex Builder 2, including bin and
html-template folders used by Flex and a SoundSyncExample.as class file that
acts as the main entry point into your new application. SoundSyncExample will instantiate the
ActionScript 3.0 version of SoundSync;
it corresponds to the Flash CS3 frame script in the previous "Using the
ActionScript 2.0 version" section.
Note that Flex Builder 2 automatically generates a package and class declaration for you (see Figure 3), which you'll flesh out later. For now, save this file and close it. (This file is not needed if you're using Flash CS3.)

Figure 3. Flex Builder 2 automatically generating a package and class declaration
Create the net.quip.sound package folders inside this project by selecting File > New > Folder. In
the New Folder dialog box, select the SoundSyncExample folder and provide the
path net/quip/sound in the Folder Name field. Click the Finish
button.
Finally, select File > New > ActionScript Class. In the New ActionScript Class dialog box, make sure the Package field reads net.quip.sound. Type SoundSync into the Name field and type flash.media.Sound into the Superclass field. Check the Generate Constructor from Superclass option (see Figure 4).

Figure 4. Creating the SoundSync class file
Here, as in the previous version, SoundSync extends the Sound class.
Click the Finish button and note that Flex Builder 2 again generates an
automatic package and class declaration for you, as well as the necessary call
to super inside the SoundSync constructor.
In ActionScript 3.0, import statements are not only a convenience, they are required when writing class
files. As part of its code completion feature set, Flex Builder 2 automatically
supplies relevant import statements as needed. For the sake of this tutorial, however, or if you're
using Flash CS3, update your code manually to reflect the following full import list, as well as the
organizational comments:
package net.quip.sound
{
import flash.events.Event;
import flash.events.TimerEvent;
import flash.media.Sound;
import flash.media.SoundChannel;
import flash.media.SoundLoaderContext;
import flash.media.SoundTransform;
import flash.net.URLRequest;
import flash.utils.Timer;
public class SoundSync extends Sound
{
// PROPERTIES
// CONSTRUCTOR
public function SoundSync(stream:URLRequest=null, context:SoundLoaderContext=null)
{
super(stream, context);
init();
}
// METHODS
// EVENT HANDLERS
}
}
Note that ActionScript 3.0 class files feature a package statement, rather than put the
package path within the class statement. Don't forget the call to init() in the constructor!
Type the following ActionScript after the //
PROPERTIES comment:
private var _cuePoints:Array; private var _currentCuePoint:uint; private var _timer:Timer; private var _timerInterval:uint; private var _startTime:Number; private var _loops:uint; private var _soundChannel:SoundChannel;
Here, _cuePoints and _currentCuePoint are identical to their
ActionScript 2.0 counterparts. The _timer and _timerInterval properties correspond to the previous version's _interval and _intervalDuration,
while _startTime corresponds
to the previous _secondOffset.
Finally, _loops and _soundChannel are new and will be
explained as the class unfolds.
Note the new uint data
type (highlighted) for several of the numeric properties. In ActionScript 3.0,
the familiar Number data
type refers to double-precision floating-point numbers, which can take up to 53
bits to represent. The new int data type refers to 32-bit signed integers (postive or negative) and uint refers to 32-bit unsigned integers
(positive only). As of Flash Player 9, ActionScript handles int and uint more efficiently than Number, so unless you specifically need floating-point
numbers, use one of the new data types.
Your constructor should already be up to date, but let's take a quick review:
// CONSTRUCTOR
public function SoundSync(stream:URLRequest=null, context:SoundLoaderContext=null) {
super(stream, context);
init();
}
This should look familiar, thanks to its similarity to the ActionScript 2.0
version. As before, this constructor defers to its superclass and then performs
its own initialization. Note, however, that in ActionScript 3.0, optional
parameters must be given a default value (here, null, for both).
Much of this will feel familiar.
Type the following ActionScript after the //
METHODS comment:
// init
private function init():void {
_cuePoints = new Array();
_currentCuePoint = 0;
_timerInterval = 50;
_startTime = 0.0;
}
Here, a number of private properties are initialized to their default
values. Note the lowercase "v" in the void data type appended to the function, which differs
from the uppercase "V" in the Void data type in ActionScript 2.0.
This method represents the first significant departure from the ActionScript
2.0 original. In ActionScript 3.0, event handling has been revamped across the
board for consistency, control, and ease of use. To accommodate this overhaul,
you must replace the generic Object instances in the _cuePoints array with instances of a custom CuePointEvent class that extends the new Event class.
If you're using Flash CS3, create a new ActionScript file and save it as CuePointEvent.as in the same folder that contains the ActionScript 3.0 version of SoundSync.as. If you're using Flex Builder 2, select File > New > ActionScript Class. In the New ActionScript Class dialog box, make sure the Package field reads net.quip.sound. Type CuePointEvent into the Name field and type Event into the Superclass field. Check the Generate Constructor from Superclass option and then click the Finish button. Update the result with—or, in Flash CS3, simply type—the following ActionScript into the CuePointEvent.as file:
package net.quip.sound
{
import flash.events.Event;
public class CuePointEvent extends Event
{
// PROPERTIES
public static const CUE_POINT:String = "cuePoint";
public var name:String;
public var time:uint;
// CONSTRUCTOR
public function CuePointEvent(type:String, cuePointName:String, cuePointTime:uint, bubbles:Boolean = false, cancelable:Boolean = false)
{
super(type, bubbles, cancelable);
this.name = cuePointName;
this.time = cuePointTime;
}
// METHODS
// Clone
public override function clone():Event
{
return new CuePointEvent(type, name, time, bubbles, cancelable);
}
}
}
This class features three public properties, the first of which is a
constant: the string cuePoint.
The reason for this property is so that the event's type can be specified in
outside code according to recommended best practices; that is, with CuePointEvent.CUE_POINT, rather than the
string it represents. The second and third properties declare the cue point's name and time properties.
As you update your copy of this code, pay attention to the order of the
parameters in the constructor. CuePointEvent requires type, cuePointName, and cuePointTime, and then accepts a couple
more that are optionally used by the base Event class. The call to super passes along some of these parameters in the order expected by Event. The others set the values of
their corresponding properties.
Finally, the Event.clone() method is overridden to return a new instance of the derivative class, CuePointEvent, rather than Event itself.
Now you're set to write the SoundSync.addCuePoint() method. Return to the SouncSync.as file and type the following ActionScript
after the init() method:
// Add Cue Point
public function addCuePoint(cuePointName:String, cuePointTime:uint):void {
_cuePoints.push(new CuePointEvent(CuePointEvent.CUE_POINT, cuePointName, cuePointTime));
_cuePoints.sortOn("time", Array.NUMERIC);
}
Outside of the new requirement for a custom Event derivative, this method is nearly identical to the
ActionScript 2.0 version.
Only a single data type change makes this method differ from its previous
version. Here, the local counter variable is typed as uint instead of Number. Type the
following after the addCuePoint() method:
// Get Cue Point
public function getCuePoint(nameOrTime:Object):Object {
var counter:uint = 0;
while (counter < _cuePoints.length) {
if (typeof(nameOrTime) == "string") {
if (_cuePoints[counter].name == nameOrTime) {
return _cuePoints[counter];
}
} else if (typeof(nameOrTime) == "number") {
if (_cuePoints[counter].time == nameOrTime) {
return _cuePoints[counter];
}
}
counter++;
}
return null;
}
The next four methods (getCurrentCuePointIndex(), getNextCuePointIndex(), removeCuePoint(), and removeAllCuePoints()) change only
superficially in the course of this migration. The Number data type is replaced with uint where appropriate, Void is replaced with void, and the previous
"challenges" dealt with in the getNextCuePoint() method are handled differently. This time, the incoming parameter is already
supplied in milliseconds and the possible null value is ascertained with isNaN() due to stricter number handling in ActionScript 3.0.
That said, type the following code after the getCuePoint() method:
// Get Current Cue Point Index
private function getCurrentCuePointIndex(cuePoint:CuePointEvent):uint {
var counter:uint = 0;
while (counter < _cuePoints.length) {
if (_cuePoints[counter].name == cuePoint.name) {
return counter;
}
counter++;
}
return null;
}
// Get Next Cue Point Index
private function getNextCuePointIndex(milliseconds:Number):uint {
if (isNaN(milliseconds)) {
milliseconds = 0;
}
var counter:uint = 0;
while (counter < _cuePoints.length) {
if (_cuePoints[counter].time >= milliseconds) {
return counter;
}
counter++;
}
return null;
}
// Remove Cue Point
public function removeCuePoint(cuePoint:CuePointEvent):void {
_cuePoints.splice(getCurrentCuePointIndex(cuePoint), 1);
}
// Remove All Cue Points
public function removeAllCuePoints():void {
_cuePoints = new Array();
}
Here is the second major divergence from the original class's approach. In
ActionScript 3.0, the Sound class features neither a start() nor a loadSound() method.
The closest counterparts are play() and load(). Of those, only
the former causes audio to actually start playing. This means you only need to
override the play() method,
but there's a bit more to account for in this version.
Type the following ActionScript after the removeAllCuePoints() method:
// Play
public override function play(startTime:Number=0.0, loops:int=0, sndTransform:SoundTransform=null):SoundChannel {
_soundChannel = super.play(startTime, loops, sndTransform);
_soundChannel.addEventListener(Event.SOUND_COMPLETE, onSoundComplete);
// Reset current cue point
_startTime = startTime;
_loops = 0;
_currentCuePoint = getNextCuePointIndex(startTime);
// Poll for cue points
_timer = new Timer(_timerInterval);
_timer.addEventListener(TimerEvent.TIMER, pollCuePoints);
_timer.start();
return _soundChannel;
}
Note the override keyword
in the function declaration, which is now required for this practice. In
ActionScript 3.0, the audible manifestation of played audio is managed by an
instance of the SoundChannel class, which happens to be set by the return value of the play() method. Your custom SoundSync class features a private _soundChannel property to reference this
instance, which is immediately employed to listen for a soundComplete event when the audio
stops. This allows you to cancel polling when appropriate.
Next, the play() method's
optional startTime and loops parameters are noted for later
use. Actually, the private _loops property isn't just a twin of the loops parameter, which refers to the number of times the audio should repeat. In the
class you're writing, _loops is incremented by the pollCuePoints() method to facilitate some required arithmetic elsewhere in the class. In
ActionScript 2.0, the Sound.position property restarts to zero when looped audio repeats, which makes it easy to
reset cue point polling. In ActionScript 3.0, the corresponding property, SoundChannel.position, does not reset to
zero. For this reason, _loops is used to compensate for the value of SoundChannel.position when dealing with looped audio.
Next, the _currentCuePoint property, as before, is set by way of getNextCuePointIndex().
The new Timer class replaces
the setInterval() function
to carry out the repeated polling. Here the _timer property stores the reference to a Timer instance and listens for the TimerEvent.TIMER event in order to trigger the pollCuePoints() method. Finally, _soundChannel is returned to satisfy the Sound.play() override.
Similar to the original version, this method cancels polling when you choose
to explicitly stop the audio. Note that this not an override because stopping
is normally handled by the SoundChannel class in ActionScript 3.0. Type the following ActionScript after the play() method:
// Stop
public function stop():void {
_soundChannel.stop();
dispatchEvent(new Event("stop"));
// Kill polling
_timer.stop();
}
The "brains of the class" in ActionScript 3.0 is remarkably
similar to its previous incarnation. Remember, in this version of the language, SoundChannel.position does
not reset to zero for looped audio, so the internal _loops property is used to multiply its
value in cases of looping. Other than that, the concept is the same: Note the
current and next cue point's time properties. If the audio's position falls between those values, dispatch a cuePoint event. In cases where no next cue point is available, check the arbitrary span
of time between this cue point and twice the timer's interval period.
Type the following ActionScript after the stop() method:
// Poll Cue Points
private function pollCuePoints(event:TimerEvent):void {
var time:Number = _cuePoints[_currentCuePoint].time + (length * _loops);
var span:Number = 0;
if (_cuePoints[_currentCuePoint + 1] == undefined) {
span = time + _timerInterval * 2;
} else {
span = _cuePoints[_currentCuePoint + 1].time + (length * _loops);
};
if (_soundChannel.position >= time && _soundChannel.position <= span) {
// Dispatch event
dispatchEvent(_cuePoints[_currentCuePoint]);
// Advance to next cue point ...
if (_currentCuePoint < _cuePoints.length - 1) {
_currentCuePoint++;
} else {
_currentCuePoint = getNextCuePointIndex(_startTime);
_loops++;
}
}
}
As before, make sure to cancel cue point polling in cases where the audio
stops on its own and dispatch a soundComplete event to make up for nabbing the one already provided.
Type the following ActionScript after the //
EVENT HANDLERS comment:
// onSoundComplete
public function onSoundComplete(event:Event):void {
// Reset current cue point
_currentCuePoint = 0;
// Kill polling
_timer.stop();
// Dispatch event
dispatchEvent(new Event(Event.SOUND_COMPLETE));
}
If you're using Flash CS3, you can call on the SoundSync class from a timeline frame script in much the
same way it was done in the earlier ActionScript 2.0 document. The only
difference, in fact, occurs in the way the audio is loaded and events are handled:
// Import class
import net.quip.sound.SoundSync;
import net.quip.sound.CuePointEvent;
// Stop main timeline
stop();
// Create an instance of SoundSync
var ss:SoundSync = new SoundSync();
// Use instance to add cue points
// (Note that even though presidents
// are added alphabetically by surname,
// they're triggered numerically by
// cue point time)
ss.addCuePoint("Benjamin Franklin", 20100);
ss.addCuePoint("Ulysses S. Grant", 16479);
ss.addCuePoint("Alexander Hamilton", 9431);
ss.addCuePoint("Andrew Jackson", 13278);
ss.addCuePoint("Thomas Jefferson", 2439);
ss.addCuePoint("Abraham Lincoln", 5480);
ss.addCuePoint("George Washington", 0);
// Use instance to load external MP3
ss.load(new URLRequest("green_presidents.mp3"));
ss.play();
// Assign event handlers
ss.addEventListener(CuePointEvent.CUE_POINT, onCuePoint);
ss.addEventListener(Event.SOUND_COMPLETE, onSoundComplete);
// On Cue Point
function onCuePoint(event:CuePointEvent):void {
play();
}
// On Sound Complete
function onSoundComplete(event:Event):void {
play();
}
The ZIP file that accompanies this article includes an ActionScript 3.0 version of the timeline example shown earlier, this time named green_presidents_as3.fla. Make sure the companion audio file, green_presidents.mp3, is in the same folder as the FLA file. Also make sure that the ActionScript 3.0 version of SoundSync.as and CuePointEvent.as are in a folder named "sound" inside a folder named "quip" inside a folder named "net" that is accessible to a folder designated in your global classpaths setting for ActionScript 3.0.
Note that Flash CS3 may give you a warning (see Figure 5) when you test your movie. The full text of this warning is: "Migration issue: The onSoundComplete event handler is not triggered automatically by Flash Player at run time in ActionScript 3.0. You must first register this handler for the event using addEventListener ('soundComplete', callback_handler)."

Figure 5. Possible warning at compile time
To be sure, this a helpful warning because it serves as a reminder that this
particular event is no longer a member of the Sound class in ActionScript 3.0; it is now a member of SoundChannel. Flash CS3 thinks you're
trying to override the Sound.onSoundComplete event of ActionScript 2.0, but the occurrence of addEventListener()inside the play() method of this class correctly
sidesteps the problem. In this case, you may ignore the warning.
Remember that SoundSyncExample.as file? If you're using Flex Builder 2, now's the time to open it again. Update your file as follows:
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.net.URLRequest;
import net.quip.sound.CuePointEvent;
import net.quip.sound.SoundSync;
public class SoundSyncExample extends Sprite
{
// CONSTRUCTOR
public function SoundSyncExample()
{
var ss:SoundSync = new SoundSync();
ss.load(new URLRequest("green_presidents.mp3"));
ss.addCuePoint("Benjamin Franklin", 20100);
ss.addCuePoint("Ulysses S. Grant", 16479);
ss.addCuePoint("Alexander Hamilton", 9431);
ss.addCuePoint("Andrew Jackson", 13278);
ss.addCuePoint("Thomas Jefferson", 2439);
ss.addCuePoint("Abraham Lincoln", 5480);
ss.addCuePoint("George Washington", 0);
ss.load(new URLRequest("green_presidents.mp3"));
ss.play();
ss.addEventListener(CuePointEvent.CUE_POINT, onCuePoint);
ss.addEventListener(Event.SOUND_COMPLETE, onSoundComplete);
}
// On Cue Point
private function onCuePoint(event:CuePointEvent):void {
trace("Cue point: " + event.name + ", " + event.time);
}
// On Sound Complete
private function onSoundComplete(event:Event):void {
trace("Audio finished: " + event.type);
}
}
}
Before you test this class in Flex Builder 2, make sure to import green_presidents.mp3 from the ZIP file that accompanies this article into the SoundSyncExample project. Select File > Import and then choose File System in the Import dialog box (see Figure 6).

Figure 6. Importing an MP3 file into a Flex Builder 2 project
Click the Next button. In the From Directory field, browse to the folder into which you unzipped this article's files. Place a check mark next to green_presidents.mp3. In the Into Folder field, browse to the SoundSyncExample project. Finally, click the Finish button.
Select Run > Debug SoundSyncExample. This launches your default browser
in order to play the SWF file that contains your application. While the SWF
file is running in the browser, switch to Flex Builder 2 and look at the
Console panel to see trace() outputs as cue points are reached. Note that even though presidents are added
alphabetically by surname, they're triggered numerically by cue point time.
Alternatively, you may compile the SoundSyncExample class in Flash CS3 by using the new Document Class field in the Property
inspector for ActionScript 3.0 documents. Select File > New > General tab
> Flash File (ActionScript 3.0) to create a new document and save it by
whatever name you choose. Since the SoundSyncExample.as file resides wherever
your Flex Builder 2 projects are stored, you'll have to update your global
classpath settings in Flash CS3 to include the root folder of your SoundSyncExample
project.
Once this setting has been updated, put a copy of the MP3 file into the same
folder as the one that holds your new FLA file. Type SoundSyncExample into the Document Class field of the Property inspector and select Control >
Test Movie to compile a SWF file. Keep an eye on the Output panel to see the
same trace() output as seen
in Flex Builder 2. (You may notice the same warning as described earlier and
may ignore it here for the same reason.)
In this tutorial, you stepped through the creation of a custom SoundSync class in both ActionScript 2.0
and 3.0. The code extends the native Sound class in both languages and adds cue point functionality to the playback of
audio files. You were introduced to a number of differences between these two
versions of ActionScript, including new data types and a change in the
structure of familiar objects.
Migrating code can be a daunting prospect, but I hope this exercise has quelled a few fears of the unknown. Although the landscape in ActionScript 3.0 has broadened significantly, it is still just ActionScript. With new trails for you to explore, the hike just got more exciting!
To learn more about ActionScript 3.0, including tips on migrating to the new language, consider the following resources:
To learn more about ActionScript 2.0, consider the following resources: