Mix Teacher: как я сделал VST3-плагин-помощник для сведения

от автора

Привет, Хабр! Меня зовут Артур Валиев. Раз в неделю я делюсь здесь своими рабочими буднями и проектами. Сегодня расскажу, чем занимался в последние дни.

Я веду свой блог и обычно пишу про кодеки и про удаленный доступ. Эта статья — немного особенная: она не только как разработчика, но и как музыканта.

Сейчас с нейросетями стало проще писать решения. Ну, «проще» — это громко сказано. Скорее, стало проще быстрее доходить до прототипа, проверять идеи, не бояться больших кодовых баз и собирать вокруг своей боли рабочий инструмент. Но сама боль от этого никуда не исчезает.

Теперь рассказываю от имени электронного продюсера:

У меня много лет была одна постоянная проблема: цифровая громкость.

Когда я был моложе и писал музыку во FL Studio, я часто задавался вопросом: почему у меня во Fruity Loops звук такой жирный, а в других DAW — какой-то другой? Почему одни сэмплы сразу звучат «мощно», а другие теряются? Почему пресет в синтезаторе вроде бы крутой, но в миксе всё разваливается?

Со временем я понял неприятную вещь: очень часто дело не в магии DAW, не в секретном плагине и не в «аналоговом тепле». Дело в громкости, gain staging и клиппинге. В перегрузе. В том, что сигнал уже на входе почти упирается в цифровой потолок.
Сэмплы, которые мы скачиваем, часто нормализованы почти в 0 dBFS: -0.1, -0.05, иногда вообще около -0.01. Пресеты в синтезаторах могут быть перегружены ещё до того, как вы повесили первый EQ. Потом сверху добавляется компрессия, сатурация, лимитер, ещё один «улучшайзер», и внезапно микс вроде громкий, но не звучит.

Я устал постоянно вручную следить за уровнями, пиками, RMS, динамикой и частотными зонами. Поэтому начал писать свой плагин — Mix Teacher AI.

Это VST3-плагин для Windows, который ставится на дорожку, анализирует сигнал в реальном времени и даёт понятные подсказки: что может быть не так, почему это мешает и какой безопасный первый шаг можно попробовать.

Скачать можно здесь:

https://github.com/vaalimusic/mix-teacher-ai/releases

Учитель громкости

Учитель громкости

Зачем вообще нужен такой плагин

Когда учишься сведению, самое сложное — понять не «какой плагин повесить», а что именно не так.

Например:

  • вокал вроде громкий, но мутный;

  • барабаны громкие, но кик не читается;

  • бас мощный в соло, но пропадает в миксе;

  • дорожка звучит жирно, но на самом деле всё пережато;

  • мастер уже клипует, хотя «я же почти ничего не делал».

Обычно новичок в этот момент начинает перебирать плагины: ещё один EQ, ещё один компрессор, ещё один сатурационный плагин, ещё один пресет. Но очень часто проблема лежит раньше — в уровне, динамике, перегрузе или конфликте частотных зон.

Моя мысль простая:

Если ваша музыка не звучит, возможно, вы забыли про самое главное. Для меня 90% сведения начинается с громкости.

Это не значит, что EQ, компрессия, реверберация и аранжировка не важны. Важны. Но если дорожка уже приходит в цепочку перегруженной, слишком громкой или без запаса по headroom, дальше всё становится сложнее.

Mix Teacher AI задуман не как автоматический мастеринг и не как замена звукорежиссёра. Это обучающий анализатор:

увидел проблему -> понял причину -> попробовал безопасный шаг -> послушал в контексте микса

Плагин не говорит: «делай только так». Он говорит: «похоже, здесь может быть проблема; вот почему; вот с чего можно начать».

Что умеет текущая версия

Сейчас в плагине есть:

  • VST3 insert-effect для Windows;

  • realtime-анализ аудио;

  • Peak / RMS / approximate LUFS short-term;

  • Crest Factor;

  • Headroom;

  • Clipping Count;

  • waveform;

  • spectrum;

  • RMS dynamics graph;

  • анализ частотных зон;

  • snapshot спектра в момент проблемы;

  • подсказки на русском и английском;

  • выбор типа источника;

  • настройка чувствительности анализа;

  • Copy JSON для будущей AI-интеграции;

  • экспериментальная ручка GOODIZER.

