Статьи:

Профиль:

Создание VST плагина

В данной теме будет рассмотрен процесс создания ВСТ плагина, на примере создания эффекта кольцевой модуляции (ring modulation). Если Вам что-то непонятно — прочитайте вводную статью, или обратитесь за помощью на форум, где есть спец. раздел по программированию.

Справка: ring modulation — звуковой эффект или соответствующее устройство, реализующее перемножение двух исходных сигналов. Своё название получил из-за технической реализации — в аналоговой схеме он содержит кольцо из четырёх диодов.

Процесс разработки плагина разделён на четыре шага:
1 — Плагин, который не имеет эффекта.
2 — Модулятор с треугольной формой волны
3 — Модулятор с синусойдной формой волны
4 — Модулятор с синусойдной формой волны и контролем генератора низких частот.

За исключением стандартных библиотек SDK (необходимых в каждом ВСТ-плагине), все представленные алгоритмы разработаны специально для этого проекта.

SDK
Стеинберг, компания-разработчик программного обеспечения, ввела на рынок ВСТ технологию, благодаря которой к хост-программе (любой секвенсор, виртуальная студия) подключаются внешние модули (ВСТ плагины) и синтезаируют или обрабатывают звук (в зависимости от функциональности). Компания предоставляет SDK 3-им лицам, для собственной разработки плагинов и их последующей популяризации. Скачать SDK можно с оф. сайта (смотри вводную статью).

Основы плагинов
ВСТ плагин — это единичный *.dl файл, который размещается в дирректории, которая задаётся при установке хост-программ (возможно изменить в настройках). При запуске хост программа анализирует папку ВСТ-инструментов, для работы с плагинами их нужно отдельно импортировать в проект (в кубейсе — F10).
Самый быстрый способ начать создавать новые плагины — это открыть пример плагина в SDK «vst2examples.dsw». В данной работе также были взяты основы этих плагинов, в качестве начального материала с корректной структурой. Плагины написаны с помощью языка C++ (также возможно программирование на дельфи, йава, .net, однако большинство плагинов пишется на С++). Хорошие знания языка С++, понимание DSP технологий — это то что Вы должны иметь для написания интересного и успешного плагина.

Элементы SDK
SDK содержит много *.h, *.hpp, и *.cpp файлов, уже размещённых на своих местах. Из всех этих файлов только один *.cpp файл будет активно редактироваться. Стеинберг просит не редактировать другие файлы SDK (хост программы будут их использовать для корректной работы с плагином).

GUI
GUI — Графический интерфейс пользователя, система средств для взаимодействия пользователя с компьютером, основанная на представлении всех доступных пользователю системных объектов и функций в виде графических компонентов экрана. Вы можете сами создать дизайн своему плагину, если же Вы это не сделаете, т.е. ГУИ будет отсутствовать — хост программа автоматически сгенерирует визуализацию Вашего плагина и его контроллеров. В различных хост-программах техника визуализации плагинов отличается.

Давайте посмотрим на отображение нашего кольцевого модулятора в разных хост-программах:

Orionuntitled-10 WaveLabuntitled-111 Cubaseuntitled-12

Часть первая — Создание нефункционального плагина

Начинать надо с чего-то очень простого, то что интуитивно понятно. Нами был взят пример из  SDK. Самый просто плагин — это тот который ничего не делает, т.е. никак не изменяет звук. Этот пример пустого плагина поможет нам объяснить некоторые важные функции, понимание которых необходимо для дальнейшей разработки плагинов.

Функции process() и processReplacing()

Хост-программа часто воспроизводит внешние аудио-файлы. Аудио воспроизводится с жёсткого диска и микшируется в программном миксере. На этом микшере есть параметры  “send” и “insert”, как и в аналоговых микшерах. С помощью этих инструментов могут быть добавлены и использованы ВСТ плагины.  Инструмент send отправляет аудио поток к плагину, перед достижением выхода на мастере (master output). Инструмент “insert” посылает сигнал через плагин, удаляя определённые параметры (используется в сайдчейне и вокодере ). untitled-13

