Pull to refresh

Создание аудиоплагинов, часть 2

Reading time 5 min
Views 16K
Все посты серии:
Часть 1. Введение и настройка
Часть 2. Изучение кода
Часть 3. VST и AU
Часть 4. Цифровой дисторшн
Часть 5. Пресеты и GUI
Часть 6. Синтез сигналов
Часть 7. Получение MIDI сообщений
Часть 8. Виртуальная клавиатура
Часть 9. Огибающие
Часть 10. Доработка GUI
Часть 11. Фильтр
Часть 12. Низкочастотный осциллятор
Часть 13. Редизайн
Часть 14. Полифония 1
Часть 15. Полифония 2
Часть 16. Антиалиасинг



Давайте получше рассмотрим наш тестовый проект. Самые важные файлы — resource.h, MyFirstPlugin.h и MyFirstPlugin.cpp. На данный момент плагин представляет собой простой регулятор громкости звука.


Изучение кода



Константы, флаги и источники изображений



Откройте в навигаторе resource.h. Этот файл содержит такие константы, как название плагина, его версию, уникальный ID и ссылки на источники изображений для GUI. В строках 23-26 можно прописать уникальный ID:

// 4 chars, single quotes. At least one capital letter
#define PLUG_UNIQUE_ID 'Ipef'
// make sure this is not the same as BUNDLE_MFR
#define PLUG_MFR_ID 'Acme'


ID важен для общей каталогизации плагинов. Можете зарегистрировать его здесь. Строки 56 и далее определяют ID и путь к изображению для той ручки громкости, которую вы видите при запуске плагина:

// Unique IDs for each image resource.
#define KNOB_ID 101

// Image resource locations for this plug.
#define KNOB_FN "resources/img/knob.png"


Найдите в навигаторе и откройте Resources → img → knob.png. Это спрайт с шестьюдесятью различными положениями ручки, каждое размером 48 на 48 пикселей. Когда вы запускаете плагин и крутите ручку, изображение ручки не вращается. Программа только показывает соответствующую часть этого спрайта.
Почему оно не вращается? Представьте себе, что ручка имеет какие-нибудь насечки и отбрасывает тень. Тогда при вращении тень тоже будет крутиться. Такого в действительности не бывает, как вы (надеюсь) знаете.

Ниже в resource.h можно установить размеры окна плагина:

#define GUI_WIDTH 300
#define GUI_HEIGHT 300


Поиграйтесь со значениями и запустите плагин.

Интерфейс класса плагина



Откройте MyFirstPlugin.h. В нем содержится интерфейс для класса плагина. Часть public включает конструктор, деструктор и три функции-члена класса:

  • Reset вызывается, когда изменяется частота дискретизации.
  • OnParamChange вызывается при изменении параметров плагина, например, когда мы крутим ручку.
  • ProcessDoubleReplacing это самое ядро плагина. Именно в ней осуществляется обработка входящего аудио.


В части private находится только переменная типа double, хранящая текущее значение громкости.

Реализация



Переходим к интересной части! Откройте MyFirstPlugin.cpp. Сразу видим интересный приемчик с типом enum:

enum EParams
{
  kGain = 0,
  kNumParams
};


Устанавливая kGain = 0 и ставя kNumParams после него, kNumParams становится количеством параметров плагина (в данном случае 1).
Следующий enum использует константы, описанные в resource.h и устанавливает координаты ручки в окне плагина:

enum ELayout
{
  kWidth = GUI_WIDTH,
  kHeight = GUI_HEIGHT,

  kGainX = 100,
  kGainY = 100,
  kKnobFrames = 60
};


Он также определяет количества кадров в knob.png равным 60.
Далее начинается имплементация конструктора. Устанавливаются атрибуты плагина:

//arguments are: name, defaultVal, minVal, maxVal, step, label
GetParam(kGain)->InitDouble("Gain", 50., 0., 100.0, 0.01, "%");


