As3 how to call and use an external class - actionscript-3

I have this cool As3 MP3pitch package, which I got from here: http://blog.andre-michelle.com/2009/pitch-mp3/#more-483
package components
{
import flash.events.Event;
import flash.events.SampleDataEvent;
import flash.media.Sound;
import flash.net.URLRequest;
import flash.utils.ByteArray;
/**
* #author Andre Michelle (andre.michelle#gmail.com)
*/
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;
public function MP3Pitch( url: String )
{
_target = new ByteArray();
_mp3 = new Sound();
_mp3.addEventListener( Event.COMPLETE, complete );
_mp3.load( new URLRequest( url ) );
_position = 0.0;
_rate = 1.0;
_sound = new Sound();
_sound.addEventListener( SampleDataEvent.SAMPLE_DATA, sampleData );
}
public function get rate(): Number
{
return _rate;
}
public function set rate( value: Number ): void
{
if( value < 0.0 )
value = 0;
_rate = value;
}
private function complete( event: Event ): void
{
_sound.play();
}
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;
}
}
}
How can I include it into my "main" as3 file and use it? I can't just slap it into the same file, since the main file already has it's own package and class.
Essentially what I'm trying to do here is to load a sound file, play it, and change it's pitch in relation to the values what I have already.
Thanks in advance.

You have to create a folder with the same name as the package (here components) and put this class in it. Then from your main, import this class:
import components.MP3Pitch
You just have to instanciate this class and use it in your code:
var pitcher:MP3Pitch = new MP3Pitch("mymp3.mp3");
pitcher.rate = 2;

package {
import components.MP3Pitch;
public class Main extends Sprite
{
public function Main() {
new MP3Pitch("http://path-to-mp3-file.com/");
}
}
}

Related

mismatched argument count in actionscript

I keep getting this ArgumentError: Error #1063: Argument count mismatch on Car(). Expected 1, got 0. I'm confused cause I am passing staggerPosition to Car(). but it still says it is expecting 1 argument. If that is not what the error means how do I fix it? I double-checked all of my connections.
...
package {
import flash.display.*;
import flash.events.*;
public class cityAPP2 extends MovieClip {
private var carList: Array;
private var nCars: int = 16;
public function cityApp2() {
//TASK 1: ADD 16 CARS
carList = new Array();
var staggerPosition: int = 15;
for (var i: int = 0; i < nCars; i++) {
var car: Car = new Car(staggerPosition);
staggerPosition += 20;
car.x = car.mX;
car.y = car.mY;
addChild(car);
carList.push(car);
}
//TASK 2: REGISTER A LISTENER EVENT
addEventListener(Event.ENTER_FRAME, update);
}
public function update(event: Event) {
for (var i: int = 0; i < nCars; i++) {
carList[i].moveIt();
carList[i].x = carList.mx;
}
}
}
}
package {
import flash.display.*;
import flash.events.*;
public class Car extends MovieClip {
//DATA MEMBERS
public var mX: int;
public var mY: int;
public var directionFactor: int;
public var velocity: Number;
public var endZone: int;
public function Car(yPosition:int) {
this.mY = yPosition;
//TASK 1: COMPUTE THE DIRECTION
this.directionFactor = (Math.floor(Math.random() * 2) == 0) ? -1 : 1;
//TASK 2: SET THE SCALE, mX, mY, AND ENDZONE
this.scaleX = this.directionFactor;
if (this.directionFactor == -1) {
this.endZone = 800;
} else {
this.endZone = -100;
}
this.mX = endZone;
//TASK 3: SET THE VELOCITY TO RUN A RANDOM VALUE
this.velocity = Math.floor(Math.random() * 15 + 2) * this.directionFactor;
}
public function moveIt(): void {
//TASK 1: UPDATE THE X LOCATION OF THE CAR
this.mX += this.velocity;
trace(this.mX);
// TASK 2: ROTATE THE WHEELS OF THE CAR
//TASK 3: CHECK IF THE CAR HAS MOVED OFF THE SCREEN
if (this.directionFactor == -1 && this.mX < -200 || this.directionFactor == 1 && this.mX > 850) {
this.mX = endZone;
}
}
}
}
...
You likely have an instance of Car on the stage somewhere (not created in code). When you do this the constructor will be called with no arguments.
You can either remove that instance from the stage or add a default value for your argument so it won't cause an error:
public function Car(yPosition:int = 0) {
...
}

