<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <title>Генератор Фона на Трафаретах</title>
    
    <!-- Подключения ваших локальных сэмплов FluidR3 -->
    <script src="webaudiofont/WebAudioFontPlayer.js"></script>
    <script src="webaudiofont/0000_FluidR3_GM_sf2_file.js"></script>
  <script src="webaudiofont/0010_FluidR3_GM_sf2_file.js"></script>
	<script src="webaudiofont/0040_FluidR3_GM_sf2_file.js"></script>
	<script src="webaudiofont/0050_FluidR3_GM_sf2_file.js"></script>
	<script src="webaudiofont/0100_FluidR3_GM_sf2_file.js"></script>
  <script src="webaudiofont/0120_FluidR3_GM_sf2_file.js"></script>
	<script src="webaudiofont/0130_GeneralUserGS_sf2_file.js"></script>
	<script src="webaudiofont/0240_FluidR3_GM_sf2_file.js"></script>
	   <script src="webaudiofont/0320_FluidR3_GM_sf2_file.js"></script>
  <script src="webaudiofont/0330_FluidR3_GM_sf2_file.js"></script>
	<script src="webaudiofont/0380_FluidR3_GM_sf2_file.js"></script>
	<script src="webaudiofont/0390_FluidR3_GM_sf2_file.js"></script>
	<script src="webaudiofont/0890_FluidR3_GM_sf2_file.js"></script>
  <script src="webaudiofont/0910_FluidR3_GM_sf2_file.js"></script>
	<script src="webaudiofont/0980_FluidR3_GM_sf2_file.js"></script>
	<script src="webaudiofont/12856_3_FluidR3_GM_sf2_file.js"></script>
	<script src="webaudiofont/12836_0_FluidR3_GM_sf2_file.js"></script>
<script src="webaudiofont/12838_0_FluidR3_GM_sf2_file.js"></script>
<script src="webaudiofont/12842_0_FluidR3_GM_sf2_file.js"></script>

	
	<style>
    :root {
        --bg-color: #0a0a0c;
        --panel-bg: rgba(20, 20, 28, 0.7);
        --accent-purple: #a370f7;
        --accent-cyan: #00adb5;
        --accent-red: #ff2e63;
        --text-main: #f5f5f7;
        --text-muted: #8e8e93;
        --border-color: rgba(255, 255, 255, 0.08);
    }

    body { 
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; 
        background: var(--bg-color); 
        color: var(--text-main); 
        text-align: center; 
        padding-top: 60px;
        margin: 0;
        background-image: radial-gradient(circle at top, rgba(163, 112, 247, 0.15) 0%, transparent 60%);
    }

    .container { 
        background: var(--panel-bg); 
        backdrop-filter: blur(16px);
        -webkit-backdrop-filter: blur(16px);
        padding: 30px; 
        display: inline-block; 
        border-radius: 20px; 
        box-shadow: 0 20px 40px rgba(0, 0, 0, 0.7);
        border: 1px solid var(--border-color);
        width: 100%;
        max-width: 440px;
        box-sizing: border-box;
    }

    .studio-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 25px;
    }

    .studio-header h2 { margin: 0; font-size: 22px; font-weight: 700; letter-spacing: -0.5px; }

    .live-badge {
        background: rgba(0, 173, 181, 0.15);
        color: var(--accent-cyan);
        padding: 4px 10px;
        border-radius: 6px;
        font-size: 11px;
        font-weight: 800;
        letter-spacing: 1px;
        border: 1px solid rgba(0, 173, 181, 0.3);
        animation: pulse 2s infinite;
    }

    @keyframes pulse {
        0% { opacity: 0.6; }
        50% { opacity: 1; }
        100% { opacity: 0.6; }
    }

    .control-group { text-align: left; margin-bottom: 20px; }
    .control-group label { display: block; font-size: 13px; color: var(--text-muted); margin-bottom: 8px; font-weight: 500; }

      /* Кастомный выпадающий список и исправление стрелки */
    .select-wrapper { 
        position: relative; 
        width: 100%; 
    }
    
    /* НАДЕЖНАЯ ТЕКСТОВАЯ СТРЕЛКА: Символ "▼" гарантированно отобразится везде */
    .select-wrapper::after {
        content: "▼";
        font-size: 11px;
        color: var(--accent-cyan); /* Наша бирюзовая неоновая подсветка */
        position: absolute;
        right: 18px;
        top: 50%;
        transform: translateY(-50%);
        pointer-events: none; /* Клик проходит сквозь стрелку прямо в список */
        opacity: 0.8;
    }

    select { 
        width: 100%;
        padding: 14px 40px 14px 16px; /* Оставляем правый зазор, чтобы текст не наезжал на стрелочку */
        font-size: 15px; 
        background: rgba(255, 255, 255, 0.04);
        color: var(--text-main);
        border: 1px solid var(--border-color);
        border-radius: 12px;
        cursor: pointer;
        appearance: none;
        -webkit-appearance: none;
        -moz-appearance: none;
        transition: 0.2s ease;
    }
    
    select:focus { 
        outline: none; 
        border-color: var(--accent-purple); 
        background: rgba(255, 255, 255, 0.07); 
    }

    select option {
        background-color: #14141c !important; 
        color: #ffffff !important;            
        font-size: 15px;
        padding: 12px;
    }


    #timer { 
        font-size: 48px; 
        font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; 
        font-weight: 600;
        margin: 25px 0; 
        color: var(--text-main);
        letter-spacing: -1px;
    }

    .btn-group { display: flex; gap: 12px; margin-bottom: 30px; }

    button { 
        flex: 1;
        padding: 16px 20px; 
        font-size: 15px; 
        font-weight: 600;
        border-radius: 12px; 
        border: none; 
        cursor: pointer;
        transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
    }

    #randomBtn { background: var(--accent-purple); color: #fff; box-shadow: 0 4px 15px rgba(163, 112, 247, 0.3); }
    #randomBtn:hover { background: #b385f9; transform: translateY(-1px); }
    #randomBtn:active { transform: translateY(1px); }

    #stopBtn { background: rgba(255, 46, 99, 0.1); color: var(--accent-red); border: 1px solid rgba(255, 46, 99, 0.2); }
    #stopBtn:hover { background: rgba(255, 46, 99, 0.2); }

    /* Панель микшера */
    .mixer-panel { 
        background: rgba(0, 0, 0, 0.2);
        padding: 20px;
        border-radius: 16px;
        border: 1px solid var(--border-color);
        margin-bottom: 20px;
    }
    .mixer-panel h4 { margin: 0 0 18px 0; font-size: 14px; text-transform: uppercase; letter-spacing: 1px; color: var(--accent-purple); text-align: center; }

    .mixer-row { margin-bottom: 16px; text-align: left; }
    .mixer-row:last-child { margin-bottom: 0; }
    .mixer-row label { display: block; font-size: 12px; color: var(--text-muted); margin-bottom: 6px; }

    /* Красивые ползунки */
    input[type="range"] {
        -webkit-appearance: none;
        width: 100%;
        height: 6px;
        background: rgba(255, 255, 255, 0.1);
        border-radius: 3px;
        outline: none;
    }
    input[type="range"]::-webkit-slider-thumb {
        -webkit-appearance: none;
        width: 16px;
        height: 16px;
        border-radius: 50%;
        background: var(--accent-cyan);
        cursor: pointer;
        transition: transform 0.1s ease;
        box-shadow: 0 0 10px rgba(0, 173, 181, 0.5);
    }
    input[type="range"]::-webkit-slider-thumb:hover { transform: scale(1.2); }

      #status { 
        font-family: ui-monospace, monospace; 
        font-size: 11px; /* Слегка уменьшили размер, чтобы длинный текст аккуратнее упаковывался */
        color: var(--text-muted);
        line-height: 1.4;
        background: rgba(0, 0, 0, 0.15);
        padding: 10px;
        border-radius: 10px;
        border: 1px solid var(--border-color);
        word-break: break-word;
        margin-bottom: 20px; /* Отступ снизу, чтобы оттолкнуть выбор стиля */
        
        /* ЖЕСТКАЯ СТАБИЛИЗАЦИЯ ВЫСОТЫ ДЛЯ ВЕРХНЕГО РАСПОЛОЖЕНИЯ */
        min-height: 54px;       /* Фиксированная высота с запасом под многострочный текст */
        display: flex;
        align-items: center;    /* Центрирование короткого текста по вертикали */
        justify-content: center;/* Центрирование по горизонтали */
        box-sizing: border-box;
    }

