Skip to content

Feat/music#297

Merged
bbstudioapp merged 15 commits into
developfrom
feat/music
May 30, 2026
Merged

Feat/music#297
bbstudioapp merged 15 commits into
developfrom
feat/music

Conversation

@bbstudioapp
Copy link
Copy Markdown
Owner

No description provided.

Grammaire de base du futur mode Music (cf. specs/music_mode.md), sans audio
ni UI — uniquement la production de figures, entièrement testée.

- BeatPattern : slots strike/release/hold, le beat = le 'to' (impact), from = ancre
- onset_figures : banque curée de 9 figures (niveaux 1-3)
- depth_contour : contours générés (alternance/climb/reach), frappes adjacentes !=
- music_capability_bounds : profil lu comme bornes (réutilise rhythmBpmCeilAxisFor)
- music_pattern_generator : escalade par phrase, mapping BPM ½×/1×/2×, soupapes depth+hold

9 tests verts, analyze propre.
- beat_clock : interface BeatClock + BeatTick (1 tick/battement, drapeaux bar/phrase)
- beat_grid : BeatGrid (maths pures temps→battement) + BeatScheduler.poll (émission déterministe, rattrapage, anti-doublon)
- tap_tempo : TapTempoEstimator (médiane d'intervalles, reset gap, clamp) + TapTempoSource (Stopwatch partagé, Timer/Stream, horloge injectable pour tests)

10 tests verts, analyze propre.
- slot_action : SlotAction (strike/release/hold + profondeur + battement + bpm)
- music_session_engine : régénère un BeatPattern par phrase, traduit chaque
  battement en SlotAction. Alignement PR1 : 1 slot = 1 battement en 1×, ½× =
  1 slot tous les 2 battements (frappes profondes espacées, pas de retempo audio).
  Double-temps reporté PR3. onBeat() pur testable + attach(clock) pour le stream.

6 tests verts (25 au total sur le mode music).
…eur)

Bloc 4 de PR1 — câblage app, contrôleur autonome (pas de SessionController).

- MusicSessionController : TapTempoSource → MusicSessionEngine → BeepEngine
  (strike→playPositionOnce, hold→playHoldOnce, release muet). BeepEngine intact.
- MusicModeScreen : zone de tap, BPM live, courbe de profondeur (CustomPaint),
  start/stop ; charge le profil via CapabilityService.snapshotProfile.
- mode_selection : 4e carte « MUSIC » ; cartes passées en liste scrollable
  (4 Expanded débordaient sur petit écran / widget_test).
- i18n fr/en/de/es (7 clés).

633 tests verts, analyze complet propre.
…nimée

Retours de test live PR1 :
- BeepEngine.setMixWithOthers : contexte audio global mixWithOthers (iOS) /
  AndroidAudioFocus.none — les bips se mélangent à Spotify au lieu d'en prendre
  le focus. Activé à l'entrée du mode Music, focus exclusif rendu en sortie.
- Remplace la courbe (buguée : repaint jamais déclenché) par _MusicDepthGauge :
  échelle verticale head→full + orbe qui glisse vers la profondeur courante
  (façon MovementAnimation), strike = pop, hold = anneau, release = atténué.
  Piloté par les SlotAction (même source que l'audio → synchro, pas de drift).

633 tests verts, analyze propre.
…icipation)

Retours de test #2 :
- setMixWithOthers pose désormais le contexte mixWithOthers sur CHAQUE player du
  pool (le contexte global seul ne suffisait pas : chaque player gardait le focus
  exclusif et coupait Spotify à la 1ʳᵉ lecture).
- Remplace la jauge par une timeline : passé court · NOW · slots à venir (droite)
  pour anticiper. MusicSessionEngine.peek (lecture seule) expose les prochains
  slots du pattern courant. y = profondeur, nodes par nature, NOW marqué/agrandi.

635 tests verts (2 nouveaux sur peek), analyze propre.
…a timeline

Retour : la timeline (points multiples) était illisible et saccadée. On revient
au visuel de la session que la joueuse connaît : MovementAnimation en rhythm,
auto-animé au tempo (beepEngine: null), un seul curseur qui glisse sur l'axe
head→full vers la profondeur cible courante (la plus profonde de la figure,
stable par phrase). Timeline + peek-viz retirés du contrôleur.

635 tests verts, analyze propre.
Retour debug : le pattern changeait à chaque phrase (jamais le temps de le
sentir → bips perçus aléatoires) et le visuel ne montrait pas la figure.

