// =====================================================================
//  setup.scd  --  Verification d'etat + init complete du projet
//
//  USAGE PRINCIPAL (workflow recommande) :
//    Tout est fait depuis 00_load.scd > bloc "CHARGER TOUT". Ce fichier
//    est charge automatiquement par le loader, qui appelle ~setupAll.()
//    a la fin. Tu n'as PAS besoin de l'ouvrir manuellement en general.
//
//  USAGE MANUEL (debug ou redemarrage rapide) :
//    Ouvre ce fichier dans l'IDE puis Cmd+Entree sur un bloc :
//      ( ~check.() )       -- rapport d'etat colore
//      ( ~setupAll.() )    -- recharge TOUT (engine + live + ...)
//      ( ~setupLive.() )   -- recharge UNIQUEMENT live/* (rapide)
//      ( ~ensureLive.() )  -- charge ce qui manque seulement
//      ( ~teardown.() )    -- stop tout proprement
//
//  HELPERS EXPOSES (tous definis dans le 1er bloc -- .load-compatible) :
//    ~check                  status global colore
//    ~setupAll               charge engine + synthdefs + scales + melodies
//                            + harmonics + boot serveur + setup live + check
//    ~setupLive              recharge UNIQUEMENT les modules live (rapide)
//    ~ensureLive             charge live/* paresseusement (seulement absents)
//    ~teardown               stop tout proprement
//    ~loadFirstBlock(path)   helper interne -- evalue le 1er bloc d'un .scd
//
//  STRUCTURE
//    Le 1er bloc top-level contient TOUS les helpers : c'est le SETUP
//    HELPERS, evaluable via .load setup.scd (ce que fait CHARGER TOUT).
//    Les blocs suivants sont des exemples Cmd+Entree (pas charges par
//    .load, evaluables manuellement dans l'IDE).
// =====================================================================