</style>
</head>
<body>

<div class="container">
    <div class="studio-header">
        <h2>Генератор Фона</h2>
        <div class="live-badge">LIVE GEN</div>
    </div>
    
    <div class="control-group">
        <!-- Блок статуса, куда движок выводит информацию о загруженных трафаретах и текущем миксе -->
        <div id="status">Статус: загрузка базы трафаретов...</div>
    </div>

    <!-- Счетчик времени -->
    <div id="timer">02:00</div>
<!-- Выбор жанра видео-контента -->
<div class="control-group" style="margin-top: 15px;">
    <label>Стиль видео-контента:</label>
    <div class="select-wrapper">
        <select id="videoProfileSelect">
            <option value="cooking">🍔 Кулинария и Эстетика (Уют, Тепло)</option>
            <option value="lifehacks">💡 Лайфхаки и DIY (Динамика, Ритм)</option>
            <option value="cinematicView">🏔️ Видовые видео и Пейзажи (Атмосфера)</option>
        </select>
    </div>
</div>
    <!-- Живой зеркальный эквалайзер -->
    <div class="visualizer-container" style="margin: 20px 0; text-align: center;">
        <canvas id="visualizer" width="400" height="100" style="background: #111; border-radius: 8px; border: 1px solid #333; display: block; width: 100%;"></canvas>
    </div>

    <!-- Панель управления -->
    <div class="btn-group">
        <button id="randomBtn">Случайный микс 🎲</button>
        <button id="stopBtn">Стоп 🛑</button>
        <button id="downloadBtn" style="background: #a370f7; color: #fff;">Скачать MP3/WebM 💾</button>
    </div>

    <!-- Микшер баланса громкости -->
    <div class="mixer-panel" style="margin-top: 25px;">
        <h4>Микшер инструментов 🎛️</h4>
        
        <div class="mixer-row">
            <label>Соло (MIDI Контур)</label>
            <input type="range" id="volSolo" min="0" max="1" step="0.05" value="0.35">
        </div>
        
        <div class="mixer-row">
            <label>Фон (Синт-Пады)</label>
            <input type="range" id="volPad" min="0" max="1" step="0.05" value="0.20">
        </div>
        
        <div class="mixer-row">
            <label>Линия баса</label>
            <input type="range" id="volBass" min="0" max="1" step="0.05" value="0.25">
        </div>
        
        <div class="mixer-row">
            <label>Ритм (Барабаны)</label>
            <input type="range" id="volDrums" min="0" max="1" step="0.05" value="0.30">
        </div>
    </div>
</div>


<script>
// ==========================================
// ЧАСТЬ 1: ИНИЦИАЛИЗАЦИЯ И МАТРИЦА ЗВУКОВЫХ СУЩНОСТЕЙ
// ==========================================
const AudioContext = window.AudioContext || window.webkitAudioContext;
let audioCtx = null;
let gainNode = null; 
let player = new WebAudioFontPlayer();

let channelSolo = null;
let channelPad = null;
let channelBass = null;
let channelDrums = null;

let delayNode = null;
let delayFeedback = null;
let reverbNode = null;

let analyserNode = null;
let visualContext = null;
let animationId = null;

let mediaRecorder = null;
let recordedChunks = [];
let latestTrackUrl = null;

// Накопительные базы данных трафаретов из MIDI
let rhythmDatabase = [];
let melodyDatabase = [];

let activeTemplate = []; 
let currentMelodyPattern = [0, 2, 4, null, 3, 5, null, 2];

let templateIndex = 0;    
let melodyPatternIndex = 0;
let nextNoteTime = 0.0;   
let chordChangeTime = 0.0; 
let lastSoloTime = 0;

let isPlaying = false;
let timerId = null;
let countdownInterval = null;
let totalSeconds = 120;
let wavePhase = 0;


// В маппинг добавлен супер-мягкий аналоговый sub_bass вместо старого лида
const instruments = {
    // Соло и Мелодические инструменты
    grand_piano:    window._tone_0000_FluidR3_GM_sf2_file,       // 0000 Рояль
    bright_piano:   window._tone_0010_FluidR3_GM_sf2_file,       // 0010 Яркое пианино
    honky_tonk:     window._tone_0040_FluidR3_GM_sf2_file,       // 0040 Регтайм пианино
    electric_piano: window._tone_0050_FluidR3_GM_sf2_file,       // 0050 Электропиано
    music_box:      window._tone_0100_FluidR3_GM_sf2_file,       // 0100 Музыкальная шкатулка
    marimba:        window._tone_0120_FluidR3_GM_sf2_file,       // 0120 Маримба
    bright_piano_gs:window._tone_0130_GeneralUserGS_sf2_file,    // 0130 Пианино (GeneralUserGS)
    nylon_guitar:   window._tone_0240_FluidR3_GM_sf2_file,       // 0240 Акустическая гитара
    
    // Басовые инструменты
    acoustic_bass:  window._tone_0320_FluidR3_GM_sf2_file,       // 0320 Акустический бас
    electric_bass:  window._tone_0330_FluidR3_GM_sf2_file,       // 0330 Электробас
    synth_bass_1:   window._tone_0380_FluidR3_GM_sf2_file,       // 0380 Плотный синт-бас
    synth_bass_2:   window._tone_0390_FluidR3_GM_sf2_file,       // 0390 Суб-синт-бас
    
       // Синтезаторные подклады (Пады)
    pad_warm:       window._tone_0890_FluidR3_GM_sf2_file,       // 0890 Мягкий теплый пад
    pad_choir:      window._tone_0910_FluidR3_GM_sf2_file,       // 0910 Обволакивающий хор
    crystal_pluck:  window._tone_0980_FluidR3_GM_sf2_file,       // 0980 Хрустальные колокольчики
    
    // НАСТОЯЩИЕ ИЗОЛИРОВАННЫЕ БАРАБАНЫ С ВАШЕГО СКРИНШОТА:
    drum_kick:      window._drum_36_0_FluidR3_GM_sf2_file,       // 12836 Бочка (Bass Drum 1) - "пум"
    drum_snare:     window._drum_38_0_FluidR3_GM_sf2_file,       // 12838 Снейр (Snare Drum 1) - "хлопок"
    drum_hihat:     window._drum_42_0_FluidR3_GM_sf2_file,       // 12842 Хэт (Closed Hi-hat) - "тик"
    
    // Шумы для кинематографичных пейзажей
    sfx_kit:        window._drum_56_3_FluidR3_GM_sf2_file        // 12856 Шумы
};

// Текущий активный режим видео-музыки (по умолчанию Кулинария)
let currentProfileKey = 'cooking'; 

