Changing the pitch of a looped sound at runtime with Actionscript.

I’m so excited to put a link to the game I am working on at work. Its a driving game for a summer sci-fi blockbuster. And that’s about all i can say about it right now. But in the process of making the game, I had to figure out how to change the pitch of a sound in AS3.

As a starting place I found this excellent blog post from Andre Michelle. It showed me everything i needed to know about how to change the pitch. But it did not work with sounds that looped. And since the engine noise for the game was a 10 second loop, I had to make some modifciations.

And then my friend Ian told me he was working on a new breed of fart app, one in which you can control the pitch of the fart as it plays. Next thing you know, this short code post:

package sound
{
	import flash.events.Event;
	import flash.events.SampleDataEvent;
	import flash.media.Sound;
	import flash.media.SoundChannel;
	import flash.media.SoundTransform;
	import flash.net.URLRequest;
	import flash.utils.ByteArray;
 
	/**
	 * @author Andre Michelle (andr...@gmail.com)
         * Modified by Zach Foley aka The Plastic Sturgeon
	 */
	public class MP3Pitch
	{
		private const BLOCK_SIZE: int = 3072;
 
		private var _mp3: Sound;
		private var _sound: Sound;
 
		private var _target: ByteArray;
 
		private var _position: Number;
		private var _rate: Number;
		private var repeat:SoundChannel;
		private var _volume:Number = 1;
		private var byteArray:ByteArray;
 
                // Pass in your looped Sound
		public function MP3Pitch( pitchedSound: Sound)
		{
			_target = new ByteArray();
			_mp3 =  pitchedSound;
 
			_position = 0.0;
			_rate = 0.0;
 
			_sound = new Sound();
			_sound.addEventListener( SampleDataEvent.SAMPLE_DATA, sampleData );
			repeat = _sound.play();
		}
 
		public function get rate(): Number
		{
			return _rate;
		}
 
                // Also added a handy volume setter
		public function set volume( value: Number ): void
		{
			_volume = value;
			repeat.soundTransform = new SoundTransform(_volume);
		}
 
                // use this to set the pitch of your sound
		public function set rate( value: Number ): void
		{
			if( value < 0.0 )
				value = 0;
 
			_rate =  value;
		}
 
		private function sampleData( event: SampleDataEvent ): void
		{
			//-- REUSE INSTEAD OF RECREATION
			_target.position = 0;
 
			//-- SHORTCUT
			var data: ByteArray = event.data;
 
			var scaledBlockSize: Number = BLOCK_SIZE * _rate;
			var positionInt: int = _position;
			var alpha: Number = _position - positionInt;
 
			var positionTargetNum: Number = alpha;
			var positionTargetInt: int = -1;
 
			//-- COMPUTE NUMBER OF SAMPLES NEED TO PROCESS BLOCK (+2 FOR INTERPOLATION)
			var need: int = Math.ceil( scaledBlockSize ) + 2;
 
			//-- EXTRACT SAMPLES
			var read: int = _mp3.extract( _target, need, positionInt );
 
			var n: int = read == need ? BLOCK_SIZE : read / _rate;
 
			var l0: Number;
			var r0: Number;
			var l1: Number;
			var r1: Number;
 
			for( var i: int = 0 ; i < n ; ++i )
			{
				//-- AVOID READING EQUAL SAMPLES, IF RATE < 1.0
				if( int( positionTargetNum ) != positionTargetInt )
				{
					positionTargetInt = positionTargetNum;
 
					//-- SET TARGET READ POSITION
					_target.position = positionTargetInt << 3; 	 					//-- READ TWO STEREO SAMPLES FOR LINEAR INTERPOLATION 					l0 = _target.readFloat(); 					r0 = _target.readFloat(); 					l1 = _target.readFloat(); 					r1 = _target.readFloat(); 				} 				 				//-- WRITE INTERPOLATED AMPLITUDES INTO STREAM 				data.writeFloat( l0 + alpha * ( l1 - l0 ) ); 				data.writeFloat( r0 + alpha * ( r1 - r0 ) ); 				 				//-- INCREASE TARGET POSITION 				positionTargetNum += _rate; 				 				//-- INCREASE FRACTION AND CLAMP BETWEEN 0 AND 1 				alpha += _rate; 				while( alpha >= 1.0 ) --alpha;
			}
 
			//-- FILL REST OF STREAM WITH ZEROs
			if( i < BLOCK_SIZE )
			{
				while( i < BLOCK_SIZE ) 				{ 					data.writeFloat( 0.0 ); 					data.writeFloat( 0.0 ); 					 					++i; 				} 			} 			//-- INCREASE SOUND POSITION 			_position += scaledBlockSize;                        // My little addition here: 			if (_position > _mp3.length * 44.1) {
				_position = 0;
				_target.position = 0;
			}
		}
	}
}

To use it just create a new MP3 Pitch:

pitched = new MP3Pitch(new CarLoopLow())

To set the rate just call:

pitched.rate = 0;

To set the volume:

pitched.rate = 0;

Also, make sure your audio is exported as MP3 and I strongly suggest using at a minimum the 64kbps quality.

About The Plastic Sturgeon

This is my site. It gets pretty biographical on the About page.
This entry was posted in Tutorials and tagged , , , . Bookmark the permalink.

3 Responses to Changing the pitch of a looped sound at runtime with Actionscript.

  1. whizzkid says:

    The example for changing pitch and volume are both:
    pitched.rate = 0;

    I’m guessing the second should’ve been:
    pitched.volume = 1; // a number between 0 and 1

    :)

  2. whizzkid says:

    Also, the source didn’t paste well in this page. lots of code on 1 line, and all ‘s have been replaced by their htmlcode.

  3. whizzkid says:

    And last but not least; I was looking for the reason why the class wasn’t working, and missed the bit where you did say sound.play() but also set the rate to 0. So that wasn’t immediately apparant to me :)

    so you might want to add something like ‘set the rate to anything above 0 to actually start playing the sound’ :)

    regards,

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>