Статью можно найти здесь. Часть 1.
В первой части, мы создали базовый плагин, который имеет два параметра gain, bypass и настроили его для компиляции плагинов vst3 и clap.
Примеры можно найти на github:
Часть 1 в ветке gain,
Часть 2 в ветке synth.
Синтезатор
Сегодня мы создадим полифонический синтезатор, который будет генерировать синусоидальную волну. Для него я создам отдельную структуру Synth, которая должна принимать midi-событие и возвращать сигнал.
В методе process(), мы использовали буфер чтобы изменять в нем значения, тем самым мы создали эффект gain (усиление). Разница том, что мы принимали входные данные (const DEFAULT_INPUT_CHANNELS), чтобы использовать свою волну мы будем подменять значение.
Пример 1 и 2 одинаковый, но во втором мы сможем подменить входные данные.
Ещё нам понадобится получить частоту дискретизации будущего сигнала.
Базовая волна.
Теперь, нам нужно создать функцию sine() -> f32 которая будет генерировать значение нашей синусоиды. При каждой итерации наша функция должна выдавать новое значение от -1.0 до 1.0. Идея состоит в том чтобы взять окружность, и разделить на фрагменты. Где полный оборот вернет 0.0 (TAU).sin() = 0, а фрагмент вернет часть от оборота (например: f = 1/4 -> 1.0, f = 3/4 -> -1.0 ).
Мы получили формулу (TAU * f).sin().
Далее уже можно подумать о частоте дискретизации, частоте волны...
Частота Найквиста - в цифровой обработке сигналов. Наивысшая частота полезного сигнала, равная половине частоты дискретизации.
Далее я наткнулся на проблему. Для index я использовал time_buffer, это индекс семпла в буфере, как оказалось. С каждым новым буфером, сигнал начинался с начала.
Решил создать новую структуру, которая будет иметь другую систему отсчета и следить за фазой волны. При каждом вызове автоматически прибавит один фрагмент.
Изменение Кода
Уже можно подставить наш осциллятор и получить синусоиду, но пока она будет звучать бесконечно.
Важно!!! Проверяйте форму волны на анализаторах или тестах. Волны не должна находиться в одном положении, большое количество времени.
Сложение волн
Полифонический синтезатор должен уметь создавать аккорды, т.е. сложение волны. Для каждой ноты он будет создавать осциллятор с частотой ноты.(например: А(ля) = 440Гц). Нам нужно получать от каждой волны семпл, сложить и это всё.
Для этого я создал два вектора, один mix, который принимает семпл от каждого осциллятора, и через преобразование в итератор, и метода их суммы, выводит результат.
второй вектор хранит список осцилляторов, которые активны. Необходимо создать несколько осцилляторов, чтобы играть больше волн или создавать их копии. Я буду использовать осциллятор через индекс их добавления.
Midi-событие.
Наш плагин должен принимать `const MIDI_INPUT: MidiConfig = MidiConfig::Basic;`, далее в методе process, приходит context в виде миди событий, которые нужно будет обработать.
Я создал новую структуру Midi, которая принимает context в метод midi_input.context - это итератор. Получаю новое midi-сообщение и в цикле while, реагируем на него, и далее затеняю следующим midi-событием, и повторяем.
Как мы будем реагировать? Создаем HashMap, и будем записывать когда нота нажата, когда мы отпустим ноту мы удалим запись. Ключ будет нота, а значение всё событие, таким образом мы сможем получить любую информацию с события.
Чтобы получить все активные ноты мы пройдемся по всем ключам в HashMap. Мы получим ноту и велосити, а так же нужно реализовать по счёт голосов.
Наша функция будет принимать анонимную функцию, которая будет находится в теле цикла итераций, и она будет запускаться для каждого ключа(ноты) отдельно.
Далее... вспомним сложение волн. Анонимная функция создана таким образом, чтобы мы знали индекс волны и её ноту, и громкость ноты. Тем самым когда мы создаем голос (это копия осциллятора в векторе) мы знаем его индекс, и по индексу мы сможем использовать осциллятор, предать частоту с которой должна играть нота и предать в mix.
Итог
После компиляции мы получили полифонический синтезатор который генерирует синусоидальную волну.