// =================================================================
// 🎛️ ПРОФЕССИОНАЛЬНО ВЫВЕРЕННЫЕ ЗВУКОВЫЕ ПРЕСЕТЫ (КАЧЕСТВЕННЫЙ ЗВУК)
// =================================================================
const VIDEO_MUSIC_PROFILES = {
    
    // 🍔 КУЛИНАРИЯ: Уютный, мягкий, ресторанный лаунж. Абсолютно чистый рояль без гула.
    cooking: {
        name: "Кулинария (Уютный Лаунж)",
        soloInstrument: 'grand_piano',    // Акустический благородный рояль
        altInstrument: 'bright_piano_gs', // Подстраховка на пианино
        bassInstrument: 'acoustic_bass',  // Глубокий, мягкий деревянный контрабас
        padInstrument: 'pad_choir',       // Очень воздушная, едва заметная подложка
        
        // Мажорная пентатоника До (C Major Pentatonic) на 3 октавы. Фальшь исключена физически.
        scale: [ 48, 50, 52, 55, 57,  60, 62, 64, 67, 69,  72, 74, 76, 79, 81 ], 
        
        noteChance: 0.40,                 // Размеренная, неторопливая игра
        structuralDelay: 0.50,            // Спокойный, размеренный темп
        noteDuration: 1.60,               // Оптимальная длительность нот без «каши»
        
        // Дорогой Lo-Fi / Lounge ритм. Бочка качает, снейр мягкий, хэт делает аккуратный «тик»
        drumPattern: {
            kick:  [ 1, 0, 0, 0, 0, 0, 1, 0 ], // Спокойный, уверенный пульс
            snare: [ 0, 0, 0, 0, 1, 0, 0, 0 ], // Четкий хлопок на 5-ю долю
            hihat: [ 1, 0, 1, 0, 1, 0, 1, 0 ]  // Легкий, прореженный «шаг» хэта
        }
    },

        // 💡 ЛАЙФХАКИ: Современный, продуктивный, прыгучий и «умный» инди-поп / электроника.
    lifehacks: {
        name: "Лайфхаки (Продуктивность)",
        soloInstrument: 'marimba',        // Сочная, быстрая деревянная маримба
        altInstrument: 'music_box',       // Озорные колокольчики музыкальной шкатулки
        bassInstrument: 'acoustic_bass',  // ИСПРАВЛЕНО: Гарантированно загруженный контрабас (трансформируется в синт-бас)
        padInstrument: 'pad_choir',       // ИСПРАВЛЕНО: Гарантированно загруженный хор (трансформируется в синт-пэд)
        
        // Мажорная пентатоника Соль (G Major Pentatonic). Позитивный, яркий и технологичный окрас.
        scale: [ 43, 45, 47, 50, 52,  55, 57, 59, 62, 64,  67, 69, 71, 74, 76 ], 
        
        noteChance: 0.65,                 // Плотная, но структурированная и внятная игра
        structuralDelay: 0.25,            // Динамичный, собранный шаг
        noteDuration: 0.22,               // Короткие, «стреляющие» и прыгучие ноты маримбы
        
        // ИСПРАВЛЕНО: Профессиональный Tech-Lounge грув. Идеальное распределение долей без наслоений.
        drumPattern: {
            kick:  [ 1, 0, 0, 0, 1, 0, 0, 0 ], // Четкий прямой пульс на сильные доли
            snare: [ 0, 0, 0, 0, 1, 0, 0, 0 ], // Мягкий ответный хлопок строго на 5-й шаг
            hihat: [ 0, 0, 1, 0, 0, 0, 1, 0 ]  // Идеальный «off-beat» хэт, создающий динамику между бочками
        }
    },


    // 🏔️ ПЕЙЗАЖИ: Глубокий оркестровый эмбиент, кинематографичный размах и полет.
    cinematicView: {
        name: "Пейзажи (Эстетика & Кинематограф)",
        soloInstrument: 'grand_piano',    // Редкие, глубокие капли рояля из темноты
        altInstrument: 'pad_choir',       
        bassInstrument: 'synth_bass',     // Обволакивающий, заполняющий комнату суб-бас
        padInstrument: 'pad_choir',       // Мощные, живые волны оркестрового хора
        
        // Натуральный минор Ля (A Natural Minor) — эпический, глубокий, слегка меланхоличный окрас
        scale: [ 45, 47, 48, 50, 52, 53, 55,  57, 59, 60, 62, 64, 65, 67, 69 ], 
        
        noteChance: 0.25,                 // Очень редкие, весомые ноты-акценты
        structuralDelay: 0.75,            // Длинное, парящее время ожидания
        noteDuration: 4.50,               // Максимально длинное, растворяющееся эхо
        
        // Глубокий Cinematic Chill-Out ритм. Барабаны дышат, бочка мягкая, хэт выключен.
        drumPattern: {
            kick:  [ 1, 0, 0, 0, 0, 0, 0, 0 ], // Глубокий, редкий удар в начале такта
            snare: [ 0, 0, 0, 0, 1, 0, 0, 0 ], // Один мягкий, размытый хлопок в центре
            hihat: [ 0, 0, 0, 0, 0, 0, 0, 0 ]  // Полный минимализм — хэт выключен для атмосферы
        },
        
        // Процедурные эмбиент-волны, генерирующие объем пространства
        sfx: {
            lowWave: { chance: 0.35, note: 33, duration: 5.0, velocity: 0.25 }, // Суб-основа такта
            midWave: { chance: 0.25, note: 45, duration: 4.0, velocity: 0.20 }, // Среднее заполнение
            highWave: { chance: 0.15, note: 57, duration: 3.5, velocity: 0.15 } // Верхний мерцающий воздух
        }
    }
};

// Глобальные ссылки на ладовую гармонию с базовыми значениями
const scale = [];
const chordRoots = [];
let currentChord = [];



// Из пула сольных инструментов убраны все гитары, банджо, саксофоны и brass
const soloInstrumentsPool = [ 'synth_piano', 'vocal_lead', 'vocal_chiff', 'lid3', 'lid5', 'lid6' ];
const padInstrumentsPool = [ 'pad_warm', 'pad_poly', 'pad_choir', 'pad_sweep', 'pad_atmos' ];
// Обновленный пул басов
const bassInstrumentsPool = [ 'synth_bass', 'electric_bass', 'sub_bass' ];


let currentSoloInst = 'synth_piano';
let currentPadInst = 'pad_warm';
let currentBassInst = 'synth_bass';
let currentSpeedModifier = 1.0;

// Аварийные пулы на случай отсутствия JSON
const fallbackRhythms = [ [0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5], [0, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75] ];
const fallbackMelodies = [ [0, 2, null, 4, 3, null, 5, 2], [0, 1, 2, 3, 2, 1, 0, null] ];

// ЧАСТЬ 2: ЗАГРУЗКА ДАННЫХ И КВАНТОВАНИЕ ВРЕМЕНИ
// ==========================================

// Запуск сквозного пайплайна загрузки шаблонов из MIDI
fetch('rhythm_templates.json')
    .then(function(response) {
        if (!response.ok) throw new Error('Файл базы данных не найден');
        return response.json();
    })
    .then(function(data) {
        rhythmDatabase = (data.rhythms && data.rhythms.length > 0) ? data.rhythms : fallbackRhythms;
        melodyDatabase = (data.melodies && data.melodies.length > 0) ? data.melodies : fallbackMelodies;

               // 1. ДИАГНОСТИКА БАРАБАНОВ ПРИ СТАРТЕ
        let statusKick  = instruments.drum_kick  ? "✅ ГОТОВ" : "❌ НЕ ЗАГРУЖЕН";
        let statusSnare = instruments.drum_snare ? "✅ ГОТОВ" : "❌ НЕ ЗАГРУЖЕН";
        let statusHat   = instruments.drum_hihat ? "✅ ГОТОВ" : "❌ НЕ ЗАГРУЖЕН";

        // 2. ВЫВОД ИСПРАВЛЕННОГО СТАТУСА НА ЭКРАН
        const statusEl = document.getElementById('status');
        if (statusEl) {
            statusEl.style.color = '#86c232';
            statusEl.innerHTML = `Статус: Генеративный движок готов! Трафареты из MIDI: ритмы (${rhythmDatabase.length}), мелодии (${melodyDatabase.length})<br/>` +
                                 `🔍 Сэмплы: Бочка: ${statusKick} | Снейр: ${statusSnare} | Хэт: ${statusHat}`;
        }

    })
    .catch(function(err) {
        console.error('Откат на процедурные матрицы:', err);
        rhythmDatabase = fallbackRhythms;
        melodyDatabase = fallbackMelodies;

        document.getElementById('status').style.color = '#ffa500';
        document.getElementById('status').innerText = 'Предупреждение: Корректный JSON не найден. Задействована базовая резервная сетка матриц.';
    });

// Генератор случайного трезвучия на основе глобальной гармонии

function generateNextChord() {
    // МАССИВ СТУПЕНЕЙ: Сдвиги в полутонах для смены аккордов (0=Тоника, -3=Параллель, 5=Субдоминанта, 7=Доминанта)
    let steps = [ 0, -3, 2, 4, 5, 7 ];
    
    // Берем текущую базовую ноту из нашей гаммы с защитой от пустоты
    let currentRoot = scale[ Math. floor( Math. random() * scale. length ) ] || 60;
    
    // Выбираем случайный гармонический шаг из массива
    let shift = steps[ Math. floor( Math. random() * steps. length ) ];
    let nextRoot = currentRoot + shift;
    
    // Возвращаем полноценное красивое мажорное трезвучие
    return [ nextRoot, nextRoot + 4, nextRoot + 7 ];
}