create sine wave audio with as3 - sweep up and down frequency

I'm making sinewave sound with AS3 using SampleDataEvent. I can make a pure sinewave easily enough, but if i try to sweep through frequencies i get horrible popping. Here's the code i'm using - any help would be great.
package
{
import flash.display.*;
import flash.events.*;
import flash.media.*;
import flash.utils.Timer;
public class DynamicSound extends Sprite
{
private var sound:Sound;
private var noise:Number = 0;
private var f:Number = 1000;
private var v:Number = 1;
private var sweepDown:Boolean = true;
// make the sound
public function DynamicSound():void
{
sound = new Sound();
sound.addEventListener(SampleDataEvent.SAMPLE_DATA, onCallback);
sound.play();
sweep();
}
// create the sinewave
private function onCallback(e:SampleDataEvent):void
{
for (var i:int = 0; i < 8192; i++)
{
noise += 1;
var sampleNumber = noise;
e.data.writeFloat(v * Math.sin(sampleNumber * f / 44100));
e.data.writeFloat(v * Math.sin(sampleNumber * f / 44100));
}
}
// sweep up and down frequency
private function sweep() {
var timer:Timer = new Timer(100);
timer.addEventListener(TimerEvent.TIMER, onTimer);
timer.start();
function onTimer(event:TimerEvent):void
{
if(sweepDown){
f--;
} else {
f++;
}
if (f <=600 ){
sweepDown = false;
}
if (f >= 1000) {
sweepDown = true;
}
trace(f);
}
}
}
}
I believe the fault is with the approach, the sweep needs to be gradual, when you step it like that you create an abrupt change in the sound wave, which is interpreted as a short high-frequency signal - a pop or click. The way I'd recommend you do this modulation would be inside the callback loop.
set a destination freq (dF) and a current frequency(cF), and instead of doing an abrupt change set cF = cF*0.8 + dF*0.2 inside the loop, this should remove the abrupt change and have it happen over several samples.
//When the frequency changes, the phase will also change.
//By adjusting the phase, there will no longer be horrible popping.
//This will solve the problem:
var f_old:Number = f;
noise=noise*f_old/f; f_old=f;
//Copy/paste in the first frame of the main timeline:
import flash.display.*;
import flash.events.*;
import flash.media.*;
import flash.utils.Timer;
var sound:Sound;
var noise:Number = 0;
var f:Number = 1000;
var v:Number = 1;
var sweepDown:Boolean = true;
var f_old:Number = f;
// make the sound
sound = new Sound();
sound.addEventListener(SampleDataEvent.SAMPLE_DATA, onCallback);
sound.play();
sweep();
// create the sinewave
function onCallback(e:SampleDataEvent):void
{
for (var i:int = 0; i < 8192; i++)
{
noise += 1;
var sampleNumber = noise;
e.data.writeFloat(v * Math.sin(sampleNumber * f / 44100));
e.data.writeFloat(v * Math.sin(sampleNumber * f / 44100));
}
}
// sweep up and down frequency
function sweep() {
var timer:Timer = new Timer(100);
timer.addEventListener(TimerEvent.TIMER, onTimer);
timer.start();
function onTimer(event:TimerEvent):void
{
if(sweepDown){
f--;
} else {
f++;
}
if (f <=600 ){
sweepDown = false;
}
if (f >= 1000) {
sweepDown = true;
}
trace(f);
//Keep the instantaneous value at the same level:
noise=noise*f_old/f;
f_old=f;
}
}
//Explanation:
//The instantaneous value of the wave must not change at the moment of frequency change.
//In this script the instantaneous value is determined by:
//v * Math.sin(sampleNumber * f / 44100)
//noise = sampleNumber → v * Math.sin(sampleNumber * f / 44100) = v * Math.sin(noise * f / 44100)
//Keep the instantaneous value at the same level:
//v * Math.sin(noise_new * f / 44100) = v * Math.sin(noise* f_old / 44100)
//Cancel out v*Math.sin and /44100 →
//noise_new * f = noise* f_old → noise_new = noise*f_old/f
//Because noise gets a new value, there is no need to give noise the name noise_new. →
//noise = noise*f_old/f

