Предыдущие посты из серии:
Часть 1. Введение и настройка
______________________________________________________________
Изучение кода
Давайте получше рассмотрим наш тестовый проект. Самые важные файлы — 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.
Автор: 1eqinfinity