// Главный планировщик событий Web Audio API с квантованием ломаных ритмов
function scheduler() {
    if ( !isPlaying || !activeTemplate || activeTemplate.length === 0 ) return;

    // Защита: подтягиваем время к текущему, если оно отстало
    if ( nextNoteTime < audioCtx.currentTime ) {
        nextNoteTime = audioCtx.currentTime + 0.01;
    }

    const profile = VIDEO_MUSIC_PROFILES[ currentProfileKey ];

    while ( nextNoteTime < audioCtx.currentTime + 0.1 ) {
        
        // ==========================================
        // ИСПРАВЛЕНИЕ: СИНХРОНИЗАЦИЯ АККОРДОВ И ПЭДОВ
        // ==========================================
        // Для Лайфхаков меняем аккорды чуть чаще (каждые 8 шагов), для остальных — каждые 16 шагов,
        // чтобы длинные пады успевали подстраиваться под сумасшедший темп электроники.
        let chordStepInterval = ( currentProfileKey === 'lifehacks' ) ? 8 : 16;

        if ( templateIndex % chordStepInterval === 0 ) {
            
            // 1. Обновляем гармонию
            currentChord = generateNextChord();
            
            // 2. ИСПРАВЛЕНИЕ: Перезаписываем ИМЕННО ГЛОБАЛЬНУЮ переменную currentPadInst, которую ищет плеер!
            currentPadInst = profile ? profile.padInstrument : null;
            
            if ( currentPadInst && instruments[ currentPadInst ] && channelPad && Array.isArray( currentChord ) ) {
                
                // ИСПРАВЛЕНИЕ ОГИБАЮЩЕЙ ПЭДА: Физически перенастраиваем сэмпл хора под плотный синт-пэд
                if ( instruments[ currentPadInst ].zones ) {
                    instruments[ currentPadInst ].zones.forEach(function(zone) {
                        zone.ahdsr = true;
                        if ( currentProfileKey === 'lifehacks' ) {
                            // Трансформируем хор в футуристический электронный "Gate Pad"
                            zone.attack = 0.01;   // Мгновенное появление (убираем кулинарный наплыв)
                            zone.decay = 0.40;    
                            zone.sustain = 0.85;  // Очень плотная неоновая подложка
                            zone.release = 0.15;  // Резко затухает, чтобы не гудеть поверх ритма
                        } else {
                            // Возвращаем воздушный бесконечный шлейф для Кулинарии и Видов
                            zone.attack = 0.08;
                            zone.decay = 2.50;
                            zone.sustain = 0.50;
                            zone.release = 1.50;
                        }
                    });
                }

                if ( channelPad.gain ) {
                    let userVol = parseFloat( document.getElementById('volPad').value || 0.5 ) * 0.85;
                    
                    if ( currentProfileKey === 'cooking' ) userVol = userVol * 0.30; 
                    else if ( currentProfileKey === 'lifehacks' ) userVol = userVol * 0.65; // Подняли громкость, чтобы пробить маримбу
                    
                    channelPad.gain.cancelScheduledValues( nextNoteTime );
                    channelPad.gain.setValueAtTime( userVol, nextNoteTime );
                }

                currentChord.forEach( function( note ) {
                    if ( Number.isFinite( note ) && note > 0 ) {
                        // Для кухни поднимаем наверх, для лайфхаков — оставляем в плотном среднем регистре (note)
                        let finalPadNote = ( currentProfileKey === 'cooking' ) ? note + 12 : note; 
                        let padDuration = ( currentProfileKey === 'cooking' ) ? 3.2 : (currentProfileKey === 'lifehacks' ? 2.5 : 6.0);
                        let padVelocity = ( currentProfileKey === 'cooking' ) ? 0.20 : (currentProfileKey === 'lifehacks' ? 0.38 : 0.50); 

                        player.queueWaveTable( audioCtx, channelPad, instruments[ currentPadInst ], nextNoteTime, finalPadNote, padDuration, padVelocity );
                    }
                });
            }
        }

        // Вызов процедурного сольного шага и баса
        playSoloNote( nextNoteTime );

        // МАТЕМАТИЧЕСКИЙ ТРИГГЕР УДАРНЫХ
        if ( channelDrums ) {
            playDrumsNode( nextNoteTime, templateIndex );

            // ЭФФЕКТ SIDECHAIN: Компрессия баса в момент удара бочки
            if ( channelBass && channelBass.gain && ( templateIndex % 4 === 0 ) ) {
                let userBassVol = parseFloat( document.getElementById('volBass').value || 0.5 );
                let bassMult = ( currentProfileKey === 'cooking' || currentProfileKey === 'cinematicView' ) ? 0.85 : 0.70;
                let currentVol = userBassVol * bassMult;
                let targetGain = ( currentProfileKey === 'cooking' || currentProfileKey === 'cinematicView' ) ? 0.80 : 0.20;
                
                channelBass.gain.cancelScheduledValues( nextNoteTime );
                channelBass.gain.setValueAtTime( currentVol * targetGain, nextNoteTime );
                channelBass.gain.linearRampToValueAtTime( currentVol, nextNoteTime + 0.15 );
            }
        }

        // --- БЛОК ЗАЩИТЫ И КВАНТОВАНИЯ РИТМА ---
        let currentDelta = activeTemplate[ templateIndex ];
        templateIndex++;

        if ( templateIndex >= activeTemplate.length ) {
            templateIndex = 0;
        }

        let nextDelta = activeTemplate[ templateIndex ];
        let diff = nextDelta - currentDelta;

        if ( diff > 0 && diff < 0.08 ) diff = 0.125; 
        if ( Number.isFinite( diff ) && diff > 0 ) {
            const grid = [ 0.125, 0.25, 0.375, 0.5, 0.75, 1.0, 1.5, 2.0 ];
            diff = grid.reduce( function( prev, curr ) {
                return ( Math.abs( curr - diff ) < Math.abs( prev - diff ) ? curr : prev );
            });
        }
        if ( !Number.isFinite( diff ) || diff <= 0 || diff > 3.0 ) diff = 0.5; 

        nextNoteTime += ( diff * currentSpeedModifier );
    }
    timerId = setTimeout( scheduler, 25 );
}


