SoundTouch AS3 is a port of the SoundTouch audio processing library. It allows realtime processing of audio in Flash 10 and includes filters that perform time compression/expansion and rate transposition. In tandem, these filters can perform pitch-shifting.

Sample

// `SimplePlayer` is an example of how to use the SoundTouch AS3 library to play
// a `Sound` object. It implements play, pause and seek, while allowing the
// SoundTouch filter parameters to be manipulated during playback.
//### License
// The SoundTouch AS3 library, like the original SoundTouch C++ library, is
// released under the LGPL.
/*
* SoundTouch AS3 audio processing library
* Copyright (c) Ryan Berdeen
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package {
import com.ryanberdeen.soundtouch.SimpleFilter;
import com.ryanberdeen.soundtouch.SoundTouch;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.SampleDataEvent;
import flash.media.Sound;
import flash.media.SoundChannel;
//### Events
// SimplePlayer dispatches two events: `play` and `pause`. There is no
// equivalent to the `progress` event; use a timer if you need to monitor
// the position.
/** Dispatched when the sound is played. */
[Event(type="flash.events.Event", name="play")]
/** Dispatched whent the sound is paused. */
[Event(type="flash.events.Event", name="pause")]
//
/**
* Example of an audio player using Soundtouch. It supports manipulating the
* tempo, rate, and pitch during playback.
*/
public class SimplePlayer extends EventDispatcher {
//### Private variables
// The `outputPosition` when the current `soundChannel` started playing.
// This value is used to calculate the `outputPosition`, as the
// `position` of the `soundChannel` only gives us the position since the
// most recent invocation of `play()`.
private var channelOffset:Number;
// The `outputPosition` and `position` when a filter parameter was
// changed. These two values are used to calculate the `position` in the
// source `sound`.
private var filterChangedOutputPosition:Number;
private var filterChangedPosition:Number;
// The `SoundTouch` object is a wrapper around two others: a `Stretch`
// and a `RateTransposer`. It takes care of wiring them all together in
// the correct order. If you only need to change the tempo, you can use
// a `Stretch` object by itself. If you only need to change the rate,
// you could use `RateTransposer`, but in that case, SoundTouch AS3 is
// probably much more than you need.
private var soundTouch:SoundTouch;
// The `SimpleFilter` handles the input and output buffering to use
// `SoundTouch` with a `sound` as input.
private var sound:Sound;
private var filter:SimpleFilter;
// Flash's source of audio. The `soundChannel` will control the
// `outputSound`, which `extract`s data from the `filter`.
private var outputSound:Sound;
private var soundChannel:SoundChannel;
//
public function SimplePlayer(sound:Sound):void {
this.sound = sound;
soundTouch = new SoundTouch();
filter = new SimpleFilter(sound, soundTouch);
outputSound = new Sound();
outputSound.addEventListener(
SampleDataEvent.SAMPLE_DATA, filter.handleSampleData);
channelOffset = 0;
filterChangedOutputPosition = 0;
filterChangedPosition = 0;
}
//### Parameters
// Expose the interesting `SoundTouch` parameters, and keep track of the
// `position` and `outputPosition` when they change.
/** The rate of the underlying SoundTouch object. */
public function get rate():Number {
return soundTouch.rate;
}
/** @private */
public function set rate(rate:Number):void {
beforeUpdateFilter();
soundTouch.rate = rate;
}
/** The tempo of the underlying SoundTouch object. */
public function get tempo():Number {
return soundTouch.tempo;
}
/** @private */
public function set tempo(tempo:Number):void {
beforeUpdateFilter();
soundTouch.tempo = tempo;
}
/** The pitchOctaves of the underlying SoundTouch object. */
public function set pitchOctaves(pitchOctaves:Number):void {
beforeUpdateFilter();
soundTouch.pitchOctaves = pitchOctaves;
}
private function beforeUpdateFilter():void {
filterChangedPosition = position;
filterChangedOutputPosition = outputPosition;
}
//### Position
// We expose positions in terms of the source `sound`.
// The current position is calculated based on the `position` and
// `outputPosition` when the filter was last changed. Since it last
// changed, we've played `outputPosition - filterChangedOutputPosition`
// milliseconds of audio. This was played at a rate of
// `soundTouch.tempo * soundTouch.rate` compared to the source--if we
// played one second of audio at with a `rate` of `2`, we moved two
// seconds through the source. If we had previously been at `position`
// `5`, we are now at `7`.
/**
* The position of the source, in milliseconds. The value returned is an
* approximation which can become less accurate if the filter parameters
* are changed.
*/
public function get position():Number {
var outputDelta:Number =
outputPosition - filterChangedOutputPosition;
var positionDelta:Number =
outputDelta * soundTouch.tempo * soundTouch.rate;
return filterChangedPosition + positionDelta;
}
// Seeks to a given position in the source. This will also eliminate any
// error that has accumulated in the calculated `position`.
/** @private */
public function set position(sourcePosition:Number):void {
var resume:Boolean = playing;
pause();
filter.sourcePosition = sourcePosition * 44.1;
filterChangedPosition = sourcePosition;
filterChangedOutputPosition = outputPosition;
if (resume) {
play();
}
}
//
/** The number of milliseconds of audio played. */
private function get outputPosition():Number {
return channelOffset +
(soundChannel != null ? soundChannel.position : 0);
}
//### Control
// Are we playing audio? If we are, the `soundChannel` will not be null.
// Otherwise, we haven't started, or `pause()` has cleared it.
/** Indicates whether the player is currently playing. */
public function get playing():Boolean {
return soundChannel != null;
}
// Start playback, but only if we aren't currently playing.
/** Starts playback. */
public function play():void {
if (!playing) {
soundChannel = outputSound.play();
soundChannel.addEventListener(
Event.SOUND_COMPLETE, soundCompleteHandler);
dispatchEvent(new Event('play'));
}
}
// When we reach the end, clean up and let everyone know.
private function soundCompleteHandler(e:Event):void {
pause();
}
// Pause playback, but only if we are currently playing.
/** Pauses playback. */
public function pause():void {
if (playing) {
// When a `SoundChannel` is stopped, all unplayed data in its
// buffer is lost. Without correction, this would lead to a few
// milliseconds of audio skipped every time the player was
// paused.
//
// `SimpleFilter` buffers the last few milliseconds of audio,
// and lets us seek back to the exact position we were at when
// we paused.
filter.position = outputPosition * 44.1;
// Stop the `soundChannel`, and keep track of how long we've
// played so far.
channelOffset += soundChannel.position;
soundChannel.stop();
soundChannel = null;
dispatchEvent(new Event('pause'));
}
}
//
/** Toggles between play and pause. */
public function togglePlayPause():void {
!playing ? play() : pause();
}
}
}