Files
intono/docs/superpowers/specs/2026-07-22-staff-rendering-design.md
T
lucianoandClaude Fable 5 21ad969b10 Initial commit: InTono — app KMP per l'apprendimento musicale
App Kotlin Multiplatform (Android/iOS/Desktop) con Compose Multiplatform:
controllo intonazione (YIN), pratica guidata, metronomo, ear training,
sfide, scansione spartiti via AI, spartiti bundled e pentagramma con
glifi tipografici, travature, battute e spaziatura proporzionale.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 10:06:59 +02:00

5.1 KiB
Raw Blame History

Design: rendering avanzato del pentagramma (InTono)

Data: 2026-07-22 — Approccio approvato: disegno raffinato su Canvas (nessun font musicale embedded).

Obiettivo

Migliorare la resa delle note sul pentagramma nelle viste Compose condivise con quattro upgrade approvati dall'utente:

  1. Glifi raffinati — teste "a pera" (Path), code curve riempite, ♯/♭ ben proporzionati, chiave a piena opacità e dimensione maggiore.
  2. Travature (beam) — crome/semicrome consecutive nello stesso beat unite da parallelogrammi inclinati.
  3. Battute — stanghette e cifre del tempo (es. 4/4) dopo la chiave.
  4. Spaziatura proporzionale — larghezza orizzontale ∝ √(durata in quarti).

Non-obiettivi: font SMuFL/Bravura (rimandabile senza rework: la geometria resta valida), pause, legature di portamento, chiavi miste nello stesso rigo, gambi condivisi tra righi.

Architettura

1. StaffLayout (esteso) — geometria pura, commonMain, testabile

  • quarterUnits(duration: NoteDuration): Double — WHOLE=4, HALF=2, QUARTER=1, EIGHTH=0.5, SIXTEENTH=0.25, THIRTY_SECOND=0.125.
  • beamGroups(notes: List<MusicalNote>, beats: Int, beatType: Int): List<IntRange> — gruppi di indici di note beamabili (durata ≤ EIGHTH) consecutive che iniziano nello stesso beat; gruppi di 1 elemento esclusi (tengono la coda). Il confine di beat spezza il gruppo.
  • measurePositions(notes: List<MusicalNote>, beats: Int, beatType: Int): List<Int> — indici dopo i quali cade una stanghetta (accumulo di quarti ≥ capacità battuta; best effort quando le durate non riempiono esattamente la battuta: la stanghetta cade al superamento, mai eccezioni).
  • spacingWeights(notes: List<MusicalNote>): List<Float> — pesi orizzontali sqrt(quarterUnits), normalizzati dal chiamante sull'ampiezza disponibile.
  • stemUp(relPos: Int): Boolean (regola esistente relPos < 4); per un beam group la direzione è quella della maggioranza delle note del gruppo.

2. StaffRenderer (nuovo file, ui/components) — estensioni DrawScope

Primitive di disegno usate da tutte le viste:

  • drawNoteHeadPath — testa a pera via Path (quadratiche, come il mockup approvato), piena per ≤ QUARTER, contorno per WHOLE/HALF; rotazione implicita nella forma.
  • drawCurvedFlag — coda a goccia riempita (Path cubica), 1–3 code per croma/semicroma/biscroma, specchiata per gambo in giù.
  • drawBeam — parallelogramma tra due estremità di gambo, spessore ~0.5·lineSpacing, pendenza limitata (±0.5·lineSpacing per nota); doppia barra per semicrome.
  • drawSharpGlyph / drawFlatGlyph — esistenti, raffinati (proporzioni del mockup).
  • drawBarline, drawTimeSignature(beats, beatType) — stanghetta sottile a tutta altezza rigo; cifre con TextMeasurer (bold, serif di sistema).
  • drawClef — glifo Unicode esistente ma a piena opacità/colore nota e dimensione maggiore.

Le funzioni esistenti in NoteRenderer.kt (drawNoteHead/drawNoteStem/drawNoteFlags) vengono sostituite dalle nuove primitive; il file viene aggiornato o assorbito da StaffRenderer.

3. Modello e parser

  • NoteSequence guadagna beats: Int = 4, beatType: Int = 4.
  • MusicXmlParser estrae la prima occorrenza di <time><beats>N</beats><beat-type>M</beat-type></time> nella prima parte; assente → 4/4.
  • Inserimento manuale, scale, esercizi, scansione AI: default 4/4.

4. Applicazione per vista

Vista Glifi Travature Battute+Tempo Spaziatura
StaffFullView ✅ ✅ ✅ ✅ (per rigo)
StaffContextView ✅ ✅ ✅ (se nella finestra) ✅
StaffPreview ✅ ✅ — ✅
StaffView (nota singola) ✅ — — —

L'evidenziazione (nota corrente oro, suonate sbiadite) resta identica. In StaffFullView il wrapping per riga passa da "N note fisse" a "capienza per pesi", con le battute che preferibilmente non si spezzano a fine riga quando possibile (best effort: si spezza sul confine di battuta più vicino che sta nella riga; se una battuta è più larga della riga, si spezza comunque).

Gestione errori

  • Sequenze senza time signature → 4/4.
  • Durate che non riempiono la battuta (anacrusi, parser lossy) → stanghetta al superamento della soglia; nessuna eccezione.
  • Beam group con note su registri lontani → pendenza della travatura clampata.
  • Sequenze vuote → nessun disegno (comportamento attuale).

Test

  • TDD (commonTest): quarterUnits per tutte le durate; beamGroups (coppie di crome, croma singola, confine di beat in 4/4 e 6/8, misto crome/semicrome, interruzione su nota lunga); measurePositions (4/4 esatto, 3/4, durate eccedenti, sequenza vuota); spacingWeights (monotonia, rapporto √); parser <time> (presente, assente, 6/8).
  • Verifica visiva: lancio dell'app desktop e ispezione di Esercizi/Spartiti/Pratica (screenshot).

Rischi

  • La resa estetica di path disegnati a mano richiede iterazione visiva: prevista una passata di tuning dopo la prima implementazione, con l'app desktop come banco di prova.
  • StaffFullView cambia il layout di wrapping: verificare l'auto-scroll alla riga corrente (già esistente) con le nuove larghezze.