// ==========================================
// ЧАСТЬ 3: ИДЕАЛЬНЫЙ СИНТ-ЭМБИЕНТ СИНТЕЗ (ОЧИЩЕН ОТ НЕКАЧЕСТВЕННЫХ ИНСТРУМЕНТОВ)
// ==========================================
function playSoloNote( time ) {
    if ( !isPlaying || !channelSolo ) return;
    
    const profile = VIDEO_MUSIC_PROFILES[ currentProfileKey ];
    const actualSoloInst = profile.soloInstrument;
    const actualBassInst = profile.bassInstrument;

    // Защита: если трафарет пуст, играть нечего
    if ( !Array.isArray(activeTemplate) || activeTemplate.length === 0 ) return;

    // Глобальный шаг по сетке activeTemplate
    let templateStep = templateIndex % activeTemplate.length;
    let rawValue = activeTemplate[ templateStep ];
    
    // Математический перевод дельты трафарета в чистые музыкальные полутона
    let mOffset = Number.isFinite(rawValue) ? Math.floor((rawValue * 12) % 24) : 0;

    // Тоника аккорда (берём первую опорную ноту текущего аккорда)
    let baseNote = Array.isArray( currentChord ) ? currentChord[0] : currentChord;
    if ( !Number.isFinite( baseNote ) ) baseNote = 60;

    // Базовая длительность из пресетов профиля
    let finalNoteDuration = profile.noteDuration || 1.50; 

    // ==========================================
    // БЛОК 1: ГЕНЕРАЦИЯ СОЛО ПАРТИИ (ПИАНИНО / МАРИМБА)
    // ==========================================
    if ( instruments[ actualSoloInst ] ) {
        let noteValue = baseNote + mOffset;
        
        let closestNote = profile.scale.reduce( function( prev, curr ) {
            return ( Math.abs( curr - noteValue ) < Math.abs( prev - noteValue ) ? curr : prev );
        });

        if ( channelSolo.pan ) {
            if ( currentProfileKey === 'cinematicView' ) {
                channelSolo.pan.setValueAtTime( 0.0, time ); 
            } else {
                channelSolo.pan.setValueAtTime( Math.random() * 0.4 - 0.2, time );
            }
        }

        let userSoloVol = parseFloat( document.getElementById('volSolo').value || 0.5 );
        if ( channelSolo.gain ) {
            let dynamicGainModifier = 0.55; 
            if ( profile.structuralDelay && profile.structuralDelay <= 0.25 ) {
                dynamicGainModifier = 0.40; 
            }
            channelSolo.gain.cancelScheduledValues( time );
            channelSolo.gain.setValueAtTime( userSoloVol * dynamicGainModifier, time );
        }

        let targetInstrumentObj = instruments[ actualSoloInst ];
        if ( targetInstrumentObj && targetInstrumentObj.zones ) {
            if ( currentProfileKey === 'cooking' ) {
                targetInstrumentObj.zones.forEach(function(zone) {
                    zone.ahdsr = true;
                    zone.attack = 0.005;  
                    zone.decay = 0.80;    
                    zone.sustain = 0.40;  
                    zone.release = 0.90;  
                });
            } else if ( currentProfileKey === 'lifehacks' ) {
                targetInstrumentObj.zones.forEach(function(zone) {
                    zone.ahdsr = true;
                    zone.attack = 0.002;  
                    zone.decay = 0.06;    
                    zone.sustain = 0.0;   
                    zone.release = 0.04;  
                });
            } else if ( currentProfileKey === 'cinematicView' ) {
                targetInstrumentObj.zones.forEach(function(zone) {
                    zone.ahdsr = true;
                    zone.attack = 0.05;   
                    zone.decay = 3.00;    
                    zone.sustain = 0.60;
                    zone.release = 2.50;  
                });
            }
        }

        // Алгоритм "живого оркестра" для соло
        let isStrongBeat = ( templateStep % 4 === 0 );
        let baseVelocity = isStrongBeat ? 0.65 : 0.42;
        let humanizedVelocity = baseVelocity + ( Math.random() * 0.12 - 0.06 ); 
        let maxTimeShift = ( currentProfileKey === 'lifehacks' ) ? 0.002 : 0.006;
        let humanizedTime = time + ( Math.random() * maxTimeShift * 2 - maxTimeShift );

        if ( typeof lastPlayedNote !== 'undefined' && lastPlayedNote === closestNote && (profile.structuralDelay || 0.5) <= 0.25 ) {
            finalNoteDuration = finalNoteDuration * 0.60;
        }
        window.lastPlayedNote = closestNote; 

        if ( Math.random() < (profile.noteChance || 0.50) ) {
            player.queueWaveTable( audioCtx, channelSolo, targetInstrumentObj, humanizedTime, closestNote, finalNoteDuration, humanizedVelocity );
        }
    }

    // ==========================================
    // БЛОК 2: ГЕНЕРАЦИЯ ГЛУБОКОГО БАСА (ИСПРАВЛЕНО И СИНХРОНИЗИРОВАНО)
    // ==========================================
    // Бас играет ТОЛЬКО на кухне и в лайфхаках. В кинематографичных видах он полностью отключен.
    if ( actualBassInst && instruments[ actualBassInst ] && channelBass && currentProfileKey !== 'cinematicView' ) {
        
        // В Кулинарии (живой контрабас) оставляем сочный низ (-24), в Лайфхаках поднимаем выше (-12), чтобы синт-бас читался
        let octaveShift = ( currentProfileKey === 'cooking' ) ? -24 : -12;
        let bassNoteValue = baseNote + octaveShift; 
        
        let bassOffset = Math.floor(mOffset / 2);
        let finalBassNote = bassNoteValue + bassOffset;

        // ИСПРАВЛЕНИЕ: Безопасное вычисление дефолтной ноты и передача её в качестве аккумулятора reduce
        let fallbackScaleNote = (profile.scale && profile.scale.length > 0) ? profile.scale[0] : 60;
        let defaultLowNote = fallbackScaleNote + octaveShift;
        
        let closestBassNote = profile.scale.reduce( function( prev, curr ) {
            let lowCurr = curr + octaveShift;
            return ( Math.abs( lowCurr - finalBassNote ) < Math.abs( prev - finalBassNote ) ? lowCurr : prev );
        }, defaultLowNote); // ИСПРАВЛЕНО: Теперь сюда безопасно передается число, а не сломанный массив

        // Настройка громкости басового канала с учетом ползунков микшера
        let userBassVol = parseFloat( document.getElementById('volBass').value || 0.5 );
        if ( channelBass.gain ) {
            // Для лайфхаков делаем чуть громче (0.75), чтобы пробить микс, для кухни — мягче (0.45)
            let bassVolumeModifier = ( currentProfileKey === 'cooking' ) ? 0.45 : 0.75;
            channelBass.gain.setValueAtTime( userBassVol * bassVolumeModifier, time );
        }

        // Настройка физики звука под сэмплы
        let targetBassObj = instruments[ actualBassInst ];
        if ( targetBassObj && targetBassObj.zones ) {
            targetBassObj.zones.forEach(function(zone) {
                zone.ahdsr = true;
                if ( currentProfileKey === 'lifehacks' ) {
                    // ТРАНСФОРМАЦИЯ: Сжимаем живой контрабас в плотный, прыгучий и качающий электронный синт-бас
                    zone.attack = 0.001; // Мгновенный бьющий щелчок
                    zone.decay = 0.12;  // Экстремально короткий спад (вместо прежних 0.40) для Tech-эффекта
                    zone.sustain = 0.45; // Собранное тело звука
                    zone.release = 0.08; // Моментально тухнет, освобождая место бочке
                } else {
                    // Классические мягкие настройки для ресторанного контрабаса в Кулинарии
                    zone.attack = 0.03;  
                    zone.decay = 1.50;   
                    zone.sustain = 0.60; 
                    zone.release = 0.50; 
                }
            });
        }

        // В лайфхаках бас фигачит на каждый шаг (templateStep % 1 === 0) сплошной стеной. На кухне — на каждый второй шаг.
        let bassTriggerRate = ( currentProfileKey === 'lifehacks' ) ? 1 : 2;

        if ( templateStep % bassTriggerRate === 0 ) {
            let bassDuration = finalNoteDuration * 1.5; 
            player.queueWaveTable( audioCtx, channelBass, targetBassObj, time, closestBassNote, bassDuration, 0.70 );
        }
    }
}


