package de.kris.sound
{
import flash.events.IOErrorEvent;
import flash.events.ProgressEvent;
import flash.media.SoundTransform;
import flash.accessibility.AccessibilityImplementation;
import flash.events.Event;
import flash.events.SampleDataEvent;
import flash.media.Sound;
import flash.media.SoundChannel;
import flash.media.SoundLoaderContext;
import flash.net.URLRequest;
import flash.utils.ByteArray;
/**
* SoundPitch extends Sound by pitching capabilities.
*
* To change the pitch of sound use the pitch property.
* You can do this anytime you want. If you want to have the change
* having a more contemporary impact on playback, you can also set
* the bufferLength property to a lower value.
*
*
* ------------------------
* NOTEs:
* Backward looping doesn't work really well.
*
*
* http://krisrok.de
*
* @author kris
* @version 0.95
*/
public class SoundPitch extends Sound
{
private const MIN_PITCH:Number = 0.01;
private const MAX_PITCH:Number = 10;
private var _tmpSound:Sound;
private var _tmpPos:int = 0;
private var _bufferLength:int = 1024 * 4;
private var _pitchBufferLength:int = _bufferLength;
private var _ba:ByteArray;
private var _samplesRead:int;
private var _samplesMax:int;
private var _bufferFill:int;
private var _pitch:Number = 1.0;
private var _userPitch:Number = _pitch;
private var _count:Number = 0;
private var _reversed:Boolean = false;
private var _loops:Number;
private var _tmpPos2:int = 0;
/**
* Creates a new SoundPitch object. Should work like the Sound object you already know from AS3.
*
* @param urlRequest_or_SoundObject You can pass a URLRequest causing to start the loading process. OR you can pass an existing Sound object to use!
* @param context I dont really know what that is. But maybe you do.
*/
public function SoundPitch(urlRequest_or_SoundObject:Object = null, context:SoundLoaderContext = null)
{
super(null, context);
if (urlRequest_or_SoundObject is URLRequest)
{
load(URLRequest(urlRequest_or_SoundObject), context);
}
else
if (urlRequest_or_SoundObject is Sound)
{
_tmpSound = Sound(urlRequest_or_SoundObject);
initPlayback();
}
}
/**
* Stops the playback and removes all internal event listeners. Use it!
*/
override public function close():void
{
if (isBuffering)
super.close();
removeEventListener(SampleDataEvent.SAMPLE_DATA, onSamples);
_tmpSound.removeEventListener(Event.COMPLETE, onTmpSoundComplete);
_tmpSound.removeEventListener(Event.ID3, onTmpSoundID3);
_tmpSound.removeEventListener(IOErrorEvent.IO_ERROR, onTmpSoundIOError);
_tmpSound.removeEventListener(Event.OPEN, onTmpSoundOpen);
_tmpSound.removeEventListener(ProgressEvent.PROGRESS, onTmpSoundProgress);
}
/**
* Stops the playback and removes all internal event listeners. Use it!
*/
public function stop():void
{
close();
}
/**
* Works just like the original Sound.play() function - EXCEPT loops when playing backwards, especially with very short sounds. Will be fixed! Maybe!
*
* @param startTime The initial position in milliseconds at which playback should start.
* @param loops Defines the number of times a sound loops before the sound channel stops playback.
* @param sndTransform The initial SoundTransform object assigned to the sound channel.
* @return A SoundChannel object, which you use to control the sound.
*/
override public function play(startTime:Number = 0, loops:int = 0, sndTransform:flash.media.SoundTransform = null):flash.media.SoundChannel
{
addEventListener(SampleDataEvent.SAMPLE_DATA, onSamplesFake);
_tmpPos2 = _tmpPos = startTime * 44.1;
_loops = loops;
var result:SoundChannel = super.play(0, 0, sndTransform);
removeEventListener(SampleDataEvent.SAMPLE_DATA, onSamplesFake);
addEventListener(SampleDataEvent.SAMPLE_DATA, onSamples);
return result;
}
/**
* Works just like the original Sound.load(): Initiates loading of an external MP3 file from the specified URL.
* @param stream
* @param context
*/
override public function load(stream:flash.net.URLRequest, context:flash.media.SoundLoaderContext = null):void
{
_tmpSound = new Sound(stream, context);
_tmpSound.addEventListener(Event.COMPLETE, onTmpSoundComplete);
_tmpSound.addEventListener(Event.ID3, onTmpSoundID3);
_tmpSound.addEventListener(IOErrorEvent.IO_ERROR, onTmpSoundIOError);
_tmpSound.addEventListener(Event.OPEN, onTmpSoundOpen);
_tmpSound.addEventListener(ProgressEvent.PROGRESS, onTmpSoundProgress);
}
private function onTmpSoundComplete(e:Event):void
{
initPlayback();
}
private function initPlayback():void
{
_ba = new ByteArray();
_samplesMax = _tmpSound.length * 44.1;
//_tmpSound.extract(_ba, _samplesMax);
//_ba = new ByteArray();
//trace("samplesmax: " + _samplesMax);
dispatchEvent(new Event(Event.COMPLETE, true, true));
}
private function onSamplesFake(e:SampleDataEvent):void
{
for ( var c:int=0; c<8192; c++ ) {
e.data.writeFloat(0);
e.data.writeFloat(0);
}
}
private function onSamples(e:SampleDataEvent):void
{
_ba = new ByteArray();
//_ba.position = 0;
_bufferFill = 0;
_samplesRead = 0;
var pitch:Number = _pitch;
if (pitch > MIN_PITCH)
{
if(!_reversed)
while (_bufferFill < _pitchBufferLength)
{
_bufferFill += (_samplesRead = _tmpSound.extract(_ba, _pitchBufferLength - _bufferFill, _tmpPos));
if (_bufferFill == _pitchBufferLength)
{
_tmpPos += _samplesRead;
}
else
{
_tmpPos = 0;
if (--_loops < 1)
{
for (var j:int = 0; j < (_pitchBufferLength - _bufferFill)*2; j++)
_ba.writeFloat(0);
_bufferFill = _pitchBufferLength;
soundComplete();
}
}
//_tmpPos = (_bufferFill == _pitchBufferLength ? _tmpPos + _samplesRead : 0);
}
else
{
_tmpPos = (_tmpPos2 - (_pitchBufferLength + 2) + _samplesMax) % _samplesMax;
_tmpPos2 = _tmpPos;
while (_bufferFill < _pitchBufferLength)
{
_bufferFill += (_samplesRead = _tmpSound.extract(_ba, _pitchBufferLength - _bufferFill, _tmpPos));
if (_bufferFill == _pitchBufferLength)
{
_tmpPos += _samplesRead;
}
else
{
_tmpPos = 0;
//if (--_loops < 1)
//{
//for (j = 0; j < (_pitchBufferLength - _bufferFill)*2; j++)
//_ba.writeFloat(0);
//
//_bufferFill = _pitchBufferLength;
//
//soundComplete();
//}
}
//_tmpPos = (_bufferFill == _pitchBufferLength ? _tmpPos + _samplesRead : 0);
}
//trace("0:",_tmpPos, tmpPos2);
}
var i:int, o:int;
if (!_reversed)
{
for (i = 0; i < _bufferLength - 1; i++)
{
_count = i * pitch;
_ba.position = Math.floor(_count)*8;
try{
e.data.writeFloat(_ba.readFloat());
e.data.writeFloat(_ba.readFloat());
}catch (err:Error) {
trace(err + "; i=" + i + ", count=" + Math.floor(_count)*8+ ", pitchBufferLength=" + _pitchBufferLength + ", bufferLength=" + _bufferLength + ", ba.len=" + _ba.length + ", pitch=" + pitch);
break;
}
}
}
else
{
for (i = _bufferLength; i > 0 ; i--)
{
_count = i * pitch;
_ba.position = Math.min(Math.floor(_count)*8, _ba.length-8);
try{
e.data.writeFloat(_ba.readFloat());
e.data.writeFloat(_ba.readFloat());
}catch (err:Error) {
trace(err + "; i=" + i + ", floor(count)*8=" + Math.floor(_count) * 8 + ", pitchBufferLength=" + _pitchBufferLength + ", bufferLength=" + _bufferLength /8 + ", ba.len=" + _ba.length + ", pitch=" + _pitch);
break;
}
}
}
}
else
{
for (i = 0; i < _bufferLength; i++)
{
e.data.writeFloat(0);
e.data.writeFloat(0);
}
}
}
private function soundComplete():void
{
removeEventListener(SampleDataEvent.SAMPLE_DATA, onSamples);
dispatchEvent(new Event(Event.SOUND_COMPLETE, true, true));
}
private function onTmpSoundID3(e:Event):void
{
var ne:Event = e.clone();
dispatchEvent(ne);
}
private function onTmpSoundIOError(e:IOErrorEvent):void
{
var ne:Event = e.clone();
dispatchEvent(ne);
}
private function onTmpSoundOpen(e:Event):void
{
var ne:Event = e.clone();
dispatchEvent(ne);
}
private function onTmpSoundProgress(e:ProgressEvent):void
{
var ne:Event = e.clone();
dispatchEvent(ne);
}
/**
* The pitch factor. A pitch of 2 plays the sound at double speed, whereas -0.5 plays it backwards at half speed.
*/
public function get pitch():Number { return _userPitch; }
/**
* The pitch factor. A pitch of 2 plays the sound at double speed, whereas -0.5 plays it backwards at half speed.
*/
public function set pitch(value:Number):void
{
if (value < 0)
_reversed = true;
else
_reversed = false;
_userPitch = Math.round(value * 1000) / 1000;
//_userPitch = value;
_pitch = Math.min(Math.max(MIN_PITCH, Math.abs(_userPitch)), MAX_PITCH);
_pitchBufferLength = _bufferLength * _pitch;
}
/**
* The size of the sound buffer. Use values between 2 and 8, higher and lower values will get clamped.
*/
public function get bufferLength():int { return _bufferLength/1024; }
/**
* The size of the sound buffer. Use values between 2 and 8, higher and lower values will get clamped.
*/
public function set bufferLength(value:int):void
{
_bufferLength = Math.round(Math.max(2, Math.min(value, 8))) * 1024;
_pitchBufferLength = _bufferLength * _pitch;
}
}
}