AS3 Softbody texture starling

I have created a soft body circle in nape. And now I'm trying to get it texturized. But I'm having trouble, and I can't find the answer. Thats why I'm turning to you guys.
Im trying to do what he is doing in this objective c tutorial:
http://www.uchidacoonga.com/2012/04/soft-body-physics-with-box2d-and-cocos2d-part-44/
any ideas on how to do this with starling and stage3d?
You have to write a custom display object (see Starling manual). Here's a basic example:
package
{
import com.adobe.utils.AGALMiniAssembler;
import flash.display3D.Context3D;
import flash.display3D.Context3DProgramType;
import flash.display3D.Context3DVertexBufferFormat;
import flash.display3D.IndexBuffer3D;
import flash.display3D.VertexBuffer3D;
import flash.geom.Point;
import flash.utils.ByteArray;
import starling.core.RenderSupport;
import starling.core.Starling;
import starling.display.DisplayObject;
import starling.errors.MissingContextError;
import starling.textures.Texture;
public class Ball extends DisplayObject
{
private static const PROGRAM_NAME:String = "ball";
private var _texture:Texture;
private var _numSides:uint;
private static const data32PerVertex:uint = 4;
private var _vertices:Vector.<Number>;
private var _indices:Vector.<uint>;
private var _vertexBuffer:VertexBuffer3D;
private var _indexBuffer:IndexBuffer3D;
public function Ball(initialX:Number, initialY:Number, initialR:Number, texture:Texture, numSides:uint = 10) {
_texture = texture;
_numSides = numSides;
// if the texture is a SubTexture (i.e. a texture from an atlas), then you need
// to modify these values to match the sub-texture UV bounds.
var minU:Number = 0, minV:Number = 0, maxU:Number = 1, maxV:Number = 1;
setupGeometry(initialX, initialY, initialR, minU, minV, maxU, maxV);
createBuffers();
registerPrograms();
}
private function setupGeometry(initialX:Number, initialY:Number, initialR:Number, uMin:Number, vMin:Number, uMax:Number, vMax:Number):void {
const numVertices:uint = _numSides + 1,
numSideVertices:uint = _numSides,
txtCu:Number = (uMin + uMax) / 2, // center of the circle in UV coords
txtCv:Number = (vMin + vMax) / 2,
txtRu:Number = uMax - txtCu, // radiuses of the circle in UV coords
txtRv:Number = vMax - txtCv;
_vertices = new Vector.<Number>(data32PerVertex * numVertices, true);
_indices = new Vector.<uint>(3 * _numSides, true);
var centerVectexIndex:uint = _numSides;
// side vertices
for (var sideVertexI:uint = 0; sideVertexI < numSideVertices; ++sideVertexI) {
var dataOffset:uint = sideVertexI * data32PerVertex,
angle:Number = 2 * Math.PI * sideVertexI / _numSides,
sinA:Number = Math.sin(angle),
cosA:Number = Math.cos(angle);
_vertices[dataOffset ] = initialX + initialR * cosA; // x
_vertices[dataOffset + 1] = initialY + initialR * sinA; // y
_vertices[dataOffset + 2] = txtCu + txtRu * cosA; // u
_vertices[dataOffset + 3] = txtCv + txtRv * sinA; // v
var indexOffset:uint = 3 * sideVertexI;
_indices[indexOffset ] = centerVectexIndex;
_indices[indexOffset + 1] = sideVertexI;
_indices[indexOffset + 2] = (sideVertexI + 1) % numSideVertices;
}
// center vertex
dataOffset = centerVectexIndex * data32PerVertex;
_vertices[dataOffset ] = initialX; // x
_vertices[dataOffset + 1] = initialY; // y
_vertices[dataOffset + 2] = txtCu; // u
_vertices[dataOffset + 3] = txtCv; // v
}
private function createBuffers():void {
var context:Context3D = Starling.context;
if (context == null) {
throw new MissingContextError();
}
_vertexBuffer && _vertexBuffer.dispose();
_indexBuffer && _indexBuffer.dispose();
const verticesCount:uint = _numSides + 1;
_vertexBuffer = context.createVertexBuffer(verticesCount, 4);
_vertexBuffer.uploadFromVector(_vertices, 0, verticesCount);
const indicesCount:uint = 3 * _numSides; // _numSides triangles, 3 indices per each triangle
_indexBuffer = context.createIndexBuffer(indicesCount);
_indexBuffer.uploadFromVector(_indices, 0, indicesCount);
}
private function registerPrograms():void {
var starling:Starling = Starling.current;
if (starling.hasProgram(PROGRAM_NAME)) {
return;
}
// va0.xy - position
// va1.xy - UV coords
// vc0-vc3 - mvp matrix
var vertexAGAL:String =
"m44 op, va0, vc0 \n" +
"mov v0, va1";
var fragmentAGAL:String =
"tex oc, v0, fs0 <2d, clamp, linear, mipnone> \n"; // just sample texture color
var asm:AGALMiniAssembler = new AGALMiniAssembler(),
vertexBytecode:ByteArray = asm.assemble(Context3DProgramType.VERTEX, vertexAGAL),
fragmentBytecode:ByteArray = asm.assemble(Context3DProgramType.FRAGMENT, fragmentAGAL);
starling.registerProgram(PROGRAM_NAME, vertexBytecode, fragmentBytecode);
}
override public function render(support:RenderSupport, parentAlpha:Number):void {
var context:Context3D = Starling.context;
if (context == null) {
throw new MissingContextError();
}
support.finishQuadBatch();
// setup
support.applyBlendMode(_texture.premultipliedAlpha);
context.setProgram(Starling.current.getProgram(PROGRAM_NAME));
context.setVertexBufferAt(0, _vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_2); // position, va0
context.setVertexBufferAt(1, _vertexBuffer, 2, Context3DVertexBufferFormat.FLOAT_2); // uv, va1
context.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, support.mvpMatrix3D, true); // mvp matrix, vc0-vc3
context.setTextureAt(0, _texture.base); // texture, fs0
// draw
context.drawTriangles(_indexBuffer);
support.raiseDrawCount();
// clean up
context.setVertexBufferAt(0, null);
context.setVertexBufferAt(1, null);
context.setTextureAt(0, null);
}
override public function hitTest(localPoint:Point, forTouch:Boolean = false):DisplayObject {
var isHit:Boolean = false;
// to achieve proper mouse handling, you need to place here the code
// that checks if localPoint is contained inside any of triangles and
// sets isHit flag accorgingly.
return isHit ? this : null;
}
}
}
Usage example:
package
{
import flash.display.BitmapData;
import flash.display.GradientType;
import flash.display.Graphics;
import flash.display.Sprite;
import flash.geom.Matrix;
import starling.display.Sprite;
import starling.textures.Texture;
public class BallExperiment extends starling.display.Sprite
{
public function BallExperiment() {
}
public function start():void {
const numSides:uint = 7;
var txt:Texture = createBallTxt(numSides, true);
var ball:Ball = new Ball(200, 200, 50, txt, numSides);
addChild(ball);
}
private function createBallTxt(numSides:uint, debugFillBcgr:Boolean = false):Texture {
var canvas:flash.display.Sprite = new flash.display.Sprite(),
g:Graphics = canvas.graphics;
// as we don't want to use sub-textures in this simple example, we need this
// number to be a power of two: otherwise Starling will internally create
// a power-of-two-sized texture and return a sub-texture of this bigger texture.
const size:Number = 512;
// we need to make the radius of a ball texture to be smaller than size/2 in order
// to prevent the texture from extending beyond our triangles.
var rScale:Number = Math.cos(Math.PI / numSides),
r:Number = rScale * (size / 2);
g.lineStyle(0, 0, 0);
// draw uniform background to show actual triangulation
if (debugFillBcgr) {
g.beginFill(0xBB4400, 0.2);
g.drawRect(0, 0, size, size);
g.endFill();
}
// draw the ball
g.beginFill(0x0000DD);
g.drawCircle(size / 2, size / 2, r);
var m:Matrix = new Matrix();
m.createGradientBox(size, size);
g.beginGradientFill(GradientType.LINEAR, [0x00DD00, 0x00DD00], [0, 1], [0, 255], m);
g.drawCircle(size / 2, size / 2, r);
g.endFill();
const smallCircleR:Number = r / 10,
smallCircleCR:Number = r - 2 * smallCircleR;
g.beginFill(0xBB0000);
for (var i:uint = 0; i < numSides; ++i) {
var angle:Number = 2 * Math.PI * i / numSides,
cx:Number = size / 2 + smallCircleCR * Math.cos(angle),
cy:Number = size / 2 + smallCircleCR * Math.sin(angle);
g.drawCircle(cx, cy, smallCircleR);
}
g.drawCircle(size / 2, size / 2, smallCircleR);
g.endFill();
// create and return the texture
var bmd:BitmapData = new BitmapData(size, size, true, 0);
bmd.draw(canvas);
return Texture.fromBitmapData(bmd);
}
}
}
Example runner:
package
{
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import starling.core.Starling;
import starling.events.Event;
[SWF(width = 600, height = 500, frameRate = 60)]
public class StarlingTestRunner extends Sprite
{
public function StarlingTestRunner() {
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
Starling.multitouchEnabled = false;
Starling.handleLostContext = false;
var starling:Starling = new Starling(BallExperiment, stage);
starling.showStats = true;
starling.simulateMultitouch = true;
starling.enableErrorChecking = true;
starling.addEventListener(Event.ROOT_CREATED, onTestCreated);
starling.start();
}
private function onTestCreated(e:Event, test:BallExperiment):void {
test.start();
}
}
}
The result:
To distort the ball, just modify those elements of _vertices vector that correspond to x and y coordinates (i.e. elements with indices 4n and 4n + 1, where n = 0 .. numSides) and then re-upload _vertices array to the vertex buffer.
Alternatively, you can implement ball geometry using VertexData helper class, as shown in Starling manual.

