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

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

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

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

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

Когда я был моложе и писал музыку во 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.

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

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

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

Auto
Vocal
Drums
Drums Bus
Kick
Snare
Bass
Guitar
Piano
Synth
FX
Master

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

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

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

  • сибилянты;

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

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

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

  • кик;

  • атаку;

  • хэты;

  • плотность;

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

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

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

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

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

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

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

Пики почти у цифрового потолка.

Почему:
Запаса почти нет. EQ, компрессия или сатурация легко доведут сигнал до клиппинга.

Что попробовать:
Убавь clip/input gain примерно на 3-5 dB.

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

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

Динамика

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

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

active_rms_p10
active_rms_p50
active_rms_p90
active_rms_range_db
transient_score
onset_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 rumble
40-80 Hz       low foundation
80-150 Hz      low body
150-350 Hz     mud
350-800 Hz     boxiness
800 Hz-2 kHz   tone/body
2-5 kHz        presence/attack
5-9 kHz        sibilance/harshness
9-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 режим:

Kick
Boom
Snare
Atk
Hats
Air
Punch
Flat

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

  • 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

passthrough
GOODIZER
peak/RMS/clipping
FIFO/history

2. Analysis layer

FFT
band energy
active RMS percentiles
validity
track detection
issue engine
JSON

3. UI layer

meters
waveform
spectrum
drum zones
teacher card
controls
animated 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 не позволяет. По крайней мере из той информации что знаю. Да и обсуждать не хочу, это сообщение не тема для разговора, просто пользуйтесь, если нужно что-то улучшить просто напишите мне куда-нибудь, давайте помогать друг другу.

Спасибо! =)