Плагин можно поставить на вокал, барабаны, бас, синт, гитару, мастер или любую другую дорожку. Он будет собирать метрики и показывать подсказки в зависимости от типа источника.

Выбор источника

Сверху можно выбрать, что именно анализируется:

AutoVocalDrumsDrums BusKickSnareBassGuitarPianoSynthFXMaster

Если стоит Auto, плагин пытается сам примерно определить тип источника. Но для более точных подсказок лучше выбирать вручную.

Например, если выбрать Vocal, анализ будет внимательнее смотреть на:

  • муть в нижней середине;

  • сибилянты;

  • читаемость;

  • скачки громкости между фразами.

Если выбрать Drums Bus, плагин будет больше смотреть на:

  • кик;

  • атаку;

  • хэты;

  • плотность;

  • транзиенты;

  • риск пережатости барабанов.

Анализ уровней

Первая вещь, за которой я хотел следить, — это уровень сигнала.

Плагин показывает базовые метрики:

RMS - Сигнал

RMS — Сигнал
Peak            максимальный пикRMS             средняя громкостьLUFS Short      примерная краткосрочная громкостьCrest Factor    разница между пиком и RMSHeadroom        запас до 0 dBFSClipping Count  количество клиппингов

Пример подсказки:

Пики почти у цифрового потолка.Почему:Запаса почти нет. EQ, компрессия или сатурация легко доведут сигнал до клиппинга.Что попробовать:
Убавь clip/input gain примерно на 3-5 dB.

Это особенно полезно в ситуации, когда дорожка «звучит нормально», но уже находится слишком близко к 0 dBFS. Дальше любой EQ boost, компрессор, сатурация или лимитер могут сделать сигнал хуже.

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

Динамика

Для динамики плагин смотрит не только на общий RMS, но и на активные участки сигнала. Это важно для вокала, рэпа, DnB, барабанов и любых материалов, где между активными моментами есть паузы.

Внутри используются такие значения:

active_rms_p10active_rms_p50active_rms_p90active_rms_range_dbtransient_scoreonset_count

Например, если у вокала сильно скачет громкость между фразами, плагин может подсказать:

Громкость дорожки скачет.Почему:Одни фразы сильно громче других. Из-за этого дорожка то вылезает, то пропадает.Что попробовать:Сначала выровняй clip gain вручную.Потом используй компрессор с умеренным gain reduction.

Для drum bus может появиться другая подсказка:

Drum bus может быть пережат.Почему:Если crest factor низкий, барабаны теряют удар и становятся плоскими.Что попробовать:Ослабь bus compression или сделай меньше gain reduction.

Мне хотелось, чтобы плагин не просто показывал цифры, а объяснял их человеческим языком. Не «crest factor = 5.1 dB», а «барабаны могут быть пережаты, попробуй ослабить bus compression».

Частотные зоны

Плагин анализирует энергию по диапазонам:

20-40 Hz       sub rumble40-80 Hz       low foundation80-150 Hz      low body150-350 Hz     mud350-800 Hz     boxiness800 Hz-2 kHz   tone/body2-5 kHz        presence/attack5-9 kHz        sibilance/harshness9-16 kHz       air

Для вокала это помогает ловить муть и сибилянты.

Пример подсказки для вокала:

Похоже, есть муть в вокале.Evidence:180-350 Hz выше соседних зон.Почему:Нижняя середина часто делает голос тяжёлым и менее читаемым.Что попробовать:Попробуй EQ cut 2-4 dB около 220-300 Hz, Q 1-2.

Пример для сибилянтов:

Есть резкие “с” и “ш”.Почему:Диапазон 5-9 kHz слишком активен.Что попробовать:Попробуй de-esser в районе 6-8 kHz.Цель — уменьшать только резкие согласные на 2-5 dB.