AS3 playback of sound byte array doesn't begin at the start

I'm currently recording and storing a ByteArray of sound and then playing it back. But for some reason the playback starting position of the ByteArray is 163840, not 0 as I need it to be.
Would anyone have any ideas why this might be happening?
Thanks,
Mark
var soundBA:ByteArray = new ByteArray();
var sound:Sound = new Sound();
var ch:SoundChannel = new SoundChannel();
var recordingsArray:Array = new Array();
//imagine I have successfully recorded and stored the sound into recordingsArray
soundBA.clear();
soundBA.length = 0;
//I collect the recorded byteArray within an array
soundBA.writeBytes(recordingsArray[0]);
soundBA.position = 0;
trace("Start POS "+soundBA.position); //traces 0
sound.addEventListener(SampleDataEvent.SAMPLE_DATA, sound_sampleDataHandler, false, 0, true);
ch=sound.play();
this.addEventListener(Event.ENTER_FRAME, updateSeek, false, 0, true);
public function updateSeek(event:Event):void {
trace("current Pos "+soundBA.position); //the first trace event is "current Pos 163840"
}
function sound_sampleDataHandler(event:SampleDataEvent):void {
for (var i:int = 0; i < 8192; i++)
{
if (soundBA.bytesAvailable < 4)
{
break;
}
var sample:Number = soundBA.readFloat();
event.data.writeFloat(sample);
event.data.writeFloat(sample);
}
}
This is because soundBA.position is position in byte array, not position of playback. It runs ahead of playback position because of sound lag. To determine current playback position use SoundChannel.position:
public function updateSeek(event:Event):void {
trace("current pos in ms: " + ch.position);
trace("current pos in bytes: " + (ch.position * 44.1 * 4 * 2));
trace("current pos in %: " + (100 * ch.position / sound.length));
}
UPD: I was referring to the case when sound is decoded using additional Sound object, e.g.:
package
{
import flash.display.Sprite;
import flash.events.Event;
import flash.events.SampleDataEvent;
import flash.media.Sound;
import flash.media.SoundChannel;
import flash.net.URLRequest;
import flash.utils.ByteArray;
public class SoundTest extends Sprite
{
private var soundSrc:Sound;
private var soundPlayer:Sound;
private var soundData:ByteArray;
private var soundChannel:SoundChannel;
public function SoundTest()
{
soundSrc = new Sound();
soundSrc.addEventListener(Event.COMPLETE, startPlayback);
soundSrc.load(new URLRequest("sound.mp3"));
}
private function startPlayback(e:Event = null):void
{
soundData = new ByteArray();
soundSrc.extract(soundData, soundSrc.length * 44.1, 0);
soundData.position = 0;
soundPlayer = new Sound();
soundPlayer.addEventListener(SampleDataEvent.SAMPLE_DATA, onSampleData);
soundChannel = soundPlayer.play();
addEventListener(Event.ENTER_FRAME, updateTime);
}
private function onSampleData(e:SampleDataEvent):void
{
for (var i:int = 0; i < 8192; i++)
{
if (soundData.bytesAvailable < 4)
{
break;
}
var sampleL:Number = soundData.readFloat();
var sampleR:Number = soundData.readFloat();
e.data.writeFloat(sampleL);
e.data.writeFloat(sampleR);
}
}
private function updateTime(e:Event):void
{
trace("current pos in ms: " + soundChannel.position);
trace("current pos in bytes: " + (soundChannel.position * 44.1 * 4 * 2));
trace("current pos in % (method 1): " + (100 * soundChannel.position / soundSrc.length));
// it also works
trace("current pos in % (method 2): " + (100 * soundChannel.position / (soundData.length / (44.1 * 4 * 2))));
}
}
}

