Как задать затухание реверберации каждой сыгранной ноты в зависимости от длительности её удержания
Чтобы у каждой сыгранной ноты была собственная реверберация, зависящая от длительности, измерьте время между её включением и выключением, преобразуйте эту длительность во время затухания и примените полученное значение к ревербератору, выделенному для этой ноты. Реверберация ноты должна продолжаться и после затухания её источника звука.
В этом руководстве такая схема реализована в SuperCollider, включая MIDI-вход и проверку наложения нот. Результат должен быть простым: коротко удерживаемая нота оставляет короткий хвост, а долго удерживаемая — длинный, даже если их реверберация перекрывается.
Зачем каждой ноте нужна отдельная реверберация?
Представьте, что звучат две ноты одновременно. Первую вы удерживаете две секунды, а вторую нажимаете на четверть секунды. Если обе ноты подаются на один ревербератор, изменение его затухания для второй ноты повлияет и на остаточную реверберацию первой. Один общий регулятор затухания не может поддерживать два независимых значения в одном состоянии ревербератора.
Поэтому схема маршрутизации должна быть такой: источник звука каждой ноты подаётся на собственный ревербератор, а выходы обработанных голосов затем смешиваются. Это архитектурный вывод: независимые хвосты требуют независимых состояний обработки, если только инструмент или эффект уже не обеспечивает равноценное внутреннее разделение. Cycling ’74: Polyphony
Полноценный полифонический патч даёт для этого удобную основу. Cycling ’74 описывает, как poly~ в Max создаёт экземпляры голосов, а thispoly~ помечает каждый экземпляр как занятый или доступный. В этой задаче голос должен оставаться занятым, пока звучит хвост реверберации, а не становиться доступным сразу после отпускания ноты.
Есть и временное ограничение: во время живого исполнения итоговая длительность удержания неизвестна, пока нота не закончится. В приведённой ниже реализации, пока клавиша нажата, используется предварительное значение затухания, а при отпускании задаётся измеренное значение. Если нужно, чтобы итоговое затухание действовало с самого начала, используйте длительности нот, заранее известные из последовательности, либо обработайте ноты по отдельности после записи.
Что должна контролировать длительность удержания?
Разделяйте три параметра: затухание громкости инструмента, затухание реверберации и уровень обработанного сигнала. Более продолжительное затухание инструмента дольше подаёт звук на ревербератор. Более высокий уровень обработанного сигнала делает эффект громче. Ни то ни другое само по себе не увеличивает время затухания реверберации.
Затухание обычно не означает точное время до полной остановки звука. В документации Ableton для Reverb регулятор Decay задаёт время, за которое хвост ослабевает до одной тысячной исходной амплитуды, то есть до −60 дБ. Это различие важно, когда значение затухания сравнивают с моментом, когда хвост становится неслышен. Ableton: Live Audio Effect Reference, Reverb
Для первой реализации используйте эту наглядную примерную зависимость; все значения указаны в секундах:
decay = min(6.0, max(0.25, 0.25 + 1.5 × heldDuration))
| Длительность удержания | Заданное затухание |
|---|---|
| 0,10 секунды | 0,40 секунды |
| 0,25 секунды | 0,625 секунды |
| 1,00 секунда | 1,75 секунды |
| 2,00 секунды | 3,25 секунды |
| 4,00 секунды | 6,00 секунды, ограничено максимумом |
Эти значения выбраны для примера, а не получены в результате акустических измерений. Минимум обеспечивает слышимый хвост у коротких нажатий; множитель определяет, насколько сильно удержание влияет на эффект; максимум ограничивает запрашиваемое затухание. Начните с этой зависимости, затем настройте эти три значения, оставляя громкость ноты и уровень обработанного сигнала постоянными.
Создайте голос, который не освобождается при отпускании ноты
В примере используется встроенный в SuperCollider GVerb: он принимает монофонический сигнал, выдаёт две выходные канала и задаёт revtime в секундах. В документации также упоминаются скачок нагрузки на процессор при создании экземпляра и артефакты при изменении размера помещения. Поэтому в этом примере размер помещения остаётся неизменным, а для начала лучше использовать лишь несколько одновременных нот. SuperCollider: GVerb
Каждый голос содержит простой источник звука с двумя осцилляторами, короткое затухание громкости и один ревербератор. Отдельная огибающая жизненного цикла освобождает голос только после того, как истечёт отведённое хвосту время. EnvGen в SuperCollider поддерживает огибающие с удержанием, которые переходят в фазу отпускания при обнулении управляющего сигнала, а также действие по завершении, освобождающее содержащий огибающую синтезатор. SuperCollider: EnvGen
Откройте новый сеанс SuperCollider с работающим стереовыходом. Выполните весь блок настройки ниже и дождитесь сообщения Held-note reverb ready в окне Post. Сэмплы и дополнительные библиотеки не требуются. Код проверен по документации, но здесь не запускался, поскольку интерпретатор SuperCollider недоступен.
(
s.waitForBoot {
SynthDef(\heldNoteVerb, {
|out = 0, freq = 440, amp = 0.035,
gate = 1, decay = 1, alive = 1|
var sourceEnv, lifetime, source, wet;
sourceEnv = EnvGen.kr(
Env.asr(0.01, 1, 0.02), gate
);
lifetime = EnvGen.kr(
Env.asr(0.01, 1, 0.03), alive,
doneAction: 2
);
source = (
SinOsc.ar(freq) + (0.25 * SinOsc.ar(freq * 2))
) * amp * sourceEnv;
wet = GVerb.ar(
source,
roomsize: 20,
revtime: Lag.kr(decay, 0.03),
damping: 0.5,
drylevel: 0,
earlyreflevel: 0.2,
taillevel: 0.5
);
Out.ar(out, ((source ! 2) + wet) * lifetime);
}).add;
s.sync;
~heldVoices = Dictionary.new;
~tailJobs = List.new;
~voiceGroup = Group.head(s);
~voiceKey = { |num, chan, src|
src.asString ++ ":" ++ chan.asString ++ ":" ++ num.asString
};
~decayFor = { |held|
(0.25 + (1.5 * held)).clip(0.25, 6.0)
};
~releaseHeld = { |num, chan = 0, src = 0|
var key, queue, entry, held, decay, node;
key = ~voiceKey.value(num, chan, src);
queue = ~heldVoices[key];
if(queue.notNil) {
if(queue.notEmpty) {
entry = queue.removeAt(0);
node = entry[0];
held = (Main.elapsedTime - entry[1]).max(0);
decay = ~decayFor.value(held);
node.set(\decay, decay, \gate, 0);
[\note, num, \held, held, \decay, decay].postln;
~tailJobs.add(Routine {
((2 * decay) + 0.1).wait;
node.set(\alive, 0);
}.play(SystemClock));
};
if(queue.isEmpty) { ~heldVoices.removeAt(key) };
};
};
~startHeld = { |vel, num, chan = 0, src = 0|
var key, node;
if(vel <= 0) {
~releaseHeld.value(num, chan, src);
} {
key = ~voiceKey.value(num, chan, src);
if(~heldVoices[key].isNil) {
~heldVoices[key] = List.new;
};
node = Synth(\heldNoteVerb, [
\freq, num.midicps,
\amp, (vel / 127) * 0.035
], ~voiceGroup);
~heldVoices[key].add([node, Main.elapsedTime]);
};
};
"Held-note reverb ready".postln;
};
)
При отпускании ноты важен порядок: голос одновременно получает значение затухания и команду изменить состояние источника. Реверберация продолжает обрабатывать сигнал, пока источник затухает в течение 20 миллисекунд. Примерно через удвоенное заданное время затухания с небольшим запасом весь голос затихает и освобождается.
Этот запас — решение по управлению ресурсами, а не измерение полной тишины. Увеличьте его, если при очистке хвост ещё слышен. Для более крупного инструмента замените фиксированный запас мониторингом уровня выходного сигнала с ограничением максимального времени жизни. В примере также сохраняются ссылки на завершённые задачи до очистки, поэтому он служит демонстрацией, а не инструментом для бесконечной работы.
Свяжите сыгранные MIDI-ноты с нужными голосами
Измеренная длительность относится к конкретному экземпляру ноты. Если отслеживать только время последнего включения ноты, схема перестанет работать, как только ноты начнут перекрываться. Вместо этого в настройке каждый голос и время его начала сохраняются под ключом, включающим источник, канал и высоту ноты.
В руководстве SuperCollider по MIDI показано, как инициализировать MIDI, подключать входы и сохранять ссылки на синтезаторы, чтобы при выключении ноты можно было отпустить нужный звук. В справке по MIDIdef указаны аргументы обратного вызова в таком порядке: значение, номер ноты, канал и источник. SuperCollider: Using MIDI, SuperCollider: MIDIdef
Когда настройка сообщит о готовности, выполните этот блок, чтобы играть на инструменте с подключённых MIDI-входов:
(
MIDIClient.init;
MIDIIn.connectAll;
MIDIdef.noteOn(\heldVerbOn, { |vel, num, chan, src|
~startHeld.value(vel, num, chan, src);
});
MIDIdef.noteOff(\heldVerbOff, { |vel, num, chan, src|
~releaseHeld.value(num, chan, src);
});
)
В этом примере длительность удержания — это интервал между полученными событиями включения и выключения ноты. Работа с педалью сустейна не реализована. Если нужно учитывать продление ноты педалью, отложите соответствующие операции отпускания до момента, когда педаль будет поднята, а затем измерьте увеличенный интервал.
Повторные нажатия одной и той же высоты ноты сохраняются в очереди и обрабатываются в порядке поступления: сначала отпускается самое раннее. Для перекрывающихся событий одной высоты на одном источнике и канале это явное правило сопоставления. Для несопоставленных событий выключения ничего не происходит; несопоставленная нота остаётся активной до очистки. Перед расширением инструмента проверьте соответствие событий.
Проверьте перекрывающиеся хвосты, прежде чем менять звук
Сначала проверьте работу без клавиатуры. В следующей последовательности используются те же функции обработки событий, что и для MIDI-входа: нота 60 звучит две секунды, а нота 67 начинается посередине этого интервала и звучит четверть секунды. Ожидаемое затухание для них — примерно 3,25 и 0,625 секунды.
(
~heldTest = Routine {
~startHeld.value(100, 60);
1.0.wait;
~startHeld.value(100, 67);
0.25.wait;
~releaseHeld.value(67);
0.75.wait;
~releaseHeld.value(60);
}.play(SystemClock);
)
SystemClock планирует события с точностью до секунды, поэтому эта проверка не зависит от темпа музыки. При реальной работе напечатанные и заданные значения длительности могут немного отличаться. SuperCollider: SystemClock
Прислушайтесь к короткому хвосту более высокой ноты, пока продолжает звучать более низкая, а затем — к более длинному хвосту низкой ноты. После этого поменяйте длительности местами. Вторая проверка помогает убедиться, что результат зависит от длительности, а не от высоты ноты. Сопоставьте звучание с напечатанными парами длительности удержания и времени затухания.
Чтобы остановить и удалить этот пример, после установки обработчиков MIDI выполните следующий блок очистки. Он также отменяет запланированные задачи затухания, прежде чем удалить связанные с ними голоса:
(
MIDIdef(\heldVerbOn).free;
MIDIdef(\heldVerbOff).free;
if(~heldTest.notNil) { ~heldTest.stop };
~tailJobs.do { |job| job.stop };
~tailJobs.clear;
~voiceGroup.free;
~heldVoices.clear;
)
Как определить, почему хвосты ведут себя неправильно?
Если при отпускании одной ноты меняются все хвосты, проверьте маршрутизацию аудиосигнала. Даже если для каждой ноты записывается отдельное время, результат будет общим, когда он управляет одним ревербератором после смешивания голосов. Перенесите ревербератор в каждый голос или направьте изолированный звук каждой ноты в отдельные экземпляры обработки.
Если хвост обрывается ровно при отпускании ноты, проверьте время жизни голоса. Огибающая громкости должна останавливать источник звука, не освобождая ревербератор, в который этот источник подаёт сигнал. В реализации на Max оставьте голос в занятом состоянии, пока не закончится обработанный сигнал, следуя задокументированной модели распределения ресурсов poly~. Cycling ’74: Polyphony
Если значения затухания неверны, прежде чем менять реверберацию, выведите ключ события и временные отметки. В Max для измерения времени используется timer: он измеряет прошедшее время между событиями и по умолчанию выдаёт миллисекунды. Если принимающий параметр ожидает секунды, явно преобразуйте единицы измерения. Cycling ’74: timer
Если производительность становится нестабильной, прежде чем расширять патч, уменьшите число одновременных голосов и проверьте выбор ревербератора. В этой демонстрации нет ограничения на количество голосов, и каждая новая нота создаёт ещё один ревербератор. Для готового инструмента нужно явно задать правила распределения ресурсов с учётом как удерживаемых нот, так и ещё звучащих хвостов.
Когда проверки синхронизации и перекрытия пройдут успешно, замените осциллятор выбранным источником звука для каждого голоса. Сохраняйте ту же последовательность: выделите голос, запишите время начала, измерьте длительность при отпускании, задайте время затухания и оставьте ревербератор работать до конца хвоста. Так длительность удержания становится воспроизводимым выразительным параметром.
Источники
- Полифония — документация Cycling ’74
- timer — справочник Max — документация Cycling ’74
- Справочник аудиоэффектов Live — руководство Ableton Live версии 12
- GVerb — справка SuperCollider
- EnvGen — справка SuperCollider
- Использование MIDI — справка SuperCollider
- MIDIdef — справка SuperCollider
- SystemClock — справка SuperCollider
