Синхронизация Web Audio: как справиться с изменением отсечки фильтра во время фазы атаки или освобождения?

Я создаю эмуляцию синтезатора Roland Juno-106 с помощью WebAudio. Живая версия WIP здесь.

Я повесил трубку о том, как бороться с обновлением фильтра, если частота отсечки или размер модуляции огибающей изменяется во время атаки или выпуска, когда фильтр одновременно модулируется огибающей. Этот код находится здесь. Текущая реализация не отвечает, как аналоговый синтезатор, но я не могу понять, как его вычислить.

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

Как бы я смоделировал это поведение?

Ответ 1

Блестящий проект!

Вам не нужно их суммировать - Web Audio AudioParams суммирует свои входы, поэтому, если у вас есть источник источника звуковой скорости, такой как LFO (OscillatorNode, подключенный к GainNode), вы просто подключаете() его к AudioParam.

Это ключевое слово здесь - AudioParams могут быть подключены() ed to - и суммируются несколько входных соединений с node или AudioParam. Поэтому вы обычно хотите модель

filter cutoff = (cutoff from envelope) + (cutoff from mod/LFO) + (cutoff from cutoff knob)

Так как отсечка является частотой и, следовательно, в логарифмической шкале не является линейной, вы хотите сделать это добавление логарифмически (в противном случае огибающая, которая увеличивает обрезание октавы на 440 Гц, будет только увеличивать ее на половину октавы при 880 Гц и т.д.), что, к счастью, легко сделать с помощью параметра "detune" на BiquadFilter.

Detune находится в центах (1200/октава), поэтому вам нужно использовать узлы усиления для настройки значений (например, если вы хотите, чтобы ваша модуляция имела диапазон + 1/-1 октавы, убедитесь, что выход генератора идет между ними - 1200 и +1200). Вы можете видеть, как я делаю этот бит в своем синтезаторе Web Audio (https://github.com/cwilso/midi-synth): в частности, проверьте synth.js, начиная с строки 500: https://github.com/cwilso/midi-synth/blob/master/js/synth.js#L497-L519. Обратите внимание на modFilterGain.connect(this.filter1.detune); в частности.

Вы не хотите устанавливать ЛЮБЫЕ значения непосредственно для модуляции, так как фактическое значение будет меняться с потенциально высокой скоростью - вы хотите использовать планировщик параметров и вводить суммирование из LFO. Вы можете установить значение ручка по мере необходимости с точки зрения времени, но оказывается, что параметр .value будет плохо взаимодействовать с настройкой запланированных значений на том же AudioParam - так что вам нужно будет иметь отдельный (суммированный) ввод в AudioParam. Это сложный бит, и, честно говоря, мой синтезатор НЕ делает этого хорошо сегодня (я должен изменить его на описанный ниже подход).

Правильный способ управления настройкой ручки - создать звуковой канал, который изменяется в зависимости от настройки ручка, то есть это AudioNode, который вы можете подключить() к фильтру .detune, хотя значения выборки, создаваемые этим AudioNode являются только положительными и изменяют только при изменении ручки. Для этого вам нужен источник смещения постоянного тока, то есть AudioNode, который создает поток постоянных значений образца. Самый простой способ сделать это - использовать AudioBufferSourceNode с созданным буфером 1:

   function createDCOffset() {
    var buffer=audioContext.createBuffer(1,1,audioContext.sampleRate);
    var data = buffer.getChannelData(0);
    data[0]=1;
    var bufferSource=audioContext.createBufferSource();
    bufferSource.buffer=buffer;
    bufferSource.loop=true;
    bufferSource.start(0);
    return bufferSource;
}

Затем просто подключите этот DCOffset к коэффициенту усиления node и подключите "ручку" к этому усилению .value, чтобы использовать коэффициент усиления node для масштабирования значений (помните, что в октаве 1200 центов, поэтому если вы хотите, чтобы ваша ручка представляла шестиоктавный диапазон отсечки, значение должно идти от нуля до 7200). Затем подключите() DCOffsetGain node к фильтру .detune(он суммирует, а не заменяет соединение с LFO, а также суммирует с запланированными значениями на AudioParam (помните, что вам нужно масштабировать запланированные значения в центах тоже)). Этот подход, BTW, позволяет легко переключаться с полярностью огибающей (что VCF ENV переключает на Juno 106) - просто инвертируйте значения, установленные в планировщике.

Надеюсь, это поможет. На данный момент я немного расстроен, поэтому, надеюсь, это было ясно.:)