Controlling Sound Pitch With Actionscript 3.0?

after browsing the documentation for the sound classes, it seems there is no way to control sound pitch with Actionscript 3.0. there is only the ability to control volume and pan. why is there no pitch property? it's the only sound property missing for the ability to create a full featured sound engine in Actionscript?
i hope i'm misinformed, but in case i'm not are there any alternatives / workarounds to control pitch in AS3?
Andre Michelle has a great article on Pitch control with actionscript 3.0
For reference, here is Andre's sample code:
package components
{
import flash.events.Event;
import flash.events.SampleDataEvent;
import flash.media.Sound;
import flash.net.URLRequest;
import flash.utils.ByteArray;
/**
* #author Andre Michelle (andre.michelle#gmail.com)
*/
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;
public function MP3Pitch( url: String )
{
_target = new ByteArray();
_mp3 = new Sound();
_mp3.addEventListener( Event.COMPLETE, complete );
_mp3.load( new URLRequest( url ) );
_position = 0.0;
_rate = 1.0;
_sound = new Sound();
_sound.addEventListener( SampleDataEvent.SAMPLE_DATA, sampleData );
}
public function get rate(): Number
{
return _rate;
}
public function set rate( value: Number ): void
{
if( value < 0.0 )
value = 0;
_rate = value;
}
private function complete( event: Event ): void
{
_sound.play();
}
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;
}
}
}
Basic usage would be something like:
//create an MP3Pitch instance and load a sound
var mp3:MP3Pitch = new MP3Pitch("/path/to/your/file.mp3");
//change the pitch via rate setter
mp3.rate += 0.5
Extract a bytearray from the sound object, and then manipulate the byte data, returning a new bytearray.
Here is the actual example from the API Reference Doc
var sourceSnd:Sound = new Sound();
var outputSnd:Sound = new Sound();
var urlReq:URLRequest = new URLRequest("test.mp3");
sourceSnd.load(urlReq); sourceSnd.addEventListener(Event.COMPLETE, loaded);
function loaded(event:Event):void {
outputSnd.addEventListener(SampleDataEvent.SAMPLE_DATA, processSound);
outputSnd.play(); }
function processSound(event:SampleDataEvent):void {
var bytes:ByteArray = new ByteArray();
sourceSnd.extract(bytes, 4096);
event.data.writeBytes(upOctave(bytes)); }
function upOctave(bytes:ByteArray):ByteArray {
var returnBytes:ByteArray = new ByteArray();
bytes.position = 0;
while(bytes.bytesAvailable > 0)
{
returnBytes.writeFloat(bytes.readFloat());
returnBytes.writeFloat(bytes.readFloat());
if (bytes.bytesAvailable > 0)
{
bytes.position += 8;
}
}
return returnBytes;
}