diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0e1ebb3..94cf493 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -38,9 +38,100 @@ jobs: " # Les tests d'interface pilotent un vrai navigateur via CDP. Sans lui ils - # s'annoncent ignorés au lieu d'échouer — mais on préfère les faire tourner. - - name: Vérifier la présence d'un navigateur - run: which google-chrome chromium chromium-browser 2>/dev/null || echo "aucun navigateur — les tests d'UI seront ignorés" + # s'annoncent ignorés SANS faire rougir la suite : 32 tests muets et une + # sortie verte. En local c'est un repli acceptable ; en CI c'est un angle + # mort, donc ici on échoue franchement. + - name: Exiger un navigateur pour les tests d'interface + run: | + node -e " + const { findBrowser } = require('./tests/browser.js'); + const bin = findBrowser(); + if (!bin) { + console.error('aucun navigateur trouvé : les 32 tests d\'interface seraient ignorés en silence'); + process.exit(1); + } + console.log('navigateur : ' + bin); + " + + # Le portillon du code d'accès ne se voit que depuis une adresse NON + # locale : la machine hôte est exemptée exprès. Sans interface réseau, ces + # tests s'annoncent ignorés sans faire rougir la suite — même angle mort + # que les tests d'interface sans navigateur. + - name: Exiger une adresse réseau pour les tests du code d'accès + run: | + node -e " + const os = require('os'); + const ip = Object.values(os.networkInterfaces() || {}).flat() + .find(i => i && i.family === 'IPv4' && !i.internal); + if (!ip) { + console.error('aucune adresse réseau : le portillon du code d\'accès serait ignoré en silence'); + process.exit(1); + } + console.log('adresse réseau : ' + ip.address); + " - name: Suite complète run: npm test + + # L'icône de zone de notification est du PowerShell embarqué dans server.js, + # écrit depuis Linux et JAMAIS EXÉCUTÉ. `tests/systray.test.js` en lit le + # source, mais lire n'est pas analyser : une parenthèse manquante y survivrait + # jusqu'à la machine de régie. + # + # Ce travail-là ne demande PAS de session graphique — l'analyseur de PowerShell + # est une classe .NET ordinaire. On ne prétend donc pas tester l'icône : on + # ferme le seul angle mort qu'un runner peut fermer, et on le dit. + powershell: + runs-on: windows-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + + - name: Le script de l'icône s'analyse-t-il seulement ? + shell: pwsh + run: | + $src = Get-Content -Raw server.js + if ($src -notmatch '(?s)const SYSTRAY_PS1 = `(.*?)`;') { + Write-Error 'SYSTRAY_PS1 introuvable dans server.js'; exit 1 + } + $ps = $Matches[1] + $tokens = $null; $erreurs = $null + [System.Management.Automation.Language.Parser]::ParseInput( + $ps, [ref]$tokens, [ref]$erreurs) | Out-Null + # ⚠ TÉMOIN POSITIF D'ABORD. `$null.Count` vaut 0 en PowerShell : si + # l'analyseur ne rendait rien du tout, « zéro erreur » serait vrai et + # cette étape passerait au vert sans avoir rien analysé. On exige donc + # d'abord d'avoir vraiment lu un script. + if ($null -eq $tokens -or $tokens.Count -lt 100) { + Write-Host "l'analyseur n'a rendu que $($tokens.Count) jeton(s) : rien n'a été analysé" + exit 1 + } + if ($null -ne $erreurs -and $erreurs.Count -gt 0) { + Write-Host "$($erreurs.Count) erreur(s) d'analyse dans SYSTRAY_PS1 :" + $erreurs | ForEach-Object { Write-Host (" ligne {0} : {1}" -f $_.Extent.StartLineNumber, $_.Message) } + exit 1 + } + Write-Host "SYSTRAY_PS1 s'analyse sans erreur ($($tokens.Count) jetons)." + + # Et l'analyseur doit être capable de dire NON : on lui donne le même + # script avec une accolade en trop. S'il ne bronche pas, c'est que + # cette étape ne mesure rien — le défaut qu'on vient d'éviter. + $t2 = $null; $e2 = $null + [System.Management.Automation.Language.Parser]::ParseInput( + $ps + "`nfunction Casse { ", [ref]$t2, [ref]$e2) | Out-Null + if ($null -eq $e2 -or $e2.Count -eq 0) { + Write-Host "l'analyseur accepte un script volontairement cassé : il ne mesure rien" + exit 1 + } + Write-Host "témoin : un script cassé est bien refusé ($($e2.Count) erreur(s))." + + # La suite ne tourne que sur Ubuntu ; ce passage-ci attrape ce qui n'existe + # que sous Windows — séparateurs de chemin, casse des noms de fichiers. + - uses: actions/setup-node@v4 + with: + node-version: '22' + - name: Suite complète, sous Windows + # Les tests d'interface s'annoncent ignorés sans navigateur : c'est + # accepté ICI, ce passage n'existe pas pour eux. Le compte reste + # affiché, et le passage Ubuntu, lui, les exige. + run: npm test diff --git a/CHANGELOG.md b/CHANGELOG.md index 79e42ce..099eb59 100644 Binary files a/CHANGELOG.md and b/CHANGELOG.md differ diff --git a/CLAUDE.md b/CLAUDE.md index 5cc6c17..a7f6cd9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,7 +2,7 @@ App web (Node.js zéro dépendance + page unique) qui pilote les fixtures DMX de MadMapper en OSC : multi-chasers, vagues, couleur, presets, MIDI/OSC, Ableton -Link. **2.0.1** · MIT · Pierre-Yves Mansour — Collectif WSK. Travail en **français**. +Link. **2.0.2** · MIT · Pierre-Yves Mansour — Collectif WSK. Travail en **français**. ## À lire EN PREMIER @@ -29,6 +29,9 @@ qui ont déjà coûté cher. `docs/madmapper-osc-api.md` = référence OSC MadMa tempo, ni la protection « on ne coupe jamais un show » de l'arrêt auto. 5. Manuel PDF : regénérer avec `python3 docs/build-manuel.py ` (reportlab + DejaVu ; pas de glyphes ⏻ ⧉ ni d'exposants Unicode → carrés vides). + Sur une machine nue : `pip install reportlab` + `apt install fonts-dejavu-core + fonts-dejavu-extra` (l'italique est dans *-extra*). Sans l'italique il génère + quand même, en romain, et le dit. ⚠ Le dossier de sortie doit EXISTER. 6. Édits ciblés (Edit/Grep) plutôt que réécritures ; réponses concises ; économiser les tokens (préférence explicite de Pym). 7. Ne pas toucher : `scenoled.madproject`, `Chaser 4.2.2 Setup.exe`, `serverA.js`. @@ -36,7 +39,7 @@ qui ont déjà coûté cher. `docs/madmapper-osc-api.md` = référence OSC MadMa ## Lancer / tester - `node server.js` → http://localhost:3333 (config générée : `cascade-config.json`). -- **`npm test`** (= `node --test`) : 248 tests, zéro dépendance. **À lancer avant +- **`npm test`** (= `node --test`) : 328 tests, zéro dépendance. **À lancer avant de conclure toute modif du serveur.** ⚠ `node --test tests/` échoue sur Node 24 (chemin pris pour un module) — utiliser `node --test` tout court. - Instance isolée pour tester à la main : `CASCADE_PORT=3461 CASCADE_NO_BROWSER=1 @@ -44,8 +47,13 @@ qui ont déjà coûté cher. `docs/madmapper-osc-api.md` = référence OSC MadMa `CASCADE_OSCIN`, `CASCADE_FEEDBACK`, `CASCADE_MMPORT`, `CASCADE_MMHOST`). - Faux Carabiner = serveur TCP local port 17000 qui pousse `status { :peers 1 :bpm 128.0 ... }\n`. -- UI : Playwright dispo ; utiliser `waitUntil: 'domcontentloaded'` - (`networkidle` ne vient jamais, l'UI poll toutes les 120 ms). +- UI : les 45 tests d'interface pilotent un vrai navigateur en CDP maison + (`tests/browser.js`, zéro dépendance) — **pas** Playwright. Sans navigateur ils + s'annoncent ignorés SANS faire rougir la suite : vérifier le compte (328), pas + la couleur. `CASCADE_NAVIGATEUR=` impose un navigateur ; + `PLAYWRIGHT_BROWSERS_PATH` est balayé tout seul. Si tu passes par Playwright + à la main, `waitUntil: 'domcontentloaded'` (`networkidle` ne vient jamais, + l'UI poll toutes les 120 ms). - Exécutables : `node build.js` → `build/` (voir `docs/ETAT-DU-PROJET.md` pour les pièges pkg : `--no-bytecode` obligatoire, cibles `node22-*` seulement). - Manuel : `python docs/build-manuel.py ` (reportlab ; les polices sont @@ -60,7 +68,11 @@ raison d'être. **Intégration continue et release, depuis la 2.0** (le dépôt n'avait rien avant) : - `.github/workflows/tests.yml` — `npm test` sur chaque push vers `main` et sur - chaque PR. Vérifie aussi qu'**aucune dépendance npm** n'a été introduite. + chaque PR, **sur Ubuntu ET sur Windows**. Vérifie aussi qu'**aucune dépendance + npm** n'a été introduite, qu'un navigateur et une adresse réseau sont bien là + (sans quoi 46 tests d'interface et le portillon s'annonceraient « ignorés » + sans faire rougir la suite), et que `SYSTRAY_PS1` s'analyse — avec un témoin + qui vérifie que l'analyseur sait dire non. - `.github/workflows/release.yml` — pousser un tag `vX.Y.Z` construit les quatre exécutables et publie la release avec eux en pièces jointes. La description vient du CHANGELOG via `tools/notes-version.js`, jamais d'un texte recopié. @@ -69,13 +81,24 @@ raison d'être. ⚠ **Attendre la CI verte avant de taguer.** Elle attrape ce qui est invisible en local : courses de timing sur runner lent, chemins spécifiques à Windows. +⚠ **La prochaine version est une MINEURE — 2.1.0, pas 2.0.3.** La section +`[Non publié]` du CHANGELOG contient des **fonctions nouvelles** (suiveur audio, +grille de presets, code d'accès, replis), pas seulement des correctifs : le +versionnage sémantique impose donc une mineure. Penser aussi à passer +`VERSION = "2.1"` dans `docs/build-manuel.py` (il porte majeure.mineure, comme le +pied de page de l'interface) et à régénérer le PDF à la racine **et** dans `dist/`. + Livré et testé : page Scène 3D et manipulation souris · moteur Champ 3D (5 formes, netteté, course, bruit dirigé) · vues par axe avec bascule de dossiers et fondu · 7 modes de fusion · perspective atmosphérique · palette à N arrêts, branchable sur la profondeur ou la hauteur · décalage réparti · crossfader A/B · modulateurs (LFO) par couche et global · coupure de secours · renvoi de disposition · démos · repère 3D. -**248 tests, 21 mutations sur 21 détectées.** Manuel PDF, README, CHANGELOG et +**328 tests, 36 mutations sur 36 détectées** — campagne complète passée d'un bloc +le 2026-08-05 : zéro aveugle, zéro motif absent, `server.js` restauré à +l'identique (le chiffre était additionné à la main avant). +⚠ La lancer dans une COPIE du dépôt : elle modifie `server.js` en place pendant +une heure, et un commit parti à ce moment-là emporterait un mutant. Manuel PDF, README, CHANGELOG et exécutables des 4 plateformes sont à jour ; l'exécutable Windows a été lancé et interrogé pour de vrai. @@ -87,31 +110,85 @@ Trois garde-fous à connaître avant de toucher au code : palette spatiale morte en silence ; - `build-manuel.py` refuse de générer si un caractère manque à la police. -Ce qui reste, par ordre de valeur : suiveur audio (à faire côté navigateur, -en Web Audio, pour tenir le zéro-dépendance), grille visuelle des 16 presets, les trois mesures MadMapper non faites (empreinte d'une -barre en pixels, pivot de `output/rot`, DMX Filtering), et « dessiner les -fixtures » — qui attend d'abord un `Export Fixture Definitions…` depuis -MadMapper. Détail dans `docs/V2-INSPIRATIONS.md` et `docs/V2-AXES-PISTES.md`. +✅ **Suiveur audio et grille de presets sont LIVRÉS** (2026-08-04) — ne pas les +reprendre. Le micro est une *source de modulateur* (`src: 'lfo' | 'audio'`), +analysé en Web Audio côté page et poussé en query sur le poll. ⚠ Il lit une +**énergie, pas un tempo**, et n'est lisible que sur la machine hôte (origine +sûre) : les deux limites sont assumées, écrites, et **décidées avec Pym** — ne +pas rouvrir sans lui. La détection de battement attend un essai en salle. + +Ce qui reste, par ordre de valeur : les **deux** mesures MadMapper non faites +(empreinte d'une barre en pixels, DMX Filtering), et « dessiner les fixtures » — +qui attend d'abord un `Export Fixture Definitions…` depuis MadMapper. Détail +dans `docs/V2-INSPIRATIONS.md` et `docs/V2-AXES-PISTES.md`. + +✅ **Le pivot de `output/rot` est mesuré** (2026-07-29) : la rotation ne +translate pas la fixture (écart 0,00 en x et y), donc l'ordre position/rotation +est libre. C'était la troisième mesure ; elle est faite. ## Prochaines demandes de Pym (exprimées, PAS encore réalisées) -1. **Icône de zone de notification (systray)** : point vert = serveur en route, - rouge = arrêté ; clic droit → ouvrir l'interface / démarrer / arrêter le - serveur. Idée : pouvoir fermer la fenêtre en laissant tourner le serveur - tout en le voyant. ⚠ Contrainte zéro-dépendance : pas de systray natif en - Node pur — pistes à discuter avec Pym (petit utilitaire par plateforme, - PowerShell/AppleScript, ou accepter une dépendance ici). +1. ✅ **Icône de zone de notification : FAITE** (2026-08-05), en PowerShell + + `NotifyIcon`, donc zéro dépendance. ⚠ **Windows uniquement — décidé avec + Pym**, macOS n'a pas d'équivalent scriptable : ne pas « réparer » cette + absence. ⚠ **Éteinte par défaut et JAMAIS EXÉCUTÉE** : écrite depuis Linux. + Le premier essai sur une vraie machine Windows reste à faire — c'est la + première chose à confirmer avec lui. Le script vit dans `SYSTRAY_PS1` + (embarqué, car le distribuable est un exécutable unique). + + ⚠ **Avant l'essai, savoir ceci** : Windows range **toute nouvelle icône dans + le tiroir caché** (chevron `^`). L'issue la plus probable d'un premier essai + est « le script tourne, l'icône existe, Pym ne la voit pas ». La note est + dans les Réglages et dans le manuel — la lui montrer. + + ⚠ Le script sonde **`/api/ping`, jamais `/api/state`** : `/api/state` remet + `lastUiPollAt` à jour et **désactivait l'arrêt automatique** (défaut réel, + trouvé en relecture, corrigé, mesuré, et rejoué par un mutant). Détail et + sept autres correctifs dans `docs/ETAT-DU-PROJET.md`. + + ⚠ **Ce que la CI dit, et ce qu'elle ne dira jamais.** Depuis le 2026-08-05 il + y a un passage `windows-latest` : il **analyse** `SYSTRAY_PS1` avec + `[Parser]::ParseInput` (aucune session graphique requise) et lance la suite + entière — 328 tests, 0 ignoré, le premier passage est vert. Une parenthèse + manquante ne partira donc plus en régie. Mais **aucun runner ne posera jamais + l'icône** : `NotifyIcon` demande un bureau. L'essai en vrai reste à faire. + `tests/systray.test.js` lit le source en plus — c'est mieux que rien. 2. **Capture ou GIF dans le README** — nécessite une vraie session MadMapper. -3. Suite de l'audit : voir la section « À faire » de `../Cascade-AUDIT.md` (hors dépôt) - (code d'accès 4 chiffres, repli « avancé » du panneau Couches, tests d'UI). -4. **`../Cascade-RELECTURES.md` (hors dépôt) — à lire avant de reprendre la v2.** - État des deux campagnes de relecture par agents : ce qui a été mesuré et - exploité, ce qui reste à mesurer sur MadMapper, et **32 trouvailles sur le - moteur de champ 3D dont trois bloquantes** — la relecture s'est arrêtée avant - la phase de vérification, donc rien n'y est confirmé contradictoirement. - Deux de ces défauts sont **déjà en v1** (couche couleur qui allume les barres - qu'elle ne pilote pas ; phase non préservée au changement de tempo pour le - moteur vague). +3. Suite de l'audit : voir la section « À faire » de `../Cascade-AUDIT.md` (hors dépôt). + ✅ Repli « avancé » du panneau Couches : FAIT (replis `advMiroirs` et + `advMelange`, 2026-08-04). ✅ Tests d'UI : faits depuis la 2.0 (CDP maison). + ✅ Code d'accès à 4 chiffres : FAIT (2026-08-04). ⚠ La machine hôte en est + EXEMPTÉE exprès (on ne peut pas s'enfermer dehors) et l'OSC/MIDI ne sont pas + couverts (autre domaine de confiance) — ne pas « corriger » ça sans Pym. + Reste : signature/notarisation macOS (compte Apple Developer, 99 $/an → + décision + dépense). +4. **`../Cascade-RELECTURES.md` (hors dépôt)** — état des deux campagnes de + relecture par agents. ⚠ Ce fichier vit **hors du dépôt** : il est absent des + clones frais (sessions distantes, CI), donc ne jamais faire dépendre une + décision de son contenu sans l'avoir sous les yeux. + + ⚠ **Les deux « défauts déjà en v1 » que ce fichier signalait sont CORRIGÉS** + — vérifié dans le code le 2026-08-04, ne pas repartir les chasser : + - *couche couleur qui allume les barres qu'elle ne pilote pas* → `mixLevel()` + décide barre par barre (`server.js`) ; la forme fautive globale + (`mix.anyCol`) a disparu. Corrigé par `648327c`, listé `CHANGELOG.md:213`, + couvert par `tests/regressions.test.js`. + - *phase non préservée au changement de tempo (moteur vague)* → `phaseContinue()` + intègre `dt / period` au lieu de `now / period` ; le chaser, lui, rebase + `startTime`. Corrigé par `619bee8`, listé `CHANGELOG.md:215`, couvert par + `tests/regressions.test.js` (`stepMs` 10000 → 9999, écart < 0,05). + + La relecture s'était arrêtée avant sa phase de vérification : ses trouvailles + n'étaient **pas** confirmées contradictoirement, et celles-là étaient périmées. + Traiter le reste de la liste avec la même prudence — vérifier dans le code + avant d'y croire. + + **Piste restée ouverte, elle, et non couverte** : `resolveBars()` replie sur + *toutes* les barres actives quand le groupe d'une couche est vide. Vider un + groupe (au lieu de le supprimer, qui remet `L.groupId = null`) fait donc + basculer une couche couleur sur tout le plateau. Comportement délibéré et + documenté, mais c'est la seule voie vivante qui reproduit le symptôme + d'origine, et aucun test ne la couvre. Le chantier v1.3 « spectacle » (features de chase, UI premium, tests, audit, exécutables, manuel, CHANGELOG) est **livré** — voir `CHANGELOG.md`. diff --git a/Cascade - Manuel.pdf b/Cascade - Manuel.pdf index f441ddd..5aa8852 100644 Binary files a/Cascade - Manuel.pdf and b/Cascade - Manuel.pdf differ diff --git a/README.md b/README.md index 6c51f63..c2680d7 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ *Pierre-Yves Mansour — Collectif WSK* -![version](https://img.shields.io/badge/version-2.0.0-orange) ![licence](https://img.shields.io/badge/licence-MIT-blue) ![dépendances](https://img.shields.io/badge/d%C3%A9pendances-aucune-brightgreen) ![tests](https://img.shields.io/badge/tests-248-green) ![plateformes](https://img.shields.io/badge/plateformes-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey) +![version](https://img.shields.io/badge/version-2.0.2-orange) ![licence](https://img.shields.io/badge/licence-MIT-blue) ![dépendances](https://img.shields.io/badge/d%C3%A9pendances-aucune-brightgreen) ![tests](https://img.shields.io/badge/tests-262-green) ![plateformes](https://img.shields.io/badge/plateformes-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey) --- @@ -24,14 +24,15 @@ Téléchargez le fichier de votre système dans la [dernière release](../../rel | Système | Fichier | |---|---| -| Windows | `Cascade-1.6.0-windows-x64.exe` | -| macOS (Apple Silicon, M1 et suivants) | `Cascade-1.6.0-macos-apple-silicon` | -| macOS (Intel) | `Cascade-1.6.0-macos-intel` | -| Linux | `Cascade-1.6.0-linux-x64` | +| Windows | `Cascade--windows-x64.exe` | +| macOS (Apple Silicon, M1 et suivants) | `Cascade--macos-apple-silicon` | +| macOS (Intel) | `Cascade--macos-intel` | +| Linux | `Cascade--linux-x64` | -> **macOS et Linux**, la première fois, dans un Terminal ouvert sur le dossier : +> **macOS et Linux**, la première fois, dans un Terminal ouvert sur le dossier +> (remplacez `` par celle que vous avez téléchargée) : > ```bash -> xattr -cr Cascade-1.6.0-macos-apple-silicon && chmod +x Cascade-1.6.0-macos-apple-silicon +> xattr -cr Cascade--macos-apple-silicon && chmod +x Cascade--macos-apple-silicon > ``` > (les binaires ne sont pas signés — sans cela macOS annonce un « fichier endommagé ») @@ -73,6 +74,7 @@ Pour quitter : bouton ⏻ en haut à droite, ou fermez simplement la fenêtre - **Décalage réparti** : la première barre à 0°, la dernière à N°, étalé sur la sélection. Une vague traverse la scéno selon un axe réel avec un seul réglage. - **Modulateur par couche** : un LFO qui fait respirer un réglage tout seul — niveau, largeur, vitesse, netteté, profondeur… Sa période se cale au choix en secondes ou sur le tempo, donc il ne dérive pas contre la musique. Il ne modifie jamais le réglage affiché, il se superpose : le couper rend la main immédiatement. - **Modulateur global** : le même, branché sur le crossfader, le master ou la vitesse. Sur le crossfader, la scène passe d'un jeu à l'autre toute seule. +- **Suiveur audio** : le micro devient une *source* de modulateur, à côté de l'oscillateur — la lumière respire avec le son. Analyse en Web Audio côté navigateur (zéro dépendance), avec bande, gain, seuil, attaque et relâchement. Il lit une **énergie**, pas un tempo. Le micro n'est lisible que sur la machine où tourne Cascade (les navigateurs l'interdisent depuis une adresse réseau), et si le son s'arrête, le réglage réglé à la main **reprend** — jamais figé, jamais tombé au noir. - **Crossfader A ↔ B** : rangez vos couches en deux jeux et passez de l'un à l'autre à la main — ou au fader d'une console, en OSC. C'est la différence entre déclencher un preset et *jouer* un passage. - **Coupure de secours** : met la sortie DMX de MadMapper à zéro d'un message. La seule voie qui coupe vraiment quand une texture joue — volontairement séparée de BLACKOUT. @@ -96,7 +98,7 @@ Pour quitter : bouton ⏻ en haut à droite, ou fermez simplement la fenêtre - **Intensité ou couleur** : dégradé A→B qui se déplace sur les barres, ou une **palette à 8 arrêts** (Feu, Glace, Coucher de soleil, Forêt, Distance). - **Vue spatiale** : barres positionnables au doigt ou importées depuis MadMapper (position, rotation, taille), allumage en temps réel. - **Groupes de barres nommés** : « sol », « contres »… Une couche qui suit un groupe se met à jour toute seule quand le groupe change. -- **16 presets nommables** qui mémorisent les couches *et* la disposition complète — rappel instantané en live. « Refrain » se retrouve plus vite que « P7 ». +- **Grille de 16 presets nommables** qui mémorisent les couches *et* la disposition complète — rappel instantané en live. « Refrain » se retrouve plus vite que « P7 ». Chaque pavé montre l'empreinte des barres qu'il pilote, lequel joue en ce moment, et signale un preset qui ne pilote plus rien. - **Fondu entre presets** (0 à 30 s, réglable) : la scène sortante continue de jouer et décroît pendant que la nouvelle monte. À 0, le rappel reste sec. - **Raccourcis clavier** : `S` start/stop, `B` blackout, `Espace` tap, `R` resync, `G` GO, `1`–`8` couches, `?` pour l'aide. - **Tempo** : tap tempo, ÷2 ×2, resync sur le temps fort, vitesse par couche et vitesse globale. @@ -115,7 +117,7 @@ Le paramètre d'intensité par défaut est `luminosity` (fixtures DMX). Pour des L'adresse réseau s'affiche en haut de l'interface, ou scannez le **QR code**. Même Wi-Fi, c'est tout. -> ⚠ **Il n'y a pas de mot de passe.** N'importe qui sur le même réseau peut piloter le show. En festival, préférez un point d'accès dédié. +> 🔒 **Code d'accès facultatif.** ⚙ Réglages → un code à 4 chiffres protège l'accès **depuis le réseau** (iPad, téléphone, Wi-Fi de la salle). Il n'est jamais demandé sur la machine où tourne Cascade — impossible de s'enfermer dehors. Cinq essais ratés bloquent l'appareil une minute. L'OSC et le MIDI ne sont pas concernés : ils passent par un câble ou une console, c'est un autre domaine de confiance. Sans code posé, n'importe qui sur le même réseau peut piloter le show. ## Contrôle OSC @@ -138,7 +140,7 @@ Le préfixe historique `/chaser/…` reste accepté. ## Développement ```bash -npm test # 111 tests, sans aucune dépendance +npm test # 262 tests, sans aucune dépendance node build.js # exécutables des 4 plateformes → build/ ``` diff --git a/dist/Cascade - Manuel.pdf b/dist/Cascade - Manuel.pdf index f441ddd..5aa8852 100644 Binary files a/dist/Cascade - Manuel.pdf and b/dist/Cascade - Manuel.pdf differ diff --git a/dist/package.json b/dist/package.json index eec2281..c8420e3 100644 --- a/dist/package.json +++ b/dist/package.json @@ -1,6 +1,6 @@ { "name": "cascade-madmapper", - "version": "2.0.1", + "version": "2.0.2", "description": "Cascade — séquenceur LED multi-couches pour MadMapper (OSC) : chases, vagues, couleur, presets, MIDI/OSC, Ableton Link", "author": "Pierre-Yves Mansour — Collectif WSK", "license": "MIT", diff --git a/dist/public/index.html b/dist/public/index.html index 55390d2..97433fe 100644 --- a/dist/public/index.html +++ b/dist/public/index.html @@ -259,31 +259,59 @@ .chip.sel { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); } .chip.off { opacity: .45; } .chip input { accent-color: var(--on); } - /* Presets */ - #presets { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; margin-bottom: 12px; - position: relative; padding-bottom: 4px; } + /* Presets — une GRILLE de pavés, pas une rangée de boutons. En régie, dans le + noir, on vise une case dont on connaît la place ; une rangée qui se replie + déplace les slots dès qu'on change la largeur de la fenêtre. 8 colonnes sur + poste (2 rangées), 4 sur tablette, pour garder une cible cliquable au doigt. */ + #presets { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 6px; + margin-bottom: 12px; position: relative; padding-bottom: 6px; } + @media (min-width: 1181px) { #presets { grid-template-columns: repeat(8, minmax(0, 1fr)); } } + /* Le libellé et le bouton Sauver traversent toute la grille */ + #presets > span, #presets > button:not(.slot) { grid-column: 1 / -1; } /* Fondu en cours : un filet de progression sous la rangée de presets */ #presets::after { content: ''; position: absolute; left: 0; bottom: 0; height: 2px; width: var(--fade, 0%); background: var(--accent); border-radius: 2px; opacity: 0; transition: opacity .2s; box-shadow: 0 0 8px var(--glow); } #presets.fading::after { opacity: 1; } - #presets button.slot { min-width: 34px; padding: 8px 0; font-weight: 700; position: relative; - font-variant-numeric: tabular-nums; } + #presets button.slot { min-width: 0; padding: 7px 7px 6px; font-weight: 700; position: relative; + font-variant-numeric: tabular-nums; display: flex; flex-direction: column; + align-items: stretch; gap: 5px; min-height: 54px; text-align: left; } + /* Empreinte : une case par barre pilotée par le preset, teintée comme la + couche qui la pilote. C'est une couverture, PAS une image du motif — un + chase n'allume qu'une barre à la fois, une photo serait presque vide. */ + #presets .emp { display: flex; gap: 1px; height: 6px; margin-top: auto; + border-radius: 2px; overflow: hidden; } + #presets .emp i { flex: 1 1 0; min-width: 0; background: var(--border); } + /* Ce preset ne pilote plus aucune barre : à dire, surtout pas à masquer */ + #presets button.slot.muet { border-style: dashed; } + /* Le pavé qui joue, et celui d'où l'on vient pendant un fondu */ + #presets button.slot.joue { border-color: var(--accent); color: var(--accent); + box-shadow: 0 0 10px var(--glow); } + #presets button.slot.sortante { border-color: var(--accent2); opacity: .7; } /* Un slot occupé porte une pastille orange : on lit la banque d'un coup d'œil */ #presets button.slot:not(.empty)::after { content: ''; position: absolute; top: 4px; right: 4px; width: 5px; height: 5px; border-radius: 50%; background: var(--accent); } /* Preset nommé : le nom prime, le numéro reste lisible pour le MIDI/OSC */ - #presets button.slot.named { min-width: 0; padding: 6px 12px 6px 9px; display: inline-flex; - align-items: baseline; gap: 6px; } + /* ⚠ Le pavé nommé garde la colonne : le numéro au-dessus, le nom en dessous, + l'empreinte en bas. Repasser en `inline-flex` remettrait l'empreinte sur la + même ligne que le nom. L'ordre numéro puis nom est un contrat de test. */ + #presets button.slot.named { padding: 7px 7px 6px; } #presets button.slot.named::after { display: none; } - #presets .pnum { font-size: 10px; color: var(--dim); font-weight: 700; } + #presets .pnum { font-size: 10px; color: var(--dim); font-weight: 700; line-height: 1; } #presets button.slot.named:hover .pnum { color: var(--accent); } - #presets .pname { font-size: 12px; font-weight: 600; letter-spacing: .3px; - max-width: 110px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + #presets button.slot.joue .pnum { color: var(--accent); } + #presets .pname { font-size: 12px; font-weight: 600; letter-spacing: .3px; line-height: 1.2; + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } #presets button.empty { opacity: .4; font-weight: 400; } #presets button.savemode { border-color: var(--off); color: var(--off); animation: blinkSave 1s steps(2, start) infinite; } @keyframes blinkSave { 50% { opacity: .55; } } + /* Suiveur audio : un vu-mètre, seule CSS nouvelle du panneau — le reste + hérite du style générique des replis `details` déjà en place. */ + #audioVu { display: block; width: 100%; height: 8px; border-radius: 4px; + background: var(--panel2); overflow: hidden; } + #audioVu i { display: block; height: 100%; width: 0; background: var(--accent); + transition: width .08s linear; } /* Vue spatiale */ #stage { position: relative; height: 320px; background: radial-gradient(ellipse at 50% 120%, #141926 0%, #0a0c10 70%); @@ -374,6 +402,17 @@