- MusicSessionEngine.repeatPhrases (défaut 4) : la même figure est conservée
  4 phrases avant régénération ; le pattern boucle en continu entre-temps.
  Expose cursor (tête de lecture).
- _PatternView (séquenceur) : 1 colonne/slot, y = profondeur réelle jouée
  (strike=plein, hold=anneau, release=point), tête de lecture sur le slot en
  cours — synchro à l'audio (même source). Montre la vraie figure + sa boucle.
- Remplace MovementAnimation (qui ne montrait que head↔profondeur-max).

635 tests verts, analyze propre.
Pour la mise au point : bouton bug dans l'AppBar du mode Music. Quand actif,
ignoreGating se propage bounds→générateur :
- profondeur libre jusqu'à full DÈS la phrase 0 (pas d'escalade lente),
- aucun plafond BPM, aucune limite de hold, tous les niveaux de figures.
Recrée le contrôleur au basculement (retaper le tempo). i18n fr/en/de/es.

636 tests verts, analyze propre.
…liers)

Retour : il manquait les points d'ancre (remontée entre frappes) et les holds
n'étaient pas des paliers.

- Ancre (release) = un cran AU-DESSUS de la plus haute des 2 frappes voisines
  (head/mid → tip), au lieu d'un head fixe gris. Ligne 'tip' ajoutée à l'échelle.
- Hold = palier plat épais à la profondeur tenue (au lieu d'un anneau).
- Frappe = creux plein ; ancre = sommet plein plus petit ; le tout relié en
  zigzag = la courbe du mouvement bouche (plonge sur le temps, remonte entre).

Tracé only, 636 tests verts, analyze propre.
…aque plongée)

Retour : on ne peut pas enchaîner 2 plongées sans ancre, et un hold doit être
encadré par des ancres. Mes figures (oo, o_o) violaient ça.

Refonte de la banque : une figure = suite de gestes (up, down) — up battements
à l'ancre puis down battements en bas (frappe + holds). Correct PAR CONSTRUCTION :
- jamais 2 frappes sans release entre elles,
- toute frappe précédée d'une ancre,
- holds encadrés par des ancres (plongée = strike+holds, bornée par des ups).
Soupape hold retirée (cassait la grammaire). Tests de validité grammaticale
(banque + sortie générateur).

637 tests verts, analyze propre.
Re-correction : l'ancre fait toujours 1 battement (« 1 fois sur 2 »), et un hold
SAUTE une ancre → la plongée (frappe + holds) doit être de longueur IMPAIRE
(1, 3, 5, 7 ; chaque cran impair = un saut d'ancre de plus). Mes plongées de 2
(longueur paire) cassaient l'alternance ancre/frappe.

Figure = liste de longueurs de plongée impaires, ancre implicite = 1 battement.
Tests durcis : ancre toujours suivie d'une frappe (unique), plongées impaires.

637 tests verts, analyze propre.
…re fluide

Retours :
- Le pattern changeait au milieu (régén sur frontière de phrase ≠ longueur du
  pattern). Désormais régén UNIQUEMENT en fin de boucle, après repeatLoops (4)
  boucles complètes. Test : tout changement tombe sur curseur 0.
- Courbe du mouvement arrondie (béziers quadratiques via les milieux de segment).
- Tête de lecture animée en continu : AnimationController par slot (durée =
  slotInterval), glisse du slot courant vers le suivant au lieu de sauter.

637 tests verts, analyze propre.
…urbe Catmull-Rom

Retours :
- 1er temps n'est plus une ancre : expansion réordonnée frappe→holds→ancre
  (la dernière ancre précède cycliquement la 1ʳᵉ frappe).
- Une frappe qui amorce un hold sonne comme un HOLD (1ᵉʳ temps seulement) ;
  les temps de hold suivants sont muets (SlotAction.sustained).
- Les ancres sonnent (tick léger, position tip).
- Courbe Catmull-Rom (cubiques de Bézier) : passe par les nœuds, ne coupe plus
  les angles sur les changements brusques.

637 tests verts, analyze propre.
Le lookahead de profondeur d'ancre tient compte de la première frappe (cyclique),
donc la dernière ancre reste bien au-dessus du premier temps.
@bbstudioapp bbstudioapp merged commit 9989d70 into develop May 30, 2026
5 checks passed
@bbstudioapp bbstudioapp deleted the feat/music branch May 30, 2026 13:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant