0

基本上我使用 AS3 来生成音调。我需要能够向我的函数传递一个数组,该数组看起来像{0,50,100,0,20,500,200,100}每个代表毫秒。它就像“关闭,打开,关闭,打开,关闭”等,我需要将音调精确到毫秒,没有延迟或打嗝。

我尝试用计时器制作一个函数来完成这个......但它真的没有我需要的那么精确。有轻微的延迟,而且很明显没有像他们需要的那样播放真正短的。

我在想我只是播放我的音调,然后使用 SoundTransform 来打开和关闭音量,这可以帮助它更快,因为我没有开始和停止声音,我只是在真实地操纵音量时间。

但也许不是音量放慢了速度,也许只是计时器不够可靠。这是我的代码,该函数只是循环,直到我用另一个函数停止它。关于如何使这个更精确的任何建议?

我使用所有计时器处理数组的函数

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();
                }
            }
        }

这是我正在使用的toneGen类

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);
            }  

        }


    }
}
4

3 回答 3

2

众所周知,计时器不准确(这是由于 Flash Player 的架构造成的:阅读此内容)。

每当您需要精确的基于时间的计算时,请使用该getTimer()方法计算两个时刻之间经过的时间。您还需要一种方法来tick()尽可能频繁地使用方法(这将是您的准确性),在这种情况下,您可以使用 aTimer甚至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;
   }

}
于 2012-10-27T22:45:33.050 回答
0

由于您是通过自己创建原始样本数据来生成音调,因此最准确的做法是调整创建音调的代码中的音量,而不是依赖在单独的线程中运行的其他东西,使用单独的时钟和相关的任意延迟来打开和关闭它。

由于您只有简单的音调,您可以通过在开始和停止之前等待音调“过零”来打开和关闭它们。这通常可以正常工作。或者,您可以乘以一个名义上为 1 或 0 的常数,并在需要更改打开和关闭它时将值在 1 和 0 之间倾斜。一个简单的斜坡方法是使用线性插值:

http://blog.bjornroche.com/2010/10/linear-interpolation-for-audio-in-cc.html

于 2012-10-28T15:27:18.233 回答
0

你必须调整发电机。你有一个毫秒序列,代表开/关开关,对吧?每次采样时,您都必须知道声音现在处于哪种状态,打开或关闭。给定 44100 Hz 的样本,您可以精确地调整声音何时停止以及何时开始,方法是在声音由您的指令停止时向通道发送 0.0 而不是正弦波。事实上,逻辑声音结构会不断播放,但音调会断断续续。

以下是如何完成的草图:

public class CustomTone {
    private var tone:Sound;
    private var delays:Vector.<Number>;
    private var frequency:Number;
    private var onThreshold:Number;
    private var offThreshold:Number;
    private var finishThreshold:Number;
    private var isOn:Boolean=true;
    private var sequenced:Number; // how many samples were sequenced total. 
    // any check is done within this.
    public function CustomTone(freq:Number,toneArray:Array) {
        tone=new Sound();
        delays=Vector.<Number>(toneArray);
        frequency=freq;
        tone.addEventListener(SampleDataEvent.SAMPLE_DATA,generateTone); 
        tone.addEventListener(Event.COMPLETE,finishedGenerating);
        sequenced=0;
        // fill other values according to array [TODO]
    }
    // other function to taste. Should have there a play and stop functions, as well as to play presampled sound
    }
    private function generateTone(e:SampleDataEvent):void {
        var ep:Number=e.position;
        var howMany:int=Math.min(finishThreshold-sequenced,8192);
        for (var i:int=0;i<howMany;i++) {
            if (isOn) { 
                sample = Math.sin((i+ep) * TWO_PI_OVER_SR * frequency);
                e.data.writeFloat(sample * amp_multiplier_left);
                sample = Math.sin((i+ep) * TWO_PI_OVER_SR * frequency);
                e.data.writeFloat(sample * amp_multiplier_right);
            } else {
                e.data.writeFloat(0.0);
                e.data.writeFloat(0.0);
            }
            if ((i+ep)>offThreshold) {
                isOn=false;
                offThreshold=getNextOffThreshold();
            }
            if (i+ep>onThreshold) {
                isOn=true;
                onThreshold=getNextOnThreshold();
            }
        }
        sequenced+=howMany;
    }
    ...
}

请注意,您可能希望不再从同一个数组中生成这样的声音,因此您可以使用一种方法来重放曾经生成的声音。下一个阈值的方法应该替换为下一个值的内联计算,注意这些是以样本而不是毫秒为单位测量的,因此您也应该将它们转换为样本。

于 2012-10-29T07:06:02.703 回答