При написании синтезатора активно использовалось ООП; предлагаю вам познакомиться с получившейся архитектурой и использовать мой код. Вы можете сделать все по-своему, но в этих статьях придется терпеть мое видение)
Класс PluginCommandStub нужен только чтобы создать и вернуть объект класса PluginController. PluginController предоставляет информацию о плагине, так же создает и владеет следующими компонентами:
· AudioProcessor — класс со всей логикой обработки аудио данных
· MidiListener — класс для обработки MIDI-сообщений (из Syntage.Framework)
· PluginUI (наследник PluginWpfUI<View>) — класс, управляющий графическим интерфейсом синтезатора, главная форма — это UserControl "View".
Чтобы обрабатывать аудиоданные есть интерфейсы IAudioChannel и IAudioStream. IAudioChannel предоставляет прямой доступ к массиву/буферу семплов (double[] Samples). IAudioStream содержит массив каналов.
Представленные интерфейсы содержат удобные методы обработки всех семплов и каналов "скопом": микширование каналов и потоков, применение метода к каждому семплу в отдельности и так далее.
Для интерфейсов IAudioChannel и IAudioStream написаны реализации AudioChannel и AudioStream. Здесь важно запомнить следующую вещь: нельзя хранить ссылки на AudioStream и AudioChannel, если они являются внешними данными в функции. Суть в том, что размеры буферов могут меняться по ходу работы плагина, буферы постоянно переиспользуются — не выгодно постоянно перевыделять и копировать память. Если вам необходимо сохранить буфер для дальнейшего использования — копируйте его в свой буфер.
IAudioStreamProvider является владельцем аудиопотоков, можно попросить создать поток функцией CreateAudioStream и вернуть поток для его удаления функцией ReleaseAudioStream.
|
В каждый момент времени длина (длина массива семплов) всех аудиопотоков и каналов одинакова, технически она определяется хостом. В коде ее можно получить либо у самого IAudioChannel или IAudioStream (свойство Length), так же у "хозяина" IAudioStreamProvider (свойство CurrentStreamLenght).
Класс AudioProcessor является "ядром" синтезатора — в нем-то и происходит синтез звука. Класс является наследником SyntageAudioProcessor, который, в свою очередь, реализует следующие интерфейсы:
· VstPluginAudioProcessorBase — чтобы обрабатывать буфер семплов (метод Process)
· IVstPluginBypass — чтобы отключать логику синтезатора, если плагин находится в режиме Bypass
· IAudioStreamProvider — чтобы предоставлять аудиопотоки для генераторов
Синтез звука проходит длинную цепочку обработки: создание простой волны в осцилляторах, микширование звука с разных осцилляторов, последовательная обработка в эффектах. Логика создания и обработки звука была разделена на классы-компоненты для AudioProcessor. Каждый компонент является наследником класса SyntageAudioProcessorComponentWithParameters<T> — содержит ссылку на AudioProcessor и возможность создавать параметры.
В синтезаторе представлены следующие компоненты:
· Input — обрабатывает сообщения о нажатии нот (MIDI-сообщения и нажатия из UI)
· Oscillator — осциллятор
· ADSR — огибающая сигнала
· ButterworthFilter — фильтр частот
· Distortion — эффект дисторшн
· Delay — эффект эхо/дилей
· Clip — ограничивает значение всех семплов от -1 до 1.
· LFO — модулирование параметров (обычно в синтезаторах модуляция осуществляется с использованием Low Frequency Oscillator — генератора низких частот)
|
· Master — мастер-обработка (финальная обработка) сигнала. В данном случае содержит ручку главной громкости.
· Oscillograph — осциллограф
· Routing — содержит в себе цепочку логики обработки звука
Все этапы создания звука вы можете найти в функции Routing.Process и на следующей схеме:
Звук одновременно создается на двух одинаковых осцилляторах (юзер может по-разному настроить их параметры). Для каждого осциллятора его звук проходит через огибающую. Два звука смешиваются в один, он проходит через фильтр частот, идет в эффект дисторшн, дилэй и клип. В мастере регулируется результирующая громкость звука. После мастера звук больше не модифицируется, но передается в осциллограф и блок LFO-модуляции (нужно для их внутренней логики).
Далее будет рассмотрено программирование логики класса Oscillator, а в следующих статьях будут рассмотрены другие классы-компоненты.
Чтобы использовать параметры, можно использовать абстрактный класс Parameter<T>, либо готовые реализации: EnumParameter, IntegerParameter, RealParameter и другие. Здесь важно понимать, что у параметра есть текущее значение Value типа T, и float-значение RealValue — отображающее обычное значение в отрезок [0,1] (нужно для работы с UI и хостом).