CML Song Maker+

Bypass all limits natively! Force 128 bars, 10 octaves, custom synths, dark mode, and local JSON loading/exporting.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

You will need to install an extension such as Tampermonkey to install this script.

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Advertisement:

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

Advertisement:

// ==UserScript==
// @name         CML Song Maker+
// @namespace    http://tampermonkey.net/
// @version      3.12
// @description  Bypass all limits natively! Force 128 bars, 10 octaves, custom synths, dark mode, and local JSON loading/exporting.
// @author       DominumNetwork
// @match        *://musiclab.chromeexperiments.com/Song-Maker/*
// @grant        none
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    window.__CML_INTERCEPTED_DATA = null;

    // ==========================================
    // 1. OMNI-INTERCEPTORS (Catches State Instantly)
    // ==========================================

    function getSafeData(data) {
        const defaults = {
            bars: 4, beats: 4, subdivision: 4, octaves: 2, scale: "chromatic",
            rootNote: 48, rootPitch: 0, rootOctave: 4, tempo: 120, notes: [], percussionNotes: []
        };
        const res = { ...defaults, ...data };
        if (data.options) res.options = { ...defaults, ...data.options };

        // Ensure arrays
        const ensureArray = (obj, key) => { if (!Array.isArray(obj[key])) obj[key] = []; };
        ensureArray(res, 'notes');
        ensureArray(res, 'percussionNotes');
        if (res.options) {
            ensureArray(res.options, 'notes');
            ensureArray(res.options, 'percussionNotes');
        }
        return res;
    }

    function getCurrentSongId() {
        const match = window.location.href.match(/song\/([^/?#]+)/);
        return match ? match[1] : null;
    }

    // Aggressive fallback capture
    const _stringify = JSON.stringify;
    JSON.stringify = function(val, replacer, space) {
        const res = _stringify.call(this, val, replacer, space);
        if (typeof res === 'string' && res.length > 50 && (res.includes('"notes"') || res.includes('"tempo"'))) {
            window.__CML_INTERCEPTED_DATA = res;
            window.__CML_GOT_DATA = true;
        }
        return res;
    };

    function verifyAndSave(str) {
        if (!str || typeof str !== 'string') return;
        if (str.includes('"tempo"') && (str.includes('"bars"') || str.includes('"notes"'))) {
            try {
                JSON.parse(str);
                window.__CML_INTERCEPTED_DATA = str;
                window.__CML_GOT_DATA = true;
            } catch(e) {}
        }
    }

    const _send = XMLHttpRequest.prototype.send;
    XMLHttpRequest.prototype.send = function(body) {
        this.__requestBody = body;

        try {
            if (typeof body === 'string') {
                verifyAndSave(body);
            } else if (typeof FormData !== 'undefined' && body instanceof FormData) {
                for (let pair of body.entries()) {
                    if (typeof pair[1] === 'string') {
                        verifyAndSave(pair[1]);
                    } else if (pair[1] instanceof Blob) {
                        pair[1].text().then(t => verifyAndSave(t));
                    }
                }
            } else if (body instanceof Blob || body instanceof File) {
                const r = new FileReader();
                r.onload = () => {
                    if (typeof r.result === 'string') verifyAndSave(r.result);
                };
                r.readAsText(body);
            } else if (body instanceof Uint8Array) {
                verifyAndSave(new TextDecoder().decode(body));
            }
        } catch(e) {}

        return _send.apply(this, arguments);
    };

    const _fetch = window.fetch;
    window.fetch = async function(...args) {
        try {
            const url = (typeof args[0] === 'string') ? args[0] : (args[0] && args[0].url);

            // XHR/Fetch mock router for injected generic IDs
            const maybeId = getCurrentSongId();
            if (maybeId && url.includes('/data/')) {
                console.log("CML-MOD: Intercepted FETCH for ID:", maybeId);
                const modData = localStorage.getItem("CML_INJECT_" + maybeId);
                if (modData) {
                    console.log("CML-MOD: Found localStorage data for injection.");
                    verifyAndSave(modData);
                    return new Response(modData, {
                        status: 200,
                        headers: { 'Content-Type': 'application/json' }
                    });
                }
            }

            // Fallback for older MOD_LOAD_ code paths
            if (url && typeof url === 'string' && (url.includes('MOD_LOAD_') || url.includes('MODLOAD') || url.includes('999999999'))) {
                const parts = url.split('/');
                const id = parts.find(p => p.startsWith('MOD_LOAD_') || p.startsWith('MODLOAD') || p.startsWith('999999999'));
                if (id) {
                    const rawId = id.split('?')[0];
                    console.log("CML-MOD: Intercepted FETCH for", rawId);
                    const data = localStorage.getItem(rawId);
                    if (data) {
                        verifyAndSave(data);
                        return new Response(data, {
                            status: 200,
                            headers: { 'Content-Type': 'application/json' }
                        });
                    }
                }
            }

            // Sniff outgoing POST bodies
            const opts = args[1];
            if (opts && opts.method && opts.method.toUpperCase() === 'POST' && opts.body) {
                if (typeof opts.body === 'string') {
                    verifyAndSave(opts.body);
                } else if (typeof FormData !== 'undefined' && opts.body instanceof FormData) {
                    for (let pair of opts.body.entries()) {
                        if (typeof pair[1] === 'string') verifyAndSave(pair[1]);
                        else if (pair[1] instanceof Blob) pair[1].text().then(t => verifyAndSave(t));
                    }
                } else if (opts.body instanceof Blob) {
                    opts.body.text().then(t => verifyAndSave(t));
                } else if (opts.body instanceof Uint8Array) {
                    verifyAndSave(new TextDecoder().decode(opts.body));
                }
            }
        } catch(e) {}

        // Execute original fetch and intercept its response to grab loaded songs
        return _fetch.apply(this, args).then(response => {
            try {
                const url = (typeof args[0] === 'string') ? args[0] : (args[0] && args[0].url);
                if (url && url.includes('/data/')) {
                    const clonedRes = response.clone();
                    clonedRes.text().then(text => verifyAndSave(text)).catch(e=>{});
                }
            } catch(e) {}
            return response;
        });
    };

    // Override XHR response to inject custom JSON payload
    const originalRText = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, 'responseText');
    if (originalRText) {
        Object.defineProperty(XMLHttpRequest.prototype, 'responseText', {
            get: function() {
                if (this.__mod_url) {
                    const maybeId = getCurrentSongId();
                    if (maybeId && this.__mod_url.includes('/data/')) {
                        const modData = localStorage.getItem("CML_INJECT_" + maybeId);
                        if (modData) {
                            console.log("CML-MOD: Intercepted XHR for ID:", maybeId);
                            verifyAndSave(modData);
                            return modData;
                        }
                    }
                }

                if (this.__mod_url && (this.__mod_url.includes('MOD_LOAD_') || this.__mod_url.includes('999999999'))) {
                    const parts = this.__mod_url.split('/');
                    const id = parts.find(p => p.startsWith('MOD_LOAD_') || p.startsWith('999999999'));
                    if (id) {
                        const rawId = id.split('?')[0];
                        console.log("CML-MOD: Intercepted XHR for", rawId);
                        const saved = localStorage.getItem(rawId);
                        if (saved) {
                            verifyAndSave(saved);
                            return saved;
                        }
                    }
                }
                return originalRText.get.call(this);
            }
        });
    }

    const _open = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function(method, url) {
        this.__mod_url = url;

        this.addEventListener('load', function() {
            // Also grab it when the save request completes just in case
            if (method.toUpperCase() === 'POST' && this.__requestBody) {
                 if (typeof this.__requestBody === 'string') {
                     verifyAndSave(this.__requestBody);
                 }
            } else if (method.toUpperCase() === 'GET' && this.__mod_url && this.__mod_url.includes('/data/')) {
                 if (typeof this.responseText === 'string') {
                     verifyAndSave(this.responseText);
                 }
            }
        });

        _open.apply(this, arguments);
    };

    // ==========================================
    // 2. SYNTH OVERRIDES
    // ==========================================
    window.__CML_CUSTOM_OSC_TYPE = "";
    const oStart = (window.OscillatorNode || window.webkitOscillatorNode).prototype.start;
    (window.OscillatorNode || window.webkitOscillatorNode).prototype.start = function(...args) {
        if (window.__CML_CUSTOM_OSC_TYPE) {
            try { this.type = window.__CML_CUSTOM_OSC_TYPE; } catch(e){}
        }
        oStart.apply(this, args);
    };

    // ==========================================
    // 3. UI INJECTION & MOD MENU
    // ==========================================
    window.addEventListener('load', () => {
        // Build style container
        const gui = document.createElement('div');
        gui.id = 'cml-mod-gui';

        gui.innerHTML = `
            <div id="cml-mod-header" style="background:linear-gradient(90deg, #14b8a6, #d946ef); color:#fff; padding:12px; cursor:move; font-weight:bold; display:flex; justify-content:space-between; align-items:center; border-radius:12px 12px 0 0; user-select: none;">
                <span style="letter-spacing:1px; text-shadow:0 1px 2px rgba(0,0,0,0.5);">CML PLUS MENU</span>
                <button id="cml-mod-toggle" style="background:transparent; border:none; color:#fff; font-weight:bold; cursor:pointer; font-size:18px;">─</button>
            </div>
            <div id="cml-mod-body" style="padding:16px; background:rgba(9, 9, 11, 0.95); backdrop-filter:blur(8px); border:1px solid #27272a; border-top:none; border-radius:0 0 12px 12px;">

                <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:15px; border-bottom:1px solid #27272a; padding-bottom:10px;">
                    <label style="color:#e4e4e7; font-size:14px; font-weight:600; cursor:pointer; display:flex; align-items:center; gap:8px;">
                        <input type="checkbox" id="cml-mod-dark" style="width:16px; height:16px; accent-color:#14b8a6;">
                        Dark Mode
                    </label>
                </div>

                <div style="margin-bottom: 15px;">
                    <label style="font-size:13px; color:#14b8a6; font-weight:bold; margin-bottom:6px; display:block;">🎹 Synth Waveform Override:</label>
                    <select id="cml-mod-synth" style="width:100%; padding:8px; background:#18181b; color:#14b8a6; border:1px solid #27272a; border-radius:6px; outline:none; font-family:inherit; cursor:pointer;">
                        <option value="">Default (No Override)</option>
                        <option value="sawtooth">🎸 Sawtooth (Aggressive/Lead)</option>
                        <option value="square">👾 Square (Chiptune 8-bit)</option>
                        <option value="triangle">☁️ Triangle (Soft Pad)</option>
                        <option value="sine">🌊 Sine (Pure/Sub)</option>
                    </select>
                </div>

                <div style="margin-bottom: 15px; background:#18181b; border:1px solid #27272a; border-radius:8px; padding:12px;">
                    <label style="font-size:13px; color:#d946ef; font-weight:bold; margin-bottom:8px; display:block;">🚀 Force Limit Injector</label>
                    <p style="font-size:11px; color:#a1a1aa; margin-bottom:10px; line-height:1.4;">Bypass internal limits seamlessly. Click the normal Save button first, then click these!</p>

                    <div style="display:grid; grid-template-columns:1fr 1fr; gap:6px;">
                        <button class="limit-btn" data-key="bars" data-val="64" style="background:#27272a; color:#fff; border:none; padding:6px; border-radius:4px; font-size:11px; cursor:pointer; font-weight:bold; transition:0.2s;">64 Bars</button>
                        <button class="limit-btn" data-key="bars" data-val="128" style="background:#27272a; color:#fff; border:none; padding:6px; border-radius:4px; font-size:11px; cursor:pointer; font-weight:bold; transition:0.2s;">128 Bars</button>
                        <button class="limit-btn" data-key="octaves" data-val="5" style="background:#27272a; color:#fff; border:none; padding:6px; border-radius:4px; font-size:11px; cursor:pointer; font-weight:bold; transition:0.2s;">5 Octaves</button>
                        <button class="limit-btn" data-key="octaves" data-val="10" style="background:#27272a; color:#fff; border:none; padding:6px; border-radius:4px; font-size:11px; cursor:pointer; font-weight:bold; transition:0.2s;">10 Octaves</button>
                    </div>

                    <div style="margin-top: 12px; padding-top: 12px; border-top: 1px solid #3f3f46;">
                        <p style="font-size:11px; color:#d946ef; margin-bottom:8px; font-weight:bold;">Custom Values</p>
                        <div style="display:flex; gap:6px; margin-bottom:6px;">
                            <input type="number" id="cml-custom-val-bars" placeholder="Len (Bars)" style="flex:1; width:50%; background:#18181b; color:#fff; border:1px solid #3f3f46; border-radius:4px; padding:4px 6px; font-size:11px;" min="1" max="500">
                            <button class="custom-val-btn" data-key="bars" style="flex:1; background:#14b8a6; color:#000; border:none; padding:4px 6px; border-radius:4px; font-size:11px; cursor:pointer; font-weight:bold;">Set Bars</button>
                        </div>
                        <div style="display:flex; gap:6px; margin-bottom:6px;">
                            <input type="number" id="cml-custom-val-octaves" placeholder="Octaves" style="flex:1; width:50%; background:#18181b; color:#fff; border:1px solid #3f3f46; border-radius:4px; padding:4px 6px; font-size:11px;" min="1" max="24">
                            <button class="custom-val-btn" data-key="octaves" style="flex:1; background:#14b8a6; color:#000; border:none; padding:4px 6px; border-radius:4px; font-size:11px; cursor:pointer; font-weight:bold;">Set Octaves</button>
                        </div>
                        <div style="display:flex; gap:6px;">
                            <input type="number" id="cml-custom-val-rootOctave" placeholder="Start Octave (e.g. 2)" title="Shifts all octaves down or up. Default is 4." style="flex:1; width:50%; background:#18181b; color:#fff; border:1px solid #3f3f46; border-radius:4px; padding:4px 6px; font-size:11px;" min="0" max="8">
                            <button class="custom-val-btn" data-key="rootOctave" style="flex:1; background:#14b8a6; color:#000; border:none; padding:4px 6px; border-radius:4px; font-size:11px; cursor:pointer; font-weight:bold;">Set Start Oct.</button>
                        </div>
                        <div style="display:flex; gap:6px; margin-top:6px;">
                            <input type="number" id="cml-custom-val-tempo" placeholder="Tempo (e.g. 500)" title="Can go beyond 240 or below 40!" style="flex:1; width:50%; background:#18181b; color:#fff; border:1px solid #3f3f46; border-radius:4px; padding:4px 6px; font-size:11px;">
                            <button class="custom-val-btn" data-key="tempo" style="flex:1; background:#14b8a6; color:#000; border:none; padding:4px 6px; border-radius:4px; font-size:11px; cursor:pointer; font-weight:bold;">Set Tempo</button>
                        </div>
                        <div style="margin-top:6px;">
                            <button id="cml-mod-unlock-tempo" style="width:100%; background:#71717a; color:#fff; border:none; padding:6px; border-radius:4px; font-size:11px; cursor:pointer; font-weight:bold; transition:0.2s;">🔓 Unlock Live Tempo Limits (1-3000)</button>
                        </div>
                    </div>
                    <div style="margin-top: 12px; padding-top: 12px; border-top: 1px solid #3f3f46;">
                        <p style="font-size:11px; color:#10b981; margin-bottom:8px; font-weight:bold;">Import MIDI / MP3</p>
                        <input type="file" id="cml-mod-audio-upload" accept=".mid,.midi,.mp3,.wav" style="font-size:11px; background:#18181b; color:#fff; width:100%; border:1px solid #3f3f46; border-radius:4px; padding:4px;" />
                        <button id="cml-mod-audio-process" style="width:100%; background:#10b981; color:#000; border:none; padding:6px; border-radius:4px; font-size:11px; cursor:pointer; font-weight:bold; margin-top:6px;">Process File to Song</button>
                        <p style="font-size:10px; color:#a1a1aa; margin-top:4px; line-height: 1.2;">MIDI perfectly supported. MP3 tries onset detection (experimental). Replaces current song.</p>
                    </div>
                </div>

                <div style="display:flex; gap:8px;">
                    <button id="cml-mod-export" style="flex:1; background:#14b8a6; color:#000; border:none; border-radius:6px; padding:10px; cursor:pointer; font-weight:bold; transition:all 0.2s;">
                        💾 Export JSON
                    </button>
                    <button id="cml-mod-import" style="flex:1; background:#d946ef; color:#fff; border:none; border-radius:6px; padding:10px; cursor:pointer; font-weight:bold; transition:all 0.2s;">
                        📂 Load JSON
                    </button>
                </div>
            </div>
        `;

        Object.assign(gui.style, {
            position: 'fixed',
            top: '20px',
            right: '20px',
            width: '300px',
            zIndex: '999999',
            fontFamily: '"Segoe UI", system-ui, sans-serif',
            boxShadow: '0 10px 30px rgba(0,0,0,0.5)',
            borderRadius: '12px',
            transition: 'opacity 0.2s'
        });

        document.body.appendChild(gui);

        // -- Draggable Logic --
        const header = document.getElementById('cml-mod-header');
        let isDragging = false, offsetX = 0, offsetY = 0;

        header.addEventListener('mousedown', (e) => {
            if (e.target.id === 'cml-mod-toggle') return;
            isDragging = true;
            offsetX = e.clientX - gui.getBoundingClientRect().left;
            offsetY = e.clientY - gui.getBoundingClientRect().top;
            gui.style.opacity = '0.8';
        });

        document.addEventListener('mousemove', (e) => {
            if (!isDragging) return;
            const x = e.clientX - offsetX;
            const y = e.clientY - offsetY;
            gui.style.left = Math.max(0, x) + 'px';
            gui.style.top = Math.max(0, y) + 'px';
            gui.style.right = 'auto';
        });

        document.addEventListener('mouseup', () => {
            if (isDragging) {
                isDragging = false;
                gui.style.opacity = '1';
            }
        });

        // -- Minimizable Logic --
        const toggleBtn = document.getElementById('cml-mod-toggle');
        const bodyWrap = document.getElementById('cml-mod-body');
        let collapsed = false;
        toggleBtn.addEventListener('click', () => {
            collapsed = !collapsed;
            bodyWrap.style.display = collapsed ? 'none' : 'block';
            toggleBtn.textContent = collapsed ? '+' : '─';
        });

        // -- Button Hovers --
        document.querySelectorAll('.limit-btn').forEach(b => {
             b.onmouseenter = () => b.style.background = '#3f3f46';
             b.onmouseleave = () => b.style.background = '#27272a';
        });

        // -- Data Modifier Function --
        const injectAndReload = (key, val) => {
            if (!window.__CML_INTERCEPTED_DATA) {
                alert("⚠️ Please click the standard Chrome Music Lab SAVE button at the bottom right FIRST. Wait a second, then try this again.");
                return;
            }
            try {
                const parts = window.location.pathname.split('/').filter(p => p.trim() !== '');
                let originalId = getCurrentSongId() || "CML_LOCAL_TEST";

                let data = JSON.parse(window.__CML_INTERCEPTED_DATA);
                data = getSafeData(data);

                console.log("CML-MOD: Injecting", key, "with val", val, "into data:", data);

                // Update the data, checking both root and options
                if (key === 'rootOctave') {
                    let ro = parseInt(val);
                    data.rootOctave = ro;
                    data.rootNote = ro * 12 + (data.rootPitch || 0);
                    if (data.options) {
                        data.options.rootOctave = ro;
                        data.options.rootNote = ro * 12 + (data.options.rootPitch || 0);
                    }
                } else {
                    data[key] = parseInt(val);
                    if (data.options) {
                        data.options[key] = parseInt(val);
                    }
                }

                console.log("CML-MOD: New data:", data);

                const strData = JSON.stringify(data);
                localStorage.setItem("CML_INJECT_" + originalId, strData);

                alert("✅ Injected! Reloading the grid with new injected limits...");
                window.location.reload();
            } catch(e) {
                console.error(e);
                alert("❌ Injection failed. Invalid grid data captured.");
            }
        };

        // -- UI Events --
        document.getElementById('cml-mod-dark').addEventListener('change', (e) => {
            const checked = e.target.checked;
            document.documentElement.style.filter = checked ? "invert(1) hue-rotate(180deg)" : "none";
            document.body.style.background = checked ? "#111" : "";
        });

        document.getElementById('cml-mod-synth').addEventListener('change', (e) => {
            window.__CML_CUSTOM_OSC_TYPE = e.target.value;
        });

        document.getElementById('cml-mod-unlock-tempo').addEventListener('click', () => {
            const inputNum = document.querySelector('input.input-number[name="tempo"]');
            const inputRange = document.querySelector('#tempo-slider input[type="range"]');
            if (inputNum) {
                inputNum.min = 1;
                inputNum.max = 3000;
            }
            if (inputRange) {
                inputRange.min = 1;
                inputRange.max = 3000;
            }
            if (inputNum || inputRange) {
                alert("🔓 UI Tempo limits removed! You can drag the slider or type freely (1 to 3000)\n\nNote: Visual bugs on the slider UI might occur, but the engine handles it.");
            } else {
                alert("⚠️ Couldn't find the tempo sliders on the page. Are you fully loaded?");
            }
        });

        document.querySelectorAll('.limit-btn').forEach(btn => {
             btn.addEventListener('click', (e) => {
                 injectAndReload(e.target.dataset.key, e.target.dataset.val);
             });
        });

        document.querySelectorAll('.custom-val-btn').forEach(btn => {
             btn.addEventListener('click', (e) => {
                 const key = e.target.dataset.key;
                 const inputEl = document.getElementById('cml-custom-val-' + key);
                 const val = inputEl.value;
                 if (val && !isNaN(val)) {
                     injectAndReload(key, val);
                 } else {
                     alert("⚠️ Please enter a valid number");
                 }
             });
        });

        document.getElementById('cml-mod-audio-process').addEventListener('click', async () => {
            const fileInput = document.getElementById('cml-mod-audio-upload');
            if (!fileInput.files.length) {
                alert("⚠️ Please select a file first.");
                return;
            }
            const file = fileInput.files[0];

            // Load Tonejs/Midi dynamically if needed
            if (!window.Midi) {
                await new Promise((resolve, reject) => {
                    const script = document.createElement('script');
                    script.src = "https://unpkg.com/@tonejs/midi";
                    script.onload = resolve;
                    script.onerror = reject;
                    document.head.appendChild(script);
                });
            }

            try {
                const arrayBuffer = await file.arrayBuffer();

                if (file.name.toLowerCase().endsWith('.mid') || file.name.toLowerCase().endsWith('.midi')) {
                    // --- MIDI PROCESSING ---
                    console.log("CML-MOD: Processing MIDI...");
                    const midi = new window.Midi(arrayBuffer);

                    if (midi.tracks.length === 0) {
                        alert("⚠️ No MIDI tracks found.");
                        console.log("CML-MOD: No MIDI tracks.");
                        return;
                    }
                    console.log("CML-MOD: MIDI tracks found:", midi.tracks.length);

                    let intercepted = window.__CML_INTERCEPTED_DATA ? getSafeData(JSON.parse(window.__CML_INTERCEPTED_DATA)) : getSafeData({});

                    let targetData = intercepted.options || intercepted;
                    targetData.scale = "chromatic";

                    const PPQ = midi.header.ppq || 480;
                    let lowestNote = 255;
                    let highestNote = 0;
                    let maxTicks = 0;

                    midi.tracks.forEach(track => {
                        track.notes.forEach(note => {
                            if (note.midi < lowestNote) lowestNote = note.midi;
                            if (note.midi > highestNote) highestNote = note.midi;
                            const endTick = note.ticks + note.durationTicks;
                            if (endTick > maxTicks) maxTicks = endTick;
                        });
                    });

                    if (lowestNote === 255) { lowestNote = 48; highestNote = 72; }

                    let rootMidi = lowestNote;
                    targetData.rootOctave = Math.floor(rootMidi / 12);
                    targetData.rootPitch = rootMidi % 12;
                    targetData.rootNote = rootMidi;

                    let octaveSpan = Math.ceil((highestNote - lowestNote + 1) / 12);
                    if (octaveSpan < 1) octaveSpan = 1;
                    targetData.octaves = octaveSpan;

                    const originalTempo = midi.header.tempos.length > 0 ? midi.header.tempos[0].bpm : 120;
                    targetData.tempo = Math.round(originalTempo);

                    let ticksPerBeat = PPQ;
                    let totalBeats = maxTicks / ticksPerBeat;
                    targetData.beats = 4;
                    let reqBars = Math.ceil(totalBeats / 4);
                    if (reqBars < 1) reqBars = 1;
                    targetData.bars = reqBars;

                    targetData.subdivision = 4;
                    const cmlNotes = [];
                    const ticksPerSubdivision = ticksPerBeat / targetData.subdivision;

                    midi.tracks.forEach(track => {
                        track.notes.forEach(note => {
                            const pitchIndex = note.midi - rootMidi;
                            const timeIndex = Math.round(note.ticks / ticksPerSubdivision);
                            cmlNotes.push(pitchIndex);
                            cmlNotes.push(timeIndex);
                        });
                    });

                    targetData.notes = cmlNotes;

                    const percussionTrack = midi.tracks.find(t => t.channel === 9);
                    if (percussionTrack) {
                        const cmlPerc = [];
                        percussionTrack.notes.forEach(note => {
                            const percIndex = note.midi < 40 ? 0 : 1;
                            const timeIndex = Math.round(note.ticks / ticksPerSubdivision);
                            cmlPerc.push(percIndex);
                            cmlPerc.push(timeIndex);
                        });
                        if (intercepted.options) {
                            intercepted.options.percussionNotes = cmlPerc;
                        } else {
                            intercepted.percussionNotes = cmlPerc;
                        }
                    }

                    finishInjection(intercepted, targetData);

                } else {
                    // --- AUDIO PROCESSING (MP3/WAV) ---
                    console.log("CML-MOD: Processing AUDIO...");
                    alert("⏳ Processing MP3/WAV. This is experimental and does monophonic pitch detection. Please wait...");
                    const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
                    console.log("CML-MOD: AudioContext created. Decoding...");
                    const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer);
                    console.log("CML-MOD: Audio decoded.");

                    // Basic parameters
                    const durationInSeconds = audioBuffer.duration;
                    const channelData = audioBuffer.getChannelData(0);
                    const sampleRate = audioBuffer.sampleRate;

                    // Very naive tempo guess = 120
                    const bpm = 120;
                    const beatsPerSecond = bpm / 60;
                    const totalBeats = Math.ceil(durationInSeconds * beatsPerSecond);

                    let intercepted = window.__CML_INTERCEPTED_DATA ? JSON.parse(window.__CML_INTERCEPTED_DATA) : {
                        bars: 4, beats: 4, subdivision: 4, octaves: 2, scale: "chromatic",
                        rootNote: 48, rootPitch: 0, rootOctave: 4, instrument: "marimba",
                        percussion: "electronic", percussionNotes: 2, tempo: bpm, notes: []
                    };

                    let targetData = intercepted.options || intercepted;
                    targetData.scale = "chromatic";
                    targetData.tempo = bpm;
                    targetData.subdivision = 4;
                    targetData.beats = 4;
                    targetData.bars = Math.max(1, Math.ceil(totalBeats / 4));

                    const cmlNotes = [];
                    const timePerSubdiv = 60 / bpm / targetData.subdivision;
                    const samplesPerSubdiv = Math.floor(timePerSubdiv * sampleRate);

                    let minNote = 1000, maxNote = -1;

                    // Simple Yin/auto-correlation for pitch detection per subdivision
                    for (let i = 0; i < targetData.bars * targetData.beats * targetData.subdivision; i++) {
                        let offset = i * samplesPerSubdiv;
                        if (offset + samplesPerSubdiv > channelData.length) break;

                        let chunk = channelData.slice(offset, offset + samplesPerSubdiv);

                        // RMS Energy to ignore silence
                        let rms = 0;
                        for(let j=0; j<chunk.length; j++) rms += chunk[j]*chunk[j];
                        rms = Math.sqrt(rms / chunk.length);

                        if (rms > 0.05) { // Threshold
                            // Autocorrelation to find fundamental freq
                            let bestLag = 0;
                            let bestCorr = 0;
                            // Search freq from 60Hz to 1000Hz -> lags from sampleRate/1000 to sampleRate/60
                            let minLag = Math.floor(sampleRate / 1000);
                            let maxLag = Math.floor(sampleRate / 60);
                            for (let lag = minLag; lag < maxLag; lag++) {
                                let corr = 0;
                                for (let j = 0; j < chunk.length - lag; j++) {
                                    corr += chunk[j] * chunk[j + lag];
                                }
                                if (corr > bestCorr) {
                                    bestCorr = corr;
                                    bestLag = lag;
                                }
                            }
                            if (bestLag > 0 && (bestCorr / rms) > 10) { // arbitrary confidence
                                let freq = sampleRate / bestLag;
                                let midiNote = Math.round(69 + 12 * Math.log2(freq / 440));
                                if (midiNote >= 21 && midiNote <= 108) {
                                    cmlNotes.push({ midi: midiNote, time: i });
                                    if (midiNote < minNote) minNote = midiNote;
                                    if (midiNote > maxNote) maxNote = midiNote;
                                }
                            }
                        }
                    }

                    if (cmlNotes.length === 0) {
                        alert("⚠️ Audio detected as silent or couldn't find pitches.");
                        return;
                    }

                    targetData.rootOctave = Math.floor(minNote / 12);
                    targetData.rootPitch = minNote % 12;
                    targetData.rootNote = minNote;
                    targetData.octaves = Math.max(1, Math.ceil((maxNote - minNote + 1) / 12));

                    const finalNotes = [];
                    for(let n of cmlNotes) {
                        finalNotes.push(n.midi - minNote); // pitch index
                        finalNotes.push(n.time);           // time index
                    }

                    targetData.notes = finalNotes;
                    finishInjection(intercepted, targetData);
                }

                function finishInjection(intercepted, targetData) {
                    console.log("CML-MOD: finishInjection called.");
                    let finalJson = intercepted;
                    if (window.__CML_INTERCEPTED_DATA && finalJson.options) {
                        finalJson.options = targetData;
                    } else {
                        finalJson = targetData;
                    }
                    const jsonStr = JSON.stringify(finalJson);
                    console.log("CML-MOD: Saving JSON to localStorage.");
                    verifyAndSave(jsonStr);

                    // Reload the page logic
                    const parts = window.location.pathname.split('/').filter(p => p.trim() !== '');
                    let originalId = parts[parts.length - 1];
                    if (!originalId || originalId === '' || originalId.toLowerCase() === 'song-maker' || originalId.toLowerCase() === 'song') {
                        originalId = "CML_LOCAL_TEST";
                        window.history.pushState({}, '', '/Song-Maker/song/' + originalId);
                    }

                    console.log("CML-MOD: Setting localStorage key for ID:", originalId);
                    localStorage.setItem("CML_INJECT_" + originalId, jsonStr);
                    alert("✅ Imported! Playing... It might need a reload. To properly save, click the official Save button in the UI.");
                    window.location.reload();
                }

            } catch (err) {
                console.error(err);
                alert("⚠️ Error processing file: " + err.message);
            }
        });
        document.getElementById('cml-mod-export').addEventListener('click', () => {
            if (window.__CML_INTERCEPTED_DATA) {
                navigator.clipboard.writeText(window.__CML_INTERCEPTED_DATA).then(() => {
                    alert("✅ Successfully copied raw song JSON to clipboard!");
                }).catch(() => {
                    const area = document.createElement("textarea");
                    area.value = window.__CML_INTERCEPTED_DATA;
                    document.body.appendChild(area);
                    area.select();
                    document.execCommand("copy");
                    document.body.removeChild(area);
                    alert("✅ Successfully copied raw song JSON to clipboard using fallback.");
                });
            } else {
                alert("⚠️ Intercept Empty: Click the main standard 'Save' checkmark button FIRST to capture your data, then click Export.");
            }
        });

        document.getElementById('cml-mod-import').addEventListener('click', () => {
            const pasted = prompt("Paste your exported JSON here:");
            if (pasted && (pasted.includes('"tempo"') || pasted.includes('"bars"'))) {
                let originalId = getCurrentSongId();
                if (!originalId) {
                    alert("⚠️ To load JSON, you first need to be on a saved song's page. (Click save and open the link)");
                    return;
                }
                localStorage.setItem("CML_INJECT_" + originalId, pasted);
                alert("✅ Loaded! Reloading...");
                window.location.reload();
            } else if (pasted) {
                alert("❌ Invalid JSON format.");
            }
        });

    });

})();