Vst что за формула
Мощность
Статья находится на проверке у методистов Skysmart.
Если вы заметили ошибку, сообщите об этом в онлайн-чат
(в правом нижнем углу экрана).
Определение мощности
Допустим, нам необходимо убрать урожай пшеницы с поля площадью 100 га. Это можно сделать вручную или с помощью комбайна. Очевидно, что пока человек обработает 1 га площади, комбайн успеет сделать намного больше. В данном случае разница между человеком и техникой — именно то, что называют мощностью. Отсюда вытекает первое определение.
Мощность в физике — это количество работы, которая совершается за единицу времени.
Рассмотрим другой пример: между точкой А и точкой Б расстояние 15 км, которое человек проходит за 3 часа, а автомобиль может проехать всего за 10 минут. Понятно, что одно и то же количество работы они сделают за разное время. Что показывает мощность в данном случае? Как быстро или с какой скоростью выполняется некая работа.
В электромеханике данная величина тоже связана со скоростью, а конкретно — с тем, как быстро передается ток по участку цепи. Исходя из этого, мы можем рассмотреть еще одно определение.
Мощность — это скалярная физическая величина, которая характеризует скорость передачи энергии от системы к системе или скорость преобразования, изменения, потребления энергии.
Напомним, что скалярными величинами называются те, значение которых выражается только числом (без вектора направления).
Мощность человека в зависимости от деятельности
Вид деятельности
Мощность, Вт
Бег со скоростью 9 км/ч
Плавание со скоростью 50 м/мин
Как обозначается мощность: единицы измерения
В таблице выше вы увидели обозначение в ваттах, и читая инструкции к бытовой технике, можно заметить, что среди характеристик прибора обязательно указано количество ватт. Это единица измерения механической мощности, используемая в международной системе СИ. Она обозначается буквой W или Вт.
Измерение мощности в ваттах было принято в честь шотландского ученого Джеймса Уатта — изобретателя паровой машины. Он стал одним из родоначальников английской промышленной революции.
В физике принято следующее обозначение мощности: 1 Вт = 1 Дж / 1с.
Это значит, что за 1 ватт принята мощность, необходимая для совершения работы в 1 джоуль за 1 секунду.
В каких единицах еще измеряется мощность? Ученые-астрофизики измеряют ее в эргах в секунду (эрг/сек), а в автомобилестроении до сих пор можно услышать о лошадиных силах.
Интересно, что автором этой последней единицы измерения стал все тот же шотландец Джеймс Уатт. На одной из пивоварен, где он проводил свои исследования, хозяин накачивал воду для производства с помощью лошадей. И Уатт выяснил, что 1 лошадь за секунду поднимает около 75 кг воды на высоту 1 метр. Вот так и появилось измерение в лошадиных силах. Правда, сегодня такое обозначение мощности в физике считается устаревшим.
Одна лошадиная сила — это мощность, необходимая для поднятия груза в 75 кг за 1 секунду на 1 метр. 🐴
Программирование&Музыка: Частотный фильтр Баттервота. Часть 3
Всем привет! Вы читаете третью часть статьи про создание VST-синтезатора на С#. В предыдущих частях был рассмотрен SDK и библиотеки для создания VST плагинов, рассмотрено программирование осциллятора и ADSR-огибающей для управления амплитудой сигнала.
В этой части я расскажу, как рассчитать и закодить фильтр частот, без которого не обходится ни один синтезатор. А без эквалайзера немыслима обработка звука.
Внимание — будет много матана — будем рассчитывать формулы для коэффициентов фильтра.
Скриншот VST плагина-эквалайзера Fab Filter Pro Q
Цикл статей
Оглавление
Эквалайзер
Часто при обработке звука мы хотим изменить его характер/окраску/тембр. Сделать звук более басовым, убрать верхние частоты, или наоборот, сделать звук «прозрачным», оставив лишь середину и верха. Уверен, многие люди, не работавшие с обработкой звука, знают что такое эквалайзер — они есть акустических колонках, музыкальных центрах, магнитофонах, плеерах, и т.д. Эквалайзер — это набор фильтров, каждый из которых изменяет амплитуду сигнала в его выбранной полосе частот. На бытовых колонках это, обычно, 2-3 крутилки — низкие частоты, средние и верха, с фиксированными полосами частот.
В Winamp’овском эквалайзере уже есть 10 заранее определенных полос.
Скриншот эквалайзера в плеере Winamp
В мире обработки звука существует множество плагинов-эквалайзеров, на любой вкус и цвет. Плагин Fab Filter Pro Q (скриншот в начале статьи) — это графический эквалайзер, позволяющий создавать большое число полос и редактировать их параметры.
Каждая полоса в эквалайзере — это, по сути, фильтр частот. Фильтры частот изменяют тембральные/частотные характеристики сигнала. В электронике существуют много типов и классификаций фильтров, с соответствующими характеристиками и параметрами — смотрим википедию.
Мы рассмотрим и запрограммируем самые простые фильтры: НЧ, ВЧ и полосовой фильтры.
Фильтрация через преобразование Фурье
По идее, вам никто не мешает делать с сигналом дискретное преобразование Фурье, обработать частоты и затем сделать обратное преобразование.
Если не думать над реализацией ДПФ, то такой подход я бы назвал достаточно интуитивным и простым в программировании (опять же, если взять ДПФ из какой-нибудь либы и не кодить самому).
Минусы подхода — во-первых, ДПФ принимает на вход массив из семплов, размер которого является степенью двойки. Это значит, что выходной сигнал уже будет с задержкой. Во вторых, каждый 512-й семпл мы будем производить данный алгоритм: ДПФ, обработка частот сигнала, обратное ДФП. Это не малые вычисления. В-третьих, есть еще минусы и тонкости, которые знают адепты цифровой обработки сигналов.
Мы не будем рассматривать применение ДПФ, а заглянем в теорию цифровых фильтров; напишем фильтр, который обрабатывает значения семплов и имеет линейную вычислительную сложность в зависимости от длины входящего массива семплов.
Цифровые фильтры
Большую часть информации и вывод формул я взял из книги Digital Signal Processing: A Practical Approach — очень рекомендую, она есть в русской версии — Цифровая обработка сигналов. Практический подход, заинтересованные найдут PDF в сети.
Хочу сделать важное замечание. Тема построения и рассчитывания фильтра действительно очень сложна, содержит массу тонкостей и нюансов, требует знания и понимания теории. В этой статье я покажу, как рассчитать формулы фильтра Баттервота, чтобы у читателя возникло понимание, откуда выводятся эти формулы. Но почему именно такие исходные формулы, почему именно такие замены — можно понять лишь погрузившись в глубокую теорию цифровой обработки сигналов.
Когда я начинал гуглить код фильтров, я сразу находил множество непонятного математического кода, и хотелось хоть чуть-чуть понять, откуда берутся такие рассчетные формулы. Осциллятор, огибающая, дилей — понимание и программирование работы этих составляющих лично мне кажется интуитивным, но только не фильтров. Этой статьей я хочу пробудить интерес к цифровой обработке сигналов) Буду рад, если возникнет желание разобраться в этой теме более основательно.
Аппроксимация АЧХ идеального фильтра (картинка из советского учебника, не нашел исходник)
Фильтр изменяет сигнал, «убирая» в нем выбранные частоты. Существующие фильтры не идеальны. Полоса пропускания — полоса частот, которую фильтр «не затрагивает» (на графике есть некоторая изменения — особенность неидеального представленного фильтра). Полоса подавления — полоса нежелательных частот. В полосе перехода происходит спад частот. Естественно, фильтр ближе к «идеальному» тем, насколько меньше он искажает полосу пропускания, насколько сильно он подавляет частоты в полосе подавления и насколько узка полоса перехода. Есть разные «приближения» фильтров — фильтр Чебышёва, Баттервота, и так далее — их вы найдете в книжках и на просторах сети.
Почему фильтр Баттервота?
Все очень просто, АЧХ фильтра Баттерворта максимально гладкая на частотах полосы пропускания — имхо, важнее всего не испортить сигнал в полосе пропускания.
Логарифмическая АЧХ для фильтров Баттерворта нижних частот разных порядков (скриншот взят с Википедии)
Вывод формулы фильтра НЧ
Передаточная функция для фильтра Баттервота на s-плоскости записывается следующими формулами:
при четных n и
при нечетных n
Чтобы получить сверточные коэффициенты, нужно получить передаточную функцию на z-плоскости в виде
Тогда свертка для фильтра будет выглядеть так:
В формуле нужно использовать денормированные частоты, т.е. произвести замену (в полосовом фильтре будут две частоты w1 и w2, определяющие полосу пропускания):
Если мы хотим рассчитывать НЧ-фильтр, то нужно сделать преобразование — произвести замену параметра s в передаточной функции:
Для рассчета других типов фильтров (ВЧ, полосового, режекторного) нужно делать другие замены. Они рассмотрены в книге Цифровая обработка сигналов. Практический подход в части 8.8.2 и далее в статье.
Далее, для перехода в z-плоскость делаем замену:
Для аналитических рассчетов я использовал пакет Mathematica.
Нужно получить числитель и знаменатель в виде полиномов от z. Приведем слагаемые знаменателя H(z) к общему знаменателю. Для этого найдем наибольший общий делитель (НОД, GCD) слагаемых знаменателя и поделим на него числитель и знаменатель исходной функции H(z).
Найдем коэффициенты при степенях у полученных полиномов, используя функцию CoefficientList:
Если все делать честно, то, по условию, a0 должен быть равен 1 — поделим на a0 все коэффициенты (для кодинга будем использовать предыдущие формулы без деления):
Вывод формулы фильтра ВЧ и полосового фильтра
Вывод формул для ВЧ-фильтра аналогичен НЧ-фильтру с другим преобразованием:
Для вывода формул полосового фильтра применяется преобразование:
Если производить замену, то степень полиномов в числителе и знаменателе H(z) удвоится (в замене есть s^2), значит порядок фильтра увеличится вдвое. Поэтому изначально используем функцию H(s) для n = 1:
Программирование фильтра Баттервота по полученным формулам
Фильтр будет иметь 2 параметра: тип фильтра (НЧ, ВЧ, полосовой) и частота среза w. Для полосового фильтра будем рассматривать частоту среза как частоту посередине полосы пропускания. Полосу пропускания же определим как отрезок частот [w — w/4, w + w/4] (можно придумать здесь более сложный и логичный здесь логарифмический закон, на ваше усмотрение).
Пусть мы определили коэффициенты b0, b1, b2, a1 и a2 (a0 по условию равен 1) по рассчитанным формулам. Алгоритм работы фильтра сводится к свертке, которая делается последовательно для каждого семпла:
y(n) — это новое значение семпла, которое нужно рассчитать. x(n) — текущее значение семпла, соответственно y(n-1) и y(n-2) — предыдущие 2 рассчитанных семпла, а x(n-1) и x(n-2) — предыдущие входные значения семплов.
Нужно организовать запоминание предыдущих семплом. Не будем мудрить с циклическими буферами, сделаем просто и понятно: два массива из трех элементов. Каждый раз будем «проталкивать» новые значения в этот массив, последовательно копируя более старые значения семплов.
Получаем простой класс:
Напишем каркасный класс для фильтра (смотри архитектуру синтезатора в первой статье). Класс BiquadConvolutionTable работает с одним сигналом, т.е. с одним каналом — моно. Поэтому нам нужны две BiquadConvolutionTable — для левого и правого каналов.
Чтобы корректно применить фильтр, нужно последовательно, для всех семплов входящей последовательности применить функцию BiquadConvolutionTable.Process и заполнить результирующий массив семплов.
Рассчетом коэффициентов для BiquadConvolutionTable будет заниматься функция CalculateCoefficients.
Функция CalculateCoefficients вызывается каждый раз в цикле — зачем? В следующей статье я расскажу про модуляцию (изменение во времени) параметров, и поэтому, частота среза может меняться, а значит, нужно перерассчитывать коэффициенты. Конечно, по-трушному, нужно подписаться на изменение частоты среза и уже в обработчике рассчитывать коэффициенты. Но в этих статьях я оптимизами заниматься не буду, цель — закодить фильтр.
Осталось закодить функцию CalculateCoefficients по рассчитаным формулам для коэффициентов.
Вспомним, что нужно использовать денормированные частоты, т.е. произвести замену:
Списываем все формулы для коэффициентов b0, b1, b2, a0, a1, a2. После рассчетов нужно поделить все коэффициенты на a0, чтобы a0 стало равно 1.
Полосовой эквалайзер в библиотеке NAudio
Она содержит пространство имен NAudio.Dsp с функционалом для фильтрации, свертки, гейта, огибающей, БПФ и других интересных штук.
Рассмотрим класс Equalizer (из примеров, пространство имен NAudioWpfDemo.EqualizationDemo), позволяющий производить эквализацию сигнала. Класс реализует ISampleProvider, который в функции Read(float[] buffer, int offset, int count) обрабатывает (изменяет) массив семплов buffer.
Конструктор принимает массив структуры EqualizerBand, которые описывают «полосы» эквалайзера:
Здесь Frequency — центральная частота полосы с параметром Q (Bandwidth, добротность фильтра), c усилением Gain dB.
Если заглянуть в реализацию, то каждой полосе EqualizerBand соответствует класс BiQuadFilter который используется как полосовой (Peaking) фильтр. Все фильтры изменяют сигнал используются последовательно.
Класс EqualizerBand является реализацией фильтра Баттервота, с большим выборов типов фильтров и параметрами. Если посмотреть реализацию, можно увидеть схожие формулы и коэффициенты.
Пример использования класса Equalizer вы найдете в проекте NAudioWpfDemo в классе EqualizationDemoViewModel.
Программы для рассчета фильтров
Прародителем цифровых фильтров были фильтры аналоговые. Теория для аналоговых схем и аналоговой обработке сигналов в дальнейшем переросла в теорию цифровой обработки сигналов.
Для рассмотренного фильтра Баттервота и рассчитанных формул для коэффициентов можно собрать аналоговую схему.
Есть много программ для рассчета и построения схем, параметров элементов для них, сверточных коэффициентов различных фильтров.
Можете погуглить по «filter calculation software».
Iowa Hills Software RF Filter Designer
В следующей статье я расскажу про эффект delay, distortion и модуляцию параметров.
Всем добра! Удачи в программировании!
Список литературы
Не забывайте смотреть списки статей и книг в предыдущих статьях.
Программирование&Музыка: понимаем и пишем VSTi синтезатор на C# WPF. Часть 1
Занимаясь музыкальным творчеством, я часто делаю аранжировки и записи на компьютере — используя кучу всяких VST плагинов и инструментов. Стыдно признаться — я никогда не понимал, как «накручивают» звуки в синтезаторах. Программирование позволило мне написать свой синтезатор, «пропустить через себя» процесс создания звука.
Я планирую несколько статей, в которых будет пошагово рассказано, как написать свой VST плагин/инструмент: программирование осциллятора, частотного фильтра, различных эффектов и модуляции параметров. Упор будет сделан на практику, объяснение программисту простым языком, как же все это работает. Теорию (суровые выводы и доказательства) обойдем стороной (естественно, будут ссылки на статьи и книги).
Ниже представлен обзорный ролик моего простого синтезатора, полученных интересных звучаний.
Предстоит нелегкий путь, если вы готовы — добро пожаловать под кат.
Цикл статей
Оглавление
Загадочный мир синтеза звука
Я очень люблю музыку, слушаю разные стили, играю на различных инструментах, и, конечно, сочиняю и записываю аранжировки. Когда я начинал использовать эмуляторы синтезаторов в звукозаписывающих программах (да и сейчас) я всегда перебирал кучу пресетов, искал подходящее звучание.
Перебирая пресеты одного синтезатора можно встретить как «ожидаемый» звук электронного синтезатора из детства (музыка из мультика Летучий Корабль) так и имитацию ударных, звуков, шума, даже голоса! И все это делает один синтезатор, с одними и теми же ручками параметров. Это меня всегда удивляло, хотя я понимал: каждый звук — суть конкретная настройка всех ручек.
Недавно я решил наконец-таки разобраться, каким же образом создаётся (или, правильнее сказать, синтезируется) звук, как и почему нужно крутить ручки, как видоизменяется от эффектов сигнал (визуально и на слух). И конечно же, научиться (хотя бы понять основы) самому «накручивать» звук, копировать понравившиеся мне стили. Я решил последовать одной цитате:
«Скажи мне — и я забуду, покажи мне — и я запомню, дай мне сделать — и я пойму.»
Конфуций
Конечно, все подряд делать не надо (куда столько велосипедов?), но сейчас я хочу получить знания и самое главное — поделиться ими с вами.
Цель: не углубляясь в теорию, создать простой синтезатор, сделав упор на объяснение процессов с точки зрения программирования, на практике.
В синтезаторе будут:
Все составляющие я планирую рассмотреть в нескольких статьях. В данной будет рассмотрено программирование осциллятора.
Программировать будем на C#; UI можно писать либо на WPF, либо на Windows Forms, либо вообще обойтись без графической оболочки. Плюс выбора WPF — красивая графика, которую достаточно быстро кодить, минус — только на Windows. Владельцы других ОС — не расстраивайтесь, всё-таки цель — понять работу синтезатора (а не запилить красивый UI), тем более, код, который я буду демонстрировать, можно быстро перенести, скажем, на С++.
Программирование логики синтезатора начнется с главы Пишем простой осциллятор. Если вам не интересны технические стороны написания VST плагинов, вы просто хотите прочитать про, собственно, синтез (и ничего не кодить) — милости прошу сразу к этой главе.
Исходный код написанного мной синтезатора доступен на GitHub’е.
Звук в цифровом виде
По-сути, конечная наша цель — создание звука на компьютере. Обязательно прочитайте (хотя бы, бегло) статью на хабре «Теория звука» — в ней изложены базовые знания о представлении звука на компьютере, понятия и термины.
Любой звуковой файл в компьютере в несжатом формате представляет собой массив семплов. Любой плагин, в конечном счете, принимает и обрабатывает на входе массив семлов (в зависимости от точности это будут float или double числа, либо можно работать с целыми числами). Почему я сказал массив, а не одиночный семпл? Этим я хотел подчеркнуть что обрабатывается звук в целом: если вам нужно сделать эквализацию, вы не сможете оперировать одним лишь семплом без информации о других.
Хотя, конечно, есть задачи, которым не важно знать, что вы обрабатываете — они рассматривают конкретный семпл. Например, задача — поднять уровень громкости в 2 раза. Мы можем работать с каждым семплом по-отдельности, и нам не нужно знать про остальные.
VST SDK
VST (Virtual Studio Technology) — это технология, позволяющая писать плагины для программ обработки звука. Сейчас существует большое множество плагинов, решающих различные задачи: синтезаторы, эффекты, анализаторы звука, виртуальные инструменты и так далее.
Чтобы создавать VST плагины, компания Steinberg (некоторые ее знают по программе Cubase) выпустила VST SDK, написанный на C++. Помимо технологии (или, как еще говорят, «формата плагинов») VST, есть и другие — RTAS, AAX, тысячи их. Я выбрал VST, из-за большей известности, большого количества плагинов и инструментов (хотя, большинство известных плагинов поставляется в разных форматах).
На данный момент актуальная версия VST SDK 3.6.6, хотя многие продолжают использовать версию 2.4. Исторически складывается, что сложно найти DAW без поддержки версии 2.4, и не все поддерживают версию 3.0 и выше.
VST SDK можно скачать с официального сайта.
В дальнейшем мы будем работать с библиотекой VST.NET, которая является оберткой для VST 2.4.
Если вы намерены серьезно разрабатывать плагины, и хотите использовать последнюю версию SDK, то вы можете самостоятельно изучить документацию и примеры (все можно скачать с официального сайта).
Сейчас я кратко изложу принципы VST SDK 2.4, для общего понимания работы плагина и его взаимодействия с DAW.
Дальнейшие функции, перечисления и структуры вы можете найти в скачанном VST SDK в исходниках из папки «VST3 SDK\pluginterfaces\vst2.x».
Библиотека должна экспортировать функцию со следующей сигнатурой:
Функция принимает указатель на коллбэк, чтобы плагин мог получать необходимую ему информацию от хоста.
Все делается на достаточно «низком» уровне — чтобы хост понял, что от него хотят, нужно передавать номер команды через параметр opcode. Перечисление всех опкодов хардкорные C-кодеры могут найти в перечислении AudioMasterOpcodesX. Остальные параметры используются аналогичным образом.
VSTPluginMain должна вернуть указатель на структуру AEffect, которая, по-сути, и является нашим плагином: она содержит информацию о плагине и указатели на функции, которые будет вызывать хост.
Основные поля структуры AEffect:
Информация о плагине. Название, версия, число параметров, число программ и пресетов (читай далее), тип плагина и прочее.
Фунции для запроса и установки значений параметров.
Функции смены пресетов/программ.
Фунция обработки массива семплов
float** — это массив каналов, каждый канал содержит одинаковое количество семплов (количество семплов в массиве зависит от звукового драйвера и его настроек). В основном встречаются плагины, обрабатывающие моно и стерео.
Супер-функция, подобна audioMasterCallback.
Вызывается хостом, по параметру opcode определяется необходимое действие (список AEffectOpcodes). Используется, чтобы узнать дополнительную информацию о параметрах, сообщать плагину об изменениях в хосте (изменение частоты дискредитации), для взаимодействия с UI плагина.
При работе с плагином было бы очень удобно, чтобы юзер мог сохранить все настроенные ручки и переключатели. А еще круче, чтобы была их автоматизация! Например, вы можете захотеть сделать знаменитый эффект rise up — тогда вам нужно менять параметр cutoff (частота среза) эквалайзера во времени.
Чтобы хост управлял параметрами вашего плагина, в AEffect есть соответствующие функции: хост может запросить общее количество параметров, узнать или установить значение конкретного параметра, узнать название параметра, его описание, получить отображаемое значение.
Хосту все равно, какая логика у параметров в плагине. Задача хоста — сохранять, загружать, автоматизировать параметры. Хосту очень удобно воспринимать параметр, как float-число от 0 до 1 — а уж плагин пусть как хочет, так его и толкует (так и сделали большинство DAW, неофициально).
Пресеты (в терминах VST SDK — programs/программы) это коллекция конкретных значений всех параметров плагина. Хост может менять/переключать/выбирать номера пресетов, узнавать их названия, аналогично с параметрами. Банки — коллекция пресетов. Банки логически существуют только в DAW, в VST SDK есть только пресеты и программы.
Поняв идею структуры AEffect можно набросать и скомпилировать простой DLL-плагинчик.
А мы пойдем дальше, на уровень выше.
WDL-OL и JUCE
Чем плоха разработка на голом VST SDK?
На сцену выходит WDL-OL. Это C++ библиотека для создания кроссплатформенных плагинов. Поддерживаются форматы VST, VST3, Audiounit, RTAS, AAX. Удобство библиотеки состоит в том, что (при правильной настройке проекта) вы пишете один код, а при компилировании получаете свой плагин в разных форматах.
WDL-OL решает, по крайней мере, первые три пункта минусов разработки на VST SDK. Все, что вам нужно — корректно настроить проект (первая статья из блога), и отнаследоваться от класса IPlug.
Теперь с чистой совестью можно реализовать функцию ProcessDoubleReplacing, которая, по сути и является «ядром» плагина. Все заботы взял на себя класс IPlug. Если его изучать, можно быстро понять, что (в формате VST) он является оберткой структуры AEffect. Коллбэки от хоста и функции для хоста превратились в удобные виртуальные функции, с понятными названиями и адекватными списками параметров.
Помимо WDL-OL я так же узнал про библиотеку JUCE. JUCE похожа на WDL-OL, решает все заявленные минусы разработки на VST SDK. Помимо всего прочего, она уже имеет в своем составе и UI-редактор, и кучу классов для работы с аудио данными. Я лично ее не использовал, поэтому советую прочитать о ней, хотя бы, на вики.
Если вы хотите писать серьезный плагин, тут я бы уже всерьез задумался над использованием библиотек WDL-OL или JUCE. Всю рутину они сделают за вас, а у вас же остается вся мощь языка C++ для реализации эффективных алгоритмов и кроссплатформенность — что не маловажно в мире большого количества DAW.
Чем же мне не угодили WDL-OL и JUCE?
Страничка библиотеки — vstnet.codeplex.com, там есть исходники, бинарники, документация. Как я понял, библиотека находится в стадии почти доделал и забил заморозки (не реализованы некоторые редко используемые функции, пару лет нет изменений репозитория).
Библиотека состоит из трех ключевых сборок:
Работает все это следующим образом
При загрузке вашей либы необходимо, чтобы в ней был класс, реализующий интерфейс IVstPluginCommandStub:
VstPluginInfo содержит базовую о плагине — версия, уникальный ID плагина, число параметров и программ, число обрабатываемых каналов. PluginConfiguration нужна для вызывающей либы-обертки Jacobi.Vst.Interop.
В свою очередь, IVstPluginCommandStub реализует интерфейс IVstPluginCommands24, который содержит методы, вызываемые хостом: обработка массива (буфера) семплов, работа с параметрами, программами (пресетами), MIDI-сообщениями и так далее.
Jacobi.Vst.Framework содержит готовый удобный класс StdPluginCommandStub, реализующий IVstPluginCommandStub. Все что нужно сделать — отнаследоваться от StdPluginCommandStub и реализовать метод CreatePluginInstance(), который будет возвращать объект (instance) вашего класса-плагина, реализующего IVstPlugin.
Опять же, есть готовый удобный класс VstPluginWithInterfaceManagerBase:
Если смотреть исходный код библиотеки, можно увидеть интерфейсы, описывающие компоненты плагина, для работы с аудио, параметрами, MIDI и т.д. :
Класс VstPluginWithInterfaceManagerBase содержит виртуальные методы, возвращающие эти интерфейсы:
Эти методы и нужно перегружать, чтобы реализовывать свою логику в кастомных классах-компонентах. Например, вы хотите обрабатывать семплы, тогда вам нужно написать класс, реализующий IVstPluginAudioProcessor, и вернуть его в методе CreateAudioProcessor.
Используя различные готовые классы-компоненты можно сосредоточиться на программировании логики плагина. Хотя, вам никто не мешает реализовывать все самому, как хочется, основываясь только на интерфейсах из Jacobi.Vst.Core.
Для тех, кто уже кодит — предлагаю вам пример просто плагина, который понижает громкость на 6 дБ (для этого нужно умножить семпл на 0.5, почему — читай в статье про звук).
При программировании синта я столкнулся с некоторыми проблемами при использовании классов из Jacobi.Vst.Framework. Основная проблема заключалась в использовании параметров и их автоматизации.
Во первых, мне не понравилась реализация событий изменения значения; во вторых, обнаружились баги при тестировании плагина в FL Studio и Cubase. FL Studio воспринимает все параметры как float-числа от 0 до 1, даже не используя специальную функцию из VST SDK с опкодом effGetParameterProperties (функция вызывается у плагина чтобы получить дополнительную информацию о параметре). В WDL-OL реализация закомментирована с пометкой:
could implement effGetParameterProperties to group parameters, but can’t find a host that supports it
Хотя, конечно же, в Cubase эта функция вызывается (Cubase — продукт компании Steinberg, которая и выпустила VST SDK).
В начале я внес правки саму библиотеку, написал автору, чтобы он дал разрешение выложить исходники в репозиторий, либо сам создал репозиторий на GitHub’е. Но внятного ответа я так и не получил, поэтому решил сделать надстройку над либой — Syntage.Framework.dll.
Помимо этого, в надстройке реализованы удобные классы для работы с UI, если вы хотите использовать WPF.
Самое время скачать исходный код моего синтезатора и скомпилировать его.
Правила использования моей надстройки просты: вместо StdPluginCommandStub юзаем SyntagePluginCommandStub, а свой плагин наследуем от SyntagePlugin.
WPF UI
В VST плагине не обязательно должен быть графический интерфейс. Я видел много плагинов без UI (одни из них — mda). Большинство DAW (по крайней мере, Cubase и FL Studio) предоставят вам возможность управлять параметрами из сгенерированного ими UI.
Автосгенерированный UI для моего синтезатора в FL Studio
Чтобы ваш плагин был с UI, во-первых, у вас должен быть класс, реализующий IVstPluginEditor; во-вторых, нужно вернуть его инстанс в перегруженной функции CreateEditor вашего класса плагина (наследник SyntagePlugin).
В своем синтезаторе Syntage я написал пару контролов — слайдер, крутилка (knob), клавиатура пианино — если вы хотите, можете их скопировать и использовать.
UI-поток (thread)
Я тестировал синтезатор в FL Studio и Cubase 5 и уверен, что, в других DAW будет тоже самое: UI плагина обрабатывается отдельным потоком. А это значит, что логики аудио и UI обрабатывается в независимых потоках. Это влечет все проблемы, или, последствия такого подхода: доступ к данным из разных потоков, критические данные, доступ к UI из другого потока.
Для облегчения решения проблем я написал класс UIThread, который, по сути, является очередью команд. Если вы в какой-то момент хотите что-то сообщить/поменять/сделать в UI, а текущий код работает не в UI-потоке, то вы можете поставить на выполнение в очередь необходимую функцию:
Здесь в очередь команд помещается анонимный метод, обновляющий нужные данные. При вызове ProcessIdle все накопившиеся в очереди команды будут выполнены.
UIThread не решает всех проблем. При программировании осциллографа необходимо было обновлять UI по массиву семплов, который обрабатывался в другом потоке. Пришлось использовать мьютексы.
Обзор архитектуры синтезатора Syntage
При написании синтезатора активно использовалось ООП; предлагаю вам познакомиться с получившейся архитектурой и использовать мой код. Вы можете сделать все по-своему, но в этих статьях придется терпеть мое видение)
Класс PluginCommandStub нужен только чтобы создать и вернуть объект класса PluginController. PluginController предоставляет информацию о плагине, так же создает и владеет следующими компонентами:
Чтобы обрабатывать аудиоданные есть интерфейсы IAudioChannel и IAudioStream. IAudioChannel предоставляет прямой доступ к массиву/буферу семплов (double[] Samples). IAudioStream содержит массив каналов.
Представленные интерфейсы содержат удобные методы обработки всех семплов и каналов «скопом»: микширование каналов и потоков, применение метода к каждому семплу в отдельности и так далее.
Для интерфейсов IAudioChannel и IAudioStream написаны реализации AudioChannel и AudioStream. Здесь важно запомнить следующую вещь: нельзя хранить ссылки на AudioStream и AudioChannel, если они являются внешними данными в функции. Суть в том, что размеры буферов могут меняться по ходу работы плагина, буферы постоянно переиспользуются — не выгодно постоянно перевыделять и копировать память. Если вам необходимо сохранить буфер для дальнейшего использования (уж не знаю, зачем) — копируйте его в свой буфер.
IAudioStreamProvider является владельцем аудиопотоков, можно попросить создать поток функцией CreateAudioStream и вернуть поток для его удаления функцией ReleaseAudioStream.
В каждый момент времени длина (длина массива семплов) всех аудиопотоков и каналов одинакова, технически она определяется хостом. В коде ее можно получить либо у самого IAudioChannel или IAudioStream (свойство Length), так же у «хозяина» IAudioStreamProvider (свойство CurrentStreamLenght).
Класс AudioProcessor является «ядром» синтезатора — в нем-то и происходит синтез звука. Класс является наследником SyntageAudioProcessor, который, в свою очередь, реализует следующие интерфейсы:
Синтез звука проходит длинную цепочку обработки: создание простой волны в осцилляторах, микширование звука с разных осцилляторов, последовательная обработка в эффектах. Логика создания и обработки звука была разделена на классы-компоненты для AudioProcessor. Каждый компонент является наследником класса SyntageAudioProcessorComponentWithParameters — содержит ссылку на AudioProcessor и возможность создавать параметры.
В синтезаторе представлены следующие компоненты:
Все этапы создания звука вы можете найти в функции Routing.Process и на следующей схеме:
Звук одновременно создается на двух одинаковых осцилляторах (юзер может по-разному настроить их параметры). Для каждого осциллятора его звук проходит через огибающую. Два звука смешиваются в один, он проходит через фильтр частот, идет в эффект дисторшн, дилэй и клип. В мастере регулируется результирующая громкость звука. После мастера звук больше не модифицируется, но передается в осциллограф и блок LFO-модуляции (нужно для их внутренней логики).
Далее будет рассмотрено программирование логики класса Oscillator, а в следующих статьях будут рассмотрены другие классы-компоненты.
Настраиваем проект для создания плагина/инструмента
Предлагаю вам использовать следующий скрипт (его нужно прописать в Project → Properties → Build Events → Post-build event command line, выполнение скрипта поставьте на On successful build):
Отладка кода
Пишем простой осциллятор
Я надеюсь, что вы прочитали главу «Обзор архитектуры синтезатора Syntage» — я буду объяснять все в терминах своей архитектуры.
Самый простой звук — это чистый тон (синусоидальный сигнал, синус) определенной частоты. В природе вы вряд ли сможете услышать чистый тон. В жизни же можно услышать чистые тона в какой-нибудь электронике (и то, уверенности мало). Фурье сказал, что любой звук можно представить как одновременное звучание тонов разной частоты и громкости. Окраска звука характеризуется тембром — грубо говоря, описанием соотношения тонов в этом звуке (спектром).
Мы пойдем схожим путем — будем генерировать простой сигнал, а затем воздействовать на него и менять с помощью эффектов.
Какие выбрать «простые» сигналы? Очевидно, сигналы, спектр которых известен и хорошо изучен, которые легко обрабатывать. Возьмем четыре знаменитые типа сигналов:
Периоды четырех типов сигналов: синус, треугольник, импульс/квадрат, пила.
Чтобы синтезировать звуки, вы должны четко представлять себе исходное звучание этих простых сигналов.
Синус имеет глухое и тихое звучание, остальные же — «острое» и громкое. Это связано с тем, что, в отличие от синуса, другие сигналы содержат большое количество других тонов (гармоник) в спектре.
Наш генерируемый сигнал будет характеризоваться двумя параметрами: типом волны и частотой.
На графике изображены периоды нужных нам волн. Заметьте, что все волны представлены в интервале от 0 до 1. Это очень удобно, так как позволяет одинаково запрограммировать расчет значений. Такой подход позволяет задать произвольную форму сигнала, я даже видел синтезаторы, где можно вручную его нарисовать.
По представленным картинкам напишем вспомогательный класс WaveGenerator, с методом GetTableSample, который будет возвращать значение амплитуды сигнала в зависимости от типа волны и времени (время должно быть в пределах от 0 до 1).
Добавим так же в тип волны белый шум — он полезен в синтезе нестандартных звуков. Белый шум характеризуется тем, что спектральные составляющие равномерно распределены по всему диапазону частот. Функция NextDouble стандартного класса Random имеет равномерное распределение — таким образом, мы можем считать, что каждый сгенерированный семпл относится к некоторой гармонике. Соответственно, мы будем выбирать гармоники равномерно, получая белый шум. Нужно лишь сделать отображение результата функции из интервала [0,1] в интервал минимального и максимального значения амплитуды [-1,1].
Необходимо запросить у IAudioStreamProvider (для нас это будет родительский AudioProcessor) аудиопоток, и в каждом вызове функции Generate заполнять его сгенерированными семплами.
Пока что у нашего осциллятора будет два параметра:
Оформим все вышесказанное:
Осталось написать функцию GenerateToneToStream.
Каждый раз когда мы будем генерировать семплы сигнала, мы должны помнить о двух значениях:
Оба параметра могут меняться во время работы плагина, поэтому не советую каким-либо образом их кешировать. Каждый вызов функции Generate() на вход плагину подается буфер конечной длины (длина определяется хостом, по времени она достаточно короткая) — звук генерируется «порциями». Мы должны запоминать, сколько времени прошло с момента начала генерирования волны, чтобы звук был «непрерывным». Пока что звук будем генерировать с момента старта плагина. Синхронизировать звук с нажатием клавиши будем в следующей статье.
Семплы генерируются в цикле от 0 до [длина текущего буфера].
Частота дискретизации — число семплов в секунду. Время, которое проходит от начала одного семпла до другого равно timeDelta = 1/SampleRate. При частоте дискретизации 44100 Гц это очень маленькое время — 0.00002267573 секунды.
Теперь мы можем знать, сколько времени в секундах прошло с момента старта до текущего семпла — заведем переменную _time и будем прибавлять к ней timeDelta каждую итерацию цикла.
Чтобы воспользоваться функцией WaveGenerator.GetTableSample нужно знать относительное время от 0 до 1, где 1 — период волны. Зная нужную частоты волны, мы знаем и ее период — значение, обратное частоте.
Нужное относительное время мы можем получить как дробную часть деления прошедшего времени на период волны.
Пример: мы генерируем синус со знаменитой частотой 440 Гц. Из частоты находим период синуса: 1/440 = 0.00227272727 секунды.
Частота дискретизации 44100 Гц.
Рассчитаем 44150-й семпл, если на нулевом семпле время равнялось нулю.
На 44150-м семпле прошло 44150/44100 = 1.00113378685 секунд.
Смотрим, сколько это в периодах — 1.00113378685/0.00227272727 = 440.498866743.
Отбрасываем целую часть — 0.498866743. Именно это значение и нужно передать в функцию WaveGenerator.GetTableSample.
Если записать все символьно, получим:
Оформим выкладки в виде отдельной функции WaveGenerator.GenerateNextSample и запишем итоговую функцию GenerateToneToStream.
Обычно, в параметры осциллятора добавляют следующие:
Данные параметры есть в реализованном мною синтезаторе — вы можете самостоятельно их реализовать.
Осталось реализовать классы AudioProcessor (будет создавать осциллятор и вызывать у него метод Generate) и PluginController (создает AudioProcessor).
Посмотрите реализацию данных классов в моем коде Syntage. На текущем этапе AudioProcessor нужен, чтобы:
В следующей статье я расскажу как написать ADSR-огибающую.