Выбор функции process() и processReplacing() зависит от того, хотите ли Вы использовать  “send” или “insert” оперции в качестве аудиоэффекта. Эти функции задают характер взаимодействия между хост-программой и плагином.

Для первого примера, плагин создан для приёма звука (input) и последующей передачи, без изменений, на выход ( output). Он работает как insert эффект и использует функцию processReplacing().

Просмотреть или скачать содержимое *.cpp файла в текстовом формате (пустой плагин).

Главная часть:

void AGain::processReplacing(float **inputs, float**outputs, long
sampleFrames)
{
float *in1 = inputs[0];
float *in2 = inputs[1];
float *out1 = outputs[0];
float *out2 = outputs[1];
while(—sampleFrames >= 0)
{
(*out1++) = (*in1++) ;
(*out2++) = (*in2++) ;
}
}

О коде:

Когда функция начинает действовать она имеет **inputs и **outputs, которые соответствуют входящему и выходящему буферам,  SampleFrames задаёт размер буфера. Значения входа ( input) и выхода (output) формируются из переменных  in1 и in2 (которые представляют левый и правый  входные каналы), и переменные out1 and out2 (левый и правый выходные каналы) соответственно.

До тех пор, пока  sampleFrames больше нуля — производится копирование значений со входа ( input) на выход (output) и переход к следующему значению (используется функция «++»). Как только значение sampleFrames равно нулю, буфер окончен, функция завершает своё действие. Этот пример не прдеставлен в SDK документации, и требует понимания синтаксиса языка С++.

Представление аудио в SDK

Важно знать, то что аудио информация в плагинах находится (варьирует) между +/-1.

Перед тем как переёти к следующей части, мы создали плагин, в котором на выход одного канала посылается значение +1, на выход другого канала посылается значение -1.

Код:

void AGain::processReplacing(float **inputs, float **outputs, long
sampleFrames)
{
float *in1 = inputs[0];
float *in2 = inputs[1];
float *out1 = outputs[0];
float *out2 = outputs[1];
while(—sampleFrames >= 0)
{
(*out1++) = (1) ;
(*out2++) = (-1) ;
}
}

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

untitled-14

Часть вторая — создание модулятора с треугольной формой волны

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

Во время написания кода плагина, используя в коде  DSP алгоритмы (Цифровая обработка сигналов) — очень важно понимать код, что делается со входящим потоком. Перед напиманием кода желательно вооружиться карандашом и бумагой, и нарисовать блок-схему действия будущего плагина.

Блок схема нашего модулятора:

untitled-15

Было решено то что лучший способ создания тремоло треугольной формы волны — это использовать прерывания при достижении определённого уровня звука. Изначально переменная «тремоло» имеет значение ноль. Далее она проверяется условием — «больше значения 0.1 ?» Если нет -у кровню звука добавляется опр. значение (гейн). Значение переменной «гейн» может быть ассоциировано с определённым регулятором (используется GUI — Графический интерфейс пользователя)

В нашем случае переменная «тремоло» умножается на 10 и посылается на выход.

Программа же возвращается на второй этап и продолжает действие, выясняя, стало ли значение тремоло больше значения 0.1. Процесс повторяется до достижения  значения 0.1, после чего значение «тремоло» обнуляется и  всё начинается заново.

Давайте посмотрим на форму волны входящего аудио потока:

untitled-16

Треугольную форму волны (показано для примера):

untitled-17

И на форму волны выходящего аудио-потока:

untitled-18

Просмотреть или скачать полный .cpp файл проекта

Код алгоритма:

double tremolo=0;
void AGain::processReplacing(float **inputs, float **outputs, long
sampleFrames)
{
float *in1 = inputs[0];
float *in2 = inputs[1];
float *out1 = outputs[0];
float *out2 = outputs[1];
if ( tremolo < 0.1 )
{
tremolo=(tremolo+fGain);
while(—sampleFrames >= 0)
{
(*out1++) = (*in1++) * tremolo *10;
(*out2++) = (*in2++) * tremolo *10;
}
}
if ( tremolo >= 0.1 )
{
tremolo=0;
while(—sampleFrames >= 0)
{
(*out1++) = (*in1++) * tremolo;
(*out2++) = (*in2++) * tremolo;
}
}
}

Этот код модулирует входящий аудио-поток, используя треугольную форму волны с частотой, заданной пользователем. Переменная «тремоло» описана снаружи функции  processReplacing(), — это необходимо для возможности реинициализации каждый раз, когда аудио буфер полон (завершён). Для того чтобы позволить пользователю модулировать входящий поток с определённой частотой необходимо внести некоторые изменения. Допустим, наша хост-программа работает с частотой дискретизации 44100Hz (т.е. 44100 отсчётов в секунду), эти значения могут быть использованы для задания частоты модуляции в С++ коде. Ниже показаны номера отсчётов, которые соответствуют критическим значениям:

untitled-19
untitled-20

Деля значения отсчётов на половину, четверть, три четверти и полную секунду мы получаем номера «крайних» отсчётов для модуляции.

Если переменная «тремоло» использует код моделирования треугольной формы (переименованой в counter для упрощения), добавляя опр. значения, то в «крайних отсчётах» теоретически будет преодолено значение 1, что будет происходить в нашем случае каждую  1/8 секунду.

1/11020 = 0.000090744
Если это значение (11020ая от 1-цы) будет разделено на 8, то мы получим:
0.000090744 / 8 = 0.000011343

Это значение может быть использовано пользователем для контроля частоты модуляци. Новая переменная topFreq содержит в себе максимальное значение амплитуды, после обработки модулятором. Переменная fГейн, которая содержит значения от 0 до 1, может быть умножена на переменную topFreq, для генерирования пропорции 1kHz на лету.

Просмотреть-скачать полный .cpp файл, в котором задаётся спец. частота модуляции

Код алгоритма:

float counter = 0;
double quarterSec = 0.000011343;
double topFreq = 1000;
void AGain::processReplacing(float **inputs, float **outputs, long
sampleFrames)
{
float *in1 = inputs[0];
float *in2 = inputs[1];
float *out1 = outputs[0];
float *out2 = outputs[1];
while(—sampleFrames >= 0)
{
counter = (counter + (quarterSec*(topFreq*fGain)));
if (counter > 0.5)
{
counter = 0;
}
(*out1++) = (*in1++ * counter);
(*out2++) = (*in2++ * counter);
}
}

3 — Модулятор с синусойдной формой волны

Замена треугольного модулятора синусойдной волной

Это необходимо сделать для того чтобы добиться более богатого гармониками тембра звука. Эти гармоники, после модулирования со вторым сигналом, производят вдое больше гармоник благодаря природе «сложения и разницы» кольцевой модуляции. Звук будет более музыкальным и насыщенным. Синусоидная волна может быть сгенерирована в нашем алгоритме с помощью переменной counter. Благодаря добавлению ранее использованого кода, синусойда может быть сгенерирована из серии цифр. Переменная counter должна иметь значения приблизительно от нуля до двух пи (3.14), для того чтобы создать один период синусойды.  Ниже расположены диаграммы, которые показывают: 1) Сгенерированую синусоидную волну (ампдитуду) 2) Быстрое преобразование Фурье простой синусойдной волны (спектрограмму):

untitled-21

untitled-22

Этот небольшой эксперимент был сделан для того чтобы показать то что при простом искажение волны , БПФ показывает гораздо больше гармоник, хотя до этого была только одна базовая частота. (Синусоидная форма волны имеет меньше всего гармоник, при искажении формы добавляются гармоники, квадратная форма волны имеет больше всего гармоник. Например  эффект дисторшен применяется для обогащения входящего потока гармониками путём искажения формы волны от синусойдной к более квадратной, по сути тут это и было сделано, путём завышения уровня сигнала и как следствие его искажения).

Для лучшего понимания ознакомтесь с блок схемой:

untitled-23

Операция while loop ( переменная sampleFrames ) — это петля, которая контролирует использование аудио буфферов с хоста. Переменная sampleFrames содержит в себе кол-во отсчётов в каждом аудио-буфере. Для каждого отсчёта, переменная sampleFrames происходит сравнение его значения с нулём. При достижении нуля буфер обнуляется и новый, полный буфер запрашивается у хоста, с последующим стремлением к нулю.

Чтобы лучше понять, ознакомтесь с диаграммами:

Входной сигусойдный сигнал:

untitled-24

Синус с небольшим периодом, который используется для модулирования входящего сигнала:

untitled-25

Результат на выходе:

untitled-261

Алгоритм кода:

float counter = 0;
double quarterSec = (0.000570162/4);
double topFreq = 10000;
void AGain::processReplacing(float **inputs, float**outputs, long
sampleFrames)
{
float *in1 = inputs[0];
float *in2 = inputs[1];
float *out1 = outputs[0];
float *out2 = outputs[1];
while(—sampleFrames >= 0)
{
counter = (counter+(quarterSec*(topFreq*fGain)));
if (counter > (2*3.14159))
{
counter -= (2*3.14159);
}
double sinMod = sin(counter);
(*out1++) = ((*in1++)*(sinMod/2));
(*out2++) = ((*in2++)*(sinMod/2));
}
}

4 — Модулятор с синусойдной формой волны и контролем генератора низких частот.

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

Блок схема:

untitled-27

Блок схема показывает то что генерируются два независимых синусоидных сигнала. Один является модулирующим с опр. частотой, и был создан также как и в более ранних примерах. Второй является генератором низких частот ( LFO ). Пользователь сможет контролировать амплитуду и частоту сигнала, изменяя значения контролируемых через GUI переменных countertoo.

Просмотреть или скачать полный .cpp файл

Код алгоритма:

float counter = 0;
float countertoo = 0;
double quarterSec = (0.000570162/4);
double topFreq = 10000;
double LFOFreq = 10;
void ADelay::processReplacing(float **inputs, float **outputs, long
sampleFrames)
{
float *in1 = inputs[0];
float *in2 = inputs[1];
float *out1 = outputs[0];
float *out2 = outputs[1];
while(—sampleFrames >= 0)
{
countertoo = (countertoo + (quarterSec*(LFOFreq*fFeedBack)));
if (countertoo > (2*3.14159))
{
countertoo -= (2*3.14159);
}
double nuMod = sin(countertoo);
counter = (counter + (quarterSec*(topFreq*fDelay)+(nuMod*(fOut/8))));
if (counter > (2*3.14159))
{
counter -= (2*3.14159);
}
double sinMod = sin(counter);
(*out1++) = ((*in1++)*(sinMod/2));
(*out2++) = ((*in2++)*(sinMod/2));
}
}

Работа с GUI:

enum

{
variable1;
variable2;
variable3;
kNumParams
};

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

Скачать готовый, откомпилированый плагин

Автор статьи:  Toby Newman

Перевод и html вёрстка: corpuscul.net

Если Вы столкнулись с трудностями при прочтении материала — то это повод задать вопросы на форуме, в разделе «программирование».

Один комментарийСоздание VST плагина

  • Serj

    А принцип разработки VSTi такой же, за исключением, что во входящем буфере — MIDI сообщения? Хотелось бы разобраться, что происходит на системном уровне, когда в DAW выходы с нескольких VSTi подаются на микшер. Судя по инфе из статьи, каждый VSTi «выплевывает» буфер одинакового размера, где записаны выходные сигналы, а микшер просто суммирует их, порождая конечный буфер, который подается на звуковую карту. Верно? Если так, что задержка будет складываться из размера буферов VSTi и микшера. Ничего не напутал?

Вы должны быть залогинены для комментирования.