Проблема с созданием функции для очень быстрой регулировки громкости вверх и вниз на Tone (в миллисекундах)

В основном я использую 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);
            }  

        }


    }
}

person brybam    schedule 27.10.2012    source источник


Ответы (3)


Общеизвестно, что таймеры не точны (это связано с архитектурой Flash Player: прочитать это).

Всякий раз, когда вам нужен точный расчет на основе времени, используйте метод getTimer() для расчета времени, прошедшего между двумя моментами времени. Вам также понадобится метод tick() как можно чаще (это будет ваша точность), и в этом случае вы можете использовать Timer или даже 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;
   }

}
person Antoine Lassauzay    schedule 27.10.2012
comment
Хорошо, я в основном воссоздал то, что у меня есть наверху, но вместо использования таймера я использую событие ввода кадра для пустого спрайта, как в вашем примере. Он кажется более последовательным, что, безусловно, является шагом в правильном направлении. Но, похоже, это все еще не так быстро, как хотелось бы. Я думаю, это потому, что это событие входа в кадр... и когда у меня есть звуковой паттерн, похожий на {30,20,30,20}, это может быть слишком быстро для 24 кадров в секунду. Вы упомянули об использовании таймера для моего тика... но именно в этом и была проблема, в первую очередь, вы упомянули, что это ненадежно. Любые идеи? - person brybam; 28.10.2012
comment
Да, 30 или 20 мс слишком мало. Вы можете рассчитать теоретическую частоту обновления, выполнив (1000 мс/FPS), например, 1000/30 = 33 мс. Если вы посмотрите на ссылку, которую я приложил в своем ответе, там есть объяснение того, как все связано с моделью рендеринга. См. также craftymind.com /2008/04/18/. Проблема в основном в том, что у вас не может быть точного таймера, потому что рендеринг и выполнение кода влияют на частоту обновления. Таймеры не работают независимо от всего остального. - person Antoine Lassauzay; 28.10.2012

Поскольку вы генерируете тоны, создавая необработанные семпловые данные самостоятельно, наиболее точным будет отрегулировать громкость в коде, который создает тоны, а не полагаться на что-то еще, что выполняется в отдельном потоке с отдельным часы и связанные с ними произвольные задержки для включения и выключения.

Поскольку у вас есть только простые тоны, вы можете включать и выключать их, ожидая, пока тон «пересечет ноль», прежде чем запускать и останавливать. Обычно это работает нормально. В качестве альтернативы вы можете умножить на константу, которая номинально равна единице или нулю, и линейно изменять значение между единицей и нулем, когда вам нужно изменить его, включив или выключив. Простой способ рампы — использовать линейную интерполяцию:

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

person Bjorn Roche    schedule 28.10.2012

Надо настроить генератор. У вас есть последовательность миллисекунд, которые представляют собой включение/выключение, верно? Каждый раз, когда вы сэмплируете, вы должны знать, в каком состоянии находится звук в данный момент, включено или выключено. Имея выборку для 44100 Гц, вы можете точно настроить, когда ваш звук должен прекратиться и когда он должен начаться, отправляя 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;
    }
    ...
}

Обратите внимание: вы можете захотеть, чтобы такой звук не генерировался снова из того же массива, поэтому вы можете использовать метод для повторного воспроизведения однажды сгенерированного звука. Методы следующего порога следует заменить встроенными вычислениями следующего значения, обратите внимание, что они измеряются в выборках, а не в миллисекундах, поэтому вы также должны преобразовать их в выборки.

person Vesper    schedule 29.10.2012