Search code examples
actionscript-3airaudiosound-synthesis

Trouble creating function to adjust volume up and down on Tone really fast (in milliseconds)


Basically I'm using AS3 to generate a tone. I need to be able to pass my function an array which would look something like {0,50,100,0,20,500,200,100} each representing milliseconds. It would just be like "off, on, off, on,off" ect and I need the tone to play exactly down to the millisecond with no delays or hiccups.

I tried making a function with timers to accomplish this...but it's really not as precise as I need it to be. There are slight delays, and it's noticeably not playing the really short ones as short as they need to be.

I was thinking I'd just play my tone, then use a SoundTransform to toggle the volume on and off, and that could help make it faster since i'm not starting and stopping a sound, i'm just manipulating the volume in real time.

But maybe it's not the volume that's slowing it down, maybe it's just the timers aren't all that reliable. Here's my code, the function just loops until I make it stop with another function I have. Any suggestions on how to get this to be more precise?

My function that handles the array with all the timers

private function soundPattern(patternArr:Array):void
        {
            //setup vars
            var pTotal:Number = patternArr.length;
            var pCount:Number = 0;

            if(pTotal >=1)
            {
                //setup listenrs
                patTimer = new Timer(patternArr[pCount],1);
                patTimer.addEventListener(TimerEvent.TIMER_COMPLETE, comp);

                function comp(e:TimerEvent=null):void
                {
                    pCount++;
                    if(pCount != pTotal)
                    {
                        patTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, comp);

                        toneGen.soundTrans.volume=1;
                        toneGen.toneChannel.soundTransform = toneGen.soundTrans;

                        patTimer = new Timer(patternArr[pCount],1);
                        patTimer.addEventListener(TimerEvent.TIMER_COMPLETE, compTwo);

                        if(patternArr[pCount]>0)
                        {
                            patTimer.reset();
                            patTimer.start();
                        }
                        else
                        {
                            compTwo();
                        }
                    }
                    else if(repeat)
                    {
                        trace("1resetting...");
                        patTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, comp);
                        pCount = 0;
                        patTimer = new Timer(patternArr[pCount],1);
                        patTimer.addEventListener(TimerEvent.TIMER_COMPLETE, compTwo);

                        toneGen.soundTrans.volume=0;
                        toneGen.toneChannel.soundTransform = toneGen.soundTrans;

                        if(patternArr[pCount]>0)
                        {
                            patTimer.reset();
                            patTimer.start();
                        }
                        else
                        {
                            compTwo();
                        }
                    }
                }

                //in-between
                function compTwo(e:TimerEvent=null):void
                {
                    pCount++;
                    if(pCount != pTotal)
                    {
                        patTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, compTwo);
                        patTimer = new Timer(patternArr[pCount],1);
                        patTimer.addEventListener(TimerEvent.TIMER_COMPLETE, comp);

                        toneGen.soundTrans.volume=0;
                        toneGen.toneChannel.soundTransform = toneGen.soundTrans;

                        if(patternArr[pCount]>0)
                        {
                            patTimer.reset();
                            patTimer.start();
                        }
                        else
                        {
                            comp();
                        }
                    }
                    else if(repeat)
                    {
                        trace("2resetting...");
                        patTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, compTwo);
                        pCount = 0;
                        patTimer = new Timer(patternArr[pCount],1);
                        patTimer.addEventListener(TimerEvent.TIMER_COMPLETE, comp);

                        toneGen.soundTrans.volume=0;
                        toneGen.toneChannel.soundTransform = toneGen.soundTrans;

                        if(patternArr[pCount]>0)
                        {
                            patTimer.reset();
                            patTimer.start();
                        }
                        else
                        {
                            comp();
                        }
                    }
                }


                //get the tone started, but remember the first is a pause
                toneGen.startTone();

                //start things
                if(patternArr[pCount]>0)
                {
                    patTimer.reset();
                    patTimer.start();
                }
                else
                {
                    comp();
                }
            }
        }

and here's the toneGen class i'm using

package
{
    import flash.events.SampleDataEvent;
    import flash.media.Sound;
    import flash.media.SoundChannel;
    import flash.media.SoundTransform;

    public class ToneGenerator {

        [Bindable]
        public var amp_multiplier_right:Number = 0.5;
        [Bindable]
        public var amp_multiplier_left:Number = 0.5;
        [Bindable]
        public var freq_right:Number = 580;
        [Bindable]
        public var freq_left:Number = 580;

        public static const SAMPLING_RATE:int = 44100;
        public static const TWO_PI:Number = 2*Math.PI;
        public static const TWO_PI_OVER_SR:Number = TWO_PI/SAMPLING_RATE;

        public var tone:Sound;
        public var toneChannel:SoundChannel;
        public var soundTrans:SoundTransform;

        public function ToneGenerator() {
        }

        public function stopTone():void {

            if(tone)
            {
                toneChannel.stop();
                tone.removeEventListener(SampleDataEvent.SAMPLE_DATA, generateSineTone);
            }

        }

        public function startTone():void {

            tone = new Sound();

            tone.addEventListener(SampleDataEvent.SAMPLE_DATA, generateSineTone);

            soundTrans = new SoundTransform(0);
            toneChannel = tone.play();
            toneChannel.soundTransform = soundTrans;

        }

        public function generateSineTone(e:SampleDataEvent):void {

            var sample:Number;

            for(var i:int=0;i<8192;i++) {
                sample = Math.sin((i+e.position) * TWO_PI_OVER_SR * freq_left);
                e.data.writeFloat(sample * amp_multiplier_left);
                sample = Math.sin((i+e.position) * TWO_PI_OVER_SR * freq_right);
                e.data.writeFloat(sample * amp_multiplier_right);
            }  

        }


    }
}

Solution

  • Timers are notoriously not accurate (this is due to the architecture of the Flash Player : read this).

    Whenever you need accurate time-based calculation, use the getTimer() method to calculate the time elapsed between two moments in time. You will also need a way to have a tick() method as frequently as possible (this will be your accuracy), and in that case you can use a Timer or even Event.ENTER_FRAME.

    var ticker:Sprite = new Sprite();
    sprite.addEventListener(Event.ENTER_FRAME, tick);
    
    const delay:uint = 300;
    
    var timeReference:uint;
    var lastTimeReference:uint = getTimer();
    
    function tick(evt:Event):void {
       timeReference = getTimer();
       if(timeReference - lastTimeReference >= delay)
       {
          trace("delay reached");
          lastTimeReference = timeReference;
       }
    
    }