function playDrumsNode( time, stepIndex ) {
    if ( !isPlaying || !channelDrums ) return;
    
    const profile = VIDEO_MUSIC_PROFILES[ currentProfileKey ];
    
    // Регулируем общую громкость ударных из ползунка
    if ( channelDrums.gain ) {
        let userVol = parseFloat( document.getElementById('volDrums').value || 0.5 );
        channelDrums.gain.setValueAtTime( userVol, time );
    }

    // ИСПРАВЛЕНИЕ ДЛЯ ВИДОВЫХ: Дополнительно накладываем фоновый хор (атмосферу) БЕЗ остановки основного барабанного ритма!
    if ( currentProfileKey === 'cinematicView' ) {
        let atmosInst = instruments.pad_choir;
        if ( atmosInst && profile.sfx ) {
            const sfx = profile.sfx;
            if ( Math.random() < sfx.lowWave.chance ) player.queueWaveTable( audioCtx, channelDrums, atmosInst, time, sfx.lowWave.note, sfx.lowWave.duration, sfx.lowWave.velocity ); 
            if ( Math.random() < sfx.midWave.chance ) player.queueWaveTable( audioCtx, channelDrums, atmosInst, time, sfx.midWave.note, sfx.midWave.duration, sfx.midWave.velocity ); 
            if ( Math.random() < sfx.highWave.chance ) player.queueWaveTable( audioCtx, channelDrums, atmosInst, time, sfx.highWave.note, sfx.highWave.duration, sfx.highWave.velocity ); 
        }
        // УБРАН RETURN! Теперь видовые видео тоже спускаются ниже и играют ритм-секцию, если она задана
    }

    // Воспроизведение ударных по паттерну
    if ( profile.drumPattern ) {
        const pattern = profile.drumPattern;
        let step = Math.abs(Math.floor(stepIndex)) % 8; 
        let baseVelocity = ( currentProfileKey === 'lifehacks' ) ? 0.70 : 0.55;

        // 1. Бочка (Kick)
        if ( pattern.kick && pattern.kick[ step ] === 1 && instruments.drum_kick ) {
            player.queueWaveTable( audioCtx, channelDrums, instruments.drum_kick, time, 36, 0.20, baseVelocity * 0.95 );
        }
        
        // 2. Малый барабан (Snare)
        if ( pattern.snare && pattern.snare[ step ] === 1 && instruments.drum_snare ) {
            player.queueWaveTable( audioCtx, channelDrums, instruments.drum_snare, time, 38, 0.15, baseVelocity * 0.85 );
        }
        
        // 3. Хэт (Closed Hi-hat)
        if ( pattern.hihat && pattern.hihat[ step ] > 0 && instruments.drum_hihat ) {
            let hihatMod = (pattern.hihat[ step ] === 2) ? 1.0 : 0.60;
            player.queueWaveTable( audioCtx, channelDrums, instruments.drum_hihat, time, 42, 0.05, baseVelocity * 0.60 * hihatMod );
        }
    }
}


// ==========================================
// ЧАСТЬ 4: СТОХАСТИЧЕСКАЯ ГЕНЕРАЦИЯ СРЕДЫ И СТУДИЙНЫЙ МАСТЕР
// ==========================================