Важно: это не «рецепт на все случаи». Это стартовая точка. После любой подсказки нужно слушать в контексте микса.

Drum Mode

Для барабанов я сделал отдельную логику. Если выбрать Drums, Drums Bus, Kick или Snare, график зон переключается в drum-oriented режим:

KickBoomSnareAtkHatsAirPunchFlat

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

  • Kick — низ и вес;

  • Boom — гул;

  • Snare — коробочность;

  • Atk — атака;

  • Hats — резкость хэтов и тарелок;

  • Air — верх;

  • Punch — транзиенты;

  • Flat — риск пережатости.

Пример подсказки:

Хэты или тарелки могут резать ухо.Почему:Для барабанов активная зона 5-9 kHz часто означает резкие хэты, тарелки или атаку сн

Что попробовать:

Если верх режет ухо, попробуй dynamic EQ или мягкий shelf-cut 1-3 dB около 6-9 kHz.

Чувствительность (Показаний)

Есть ручка чувствительности анализа.

Она нужна для сложного материала: DnB-барабаны, быстрые паузы, резкие transient-звуки, тихие хвосты, вокал с большими паузами.

Если плагин слишком часто говорит «сигнал слишком тихий», можно поднять Sensitivity. Тогда анализ будет больше доверять активным участкам, а не последней тишине после остановки playback.

GOODIZER (Компрессор — EQ — сатурация — одна ручка)

Отдельно я добавил экспериментальную центральную ручку GOODIZER.

Важный момент:

0%       звук не меняется> 0%     включается обработка

На небольших значениях она добавляет мягкую сатурацию, presence и лёгкий safety soft-clip. Это не «магическая кнопка сделать микс идеальным», а творческий эффект.

На drum bus можно попробовать значения примерно:

10-25%

На больших значениях эффект становится заметнее и агрессивнее. В интерфейсе ручка анимируется: чем больше значение, тем ярче и «злее» визуальная реакция.

JSON Export

Кнопка Copy JSON копирует отчёт анализа в буфер обмена.

Пример структуры:

{  "plugin": "Mix Teacher AI",  "version": "0.2",  "track": {    "manual_type": "drums_bus",    "detected_type": "drums_bus",    "effective_type": "drums_bus"  },  "levels": {    "peak_dbfs": -3.2,    "rms_dbfs": -18.5,    "crest_factor_db": 15.3  },  "issues": [    {      "kind": "hat_harshness",      "title": "Хэты или тарелки могут резать ухо",      "action": "Попробуй dynamic EQ или мягкий shelf-cut 1-3 dB около 6-9 kHz."    }  ]}

Сейчас подсказки в основном rule-based. В будущем этот JSON можно отправлять в локальный AI/Ollama, чтобы получать более развёрнутое объяснение прямо внутри плагина.

Техническая часть

Плагин написан на:

  • C++;

  • JUCE;

  • CMake;

  • VST3.

Целевой формат на первом этапе — VST3 для Windows.

Главный принцип архитектуры: audio callback должен быть лёгким. В нём нельзя делать тяжёлые вычисления, аллокации, долгие операции, работу с UI или что-то, что может затормозить playback.

Поэтому в audio thread выполняется только минимальная работа:

  • audio processing;

  • peak/RMS;

  • clipping count;

  • запись samples в ring buffer.

Более тяжёлые вещи считаются отдельно:

  • spectrum;

  • band energy;

  • dynamics;

  • confidence;

  • issues;

  • JSON report.

CMake и JUCE

JUCE подтягивается через FetchContent, поэтому проект можно собрать без локальной установки JUCE:

include(FetchContent)set(MIX_TEACHER_JUCE_TAG "8.0.14" CACHE STRING "JUCE git tag used to build Mix Teacher")FetchContent_Declare(    JUCE    GIT_REPOSITORY https://github.com/juce-framework/JUCE.git    GIT_TAG        ${MIX_TEACHER_JUCE_TAG})FetchContent_MakeAvailable(JUCE)juce_add_plugin(MixTeacher    COMPANY_NAME "Arthur Valiev"    PLUGIN_MANUFACTURER_CODE ArVa    PLUGIN_CODE Mtch    FORMATS VST3    PRODUCT_NAME "Mix Teacher")

Параметры плагина

Все параметры живут в AudioProcessorValueTreeState, чтобы DAW могла сохранять состояние проекта:

params.push_back(std::make_unique<juce::AudioParameterChoice>(    juce::ParameterID { "trackType", 1 },    "Source",    juce::StringArray {        "Auto", "Vocal", "Drums", "Drums Bus", "Kick", "Snare",        "Bass", "Guitar", "Piano", "Synth", "FX", "Master"    },    0));params.push_back(std::make_unique<juce::AudioParameterFloat>(    juce::ParameterID { "sensitivity", 1 },    "Sensitivity",    juce::NormalisableRange<float> { 0.0f, 1.0f, 0.01f },    0.65f));params.push_back(std::make_unique<juce::AudioParameterFloat>(    juce::ParameterID { "goodizer", 1 },    "Goodizer",    juce::NormalisableRange<float> { 0.0f, 1.0f, 0.01f },    0.0f));

Audio callback

В processBlock() плагин делает только лёгкие операции:

void MixTeacherAudioProcessor::processBlock(    juce::AudioBuffer<float>& buffer,    juce::MidiBuffer& midiMessages){    juce::ScopedNoDenormals noDenormals;    midiMessages.clear();    applyGoodizer(buffer);    float peak = 0.0f;    double sumSquares = 0.0;    int blockClips = 0;    for (int sample = 0; sample < numSamples; ++sample)    {        float mono = 0.0f;        for (int channel = 0; channel < totalNumInputChannels; ++channel)        {            const auto value = buffer.getReadPointer(channel)[sample];            const auto absValue = std::abs(value);            peak = juce::jmax(peak, absValue);            sumSquares += value * value;            if (absValue >= 0.999f)                ++blockClips;            mono += value;        }        mono /= static_cast<float>(channelsToAnalyse);        if (sample < samplesToStore)            monoScratch[static_cast<size_t>(sample)] = mono;    }    latestPeakLinear.store(peak, std::memory_order_relaxed);    latestRmsLinear.store(static_cast<float>(rms), std::memory_order_relaxed);    clippingCount.fetch_add(blockClips, std::memory_order_relaxed);}

Если GOODIZER = 0%, обработка звука не выполняется. Если ручка выше нуля, включается творческий DSP.

GOODIZER DSP

GOODIZER сделан как мягкий soundgoodizer-style эффект: drive, saturation, presence и safety soft-clip.

void MixTeacherAudioProcessor::applyGoodizer(juce::AudioBuffer<float>& buffer){    const auto amount = parameters.getRawParameterValue("goodizer")->load();    if (amount <= 0.0001f)        return;    const auto shaped = amount * amount;    const auto drive = 1.0f + shaped * 7.5f;    const auto wet = juce::jlimit(0.0f, 0.92f, amount);    const auto presence = shaped * 0.42f;    for (int channel = 0; channel < channelCount; ++channel)    {        auto* data = buffer.getWritePointer(channel);        auto low = goodizerLowState[channel];        for (int sample = 0; sample < buffer.getNumSamples(); ++sample)        {            const auto dry = data[sample];            low += lowCoeff * (dry - low);            const auto high = dry - low;            const auto driven = dry * drive + high * presence;            const auto saturated = std::tanh(driven) / std::tanh(drive);            const auto excited = saturated + high * presence;            const auto mixed = dry + (excited - dry) * wet;            data[sample] = std::tanh(mixed * outputTrim * 1.08f);        }        goodizerLowState[channel] = low;    }}

На малых значениях эффект даёт лёгкое уплотнение. На больших — начинает сильнее красить сигнал.

История аудио и анализ

Аудио из callback складывается в FIFO, затем UI/analysis-поток вытаскивает данные в историю:

void MixTeacherAudioProcessor::drainFifo(){    const auto ready = juce::jmin(sampleFifo.getNumReady(), fifoCapacity);    sampleFifo.prepareToRead(ready, start1, size1, start2, size2);    for (int i = 0; i < size1; ++i)        appendToHistory(sampleFifoBuffer[start1 + i]);    sampleFifo.finishedRead(size1 + size2);}

Главная идея: audio thread не делает тяжёлый FFT и не строит подсказки. Он только собирает данные.

Snapshot анализа

Плагин собирает один объект AnalysisSnapshot, который потом используют UI и JSON export:

struct AnalysisSnapshot{    juce::String plugin = "Mix Teacher AI";    juce::String version = "0.2";    juce::String trackType = "auto";    juce::String language = "ru";    float peakDbfs = -120.0f;    float rmsDbfs = -120.0f;    float lufsShortTerm = -120.0f;    float crestFactorDb = 0.0f;    float headroomDb = 120.0f;    float activeRmsP10 = -120.0f;    float activeRmsP50 = -120.0f;    float activeRmsP90 = -120.0f;    BandLevels bands;    BandEnergyDb bandDb;    DrumProfile drumProfile;    std::vector<TeacherIssue> issues;    FirstStep firstStep;};

Pipeline анализа выглядит так:

updateDynamics(snapshot);updateHistoryLevels(snapshot);updateSpectrum(snapshot);updateValidity(snapshot);updateTrackDetection(snapshot);updateDrumProfile(snapshot);snapshot.issues = mixteacher::buildTeacherIssues(snapshot);snapshot.firstStep = mixteacher::buildFirstStep(snapshot);

FFT и частотные зоны

Спектр считается не по последней тишине, а по последнему активному участку истории:

const auto activeOffset = juce::jmax(0, findRecentActiveOffset(1.0e-4f));for (int i = 0; i < fftSize; ++i){    const auto offset = activeOffset + (fftSize - 1 - i);    const auto sourceIndex =        (historyWriteIndex + analysisHistory.size() - 1 - offset)        % analysisHistory.size();    fftBuffer[i] = analysisHistory[sourceIndex];}window.multiplyWithWindowingTable(fftBuffer.data(), fftSize);fft.performFrequencyOnlyForwardTransform(fftBuffer.data());

После FFT энергия раскладывается по зонам:

snapshot.bandDb.lowFoundation = bandEnergyDb(40.0f, 80.0f);snapshot.bandDb.lowBody       = bandEnergyDb(80.0f, 150.0f);snapshot.bandDb.mud           = bandEnergyDb(150.0f, 350.0f);snapshot.bandDb.boxiness      = bandEnergyDb(350.0f, 800.0f);snapshot.bandDb.presence      = bandEnergyDb(2000.0f, 5000.0f);snapshot.bandDb.sibilance     = bandEnergyDb(5000.0f, 9000.0f);snapshot.bandDb.air           = bandEnergyDb(9000.0f, 16000.0f);

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

Чтобы плагин не делал выводы по тишине, есть проверка валидности:

const auto usefulRmsDb =    snapshot.activeRmsP50 > -119.0f        ? snapshot.activeRmsP50        : snapshot.rmsDbfs;if (snapshot.peakDbfs < tooQuietPeak || usefulRmsDb < tooQuietRms){    snapshot.validity.state = ValidityState::tooQuiet;    snapshot.validity.isValidForAnalysis = false;    return;}

Это особенно важно для DnB, drums и вокала с паузами. Иначе анализатор может смотреть не на активный материал, а на хвост после остановки playback.

JSON export

Кнопка Copy JSON вызывает:

juce::SystemClipboard::copyTextToClipboard(snapshot.toJson());

JSON строится через juce::DynamicObject:

auto* root = new juce::DynamicObject();root->setProperty("plugin", plugin);root->setProperty("version", version);root->setProperty("language", language);root->setProperty("sensitivity", sensitivity);root->setProperty("goodizer_amount", goodizerAmount);auto* levels = new juce::DynamicObject();levels->setProperty("peak_dbfs", peakDbfs);levels->setProperty("rms_dbfs", rmsDbfs);levels->setProperty("crest_factor_db", crestFactorDb);levels->setProperty("clipping_detected", clippingDetected);root->setProperty("levels", levels);return juce::JSON::toString(juce::var(root), true);

Такой отчёт можно будет отправлять в AI/Ollama, чтобы получать более подробные объяснения.

UI

UI написан на стандартных JUCE-компонентах:

juce::ComboBox trackTypeBox;juce::ComboBox explanationModeBox;juce::ComboBox languageBox;juce::Slider sensitivitySlider;juce::Slider goodizerSlider;juce::ToggleButton freezeButton;juce::TextButton resetButton;juce::TextButton copyJsonButton;

Параметры привязаны через attachments:

trackTypeAttachment =    std::make_unique<ComboAttachment>(        audioProcessor.getParameters(), "trackType", trackTypeBox);goodizerAttachment =    std::make_unique<SliderAttachment>(        audioProcessor.getParameters(), "goodizer", goodizerSlider);

Центральная ручка GOODIZER дополнительно рисуется вручную: кольца, цвет и анимация зависят от значения параметра.

const auto amount = getGoodizerAmount();const auto colour = goodizerColour(amount);if (amount > 0.001f){    // animated rings    g.drawEllipse(...);}

Итоговая архитектура

Технически Mix Teacher AI состоит из трёх слоёв.

1. Audio/DSP layer

passthroughGOODIZERpeak/RMS/clippingFIFO/history

2. Analysis layer

FFTband energyactive RMS percentilesvaliditytrack detectionissue engineJSON

3. UI layer

meterswaveformspectrumdrum zonesteacher cardcontrolsanimated GOODIZER knob

Главный принцип разработки: audio callback должен оставаться лёгким, а вся «умная» логика должна работать по уже накопленной истории сигнала.

Что дальше

В планах:

  • AI/Ollama-интеграция;

  • Deep Analyze mode;

  • сравнение before/after;

  • Mix Hub для нескольких дорожек;

  • сравнение kick/bass;

  • сравнение vocal/instrumental;

  • улучшенный LUFS;

  • более точный анализ сибилянтов;

  • улучшенный transient detection;

  • более красивый интерфейс.

Особенно интересная часть — Mix Hub. Хочется, чтобы несколько инстансов плагина могли обмениваться данными и анализировать не только одну дорожку, а отношения между дорожками: например, конфликт kick/bass или vocal/instrumental. Но тут, моя поддержка тормозится, я никогда не мог и не умел поддерживать такие проекты, хотя взял обещание добавить несколько важных функций от рандомных чуваков с ВК.

Установка

Скачать можно здесь:

https://github.com/vaalimusic/mix-teacher-ai/releases

Установка:

  1. Скачать архив.

  2. Содержимое архива положить в:

C:\Program Files\Common Files\VST3
  1. Сделать rescan VST3-плагинов в DAW.

  2. Открыть Mix Teacher AI как VST3 insert-effect.

Заключение

Я это делал для себя, у меня конкретные проблемы с уровнями, если бы мне объяснили 15 лет назад что сэмпл паки и пресеты это твой первый враг, да в целом цифровому звуку меня учил только тот самый чувак который появился пару лет назад. Потому — что в моих краях туда не капали. Да кому я объясняю?)

Я хотел инструмент, который не просто показывает график, а объясняет:

что происходитпочему это может мешатьс чего безопасно начать

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

Буду рад обратной связи от звукорежиссёров, музыкантов и продюсеров: что работает полезно, где подсказки ошибаются, какие режимы стоит добавить и какие реальные сценарии сведения хочется закрыть.

PS: Почему в названии AI — Скопируйте JSON отдайте его на корм нейросетям Вам помогут свести дорожку (я специально сделал что-бы json собрал всю нужную инфу про дорожку). А о политике общения из DAW с нейросетями читайте документацию. Мой StudioOne не позволяет. По крайней мере из той информации что знаю. Да и обсуждать не хочу, это сообщение не тема для разговора, просто пользуйтесь, если нужно что-то улучшить просто напишите мне куда-нибудь, давайте помогать друг другу.

Спасибо! =)

ссылка на оригинал статьи https://habr.com/ru/articles/1055440/