// =====================================================================
//  SETUP HELPERS (1er bloc top-level -- .load-compatible)
//
//  Definit ~base, ~loadFirstBlock, ~check, ~setupAll, ~setupLive,
//  ~ensureLive, ~teardown. Ce bloc est execute par .load setup.scd
//  depuis le bloc CHARGER TOUT de 00_load.scd.
// =====================================================================
(
// --- BASE PATH (auto-detection si pas deja defini) -------------------
~base = ~base ?? { "/Users/electron/Documents/Projets_Creatifs/sound_algo-refonte-v2/" };
"~base = ".post; ~base.postln;


// --- HELPER : ~loadFirstBlock ---------------------------------------
//   Lit un fichier .scd et evalue son PREMIER bloc top-level (...) en
//   comptant les parens balancees. Sert pour les fichiers multi-blocs
//   dont seul le 1er bloc est le SETUP a evaluer (live/*, jump.scd, ...).
//
//   Robuste : try/protect pour qu'une erreur d'un fichier n'arrete pas
//   les autres. Print clair de chaque etape ([setupAll] ... OK / FAIL).
// --------------------------------------------------------------------
~loadFirstBlock = { |path|
    var src, depth = 0, start = nil, firstBlock = nil;
    var label = path.basename;
    var i = 0, n, ch, ch2;
    var inLineComment = false, inBlockComment = false, inString = false;
    if (File.exists(path).not) {
        ("[setupAll] " ++ label ++ " ... FAIL (fichier introuvable)").postln;
    } {
        src = File.readAllString(path);
        n = src.size;
        block { |break|
            while { i < n } {
                ch = src[i];
                ch2 = if (i + 1 < n) { src[i + 1] } { $  };
                // ----- gestion des etats -----
                if (inLineComment) {
                    if (ch == $\n) { inLineComment = false };
                    i = i + 1;
                } {
                if (inBlockComment) {
                    if ((ch == $*) and: { ch2 == $/ }) { inBlockComment = false; i = i + 2 }
                    { i = i + 1 };
                } {
                if (inString) {
                    if (ch == $\\) { i = i + 2 }       // skip escape
                    { if (ch == $") { inString = false; i = i + 1 } { i = i + 1 } };
                } {
                    // ----- pas dans un comment / string -----
                    // entree commentaire ligne
                    if ((ch == $/) and: { ch2 == $/ }) { inLineComment = true; i = i + 2 }
                    // entree commentaire bloc
                    { if ((ch == $/) and: { ch2 == $* }) { inBlockComment = true; i = i + 2 }
                    // char literal SC : $( ou $) ou $" -- on saute le caractere suivant
                    { if ((ch == $$) and: { (ch2 == $() or: { (ch2 == $)) or: { ch2 == $" } } }) { i = i + 2 }
                    // entree string
                    { if (ch == $") { inString = true; i = i + 1 }
                    // parens REELLES
                    { if (ch == $() {
                        if (depth == 0) { start = i };
                        depth = depth + 1;
                        i = i + 1;
                    } { if (ch == $)) {
                        depth = depth - 1;
                        if ((depth == 0) and: { start.notNil }) {
                            firstBlock = src.copyRange(start, i);
                            break.value;
                        };
                        i = i + 1;
                    } { i = i + 1 } } } } } };
                } } };
            };
        };
        if (firstBlock.isNil) {
            ("[setupAll] " ++ label ++ " ... FAIL (aucun bloc trouve)").postln;
        } {
            ("[setupAll] " ++ label ++ " ... loading [" ++ firstBlock.size ++ " chars]").post;
            try {
                firstBlock.interpret;
                "  OK".postln;
            } { |err|
                "  FAIL".postln;
                ("    " ++ err.errorString).postln;
            };
        };
    };
};


// --- HELPER : ~loadAllBlocks ----------------------------------------
//   Comme ~loadFirstBlock mais charge TOUS les blocs top-level d'un
//   fichier. Utile pour fichiers comme sequences.scd qui ont leurs
//   helpers en bloc 1 puis ~seqIntro/Buildup/Drop en blocs separes.
//   Ignore comments / strings / char literals.
// --------------------------------------------------------------------
~loadAllBlocks = { |path|
    var src, depth = 0, start = nil, blocks = List.new;
    var label = path.basename;
    var i = 0, n, ch, ch2;
    var inLineComment = false, inBlockComment = false, inString = false;
    if (File.exists(path).not) {
        ("[setupAll] " ++ label ++ " ... FAIL (fichier introuvable)").postln;
    } {
        src = File.readAllString(path);
        n = src.size;
        while { i < n } {
            ch = src[i];
            ch2 = if (i + 1 < n) { src[i + 1] } { $  };
            if (inLineComment) {
                if (ch == $\n) { inLineComment = false }; i = i + 1;
            } {
            if (inBlockComment) {
                if ((ch == $*) and: { ch2 == $/ }) { inBlockComment = false; i = i + 2 } { i = i + 1 };
            } {
            if (inString) {
                if (ch == $\\) { i = i + 2 } { if (ch == $") { inString = false; i = i + 1 } { i = i + 1 } };
            } {
                if ((ch == $/) and: { ch2 == $/ }) { inLineComment = true; i = i + 2 }
                { if ((ch == $/) and: { ch2 == $* }) { inBlockComment = true; i = i + 2 }
                { if ((ch == $$) and: { (ch2 == $() or: { (ch2 == $)) or: { ch2 == $" } } }) { i = i + 2 }
                { if (ch == $") { inString = true; i = i + 1 }
                { if (ch == $() {
                    if (depth == 0) { start = i };
                    depth = depth + 1; i = i + 1;
                } { if (ch == $)) {
                    depth = depth - 1;
                    if ((depth == 0) and: { start.notNil }) {
                        blocks.add(src.copyRange(start, i));
                        start = nil;
                    };
                    i = i + 1;
                } { i = i + 1 } } } } } };
            } } };
        };
        ("[setupAll] " ++ label ++ " ... " ++ blocks.size ++ " blocs ").post;
        try {
            blocks.do { |b| b.interpret };
            "  OK".postln;
        } { |err|
            "  FAIL".postln;
            ("    " ++ err.errorString).postln;
        };
    };
};


// --- HELPER : ~check ------------------------------------------------
//   Affiche un rapport coloree (postln) listant l'etat de chaque
//   module. Utilise des prefixes ASCII :
//     [OK] = tout est la
//     [!!] = certains helpers manquent (action recommandee)
//     [--] = pas initialise (optionnel : MIDI, jump)
// --------------------------------------------------------------------
~check = {
    var sd = SynthDescLib.global.synthDescs;
    var hasSd = { |sym| sd[sym].notNil };
    var hasV  = { |sym| currentEnvironment[sym].notNil };
    var allSd = { |list| list.every { |s| hasSd.(s) } };
    var allV  = { |list| list.every { |s| hasV.(s) } };
    var missingSd = { |list| list.select { |s| hasSd.(s).not } };
    var missingV  = { |list| list.select { |s| hasV.(s).not } };
    var actions = List.new;
    var line = { |label, status, msg|
        ("[" ++ label ++ "]").padRight(13).post;
        status.post; "  ".post; msg.postln;
    };

    "".postln;
    "=== sound_algo STATUS ===".postln;

    // ----- Server -----
    if (s.serverRunning) {
        ("[server]     running, " ++ s.numSynths ++ " synths, "
            ++ s.avgCPU.round(0.1) ++ "% CPU").postln;
    } {
        "[server]     [--]  pas demarre  (lance ~setupAll ou s.boot)".postln;
        actions.add("Lance ~setupAll.() pour booter le serveur");
    };

    // ----- engine.scd -----
    block { |b|
        var engineSd = [\kick, \hat, \snare, \clap, \acid, \lead, \pad, \pluck,
                        \fmBell, \stab, \saw3, \openHat, \crash, \tom, \rim,
                        \cowbell, \reese, \bass808, \impact, \riser, \sweep,
                        \vinyl, \masterLim];
        var engineHelpers = [\reload, \bpm, \kickAmp, \melodyAmp, \melodyNotes,
                             \kickSteps, \scBus, \masterLim];
        var miss = missingSd.(engineSd) ++ missingV.(engineHelpers);
        if (miss.isEmpty) {
            line.("engine", "[OK]", engineSd.size.asString
                ++ " SynthDefs + ~reload + ~scBus + ~masterLim");
        } {
            line.("engine", "[!!]", "manquant : " ++ miss);
            actions.add("CHARGER TOUT depuis 00_load.scd (engine + synthdefs + scales + melodies + harmonics)");
        };
    };

    // ----- synth/_index.scd : asia palette -----
    block { |b|
        var asia = [\koto, \erhu, \pipa, \gong, \growl, \reeseHard];
        var miss = missingSd.(asia);
        if (miss.isEmpty) { line.("asia", "[OK]", asia.size.asString ++ " SynthDefs"); }
        { line.("asia", "[!!]", "manquant : " ++ miss); };
    };

    // ----- synth/_index.scd : extra palette -----
    block { |b|
        var extra = [\squareLead, \fmLead, \organLead, \hardLead, \warmPad,
                     \strings, \choir, \fmBass, \subBoom, \handpan, \flute, \drone];
        var miss = missingSd.(extra);
        if (miss.isEmpty) { line.("extra", "[OK]", extra.size.asString ++ " SynthDefs"); }
        { line.("extra", "[!!]", "manquant : " ++ miss); };
    };

    // ----- synth/_index.scd : authentic palette -----
    block { |b|
        var auth = [\kickGabber, \kick808, \snareGated, \reeseDnB, \supersaw,
                    \hooverbass, \rhodes, \vocalPad, \didgeridoo, \guzheng,
                    \shakuhachi, \taiko, \tabla, \scratch];
        var miss = missingSd.(auth);
        if (miss.isEmpty) { line.("authentic", "[OK]", auth.size.asString ++ " SynthDefs"); }
        { line.("authentic", "[!!]", "manquant : " ++ miss); };
    };

    // ----- palette/scales.scd -----
    block { |b|
        var scales = [\scaleMinor, \scaleDorian, \scaleMinorPent, \scaleAcid,
                      \scaleHijaz, \scaleVietnam];
        var helpers = [\genMelody, \mutate, \octavate, \transpose, \retrograde,
                       \densify, \sparsify, \jitter, \genAcidNotes, \genKickPattern,
                       \randomTweak];
        var miss = missingV.(scales) ++ missingV.(helpers);
        if (miss.isEmpty) {
            line.("scales", "[OK]", "16 scales + ~genMelody/mutate/octavate/...");
        } {
            line.("scales", "[!!]", "manquant : " ++ miss);
            actions.add("CHARGER TOUT (scales = source unique helpers melodiques)");
        };
    };

    // ----- palette/melodies/* -----
    block { |b|
        var probes = [\m1, \m78, \ml1, \ml30, \mxl1, \mxl15, \mepic1, \mepic10,
                      \melodyBank, \useMelody, \melodyByGenre, \chainPatterns, \buildArc];
        var miss = missingV.(probes);
        if (miss.isEmpty) {
            line.("melodies", "[OK]",
                "~m1..78, ~ml1..30, ~mxl1..15, ~mepic1..10 + ~useMelody/byGenre");
        } {
            line.("melodies", "[!!]", "manquant : " ++ miss);
            actions.add("CHARGER TOUT (charge palette/melodies/index.scd)");
        };
    };

    // ----- palette/harmonics/* -----
    block { |b|
        var probes = [\enableHarmony, \genThirdHarmony, \genFifthHarmony,
                      \genCounterMelody, \genArpFromChords];
        var miss = missingV.(probes);
        if (miss.isEmpty) {
            line.("harmonics", "[OK]", "~enableHarmony + 4 helpers");
        } {
            line.("harmonics", "[!!]", "manquant : " ++ miss);
            actions.add("CHARGER TOUT (charge palette/harmonics/index.scd)");
        };
    };

    // ----- palette/melodies/tracks.scd (multi-pistes) -----
    block { |b|
        var probes = [\mel, \melTrackAdd, \melTrackOn, \melTrackOff, \melTrackSet,
                      \melTrackList, \melTrackSolo];
        var miss = missingV.(probes);
        if (miss.isEmpty) {
            line.("mel-track", "[OK]", "~mel + ~melTrackAdd/On/Off/Set/...");
        } {
            line.("mel-track", "[!!]", "manquant : " ++ miss);
        };
    };

    // ----- live API wrappers (~kk / ~mm / ~ff / ~cc / ~p) -----
    //   Ces wrappers (live/_load.scd) remplacent fonctionnellement les
    //   ~kK1, ~clC1, ~pcP1 des palette/rhythm/* (qui sont multi-blocs).
    block { |b|
        var probes = [\kk, \kPat, \kTim, \kList,
                      \mm, \mShort, \mLong, \mEpic, \mGen, \mList,
                      \ff, \fxSnd, \fxSt, \fxDrop, \fxList,
                      \chIntro, \chDrop, \chBuildup, \chList,
                      \pKit, \pGenre, \pHat, \pList];
        var miss = missingV.(probes);
        if (miss.isEmpty) {
            line.("live-api", "[OK]", "~kk ~mm ~ff ~chIntro ~pKit + 19 wrappers");
        } {
            line.("live-api", "[!!]", "manquant : " ++ miss);
            actions.add("Lance (~base++\"live/_load.scd\").load (charge les 5 wrappers live)");
        };
    };

    // ----- palette/rhythm/fills.scd (~snareFill / ~tomFill) -----
    block { |b|
        var probes = [\snareFill, \tomFill];
        var miss = missingV.(probes);
        if (miss.isEmpty) {
            line.("fills", "[OK]", "~snareFill / ~tomFill");
        } {
            line.("fills", "[!!]", "~snareFill et/ou ~tomFill manquent");
            actions.add("Lance ~setupAll.() (charge palette/rhythm/fills.scd)");
        };
    };

    // ----- palette/rhythm/sequences.scd (~seqPlay / ~seqIntro / ...) -----
    block { |b|
        var probes = [\seqPlay, \seqStop, \seqIntro, \seqBuildup, \seqDrop];
        var miss = missingV.(probes);
        if (miss.isEmpty) {
            line.("sequences", "[OK]", "~seqPlay / ~seqIntro / ~seqBuildup / ~seqDrop / ...");
        } {
            line.("sequences", "[!!]", "manquant : " ++ miss);
            actions.add("Lance ~setupAll.() (charge palette/rhythm/sequences.scd)");
        };
    };

    // ----- palette/chords_progressions.scd -----
    block { |b|
        var probes = [\progDetroit, \progTrance, \progDnB, \chordsToMelody];
        var miss = missingV.(probes);
        if (miss.isEmpty) {
            line.("chords", "[OK]", "~progXxx + ~chordsToMelody/Harmony");
        } {
            line.("chords", "[--]", "non charge (optionnel) : " ++ miss);
        };
    };

    // ----- live/live.scd -----
    block { |b|
        var probes = [\lfoTo, \vcfOn, \drop, \breakdown, \stutter, \glitch,
                      \freeze, \addFx, \compOn, \satOn];
        var miss = missingV.(probes);
        if (miss.isEmpty) {
            line.("live", "[OK]", "~lfoTo / ~vcfOn / ~drop / ~stutter / ~addFx / ~compOn / ...");
        } {
            line.("live", "[!!]", "manquant : " ++ miss);
            actions.add("Lance ~setupAll.() (charge live/live.scd)");
        };
    };

    // ----- live/mixer.scd -----
    block { |b|
        var probes = [\setVol, \mute, \solo, \mixPreset, \mixMaster, \mix];
        var miss = missingV.(probes);
        if (miss.isEmpty) {
            line.("mixer", "[OK]", "~setVol / ~mute / ~solo / ~mixPreset / ~mixMaster");
        } {
            line.("mixer", "[!!]", "manquant : " ++ miss);
            actions.add("Lance ~setupAll.() (charge live/mixer.scd)");
        };
    };

    // ----- live/fx_bus.scd -----
    block { |b|
        var probes = [\fxFx, \fxBuses, \send, \setFx, \lfo, \fxPreset, \tap];
        var miss = missingV.(probes);
        if (miss.isEmpty) {
            line.("fx_bus", "[OK]", "9 bus FX + ~send / ~setFx / ~lfo / ~fxPreset / ~tap");
        } {
            line.("fx_bus", "[!!]", "manquant : " ++ miss);
            actions.add("Lance ~setupAll.() (charge live/fx_bus.scd EN PREMIER)");
        };
    };

    // ----- live/scenes.scd -----
    block { |b|
        var probes = [\saveScene, \loadScene, \listScenes];
        var miss = missingV.(probes);
        if (miss.isEmpty) {
            line.("scenes", "[OK]", "~saveScene / ~loadScene / ~listScenes");
        } {
            line.("scenes", "[!!]", "manquant : " ++ miss);
            actions.add("Lance ~setupAll.() (charge live/scenes.scd)");
        };
    };

    // ----- live/loops.scd -----
    block { |b|
        var probes = [\loopOn, \loopOff, \loopRepeat, \loopProgress, \loopList];
        var miss = missingV.(probes);
        if (miss.isEmpty) {
            line.("loops", "[OK]", "~loopOn / ~loopOff / ~loopRepeat / ~loopProgress");
        } {
            line.("loops", "[!!]", "manquant : " ++ miss);
            actions.add("Lance ~setupAll.() (charge live/loops.scd)");
        };
    };

    // ----- live/memory_guard.scd -----
    block { |b|
        var probes = [\killAllOrphans, \memReport, \watchdogOn, \watchdogOff];
        var miss = missingV.(probes);
        if (miss.isEmpty) {
            line.("guard", "[OK]", "~killAllOrphans / ~memReport / ~watchdogOn / ~watchdogOff");
        } {
            line.("guard", "[!!]", "manquant : " ++ miss);
            actions.add("Lance ~setupAll.() (charge live/memory_guard.scd)");
        };
    };

    // ----- control/midi.scd (optionnel) -----
    block { |b|
        if (~midiFuncs.notNil and: { ~midiFuncs.size > 0 }) {
            line.("midi", "[OK]", "~midiFuncs (" ++ ~midiFuncs.size ++ " mappings)");
        } {
            line.("midi", "[--]", "pas init (optionnel) -- ouvre control/midi.scd");
        };
    };

    // ----- control/playlist.scd (optionnel) -----
    block { |b|
        if (hasV.(\playPlaylist)) {
            line.("playlist", "[OK]", "~playPlaylist pret");
        } {
            line.("playlist", "[--]", "pas charge (optionnel) -- ouvre control/playlist.scd");
        };
    };

    // ----- control/jump.scd -----
    block { |b|
        if (hasV.(\jumpTo) and: { hasV.(\sections) }) {
            var nbTracks = ~sections.size;
            line.("jump", "[OK]", "~jumpTo + ~sections (" ++ nbTracks ++ " tracks chargees)");
        } {
            line.("jump", "[!!]", "pas init");
            actions.add("Lance ~setupAll.() (charge control/jump.scd)");
        };
    };

    "=== fin status ===".postln;

    if (actions.size > 0) {
        "".postln;
        "ACTIONS RECOMMANDEES :".postln;
        actions.do { |a| ("  - " ++ a).postln };
    } {
        "".postln;
        "Tout est OK -- pret a jouer.".postln;
    };
    "".postln;
};


// --- HELPER : ~setupAll ---------------------------------------------
//   Charge engine + synthdefs + scales + melodies + harmonics, attend
//   le boot du serveur (skip si deja boote), puis evalue le 1er bloc
//   (SETUP) de chaque module live + rhythm/fills + rhythm/sequences +
//   control/jump. Termine par ~check.()
//
//   Print clair de chaque etape via ~loadFirstBlock (qui catche les
//   erreurs : un fichier qui plante n'arrete PAS les autres).
//
//   Ordre d'init des modules live :
//     1. fx_bus.scd            (alloue les bus FX, doit etre en PREMIER)
//     2. live.scd              (LFO/VCF/tricks/SynthDefs additionnels)
//     3. mixer.scd             (~mix / ~setVol / ~mute / ~solo)
//     4. scenes.scd            (~saveScene / ~loadScene)
//     5. loops.scd             (~loopOn / ~loopOff / ~loopRepeat)
//     6. rhythm/fills.scd      (~snareFill / ~tomFill)
//     7. rhythm/sequences.scd  (~seqPlay / ~seqIntro / ~seqDrop / ...)
//     8. control/jump.scd      (~jumpTo + ~sections)
//
//   PAS init automatiquement :
//     - control/midi.scd       (init MIDI -- a faire manuellement)
//     - control/playlist.scd   (juste un helper, charge a la demande)
//     - live/tweaks.scd        (one-liners, pas de SETUP)
// --------------------------------------------------------------------
~setupAll = {
    var liveSetup = {
        "".postln;
        "=== ~setupAll : init modules live ===".postln;

        // Ordre critique : fx_bus.scd EN PREMIER (alloue ~fxBuses / ~fxFx
        // que les autres modules peuvent referencer)
        ~loadFirstBlock.(~base ++ "live/fx_bus.scd");
        ~loadFirstBlock.(~base ++ "live/live.scd");
        ~loadFirstBlock.(~base ++ "live/mixer.scd");
        ~loadFirstBlock.(~base ++ "live/scenes.scd");
        ~loadFirstBlock.(~base ++ "live/loops.scd");
        ~loadFirstBlock.(~base ++ "live/memory_guard.scd");
        (~base ++ "palette/rhythm/_index.scd").load;  // fills + sequences (auto-discovery)
        ~loadAllBlocks.(~base ++ "palette/chords_progressions.scd");
        ~loadFirstBlock.(~base ++ "control/jump.scd");

        // 5 wrappers API live (~kk / ~mm / ~ff / ~cc / ~p) -- 1er bloc
        // chacun contient les definitions ; les blocs suivants sont des
        // exemples interactifs Cmd+Entree qu'on ne veut PAS executer au boot.
        ~loadFirstBlock.(~base ++ "live/kicks.scd");
        ~loadFirstBlock.(~base ++ "live/melodies.scd");
        ~loadFirstBlock.(~base ++ "live/fx.scd");
        ~loadFirstBlock.(~base ++ "live/chains.scd");
        ~loadFirstBlock.(~base ++ "live/patterns.scd");

        s.sync;

        // Hook CmdPeriod : chaque Cmd-. declenche aussi ~killAllOrphans
        // pour eviter l'accumulation de Routines/Bus apres un panic stop.
        ~guardCmdPeriodOn !? { ~guardCmdPeriodOn.value };

        "".postln;
        "=== setupAll DONE ===".postln;
        "".postln;
        ~check.value;
    };

    "=== ~setupAll : start ===".postln;
    ~base = ~base ?? { "/Users/electron/Documents/Projets_Creatifs/sound_algo-refonte-v2/" };

    // --- 1. Charge tous les fichiers .load-compatibles ---
    "[setupAll] engine.scd ... loading".postln;
    (~base ++ "engine.scd").load;
    "[setupAll] synth/_index.scd ... loading".postln;
    (~base ++ "synth/_index.scd").load;
    "[setupAll] fx/_index.scd ... loading".postln;
    (~base ++ "fx/_index.scd").load;
    "[setupAll] palette/scales.scd ... loading".postln;
    (~base ++ "palette/scales.scd").load;
    "[setupAll] palette/melodies/index.scd ... loading".postln;
    (~base ++ "palette/melodies/index.scd").load;
    "[setupAll] palette/harmonics/index.scd ... loading".postln;
    (~base ++ "palette/harmonics/index.scd").load;

    // --- 2. Boot serveur (skip si deja boote) puis init modules live ---
    // Augmente maxSynthDefs (defaut 1024) car le projet en charge >1100
    // (1060 dans synth/ + ~40 dans fx/ + inline). A passer en option scsynth
    // AVANT s.boot, sinon les SynthDef au-dela de 1024 echouent
    // silencieusement avec "Could not add SynthDef X".
    s.options.maxSynthDefs = 4096;
    if (s.serverRunning) {
        "=== ~setupAll : serveur deja boote, skip waitForBoot ===".postln;
        liveSetup.value;
    } {
        "=== ~setupAll : boot serveur en cours... ===".postln;
        s.waitForBoot {
            s.sync;
            "=== ~setupAll : boot OK ===".postln;
            liveSetup.value;
        };
    };
};


// --- HELPER : ~setupLive --------------------------------------------
//   Charge UNIQUEMENT les modules live (ne touche pas engine /
//   synthdefs / scales / melodies / harmonics). Pour redemarrage
//   rapide quand le serveur tourne deja avec engine charge.
// --------------------------------------------------------------------
~setupLive = {
    ~base = ~base ?? { "/Users/electron/Documents/Projets_Creatifs/sound_algo-refonte-v2/" };
    if (s.serverRunning.not) {
        "!! ~setupLive : serveur pas demarre. Lance ~setupAll a la place.".postln;
    } {
        "=== ~setupLive : init modules live seulement ===".postln;
        ~loadFirstBlock.(~base ++ "live/fx_bus.scd");
        ~loadFirstBlock.(~base ++ "live/live.scd");
        ~loadFirstBlock.(~base ++ "live/mixer.scd");
        ~loadFirstBlock.(~base ++ "live/scenes.scd");
        ~loadFirstBlock.(~base ++ "live/loops.scd");
        ~loadFirstBlock.(~base ++ "live/memory_guard.scd");
        (~base ++ "palette/rhythm/_index.scd").load;  // fills + sequences (auto-discovery)
        ~loadAllBlocks.(~base ++ "palette/chords_progressions.scd");
        ~loadFirstBlock.(~base ++ "control/jump.scd");
        // 5 wrappers API live (~kk / ~mm / ~ff / ~cc / ~p)
        ~loadFirstBlock.(~base ++ "live/kicks.scd");
        ~loadFirstBlock.(~base ++ "live/melodies.scd");
        ~loadFirstBlock.(~base ++ "live/fx.scd");
        ~loadFirstBlock.(~base ++ "live/chains.scd");
        ~loadFirstBlock.(~base ++ "live/patterns.scd");
        "=== ~setupLive : OK ===".postln;
        ~check.value;
    };
};


// --- HELPER : ~ensureLive -------------------------------------------
//   Helper paresseux : detecte ce qui manque dans live/* + fills +
//   sequences + jump et charge SEULEMENT les modules absents.
//   Idempotent : peut etre appele plusieurs fois sans rien recharger.
// --------------------------------------------------------------------
~ensureLive = {
    var loaded = 0;
    ~base = ~base ?? { "/Users/electron/Documents/Projets_Creatifs/sound_algo-refonte-v2/" };
    if (s.serverRunning.not) {
        "!! ~ensureLive : serveur pas demarre. Lance ~setupAll d'abord.".postln;
    } {
        // fx_bus en premier (les autres modules peuvent en dependre)
        if (~fxFx.isNil or: { ~send.isNil }) {
            "ensure : fx_bus.scd".postln;
            ~loadFirstBlock.(~base ++ "live/fx_bus.scd");
            loaded = loaded + 1;
        };
        if (~lfoTo.isNil or: { ~vcfOn.isNil }) {
            "ensure : live.scd".postln;
            ~loadFirstBlock.(~base ++ "live/live.scd");
            loaded = loaded + 1;
        };
        if (~setVol.isNil or: { ~mixPreset.isNil }) {
            "ensure : mixer.scd".postln;
            ~loadFirstBlock.(~base ++ "live/mixer.scd");
            loaded = loaded + 1;
        };
        if (~saveScene.isNil) {
            "ensure : scenes.scd".postln;
            ~loadFirstBlock.(~base ++ "live/scenes.scd");
            loaded = loaded + 1;
        };
        if (~loopOn.isNil) {
            "ensure : loops.scd".postln;
            ~loadFirstBlock.(~base ++ "live/loops.scd");
            loaded = loaded + 1;
        };
        if (~killAllOrphans.isNil) {
            "ensure : memory_guard.scd".postln;
            ~loadFirstBlock.(~base ++ "live/memory_guard.scd");
            loaded = loaded + 1;
        };
        if (~snareFill.isNil or: { ~tomFill.isNil }
            or: { ~seqPlay.isNil } or: { ~seqIntro.isNil }) {
            "ensure : palette/rhythm/_index.scd (fills + sequences)".postln;
            (~base ++ "palette/rhythm/_index.scd").load;
            loaded = loaded + 1;
        };
        if (~jumpTo.isNil) {
            "ensure : jump.scd".postln;
            ~loadFirstBlock.(~base ++ "control/jump.scd");
            loaded = loaded + 1;
        };
        if (~progDetroit.isNil or: { ~chordsToMelody.isNil }) {
            "ensure : chords_progressions.scd".postln;
            ~loadAllBlocks.(~base ++ "palette/chords_progressions.scd");
            loaded = loaded + 1;
        };
        // 5 wrappers API live (chacun expose un dict + des wrappers env)
        if (~kk.isNil) {
            "ensure : live/kicks.scd".postln;
            ~loadFirstBlock.(~base ++ "live/kicks.scd");
            loaded = loaded + 1;
        };
        if (~mm.isNil) {
            "ensure : live/melodies.scd".postln;
            ~loadFirstBlock.(~base ++ "live/melodies.scd");
            loaded = loaded + 1;
        };
        if (~ff.isNil) {
            "ensure : live/fx.scd".postln;
            ~loadFirstBlock.(~base ++ "live/fx.scd");
            loaded = loaded + 1;
        };
        if (~cc.isNil) {
            "ensure : live/chains.scd".postln;
            ~loadFirstBlock.(~base ++ "live/chains.scd");
            loaded = loaded + 1;
        };
        if (~p.isNil) {
            "ensure : live/patterns.scd".postln;
            ~loadFirstBlock.(~base ++ "live/patterns.scd");
            loaded = loaded + 1;
        };
        if (loaded == 0) {
            "=== ~ensureLive : tout est deja la, rien a charger ===".postln;
        } {
            ("=== ~ensureLive : " ++ loaded ++ " module(s) charge(s) ===").postln;
        };
    };
};


// --- HELPER : ~teardown ---------------------------------------------
//   Stop tout proprement :
//     - ~track et autres routines
//     - tous les Pdef de l'engine
//     - LFO / VCF / loops / playlist / sequences
//     - libere ~masterLim et ~scBus
//     - libere les bus FX et synths FX si presents
// --------------------------------------------------------------------
~teardown = {
    "=== ~teardown : stop tout ===".postln;

    // Watchdog memory_guard et album orchestrateur
    ~watchdogOff !? { ~watchdogOff.value };
    ~albumRoutine !? { ~albumRoutine.stop; ~albumRoutine = nil };

    // Routines de track / sweep / loops engine
    ~track !? { ~track.stop; ~track = nil };
    ~filterSweep !? { ~filterSweep.stop; ~filterSweep = nil };
    ~randomLoop !? { ~randomLoop.stop; ~randomLoop = nil };
    ~melodyEvolve !? { ~melodyEvolve.stop; ~melodyEvolve = nil };
    ~lRotate !? { ~lRotate.stop; ~lRotate = nil };

    // Tous les Pdef
    [\kickSeq, \hatSeq, \snareSeq, \clapSeq, \percSeq,
     \acidSeq, \melodySeq, \harmonySeq].do { |k| Pdef(k).stop };

    // LFO live (live.scd)
    ~lfoStopEverything !? { ~lfoStopEverything.value };

    // LFO fx_bus (fx_bus.scd)
    ~lfoStopAll !? { ~lfoStopAll.value };

    // VCF master
    ~vcfOff !? { ~vcfOff.value };

    // Loops live
    ~loopOffAll !? { ~loopOffAll.value };

    // Sequences (palette/rhythm/sequences.scd)
    ~seqStop !? { ~seqStop.value };

    // Auto-fx (autoPan / tremolo)
    ~stopAutoFx !? {
        [\kick, \hat, \snare, \clap, \perc, \acid, \melody, \harmony]
            .do { |v| ~stopAutoFx.(v) };
    };

    // Compresseur / saturation master
    ~compOff !? { ~compOff.value };
    ~satOff  !? { ~satOff.value };

    // Playlist
    ~playlist !? { ~playlist.stop; ~playlist = nil };

    // Free pdef tracks melodiques (multi-pistes)
    ~melTrackChoke !? { ~melTrackChoke.value };

    // Master limiter + sidechain bus
    ~masterLim !? { ~masterLim.free; ~masterLim = nil };
    ~scBus !? { ~scBus.free; ~scBus = nil };

    // Bus FX + synths FX
    ~fxFx !? {
        ~fxFx.do { |syn| syn !? { syn.free } };
        ~fxFx = nil;
    };
    ~fxBuses !? {
        ~fxBuses.do { |b| b !? { b.free } };
        ~fxBuses = nil;
    };

    "=== ~teardown OK ===".postln;
};

"~loadFirstBlock / ~check / ~setupAll / ~setupLive / ~ensureLive / ~teardown prets".postln;
)


// =====================================================================
//  BLOCS Cmd+Entree -- workflows manuels (executes a la demande)
//
//  IMPORTANT : ces blocs sont COMMENTES car .load echoue avec >1 bloc
//  top-level. Pour les utiliser : DECOMMENTE la ligne (retire les "//")
//  puis Cmd+Entree dessus dans l'IDE. Ou copie-colle le contenu.
// =====================================================================

// --- VERIF rapide de l'etat ----------
// ( ~check.(); )

// --- INIT COMPLETE (charge tout + boot + setup live + check) ---------
// ( ~setupAll.(); )

// --- INIT LIVE SEULEMENT (serveur deja boote) ------------------------
// ( ~setupLive.(); )

// --- INIT PARESSEUSE (charge ce qui manque seulement) ----------------
// ( ~ensureLive.(); )

// --- ARRET PROPRE ----------------------------------------------------
// ( ~teardown.(); )

// --- COMBO : check d'abord, puis setup auto si besoin ----------------
// ( ~check.(); ~setupAll.(); )

// --- COMBO : teardown puis re-setupAll (full restart sans rebooter) --
// ( ~teardown.(); ~setupAll.(); )