document.getElementById('randomBtn').addEventListener('click', async function() {
    if (rhythmDatabase.length === 0 || melodyDatabase.length === 0) return;
    
    stopMusic();

       
        // ПОЛУЧАЕМ НАСТРОЙКИ ИЗ АКТИВНОГО ПРОФИЛЯ ВИДЕО
    const profile = VIDEO_MUSIC_PROFILES[ currentProfileKey ];

    // Базовое назначение инструментов и падов
    currentSoloInst = profile. soloInstrument;
    currentBassInst = profile. bassInstrument;
    currentPadInst  = profile. padInstrument ? profile. padInstrument : null;

    // ОБЪЕДИНЕННАЯ НАСТРОЙКА ИНСТРУМЕНТОВ И СКОРОСТИ ПО ЖАНРАМ:
    if ( currentProfileKey === 'lifehacks' ) {
        // 1. Устанавливаем быстрый, бодрый темп для DIY
        currentSpeedModifier = 0.70; 

        // 2. Выбираем ОДИН инструмент на весь трек (50% пианино / 50% маримба)
        if ( Math. random() < 0.50 ) {
            currentSoloInst = 'bright_piano_gs'; // Включаем новое пианино 0130 GS
        } else {
            currentSoloInst = 'marimba';         // Включаем маримбу 0120
        }
    } 
    else if ( currentProfileKey === 'cinematicView' ) {
        // Медленный, летящий темп для пейзажей
        currentSpeedModifier = 1.35; 
    } 
    else {
        // Комфортный средний темп для кулинарии (cooking)
        currentSpeedModifier = 1.00; 
    }

    // Заполняем глобальную гамму шкалы из настроек нашего профиля
    scale. length = 0;
    profile. scale. forEach( function( n ) { 
        scale. push( n ); 
    });

         // ==========================================
    // 1. ИСПРАВЛЕННАЯ НАСТРОЙКА ТОНАЛЬНОСТИ И АККОРДОВ (БЕЗ БРИДЖЕЙ В КУЛИНАРИИ)
    // ==========================================
    let baseChordNote = 60; // По умолчанию чистый До-мажор

    if (currentProfileKey === 'cooking') {
        // Для кулинарии намертво фиксируем До-мажор, чтобы музыка была уютной и предсказуемой
        baseChordNote = 60; 
    } else {
        // Для остальных режимов оставляем контролируемую рандомизацию тоники
        let rootNotes = [ 60, 62, 65, 67, 69 ];
        baseChordNote = rootNotes[ Math.floor( Math.random() * rootNotes.length ) ];
    }

    // Безопасное заполнение опорных нот аккордов с защитой от неопределенной шкалы
    chordRoots.length = 0;
    if ( Array.isArray( scale ) && scale.length >= 6 ) {
        chordRoots.push( scale[ 0 ], scale[ 2 ], scale[ 4 ], scale[ 5 ] );
    } else {
        chordRoots.push( baseChordNote, baseChordNote + 4, baseChordNote + 7, baseChordNote + 9 );
    }

    // Запускаем первую генерацию сетки аккордов (для кулинарии это всегда будет красивое трезвучие До-мажор)
    currentChord = [ baseChordNote, baseChordNote + 4, baseChordNote + 7 ];
    chordChangeTime = audioCtx ? audioCtx.currentTime + 4.0 : 4.0;

    // 2. УМНЫЙ ВЫБОР И СИНХРОНИЗАЦИЯ ВАШИХ MIDI-ТРАФАРЕТОВ (Ритмы: 124, Мелодии: 35)
    randomRhythmIdx = Math. floor( Math. random() * rhythmDatabase. length );
    randomMelodyIdx = Math. floor( Math. random() * melodyDatabase. length );

    // Подтягиваем ваши реальные MIDI-шаблоны
    activeTemplate = rhythmDatabase[ randomRhythmIdx ];
    let rawMelodyPattern = melodyDatabase[ randomMelodyIdx ];

    // ИСПРАВЛЕНИЕ ФАЗОВОГО СДВИГА: Выравниваем длину мелодии под длину текущего ритма
    currentMelodyPattern = [];
    if ( Array. isArray( activeTemplate ) && activeTemplate. length > 0 ) {
        for ( let i = 0; i < activeTemplate. length; i++ ) {
            // Безопасно берем ноту из MIDI-мелодии, зацикливая её по кругу при необходимости
            let noteFromMIDI = Array. isArray( rawMelodyPattern ) && rawMelodyPattern. length > 0
                ? rawMelodyPattern[ i % rawMelodyPattern. length ]
                : 0;
            
            // Музыкальное «дыхание»: с вероятностью 15% аккуратно сдвигаем ноту на ступень,
            // чтобы один и тот же MIDI-трафарет каждый раз выдавал новые полутона
            if ( Math. random() < 0.15 ) {
                noteFromMIDI += ( Math. random() < 0.50 ? 2 : -2 ); 
            }
            currentMelodyPattern. push( noteFromMIDI );
        }
    } else {
        // Аварийный фоллбэк на случай пустого массива в базе
        activeTemplate = [ 0.25, 0.25, 0.25, 0.25 ];
        currentMelodyPattern = [ 0, 2, 4, 7 ];
    }

    templateIndex = 0;
    melodyPatternIndex = 0;

    // 3. АКТИВАЦИЯ АУДИО-КОНТЕКСТА И МАСТЕР-ШИНЫ
    if ( ! audioCtx ) audioCtx = new AudioContext();
    if ( audioCtx. state === 'suspended' ) await audioCtx. resume();
    
    // Входной Gain: Снижен с 0.75 до 0.45 для чистоты микса без каши
    let gainNode = audioCtx. createGain();
    gainNode. gain. setValueAtTime( 0.45, audioCtx. currentTime ); 


// 2. Мастер-компрессор: Время восстановления (release) уменьшено с 0.25 до 0.10.
// Теперь компрессор успевает "отпускать" звук между быстрыми нотами (каждые 0.25 сек).
let masterCompressor = audioCtx.createDynamicsCompressor();
masterCompressor.threshold.setValueAtTime(-14.0, audioCtx.currentTime); 
masterCompressor.knee.setValueAtTime(8, audioCtx.currentTime);          
masterCompressor.ratio.setValueAtTime(4, audioCtx.currentTime);          
masterCompressor.attack.setValueAtTime(0.015, audioCtx.currentTime);    
masterCompressor.release.setValueAtTime(0.10, audioCtx.currentTime); // Было 0.25   

// 3. Лимитер (Brickwall): Время атаки чуть сглажено, чтобы убрать цифровой треск (клиппинг).
// Релиз ускорен до 0.03, чтобы лимитер не "плющил" следующие за пиками ноты.
let brickwallLimiter = audioCtx.createDynamicsCompressor();
brickwallLimiter.threshold.setValueAtTime(-0.5, audioCtx.currentTime);  
brickwallLimiter.knee.setValueAtTime(0, audioCtx.currentTime);          
brickwallLimiter.ratio.setValueAtTime(20, audioCtx.currentTime);        
brickwallLimiter.attack.setValueAtTime(0.003, audioCtx.currentTime); // Было 0.001   
brickwallLimiter.release.setValueAtTime(0.03, audioCtx.currentTime); // Было 0.05

    analyserNode = audioCtx.createAnalyser();
    analyserNode.fftSize = 64; 
    
    let dest = audioCtx.createMediaStreamDestination();
    recordedChunks = [];
    mediaRecorder = new MediaRecorder(dest.stream, { mimeType: 'audio/webm' });
    
    mediaRecorder.ondataavailable = function(e) {
        if (e.data && e.data.size > 0) {
            recordedChunks.push(e.data);
        }
    };

    gainNode.connect(masterCompressor);
    masterCompressor.connect(brickwallLimiter);
    brickwallLimiter.connect(analyserNode);
    
    analyserNode.connect(audioCtx.destination);
    analyserNode.connect(dest);

      channelSolo = audioCtx. createGain();
    channelPad = audioCtx. createGain();
    channelBass = audioCtx. createGain();
    channelDrums = audioCtx. createGain();

    // СЧИТЫВАЕМ ЗНАЧЕНИЯ ПОЛЗУНКОВ ИЗ ИНТЕРФЕЙСА ДЛЯ КАЖДОГО КАНАЛА
    let volSoloVal = parseFloat( document. getElementById('volSolo'). value );
    let volPadVal  = parseFloat( document. getElementById('volPad'). value );
    let volBassVal = parseFloat( document. getElementById('volBass'). value );
    let volDrumsVal = parseFloat( document. getElementById('volDrums'). value );

    // ИСПРАВЛЕНИЕ: Выставляем чистую рабочую громкость без чрезмерного занижения
    channelSolo. gain. setValueAtTime( volSoloVal * 0.75, audioCtx. currentTime );
    channelPad. gain. setValueAtTime( volPadVal * 0.85, audioCtx. currentTime );
    channelBass. gain. setValueAtTime( volBassVal * 0.55, audioCtx. currentTime );
    
    // Для Кулинарии и Лайфхаков даем барабанам играть в полную силу ползунка
    if ( currentProfileKey === 'cinematicView' ) {
        channelDrums. gain. setValueAtTime( volDrumsVal * 0.40, audioCtx. currentTime ); // Атмосферный фон тише
    } else {
        channelDrums. gain. setValueAtTime( volDrumsVal * 0.85, audioCtx. currentTime ); // Чистые барабаны громче!
    }

    // Привязка динамического изменения ползунков на лету во время проигрывания
    // Привязка динамического изменения ползунков на лету во время проигрывания
    document. getElementById('volSolo'). oninput = function( e ) { if ( channelSolo ) channelSolo. gain. setValueAtTime( parseFloat( e. target. value ) * 0.75, audioCtx. currentTime ); };
    document. getElementById('volPad'). oninput = function( e ) { if ( channelPad ) channelPad. gain. setValueAtTime( parseFloat( e. target. value ) * 0.85, audioCtx. currentTime ); };
    document. getElementById('volBass'). oninput = function( e ) { if ( channelBass ) channelBass. gain. setValueAtTime( parseFloat( e. target. value ) * 0.55, audioCtx. currentTime ); };
    document. getElementById('volDrums'). oninput = function( e ) { 
        if ( channelDrums ) {
            let mult = ( currentProfileKey === 'cinematicView' ) ? 0.40 : 0.85;
            channelDrums. gain. setValueAtTime( parseFloat( e. target. value ) * mult, audioCtx. currentTime ); 
        }
    };

    // НАСТРОЙКА ЭФФЕКТА ЗАДЕРЖКИ (DELAY)
    delayNode = audioCtx. createDelay();
    delayFeedback = audioCtx. createGain();
    delayNode. delayTime. setValueAtTime( 0.4, audioCtx. currentTime );     
    delayFeedback. gain. setValueAtTime( 0.35, audioCtx. currentTime );    
    
    // Зацикливаем дилей сам на себя через обратную связь (feedback)
    delayNode. connect( delayFeedback );
    delayFeedback. connect( delayNode );

    // НАСТРОЙКА РЕВЕРБЕРАЦИИ ПОД ТИП КОНТЕНТА
    reverbNode = audioCtx. createConvolver();
    if ( currentProfileKey === 'cinematicView' ) {
        reverbNode. buffer = createReverbImpulse( audioCtx, 4.2, 2.5 ); // Огромный глубокий шлейф
    } else if ( currentProfileKey === 'lifehacks' ) {
        reverbNode. buffer = createReverbImpulse( audioCtx, 0.8, 1.2 ); // Сухой собранный отклик
    } else {
        reverbNode. buffer = createReverbImpulse( audioCtx, 1.6, 1.5 ); // Мягкое комнатное эхо
    }

    // КОММУТАЦИЯ: Подключаем все каналы к главному входному Gain-узлу (gainNode)
    channelBass. connect( gainNode );
    channelDrums. connect( gainNode );
    channelSolo. connect( gainNode );
    channelPad. connect( gainNode );

    // ПОД КЛЮЧЕНИЕ ПРОСТРАНСТВЕННЫХ ЭФФЕКТОВ
    channelSolo. connect( delayNode );     // Соло-линия уходит в дилей
    channelPad. connect( reverbNode );      // Пад уходит в реверберацию
    delayNode. connect( gainNode );        // Выход дилея подмешивается в мастер
    reverbNode. connect( gainNode );       // Выход реверберации подмешивается в мастер

    isPlaying = true;
    mediaRecorder. start(); 
    nextNoteTime = audioCtx. currentTime + 0.05;
    chordChangeTime = audioCtx. currentTime;

    scheduler();

    const canvas = document. getElementById('visualizer');
    visualContext = canvas. getContext('2d');
    drawVisualizer();

    startTimer();

    // ДИНАМИЧЕСКИЙ ВЫВОД РИТМ-СЕКЦИИ В СТАТУС
    let drumsList = ( currentProfileKey === 'cinematicView' ) ? 'Атмосферный хор-эффект 🌌' : 'Чистые акустические барабаны 🥁';

    // ВЫВОДИМ В СТАТУС АКТУАЛЬНЫЕ НАСТРОЙКИ ПРОФИЛЯ ВИДЕО
    document. getElementById('status'). style. color = '#a370f7';
    document. getElementById('status'). innerText = 
        '🎬 Режим: ' + profile. name + 
        ' | Скорость: x' + currentSpeedModifier + 
        ' | [Соло: ' + currentSoloInst + ']' +
        ' | [Бас: ' + currentBassInst + ']' +
        ' | [Ритм: ' + drumsList + ']' +
        ' | Ритм-маска №' + ( randomRhythmIdx + 1 ) + 
        ' | Мелодия №' + ( randomMelodyIdx + 1 );
});



// ==========================================
// ЧАСТЬ 5: ТАЙМЕРЫ, СТУДИЙНЫЙ ЭКВАЛАЙЗЕР И СКАЧИВАНИЕ
// ==========================================

function startTimer() {
    totalSeconds = 120;
    updateTimerDisplay();
    
    countdownInterval = setInterval(function() {
        totalSeconds--;
        updateTimerDisplay();

        if (totalSeconds === 5) {
            gainNode.gain.setValueAtTime(gainNode.gain.value, audioCtx.currentTime);
            gainNode.gain.linearRampToValueAtTime(0, audioCtx.currentTime + 5);
            document.getElementById('status').style.color = '#00adb5';
            document.getElementById('status').innerText = 'Завершение трека (Fade-out)...';
        }

        if (totalSeconds <= 0) {
            stopMusic();
            document.getElementById('status').style.color = '#888';
            document.getElementById('status').innerText = '2 минуты истекли. Трек завершен.';
        }
    }, 1000);
}

