chordlab V2.1

This commit is contained in:
lrdlg
2026-07-16 17:17:03 +02:00
parent 5d227c8ac9
commit 5f1d3fc5c7
11 changed files with 1690 additions and 17 deletions
Vendored
BIN
View File
Binary file not shown.
+2 -1
View File
@@ -27,8 +27,9 @@ Générateur d'accords et de progressions harmoniques dans le navigateur, inspir
- 8 presets de cadences générés dans la tonalité choisie - 8 presets de cadences générés dans la tonalité choisie
**Progression et lecture** **Progression et lecture**
- Rack de slots : durée individuelle (1/2/4/8 temps), réordonnancement, audition au clic - Rack de slots : durée individuelle réglable de **1 à 16 temps** (boutons /+), réordonnancement, audition au clic
- Modes de performance : block · strum ↑↓ · harp · arp ↑/↓/↕/random (1/8 à 1/32) - Modes de performance : block · strum ↑↓ · harp · arp ↑/↓/↕/random (1/8 à 1/32)
- **10 rythmiques de basse** (façon Scaler) : tenue · noires · croches · contretemps · octaves · fondamentalequinte · boogie · 332 · pompe · aléatoire
- Humanisation vélocité/timing - Humanisation vélocité/timing
**Entrées / sorties** **Entrées / sorties**
+19 -2
View File
@@ -111,9 +111,26 @@ Appliqués après la rotation, ils écartent les voix comme le ferait un pianist
### 4.3 Accords slash ### 4.3 Accords slash
Le sélecteur **basse /** choisit quelle note de l'accord la basse joue : fondamentale, tierce, quinte ou septième. Le LCD affiche alors `C/E`, `C/G`… La détection est intelligente : sur un accord mineur elle prend la tierce mineure, sur un ♭5 la quinte diminuée. Le sélecteur **basse /** choisit quelle note de l'accord la basse joue : fondamentale, tierce, quinte ou septième. Le LCD affiche alors `C/E`, `C/G`… La détection est intelligente : sur un accord mineur elle prend la tierce mineure, sur un ♭5 la quinte diminuée.
### 4.4 BASS ### 4.4 BASS et ses rythmiques
Le bouton **BASS** active le moteur basse (1 ou 2 octaves). En MIDI live et à l'export, la basse sort sur son propre canal — c'est elle qui pilote ta TD-3 pendant que les accords vont au Pro-800. Le bouton **BASS** active le moteur basse (1 ou 2 octaves). En MIDI live et à l'export, la basse sort sur son propre canal — c'est elle qui pilote ta TD-3 pendant que les accords vont au Pro-800.
Le sélecteur **rythme B** (inspiré de Scaler) transforme la note tenue en pattern rythmique, appliqué partout — audition, MIDI live et export `.mid` :
| Rythme | Comportement |
|---|---|
| tenue | une note sur toute la durée de l'accord (défaut) |
| noires | une frappe par temps |
| croches | deux frappes par temps |
| contretemps | frappes sur les contretemps uniquement (dub, ska) |
| octaves | croches alternant fondamentale / octave (synthwave, italo) |
| fond.quinte | noires alternant fondamentale / quinte |
| boogie | noires cyclant fondamentale → quinte → octave → quinte |
| 332 | tresillo (positions 1, 2.5, 4 sur un cycle de 4 temps) |
| pompe | noires à gate très court (house, techno) |
| aléatoire | croches probabilistes sur fondamentale/quinte/octave (IDM) |
Tous les patterns se tronquent proprement sur les durées impaires : des noires sur un slot de 3 temps donnent exactement 3 frappes.
--- ---
## 5. Key Mode — accords diatoniques à un doigt ## 5. Key Mode — accords diatoniques à un doigt
@@ -134,7 +151,7 @@ Le mineur harmonique vaut le détour : le degré V y devient un vrai accord de d
Jouez un accord, puis **ADD** (ou barre espace). Il apparaît dans un slot avec son nom, ses notes et sa durée. Jouez un accord, puis **ADD** (ou barre espace). Il apparaît dans un slot avec son nom, ses notes et sa durée.
Sur chaque slot : Sur chaque slot :
- **badge durée** (`4♩`) — cliquer fait tourner 4 → 8 → 2 → 1 temps - ** / +** autour du badge durée (`3♩`) — règle la durée de **1 à 16 temps**, au pas de 1. Un accord de 3, 5 ou 7 temps est désormais un seul slot, plus besoin de le découper.
- ** ** — déplacer le slot dans la séquence - ** ** — déplacer le slot dans la séquence
- **×** — supprimer - **×** — supprimer
- **clic sur le slot** — l'auditionner seul - **clic sur le slot** — l'auditionner seul
BIN
View File
Binary file not shown.
+96
View File
@@ -0,0 +1,96 @@
# _chordlab ORC·v2
Générateur d'accords et de progressions harmoniques dans le navigateur, inspiré de la logique du **Telepathic Instruments Orchid ORC-1** (implémentation indépendante, étendue). Un seul fichier HTML, zéro dépendance, zéro backend.
> Construis un accord avec un doigt, façonne son voicing, chaîne une progression, envoie-la en MIDI live vers ton studio dawless ou exporte-la en `.mid` pour ton DAW.
---
## Fonctionnalités
**Construction d'accords**
- Clavier une octave pour la fondamentale (souris, tactile ou clavier PC)
- 8 types d'accords : Dim · Min · Maj · Aug · Sus2 · Sus4 · 5 (power chord) · m♭6
- 12 modificateurs empilables : 6 · m7 · M7 · 9 · ♭9 · ♯9 · 11 · ♯11 · 13 · ♭5 · ♯5 · no3
- Nommage automatique (Cm7, Cdim7, Cø7, C7(♭9)…) affiché sur le LCD
**Voicing**
- Knob de voicing rotatif ±8 crans (inversions en cascade, une note à la fois)
- Drop voicings : close · drop 2 · drop 3 · drop 2+4 · spread
- Accords slash : basse sur la fondamentale, la tierce, la quinte ou la septième
- Moteur basse séparé (1 ou 2 octaves)
**Tonalité et intelligence harmonique**
- Key Mode : accords diatoniques à un doigt dans 8 gammes (majeur, mineur naturel/harmonique, dorien, phrygien, lydien, mixolydien, locrien)
- Voice-leading automatique (VL AUTO) et optimisation globale de la progression
- Suggestion d'accord suivant par table d'harmonie fonctionnelle
- 8 presets de cadences générés dans la tonalité choisie
**Progression et lecture**
- Rack de slots : durée individuelle (1/2/4/8 temps), réordonnancement, audition au clic
- Modes de performance : block · strum ↑↓ · harp · arp ↑/↓/↕/random (1/8 à 1/32)
- Humanisation vélocité/timing
**Entrées / sorties**
- **Web MIDI out live** : accords et basse sur deux canaux séparés, vers n'importe quelle interface USB-MIDI
- **Export `.mid`** : format 1, PPQ 480, tempo inclus, performance rendue en option
- **Session `.json`** : sauvegarde/chargement complet (réglages + progression)
---
## Démarrage rapide
1. Ouvrir `index.html` dans **Chrome, Edge ou Firefox** (Safari ne supporte pas Web MIDI).
2. Cliquer une note du clavier → l'accord sonne et s'affiche sur le LCD.
3. `ADD` (ou barre espace) pour l'empiler dans la progression, `PLAY` pour l'écouter.
4. Pour le MIDI live : brancher une interface USB-MIDI, autoriser l'accès MIDI, sélectionner le périphérique dans le panneau **MIDI out**.
> ⚠️ Web MIDI exige un **contexte sécurisé** : `https://` ou `http://localhost`. Un fichier ouvert en `file://` fonctionne pour l'audio et l'export, mais pas pour la sortie MIDI live selon les navigateurs. D'où l'intérêt de l'héberger (voir tutoriel de déploiement).
---
## Canaux MIDI par défaut
| Rôle | Canal | Machine cible (setup de référence) |
|---|---|---|
| Accords | 3 | Behringer Pro-800 (nappes) |
| Basse | 5 | TD-3 + K-2 en unisson |
Modifiables dans le panneau **MIDI out (live)**. L'export `.mid` reprend les mêmes canaux.
---
## Structure du dépôt
```
chordlab/
├── index.html # l'application complète
├── README.md
└── docs/
├── TUTORIEL_UTILISATION.md # guide utilisateur complet
└── TUTORIEL_DEPLOIEMENT_COOLIFY.md # mise en ligne + mises à jour
```
---
## Compatibilité
| Navigateur | Audio | MIDI live | Export .mid |
|---|---|---|---|
| Chrome / Edge | ✅ | ✅ | ✅ |
| Firefox | ✅ | ✅ | ✅ |
| Safari | ✅ | ❌ (pas de Web MIDI) | ✅ |
---
## Notes de conception
- Le moteur de voicing reproduit le principe du dial de l'Orchid : chaque cran fait une **rotation** de l'accord (la note la plus grave remonte d'une octave, ou l'inverse), sur ±8 crans.
- Les combinaisons classiques tombent naturellement : `DIM + 6 = dim7`, `DIM + m7 = ø7` (demi-diminué), `MAJ + m7 + ♯9 = accord Hendrix`.
- Empiler `m7 + M7` affiche `WTF?` sur le LCD — clin d'œil assumé à l'Orchid.
- Le voice-leading minimise la somme des déplacements de voix entre deux accords en testant les rotations de voicing candidates.
- Aucune donnée n'est stockée dans le navigateur : la persistance passe exclusivement par les fichiers `.json` de session.
## Licence & crédit
Projet personnel underscorelab / leradiologue. La logique d'accords s'inspire du concept de l'Orchid ORC-1 de Telepathic Instruments, sans réutiliser ni code, ni sons, ni assets de l'instrument original.
+236
View File
@@ -0,0 +1,236 @@
# _chordlab v2 — Tutoriel complet d'utilisation
Ce guide couvre l'intégralité de l'application, du premier accord jusqu'aux workflows dawless et DAW. Comptez 10 minutes pour tout lire, 2 minutes pour être opérationnel.
---
## 1. Vue d'ensemble de l'interface
L'écran est organisé comme une face avant de machine hardware :
```
┌─────────────────────────────────────────────────────┐
│ LCD — nom de l'accord, notes, état des modes │
├──────────────┬──────────────────────┬───────────────┤
│ CHORD TYPE │ ROOT │ VOICING │
│ TENSIONS │ (clavier 1 oct.) │ drop/slash │
│ ALTÉRATIONS │ │ BASS / KEY │
├──────────────┴──────────────────────┴───────────────┤
│ PROGRESSION — slots, transport, perf, BPM │
├──────────────────┬──────────────────┬───────────────┤
│ MIDI OUT LIVE │ EXPORT MIDI │ SESSION │
└──────────────────┴──────────────────┴───────────────┘
```
Le **LCD** est votre référence permanente : il affiche le nom de l'accord courant, ses notes réelles (avec octaves), et l'état des modes (voicing, drop, key, perf, MIDI, voice-leading).
---
## 2. Jouer un accord
Le principe fondamental, hérité de l'Orchid : **vous ne jouez jamais les notes de l'accord, vous le décrivez**.
1. **Type** — un seul actif à la fois (LED allumée). MAJ est sélectionné au démarrage.
2. **Fondamentale** — cliquez une touche du clavier, ou utilisez le clavier PC : `A W S E D F T G Y H U J` = C C# D D# E F F# G G# A A# B.
3. L'accord sonne immédiatement et s'affiche sur le LCD.
Les huit types :
| Bouton | Accord | Intervalles |
|---|---|---|
| DIM | diminué | 1 · ♭3 · ♭5 |
| MIN | mineur | 1 · ♭3 · 5 |
| MAJ | majeur | 1 · 3 · 5 |
| AUG | augmenté | 1 · 3 · ♯5 |
| SUS2 | suspendu 2 | 1 · 2 · 5 |
| SUS4 | suspendu 4 | 1 · 4 · 5 |
| 5 | power chord | 1 · 5 |
| m♭6 | mineur sixte bémol | 1 · ♭3 · ♭6 |
---
## 3. Tensions et altérations
Les deux rangées inférieures s'**empilent** librement sur le type (plusieurs LEDs actives simultanément).
**Tensions** — elles *ajoutent* des notes :
| Bouton | Ajoute | Exemple avec MAJ + C |
|---|---|---|
| 6 | sixte majeure | C6 |
| m7 | septième mineure | C7 |
| M7 | septième majeure | Cmaj7 |
| 9 | neuvième | Cadd9 (ou C7(9) si une 7e est là) |
| ♭9 / ♯9 | neuvièmes altérées | C7(♭9), C7(♯9) « Hendrix » |
| 11 / ♯11 | onzièmes | C7(11), Cmaj7(♯11) |
| 13 | treizième | C7(13) |
**Altérations** — elles *modifient ou retirent* des notes :
| Bouton | Effet |
|---|---|
| ♭5 | remplace la quinte juste par une quinte diminuée |
| ♯5 | remplace la quinte juste par une quinte augmentée |
| no3 | retire la tierce (accords ouverts, parfaits pour l'ambient) |
**Combinaisons remarquables** :
- `DIM + 6` → accord de **septième diminuée** (Cdim7)
- `DIM + m7`**demi-diminué** (Cø7)
- `MIN + m7 + 9` → le m9 des nappes deep
- `m7 + M7` ensemble → le LCD affiche `WTF?`. Vous êtes prévenu.
---
## 4. Le voicing
C'est le cœur expressif de la machine. Trois outils indépendants qui se cumulent :
### 4.1 Le knob rotatif (±8)
Chaque cran fait une **rotation** de l'accord : à +1, la note la plus grave remonte d'une octave ; à 1, la plus aiguë descend. Exemple sur Cmaj7 :
```
0 : C4 E4 G4 B4
+1 : E4 G4 B4 C5
+2 : G4 B4 C5 E5
1 : B3 C4 E4 G4
```
Manipulation : glisser verticalement sur le knob, boutons /0/+, flèches ←/→ du clavier, ou double-clic pour revenir à 0. Le même accord à +3 et à 2 raconte deux histoires complètement différentes — c'est l'outil n°1 pour trouver *la* couleur.
### 4.2 Drop voicings
Appliqués après la rotation, ils écartent les voix comme le ferait un pianiste ou un arrangeur :
| Mode | Effet | Usage typique |
|---|---|---|
| close | rien (position serrée) | défaut |
| drop 2 | 2e voix depuis le haut → 1 octave | le voicing jazz standard |
| drop 3 | 3e voix depuis le haut → 1 octave | grands écarts, guitare |
| drop 2+4 | deux voix descendues | très ouvert, cinématique |
| spread | une voix sur deux → +1 octave | nappes larges, ambient |
### 4.3 Accords slash
Le sélecteur **basse /** choisit quelle note de l'accord la basse joue : fondamentale, tierce, quinte ou septième. Le LCD affiche alors `C/E`, `C/G`… La détection est intelligente : sur un accord mineur elle prend la tierce mineure, sur un ♭5 la quinte diminuée.
### 4.4 BASS
Le bouton **BASS** active le moteur basse (1 ou 2 octaves). En MIDI live et à l'export, la basse sort sur son propre canal — c'est elle qui pilote ta TD-3 pendant que les accords vont au Pro-800.
---
## 5. Key Mode — accords diatoniques à un doigt
Activez **KEY**, choisissez une tonique et une gamme parmi les huit disponibles. À partir de là :
- Chaque touche du clavier génère automatiquement **l'accord diatonique du degré correspondant** (en Do majeur : C→C, D→Dm, E→Em, F→F, G→G, A→Am, B→Bdim).
- Les touches hors tonalité sont **grisées** et n'émettent rien (le LCD affiche `hors tonalité`).
- Les tensions 6/7/9/11/13 restent actives et sont harmonisées **dans la gamme** (la 7e de G en Do majeur sera F naturel → G7, pas Gmaj7). Un astérisque dans le nom (`G7*`) signale un accord harmonisé diatoniquement.
Le mineur harmonique vaut le détour : le degré V y devient un vrai accord de dominante avec sensible — la cadence mineure classique à un doigt.
---
## 6. Construire une progression
### 6.1 Manuellement
Jouez un accord, puis **ADD** (ou barre espace). Il apparaît dans un slot avec son nom, ses notes et sa durée.
Sur chaque slot :
- **badge durée** (`4♩`) — cliquer fait tourner 4 → 8 → 2 → 1 temps
- ** ** — déplacer le slot dans la séquence
- **×** — supprimer
- **clic sur le slot** — l'auditionner seul
### 6.2 Avec les presets
Le menu déroulant propose huit cadences classiques (IVviIV, iiVI, cycle des quintes…). **Charger** les génère dans la tonalité et la gamme sélectionnées, avec vos tensions et votre voicing courants. C'est le moyen le plus rapide d'avoir une base à triturer.
### 6.3 Avec la suggestion harmonique
**✦ suggérer** ajoute un accord choisi par une table d'harmonie fonctionnelle (après un I : IV, V, vi… ; après un ii : V ; etc.), dans la tonalité courante. Enchaînez les clics pour laisser la machine dériver, reprenez la main quand ça devient intéressant.
### 6.4 Voice-leading
Deux outils pour des enchaînements fluides :
- **VL AUTO** (transport) — chaque accord ajouté est automatiquement re-voicé pour minimiser le mouvement des voix par rapport au précédent.
- **⇄ voice-leading** — optimise d'un coup toute la progression existante. À utiliser après un chargement de preset : la différence s'entend immédiatement.
---
## 7. Lecture et modes de performance
**PLAY** lance la progression au BPM affiché ; **LOOP** la fait boucler ; le slot en cours s'illumine.
Le sélecteur **perf** transforme la restitution de chaque accord :
| Mode | Rendu |
|---|---|
| block | accord plaqué |
| strum ↑ / ↓ | notes égrenées vers le haut / le bas (façon guitare) |
| harp | balayage rapide (glissando) |
| arp ↑ / ↓ / ↕ / rnd | arpège à la vitesse du sélecteur **rate** (1/8, 1/16, 1/32) |
**HUM** humanise vélocités (±10) et timing (±15 ms) — utile pour que les strums et arps respirent.
> Ces modes s'appliquent partout : à l'audition, en MIDI live, et **dans l'export .mid** (désactivable, voir §9).
---
## 8. MIDI live vers le hardware
1. Branchez votre interface USB-MIDI **avant** d'ouvrir la page (ou rechargez-la après).
2. Le navigateur demande l'autorisation d'accès MIDI — acceptez.
3. Panneau **MIDI out (live)** : sélectionnez le périphérique.
4. Vérifiez les canaux : **accords ch3** (Pro-800), **basse ch5** (TD-3/K-2) par défaut — adaptez à votre routage.
5. Jouez : chaque accord cliqué, chaque slot auditionné et la lecture complète partent en MIDI, avec les modes de performance rendus.
Le LCD affiche `MIDI ON` quand un périphérique est actif. En cas de note bloquée sur une machine, **STOP** envoie un All Notes Off sur les 16 canaux.
> Contexte sécurisé requis : Web MIDI ne fonctionne qu'en `https://` ou sur `localhost`. C'est la raison principale d'héberger l'app (voir le tutoriel Coolify).
---
## 9. Export .mid
Panneau **Export MIDI** :
- **pistes** : `accords + basse` (2 pistes, chacune sur son canal) ou `accords seuls`
- **perf rendue** : `oui` écrit les strums/arps note par note dans le fichier ; `non` exporte des accords plaqués (préférable si vous voulez arpéger dans le DAW ou le Tracker)
Le fichier généré (`chordlab_Cm7-Fm7-Ab-Bb_112bpm.mid`, format 1, PPQ 480, tempo inclus) respecte la durée individuelle de chaque slot.
**Destinations typiques** :
- **DAW** — glisser-déposer sur une piste instrument, c'est tout.
- **Polyend Tracker Mini** — copier le `.mid` sur la carte SD et l'importer ; chaque piste MIDI devient une piste du pattern.
---
## 10. Sessions
**↓ sauver la session** génère un `.json` contenant tous les réglages (type, tensions, voicing, drop, slash, tonalité, perf, BPM…) et la progression complète. **↑ charger une session** restaure tout.
L'application ne stocke rien dans le navigateur : le fichier `.json` est l'unique persistance. Versionnez vos sessions dans Gitea comme n'importe quel fichier de projet.
---
## 11. Raccourcis clavier
| Touche | Action |
|---|---|
| A W S E D F T G Y H U J | fondamentales C → B |
| Espace | ADD (ajouter l'accord courant à la progression) |
| ← / → | voicing 1 / +1 |
| ↑ ↓ ← → (knob focalisé) | voicing fin |
| double-clic sur le knob | voicing = 0 |
---
## 12. Deux workflows de référence
### Workflow dawless (session hardware)
1. KEY ON, tonique et gamme du morceau.
2. Preset ou ✦ suggérer pour poser 48 accords ; ⇄ voice-leading.
3. MIDI live branché : accords ch3 → Pro-800 (nappe lente), basse ch5 → TD-3.
4. LOOP + perf `block`, BPM du morceau : la progression tourne pendant que vous sculptez les sons sur les machines et enregistrez sur le L-12.
5. Session sauvée en `.json` pour reprendre la structure plus tard.
### Workflow DAW / Tracker (composition)
1. Construction libre (KEY OFF) : types + tensions + drop voicings pour chercher des couleurs.
2. VL AUTO activé, on empile au fil de l'inspiration.
3. Export `.mid` avec `perf rendue : non` (accords propres) → import dans le DAW ou le Tracker Mini.
4. L'arpégiation et le sound design se font dans la machine de destination.
Binary file not shown.
+1268
View File
File diff suppressed because it is too large Load Diff
+69 -14
View File
@@ -246,7 +246,7 @@
</div> </div>
<div class="lcd-right"> <div class="lcd-right">
<div>VOICING <b id="lcdVoicing">0</b> · <b id="lcdDrop">CLOSE</b></div> <div>VOICING <b id="lcdVoicing">0</b> · <b id="lcdDrop">CLOSE</b></div>
<div>KEY <b id="lcdKey">OFF</b> · PERF <b id="lcdPerf">BLOCK</b></div> <div>KEY <b id="lcdKey">OFF</b> · PERF <b id="lcdPerf">BLOCK</b> · B <b id="lcdBassPerf">TENUE</b></div>
<div>MIDI <b id="lcdMidi"></b> · VL <b id="lcdVl">OFF</b></div> <div>MIDI <b id="lcdMidi"></b> · VL <b id="lcdVl">OFF</b></div>
</div> </div>
</div> </div>
@@ -333,6 +333,20 @@
<option value="-12" selected>1 oct</option> <option value="-12" selected>1 oct</option>
</select> </select>
</div> </div>
<div class="field"><label>rythme B</label>
<select id="bassPerfSel">
<option value="held">tenue</option>
<option value="quarter">noires</option>
<option value="eighth">croches</option>
<option value="offbeat">contretemps</option>
<option value="octave">octaves</option>
<option value="root5">fond.quinte</option>
<option value="walk">boogie</option>
<option value="tresillo">332</option>
<option value="pump">pompe</option>
<option value="rnd">aléatoire</option>
</select>
</div>
<div class="toggle-line"> <div class="toggle-line">
<button class="hwbtn" id="keyBtn"><span class="led"></span>KEY</button> <button class="hwbtn" id="keyBtn"><span class="led"></span>KEY</button>
<select id="keyRoot"></select> <select id="keyRoot"></select>
@@ -435,7 +449,7 @@
</div> </div>
</div> </div>
<footer>_chordlab v2.0 · logique d'accords inspirée de l'Orchid ORC-1 (implémentation indépendante étendue)</footer> <footer>_chordlab v2.1 · logique d'accords inspirée de l'Orchid ORC-1 (implémentation indépendante étendue)</footer>
</div> </div>
<script> <script>
@@ -465,6 +479,7 @@ const state = {
playing:false, loop:false, playIdx:-1, playTimer:null, playing:false, loop:false, playIdx:-1, playTimer:null,
vlAuto:false, vlAuto:false,
perf:"block", rate:0.25, perf:"block", rate:0.25,
bassPerf:"held",
humanize:false, humanize:false,
}; };
@@ -662,6 +677,35 @@ function suggestNext(){
RENDU D'ÉVÉNEMENTS — partagé audio / MIDI live / export RENDU D'ÉVÉNEMENTS — partagé audio / MIDI live / export
events: [{n, at (beats), dur (beats), vel, isBass}] events: [{n, at (beats), dur (beats), vel, isBass}]
========================================================= */ ========================================================= */
function renderBassEvents(bassMidi, beats, hV, hT){
const p = state.bassPerf, ev = [];
const push = (n, at, dur, vel=110)=>{
at = Math.max(0, at + hT());
dur = Math.min(dur, Math.max(0.1, beats - at - 0.02));
ev.push({n, at, dur, vel:vel+hV(), isBass:true});
};
if (p==="held"){ push(bassMidi, 0, beats*0.97); }
else if (p==="quarter"){ for(let t=0;t<beats;t+=1) push(bassMidi,t,0.9); }
else if (p==="eighth"){ for(let t=0;t<beats;t+=0.5) push(bassMidi,t,0.42); }
else if (p==="offbeat"){ for(let t=0.5;t<beats;t+=1) push(bassMidi,t,0.42); }
else if (p==="octave"){ let i=0; for(let t=0;t<beats;t+=0.5) push(i++%2?bassMidi+12:bassMidi,t,0.42); }
else if (p==="root5"){ let i=0; for(let t=0;t<beats;t+=1) push(i++%2?bassMidi+7:bassMidi,t,0.9); }
else if (p==="walk"){ const seq=[0,7,12,7]; let i=0; for(let t=0;t<beats;t+=1) push(bassMidi+seq[i++%4],t,0.9); }
else if (p==="tresillo"){
for(let c=0;c<beats;c+=4)
[0,1.5,3].forEach(o=>{ if(c+o<beats) push(bassMidi,c+o,1.2,114); });
}
else if (p==="pump"){ for(let t=0;t<beats;t+=1) push(bassMidi,t,0.38,114); }
else if (p==="rnd"){
const opts=[0,0,7,12];
for(let t=0;t<beats;t+=0.5)
if (Math.random()<0.65) push(bassMidi+opts[Math.floor(Math.random()*opts.length)],t,0.42);
if (!ev.length) push(bassMidi,0,0.9); // jamais de silence total
}
else { push(bassMidi, 0, beats*0.97); }
return ev;
}
function renderEvents(chord, beats, perfOverride){ function renderEvents(chord, beats, perfOverride){
const perf = perfOverride || state.perf; const perf = perfOverride || state.perf;
const rate = state.rate; const rate = state.rate;
@@ -670,7 +714,7 @@ function renderEvents(chord, beats, perfOverride){
const hV = ()=> hum ? Math.round((Math.random()-0.5)*20) : 0; const hV = ()=> hum ? Math.round((Math.random()-0.5)*20) : 0;
const hT = ()=> hum ? (Math.random()-0.5)*0.03 : 0; const hT = ()=> hum ? (Math.random()-0.5)*0.03 : 0;
if (chord.bass !== null && chord.bass !== undefined) if (chord.bass !== null && chord.bass !== undefined)
ev.push({n:chord.bass, at:0, dur:beats*0.97, vel:110+hV(), isBass:true}); ev.push(...renderBassEvents(chord.bass, beats, hV, hT));
const notes = chord.notes; const notes = chord.notes;
if (perf==="block"){ if (perf==="block"){
notes.forEach(n=>ev.push({n, at:Math.max(0,hT()), dur:beats*0.95, vel:100+hV(), isBass:false})); notes.forEach(n=>ev.push({n, at:Math.max(0,hT()), dur:beats*0.95, vel:100+hV(), isBass:false}));
@@ -869,6 +913,7 @@ function saveSession(){
drop:state.drop, slash:state.slash, bass:state.bass, bassOct:state.bassOct, drop:state.drop, slash:state.slash, bass:state.bass, bassOct:state.bassOct,
keyMode:state.keyMode, keyRoot:state.keyRoot, keyScale:state.keyScale, keyMode:state.keyMode, keyRoot:state.keyRoot, keyScale:state.keyScale,
perf:state.perf, rate:state.rate, humanize:state.humanize, perf:state.perf, rate:state.rate, humanize:state.humanize,
bassPerf:state.bassPerf,
bpm:getBpm(), vlAuto:state.vlAuto, bpm:getBpm(), vlAuto:state.vlAuto,
}, },
progression:state.progression, progression:state.progression,
@@ -898,9 +943,11 @@ function loadSession(file){
state.keyScale=s.keyScale||"major"; state.keyScale=s.keyScale||"major";
state.perf=s.perf||"block"; state.perf=s.perf||"block";
state.rate=s.rate||0.25; state.rate=s.rate||0.25;
state.bassPerf=s.bassPerf||"held";
state.humanize=!!s.humanize; state.humanize=!!s.humanize;
state.vlAuto=!!s.vlAuto; state.vlAuto=!!s.vlAuto;
state.progression=Array.isArray(d.progression)?d.progression:[]; state.progression=Array.isArray(d.progression)?d.progression:[];
state.progression.forEach(c=>{ c.beats=Math.max(1,Math.min(16,c.beats||4)); });
if (s.bpm) $("bpm").value=s.bpm; if (s.bpm) $("bpm").value=s.bpm;
syncUiFromState(); syncUiFromState();
renderProgression(); renderProgression();
@@ -919,6 +966,8 @@ function syncUiFromState(){
$("keyRoot").value=state.keyRoot; $("keyRoot").value=state.keyRoot;
$("keyScaleSel").value=state.keyScale; $("keyScaleSel").value=state.keyScale;
$("perfSel").value=state.perf; $("rateSel").value=state.rate; $("perfSel").value=state.perf; $("rateSel").value=state.rate;
$("bassPerfSel").value=state.bassPerf;
$("lcdBassPerf").textContent=$("bassPerfSel").selectedOptions[0].textContent.toUpperCase();
$("humanBtn").classList.toggle("on", state.humanize); $("humanBtn").classList.toggle("on", state.humanize);
$("vlBtn").classList.toggle("on", state.vlAuto); $("vlBtn").classList.toggle("on", state.vlAuto);
$("lcdDrop").textContent=state.drop.toUpperCase(); $("lcdDrop").textContent=state.drop.toUpperCase();
@@ -1085,6 +1134,10 @@ $("perfSel").addEventListener("change",function(){
$("lcdPerf").textContent=this.selectedOptions[0].textContent.toUpperCase(); $("lcdPerf").textContent=this.selectedOptions[0].textContent.toUpperCase();
}); });
$("rateSel").addEventListener("change",function(){state.rate=parseFloat(this.value);}); $("rateSel").addEventListener("change",function(){state.rate=parseFloat(this.value);});
$("bassPerfSel").addEventListener("change",function(){
state.bassPerf=this.value;
$("lcdBassPerf").textContent=this.selectedOptions[0].textContent.toUpperCase();
});
$("humanBtn").addEventListener("click",function(){ $("humanBtn").addEventListener("click",function(){
state.humanize=!state.humanize; state.humanize=!state.humanize;
this.classList.toggle("on",state.humanize); this.classList.toggle("on",state.humanize);
@@ -1121,7 +1174,6 @@ $("optimizeBtn").addEventListener("click",()=>{
$("sessionStatus").textContent="Voice-leading appliqué."; $("sessionStatus").textContent="Voice-leading appliqué.";
}); });
const BEAT_CYCLE=[4,8,2,1];
function renderProgression(){ function renderProgression(){
const el=$("progSlots"); const el=$("progSlots");
el.innerHTML=""; el.innerHTML="";
@@ -1139,28 +1191,31 @@ function renderProgression(){
<div class="sname">${c.label}</div> <div class="sname">${c.label}</div>
<div class="snotes">${c.notes.map(n=>NOTE_NAMES[n%12]).join(" ")}${c.bass!==null?" +B":""}</div> <div class="snotes">${c.notes.map(n=>NOTE_NAMES[n%12]).join(" ")}${c.bass!==null?" +B":""}</div>
<div class="srow"> <div class="srow">
<button class="mv" title="déplacer à gauche"></button> <button class="mv sl" title="déplacer à gauche"></button>
<span class="badge" title="durée en temps (clic pour changer)">${c.beats}♩</span> <button class="mv bminus" title="1 temps"></button>
<button class="mv" title="déplacer à droite"></button> <span class="badge" title="durée en temps (1 à 16)">${c.beats}♩</span>
<button class="mv bplus" title="+1 temps">+</button>
<button class="mv sr" title="déplacer à droite"></button>
</div>`; </div>`;
s.querySelector(".del").addEventListener("click",ev=>{ s.querySelector(".del").addEventListener("click",ev=>{
ev.stopPropagation(); ev.stopPropagation();
state.progression.splice(i,1);renderProgression(); state.progression.splice(i,1);renderProgression();
}); });
const [left,right]=s.querySelectorAll(".mv"); s.querySelector(".sl").addEventListener("click",ev=>{
left.addEventListener("click",ev=>{
ev.stopPropagation(); ev.stopPropagation();
if (i>0){[state.progression[i-1],state.progression[i]]=[state.progression[i],state.progression[i-1]];renderProgression();} if (i>0){[state.progression[i-1],state.progression[i]]=[state.progression[i],state.progression[i-1]];renderProgression();}
}); });
right.addEventListener("click",ev=>{ s.querySelector(".sr").addEventListener("click",ev=>{
ev.stopPropagation(); ev.stopPropagation();
if (i<state.progression.length-1){[state.progression[i+1],state.progression[i]]=[state.progression[i],state.progression[i+1]];renderProgression();} if (i<state.progression.length-1){[state.progression[i+1],state.progression[i]]=[state.progression[i],state.progression[i+1]];renderProgression();}
}); });
s.querySelector(".badge").addEventListener("click",ev=>{ s.querySelector(".bminus").addEventListener("click",ev=>{
ev.stopPropagation(); ev.stopPropagation();
const idx=BEAT_CYCLE.indexOf(c.beats); c.beats=Math.max(1,c.beats-1);renderProgression();
c.beats=BEAT_CYCLE[(idx+1)%BEAT_CYCLE.length]; });
renderProgression(); s.querySelector(".bplus").addEventListener("click",ev=>{
ev.stopPropagation();
c.beats=Math.min(16,c.beats+1);renderProgression();
}); });
s.addEventListener("click",()=>{ s.addEventListener("click",()=>{
const ev=renderEvents(c, Math.min(c.beats,2)); const ev=renderEvents(c, Math.min(c.beats,2));