В студии упавший плагин - просто неприятность: перезапустил DAW, открыл проект, работаешь дальше. На сцене это тишина посреди сета. Свои плагины я делаю прежде всего для живых выступлений, поэтому падать им нельзя так же, как нельзя плохо звучать.
Расскажу несколько историй о том, как плагин может упасть, хотя при обычной игре ничего такого не видно. Все эти ошибки нашли проверочные инструменты (pluginval, TSan, стресс-стенд и ревью), а не сцена. Так и было задумано.
D-drum. Падение, которое нашёл pluginval, случилось именно в ней, при загрузке патча.
Почему плагин вообще падает
Внутри плагина одновременно работают минимум два потока. Аудиопоток считает звук и должен укладываться в доли миллисекунды, поэтому ему нельзя ждать, блокироваться и выделять память. Остальные потоки делают всё прочее: хост готовит плагин к новой частоте дискретизации, интерфейс загружает пресет, обработчик кнопки перестраивает модуляцию.
Если два потока одновременно лезут в одни и те же данные и хотя бы один из них пишет, это гонка. Коварство гонок в том, что они случаются редко и каждый раз в другом месте, а у разработчика на машине могут не случиться ни разу.
Случай первый: «хост же всё останавливает»
pluginval на самом строгом уровне нашёл падение в драм-машине D-drum: обращение по нулевому указателю при загрузке патча. Место падения плавало между прогонами, то на 96 кГц с блоком 64, то на 48 кГц с блоком 512. Так ведёт себя гонка, а не выход за границу массива.
В описании движка было написано, что подготовка к воспроизведению и загрузка патча приходят «с одного потока». Это была неправда: подготовку вызывает хост со своего потока, а патч загружается с потока интерфейса. При смене частоты подготовка сама перезагружает патч под новые параметры. Один поток удаляет узлы, а второй в это же время расставляет новые и натыкается на пустое место.
Раньше эту пару методов не проверяли, ведь хост вызывает подготовку, когда обработка остановлена. Аудиопоток в этот момент действительно стоит. А вот поток интерфейса - нет.
Чтобы убедиться, я написал стенд на голом движке, без JUCE и без плагина: один поток без конца вызывает подготовку, другой загружает тот же кит. До правки процесс падал за секунды, в трёх прогонах из трёх. Исправил замком, который берут только эти два метода. Аудиопоток замка не касается, для него ничего не поменялось. После правки стенд выдерживает тысячи циклов подряд, а проверка именно этой пары стала в нём постоянной.
Drone Random 2. Кнопки RANDOM и ручки MUTATION перестраивают модуляцию с потока интерфейса, поэтому им тоже нужен замок.
Случай второй: урок, выученный наполовину
Из первой истории я вынес вывод: перечислять надо все методы, которые работают вне аудиопотока, а не только те пары, что уже проверены. Вывод записал, но применил не до конца. На следующую ночь ревью нашло третий метод, который читал те же данные без замка, - живое переназначение ручек.
Как это бывает в жизни: жмёшь Random в Drone Random 2 или крутишь Mutation, а хост как раз в этот момент переоткрывает звук на другой частоте. Санитайзер потоков (TSan) на движке без замка дал три отчёта о гонке, с замком ни одного. Теперь замок берут три метода, и все они перечислены в заголовке движка. А для новых методов появилось правило: если метод трогает эти данные вне аудиопотока, он обязан брать замок, и для него в стресс-стенде заводится своя проверка.
Случай третий: валидатор, который нарушает контракт
По правилам, пока хост готовит плагин, звук не считается. Но хосты эти правила соблюдают не всегда. Например, auval, валидатор Audio Unit от Apple, их не соблюдает.
При смене частоты подготовка удаляла узлы графа, а аудиопоток продолжал в них писать. Это порча памяти, самая неприятная ошибка: падает потом и совсем в другом месте, где-нибудь при выделении памяти, которое к причине никак не относится.
Защитился так: аудиопоток отмечает, что сейчас считает звук, а подготовка ждёт, пока он закончит, но не дольше 50 мс. Не дождалась - ничего не трогает. Лучше пропустить одну подготовку, чем испортить память.
Случай четвёртый: стенд, который проходил, ничего не проверяя
Стресс-стенд - тоже код, и он тоже может врать. Я ловил его на этом дважды.
В первой версии проверка переназначения ручек получала одну принятую заявку на двадцать отказов. Отказ возвращался раньше, чем метод доходил до проверяемого места, так что проверка «проходила», почти ничего не проверив. Теперь она требует, чтобы принятых заявок было большинство.
Второй раз - с секундомером: каждая проверка работала три секунды. Под санитайзером адресов всё медленнее, и за те же три секунды успевало пройти 3 подготовки вместо 168. Проверка «счётчик не ноль» этого не замечала. Теперь проверки идут, пока каждый поток не наберёт по 20 событий, но не дольше 60 секунд.
Вывод общий: любую проверку надо проверять контрольным замером, иначе она меряет сама себя.
Случай пятый: что репетировал, то и сыграл
Безотказность - это не только «не падает». Это ещё и «звучит так же, как вчера».
DAW режет звуковой блок на куски по MIDI-событиям, и куски бывают любой длины: 37 отсчётов, 3, 41. Оказалось, что у патчей с обратной связью звук зависел от этой нарезки. Задержка в петле обратной связи равнялась длине предыдущего куска. На ровных блоках по 64 отсчёта всё совпадало, а на нарезке блоками по 37 отсчётов расходились 99.9 % сэмплов с ошибкой 11.4 %.
На деле это значило бы, что трек после экспорта в файл звучит не так, как при живом воспроизведении. Исправил так: задержка в петле теперь всегда ровно 64 отсчёта, а время в движке хранится как точка привязки плюс целое число отсчётов, а не как накопленная сумма дробных чисел. Модуляторы считают время от позиции в песне, а не от того, как шло воспроизведение. Теперь звук совпадает бит-в-бит при любой нарезке, и за этим следит отдельный тест на блоках, не кратных 64.
Что из этого вышло
26 сентября 2026 года я отыграл примерно часовой сет только на своих плагинах: Drone Random 2, DroneMatrix, D-Resonator, D-Space, Bleep и D-drum. Ключевые ручки были на контроллере APC40, мышь не понадобилась ни разу. В заметках после выступления: «стабильно, ни багов, ни крашей».
Что я из этого вынес:
- Перечислять все методы, которые работают вне аудиопотока, и с каких потоков они реально приходят. Список «вроде бы проверенных» пар тут не годится.
- Не верить, что хост соблюдает правила. Защищаться так, чтобы их нарушение не портило память.
- Давать стресс-стенду контрольный замер и считать события, а не секунды.
- Проверять, что звук не зависит от нарезки блока, на блоках, не кратных степени двойки.
Плагины из этих историй: D-drum, Drone Random, DroneMatrix. Все они сделаны для живой игры.