function updateTimerDisplay() {
    let minutes = Math.floor(totalSeconds / 60);
    let seconds = totalSeconds % 60;
    document.getElementById('timer').innerText = 
        minutes.toString().padStart(2, '0') + ':' + seconds.toString().padStart(2, '0');
}

function createReverbImpulse(context, duration, decay) {
    if (!duration) duration = 3.0;
    if (!decay) decay = 2.0;
    let sampleRate = context.sampleRate;
    let length = sampleRate * duration;
    let impulse = context.createBuffer(2, length, sampleRate);
    let left = impulse.getChannelData(0);
    let right = impulse.getChannelData(1);

    for (let i = 0; i < length; i++) {
        let percent = i / length;
        let scaleVal = Math.pow(1 - percent, decay);
        left[i] = (Math.random() * 2 - 1) * scaleVal;
        right[i] = (Math.random() * 2 - 1) * scaleVal;
    }
    return impulse;
}

function stopMusic() {
    if (mediaRecorder && mediaRecorder.state !== 'inactive') {
        try { mediaRecorder.stop(); } catch(e) { console.error(e); }
    }

    isPlaying = false;
    clearTimeout(timerId);
    clearInterval(countdownInterval);
    
    melodyPatternIndex = 0;

    if (animationId) {
        cancelAnimationFrame(animationId);
        animationId = null;
    }
    
    if (gainNode) { try { gainNode.gain.setValueAtTime(0, audioCtx.currentTime); gainNode.disconnect(); } catch(e){} }
    if (channelSolo)  { try { channelSolo.gain.setValueAtTime(0, audioCtx.currentTime);  channelSolo.disconnect();  } catch(e){} }
    if (channelPad)   { try { channelPad.gain.setValueAtTime(0, audioCtx.currentTime);   channelPad.disconnect();   } catch(e){} }
    if (channelBass)  { try { channelBass.gain.setValueAtTime(0, audioCtx.currentTime);  channelBass.disconnect();  } catch(e){} }
    if (channelDrums) { try { channelDrums.gain.setValueAtTime(0, audioCtx.currentTime); channelDrums.disconnect(); } catch(e){} }
    
    if (delayNode)     { try { delayNode.disconnect(); } catch(e){} }
    if (delayFeedback) { try { delayFeedback.disconnect(); } catch(e){} }
    if (reverbNode)    { try { reverbNode.disconnect(); } catch(e){} }

    channelSolo = null; channelPad = null; channelBass = null; channelDrums = null;
    delayNode = null; delayFeedback = null; reverbNode = null;
    
    // СБРОС АНАЛИЗАТОРА: Отключаем визуализатор от звуковой шины
    analyserNode = null;

    document.getElementById('status').style.color = '#888';
    document.getElementById('status').innerText = 'Статус: остановлен';
    
    // Мгновенно перерисовываем экран, чтобы показать ровную линию покоя
    const canvas = document.getElementById('visualizer');
    if (canvas && visualContext) {
        visualContext.fillStyle = '#111111';
        visualContext.fillRect(0, 0, canvas.width, canvas.height);
        
        // Рисуем одну тонкую статичную линию по центру
        visualContext.beginPath();
        visualContext.lineWidth = 2;
        visualContext.strokeStyle = '#00adb5';
        visualContext.moveTo(0, canvas.height / 2);
        visualContext.lineTo(canvas.width, canvas.height / 2);
        visualContext.stroke();
    }
}

function drawVisualizer() {
    // Разрешаем анимацию только если музыка играет
    if (isPlaying) {
        animationId = requestAnimationFrame(drawVisualizer);
    }

    const canvas = document.getElementById('visualizer');
    if (!canvas || !visualContext) return;
    
    const width = canvas.width;
    const height = canvas.height;
    const centerY = height / 2;
    
    visualContext.fillStyle = 'rgba(17, 17, 17, 0.25)';
    visualContext.fillRect(0, 0, width, height);

    let hasAudio = false;
    let bufferLength = 32;
    let dataArray = [];

    // Строгая проверка: опрашиваем частоты только если нода физически существует
    if (isPlaying && analyserNode) {
        bufferLength = analyserNode.frequencyBinCount;
        dataArray = new Uint8Array(bufferLength);
        analyserNode.getByteFrequencyData(dataArray);
        hasAudio = dataArray.some(function(volume) { return volume > 5; });
    }

    if (hasAudio) {
        const halfLength = Math.floor(bufferLength * 0.7); 
        const barWidth = (width / 2) / halfLength;
        
        for (let i = 0; i < halfLength; i++) {
            let barHeight = (dataArray[i] / 255) * (height * 0.85); 
            if (barHeight < 2) barHeight = 2;

            let percent = i / halfLength;
            let r = Math.floor(163 - (percent * 63));
            let g = Math.floor(112 + (percent * 100));
            let b = Math.floor(247 - (percent * 47));
            visualContext.fillStyle = 'rgb(' + r + ',' + g + ',' + b + ')';

            let xLeft = (width / 2) - (i * barWidth) - barWidth;
            let xRight = (width / 2) + (i * barWidth);
            let yPos = centerY - (barHeight / 2);

            visualContext.fillRect(xLeft, yPos, barWidth - 2, barHeight);
            visualContext.fillRect(xRight, yPos, barWidth - 2, barHeight);
        }
    } else {
        // Если трек на паузе или остановлен, рисуем спокойную ровную линию
        visualContext.beginPath();
        visualContext.lineWidth = 2;
        visualContext.strokeStyle = '#00adb5';
        visualContext.moveTo(0, centerY);
        visualContext.lineTo(width, centerY);
        visualContext.stroke();
    }
}

document.getElementById('stopBtn').addEventListener('click', stopMusic);

const initialCanvas = document.getElementById('visualizer');
if (initialCanvas) {
    visualContext = initialCanvas.getContext('2d');
    drawVisualizer();
}

document.getElementById('downloadBtn').addEventListener('click', function() {
    const triggerDownload = function(url) {
        const a = document.createElement('a');
        a.style.display = 'none';
        a.href = url;
        a.download = 'generative-mix-' + Math.floor(Date.now() / 1000) + '.webm';
        document.body.appendChild(a);
        a.click();
        setTimeout(function() { document.body.removeChild(a); }, 100);
    };

    if (isPlaying && mediaRecorder && mediaRecorder.state === 'recording') {
        mediaRecorder.onstop = function() {
            if (recordedChunks.length === 0) return;
            const blob = new Blob(recordedChunks, { type: 'audio/webm' });
            latestTrackUrl = URL.createObjectURL(blob);
            triggerDownload(latestTrackUrl);
        };
        stopMusic();
        return;
    }

    if (latestTrackUrl) {
        triggerDownload(latestTrackUrl);
    } else {
        alert('Сначала сгенерируйте и запустите микс!');
    }
});

// ОБРАБОТЧИК ВЫПАДАЮЩЕГО СПИСКА ВЫБОРА ФОНА С ИСПРАВЛЕНИЕМ СТАТУСА
const profileSelector = document.getElementById('videoProfileSelect');
if (profileSelector) {
    profileSelector.addEventListener('change', function(e) {
        // Меняем глобальный ключ на выбранный (cooking, lifehacks или cinematicView)
        currentProfileKey = e.target.value;
        
        const currentProfile = VIDEO_MUSIC_PROFILES[currentProfileKey];
        const statusElement = document.getElementById('status');
        
        if (statusElement && currentProfile) {
            if (isPlaying) {
                statusElement.style.color = '#00adb5';
                statusElement.innerText = 'Выбран стиль: "' + currentProfile.name + '". Нажмите "Случайный микс 🎲", чтобы применить новые инструменты!';
            } else {
                statusElement.style.color = '#8e8e93';
                statusElement.innerText = 'Подготовлен профиль: ' + currentProfile.name + '. Готов к генерации.';
            }
        }
    });
}
</script>