Raccourcis & gestes

Double-clicSur une ligne de réglage : retour à la valeur par défaut Double-clicSur un nom de couche : renommer · sur une barre : pivoter Double-clicSur un preset : renommer + Code d'accès + À quoi ça sertUn code facultatif à 4 chiffres (⚙ Réglages) protège l'accès depuis le réseau — iPad, téléphone, Wi-Fi de la salle. + Jamais enfermé dehorsIl n'est jamais demandé sur la machine où tourne Cascade : il y a donc toujours une voie pour le retirer. + Ce qu'il ne couvre pasL'OSC et le MIDI : ils passent par un câble ou une console sur le réseau de production, c'est un autre domaine de confiance. + Essais limitésCinq essais ratés bloquent l'appareil une minute — sans ça, 10 000 combinaisons se passeraient en revue en quelques secondes. Changer ou retirer le code déconnecte les appareils déjà entrés. + Suiveur audio + PrincipeLe micro est une source de modulateur, comme l'oscillateur : Global → repli Suiveur audio, puis Source = Micro sur le modulateur d'une couche ou sur le modulateur global. + Ce qu'il litUne énergie, pas un tempo : il fait respirer la lumière avec le son. Il ne détecte pas les battements et ne cale pas le chase sur la musique. + LimiteLe micro n'est lisible que sur la machine où tourne Cascade — jamais depuis une adresse réseau, les navigateurs l'interdisent. Depuis un iPad, tout le reste fonctionne. + SécuritéSi le son s'arrête d'arriver (onglet fermé, micro débranché), le réglage revient à ce que vous aviez posé — il ne reste jamais figé sur la dernière valeur entendue. + Grille de presetsLa bande de couleur montre les barres que le preset pilote — une couverture, pas une image du motif. Bord en pointillé : ce preset ne pilote plus aucune barre. Pavé orange : le dernier rappelé. GlisserUne barre dans la vue spatiale : la déplacer Page Scène (vue 3D) Glisser une barreLa déplacer · Maj = en hauteur · Alt = l'orienter @@ -474,7 +513,7 @@

Premiers pas

sur un iPad en un scan.

-
+
@@ -539,12 +578,15 @@

Chasers (couches — cocher = actif, cliquer = éditer)

Pattern

+
+ Symétries +
+
+ Mélange & espace