В GUI пока что не видно ни значения, ни значка процента, но значение может меняться от 0 до 100. Значение по умолчанию равно 50. Можно заметить, что градация значений не равномерна на окружности. Это из-за SetShape(2.). Если заменить это на SetShape(1.), то распределение значений будет линейным. Именно SetShape задает нелинейное поведение.
Далее конструктор создает графический контекст нужного размера и задает фоновый красный цвет:

IGraphics* pGraphics = MakeGraphics(this, kWidth, kHeight);
pGraphics->AttachPanelBackground(&COLOR_RED);


После этого загружается knob.png, создается новый IKnobMultiControl с изображением и привязывается к GUI. IKnobMultiControl — это класс для ручек интерфейса.
Обратите внимание, как передается параметр kKnobFrames для обозначения количества кадров в спрайте:

IBitmap knob = pGraphics->LoadIBitmap(KNOB_ID, KNOB_FN, kKnobFrames);
pGraphics->AttachControl(new IKnobMultiControl(this, kGainX, kGainY, kGain, &knob));


Наконец, конструктор привязывает графический контекст и создает для плагина пресет по умолчанию:

MakeDefaultPreset((char *) "-", kNumPrograms);


Взглянем на OnParamChange (в конце файла). IMutexLock обеспечивает потоковую безопасность — концепт, который мы разберем попозже. Все остальное — это просто набор вариантов действий в зависимости от того, какой параметр изменяется:

case kGain:
    mGain = GetParam(kGain)->Value() / 100.;
    break;


Как мы помним, kGain изменяется от 0 до 100. Так что после деления значения на 100 мы назначаем величину от 0 до 1 private члену класса mGain.

Итак, мы немного разобрали процесс создания GUI и привязку к нему таких параметров, как mGain. Давайте теперь взглянем на то, как плагин обрабатывает входящее аудио. В нашем случае аудио поток — это последовательность семплов, представленная типом данных double, каждый из которых содержит значение амплитуды сигнала в заданный момент времени.
Первый параметр, передаваемый функции ProcessDoubleReplacing, это double** inputs. Последовательность значений типа double можно передать, используя double*. Но плагин обрабатывает два канала (стерео) или даже больше, так что нам нужны несколько последовательностей семплов, и мы сообщаем это при помощи double**. Первые две строки в функции иллюстрируют это:

double* in1 = inputs[0];
double* in2 = inputs[1];


in1 указывает на первую последовательность семплов (левый канал), in2 – на семплы правого канала. После выполнения аналогичных действий для выходного буфера мы можем итерировать над элементами входного и выходного буферов:

for (int s = 0; s < nFrames; ++s, ++in1, ++in2, ++out1, ++out2)
{
  *out1 = *in1 * mGain;
  *out2 = *in2 * mGain;
}


Для каждого семпла мы берем входное значение, умножаем его на mGain и записываем его в выходной буфер. nFrames сообщает нам, сколько семплов на канал имеется, чтобы мы знали длину буферов.
Вы могли заметить, что когда запускаете плагин как самостоятельное приложение, можно слышать себя из динамиков, если в компьютере есть встроенный микрофон. Это из-за того, что по умолчанию приложение использует этот микрофон как источник входного сигнала. Чтобы изменить это (и кое-что еще), зайдите в preferences:





Установите брэйкпоинт в функции Reset. Измените Sampling Rate справа и примените изменения. Отладчик прервет исполнение кода в функции Reset. Внизу справа, где работает lldb, Введите print GetSampleRate().



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

Дополнительное чтение



Чтобы заполнить некоторые пробелы, настоятельно рекомендую прочесть эти слайды авторства изобретательного г-на Оли Ларкина. В них найдутся некоторые из ключевых разъяснений о WDL-OL.

Оригинал статьи:
martin-finke.de/blog/articles/audio-plugins-003-examining-the-code
Tags:
Hubs:
+2
Comments 0
Comments Leave a comment

Articles