Vai al contenuto

Collezioni (book / playlist)

Questo documento definisce il formato testuale con cui un insieme ordinato di brani — un book o una playlist — è rappresentato in un unico file neumaRk.

Una collezione non introduce un nuovo linguaggio: è un sottile strato contenitore attorno a una sequenza di brani, ciascuno dei quali è un documento neumaRk standalone già definito dal resto della specifica. Lo stesso parser legge brani singoli e collezioni; la distinzione avviene sulla prima riga del file.


1. Scopo e ruolo

Il formato collezione serve a esportare, condividere e re-importare un book o una playlist come singolo file .nrk, autonomo e leggibile.

Il principio guida è la portabilità: il file è una foto autosufficiente della collezione. Tutti i brani sono incorporati come snapshot (copie congelate), così che il file possa essere aperto e importato da chiunque, anche senza accesso ai brani originali e anche se gli originali cambiano o spariscono.

Tutto ciò che è specifico del prodotto (e non del linguaggio musicale) — copertina, tag curati, condivisione, ruoli, identificatori di database — non fa parte del file (vedi §7).


2. Collezione e brano singolo

Un file neumaRk può rappresentare:

  • un brano singolo, la cui prima riga è la dichiarazione di versione nrk:<major>.<minor> (vedi neumaRk_header.md §2);
  • una collezione, la cui prima riga è nrk-book:<v> oppure nrk-playlist:<v> (§3).

La distinzione è data dal solo contenuto della prima riga, mai dall'estensione del file: un .nrk può contenere l'uno o l'altra.

Un file che inizia con nrk: è un brano singolo come sempre: il formato collezione è retro-compatibile e non altera la lettura dei file esistenti.

Il termine collezione è un'ombrello concettuale usato in questa specifica e nel tooling; non compare mai come token nel file. Il tipo (book o playlist) è scritto direttamente nel marcatore di riga 1.


3. Riga di apertura e tipo

La prima riga di una collezione è una dichiarazione di versione che incorpora il tipo:

nrk-book:0.6

oppure

nrk-playlist:0.6

Caratteristiche:

  • è obbligatoria e deve essere la prima riga del file;
  • il tipo (book / playlist) è parte del marcatore: leggendo la prima riga si conosce subito sia che il file è una collezione sia di quale tipo;
  • la versione segue la stessa numerazione nrk: del linguaggio.

Differenza funzionale fra i due tipi:

  • un book è una raccolta curata di brani, senza override per-brano;
  • una playlist è una sequenza eseguibile in cui ogni brano può portare un override per-occorrenza (§6).

4. Header di collezione

Dopo la riga di apertura, ed eventuali righe vuote, può comparire un header di collezione: un blocco di direttive nella forma chiave: valore.

nrk-playlist:0.6
name: My Setlist
desc: live al Blue Note
Direttiva Significato Obbligatoria
name: nome della collezione no (consigliata)
desc: descrizione libera no

Le direttive di header:

  • usano : come separatore (stessa famiglia sintattica di nrk:);
  • precedono qualunque blocco-brano;
  • sono parte di neumaRk (come il titolo HT) lo è per il brano): identità testuale della collezione.

Una direttiva di header che compaia dopo il primo blocco-brano è fuori posizione (W155).


5. Blocchi-brano

Il corpo della collezione è una sequenza di blocchi-brano. Ogni blocco:

  • è un documento neumaRk standalone, che inizia con la propria riga nrk:<v> e prosegue con header e datapack come di consueto;
  • è una snapshot autosufficiente (il contenuto musicale è incorporato, non riferito);
  • è copia-incollabile come file .nrk valido a sé.

L'ordine dei brani è l'ordine dei blocchi nel file: non esiste una chiave d'ordinamento numerica.

nrk-playlist:0.6
name: My Setlist

nrk:0.6
First Tune (Author)
F 120bpm
F| Bb| C7| F|

nrk:0.6
Second Tune (Author)
Bb 90bpm
Bb| Eb| F7| Bb|

Una collezione priva di blocchi-brano è degenere (W156).


6. Override per-item (solo playlist)

In una playlist, ciascun brano può essere preceduto da una riga item: che ne dichiara gli override per quell'occorrenza nella scaletta. Gli override non sono proprietà del brano (lo stesso brano in due playlist può averne di diversi): sono uno strato della voce di scaletta.

item: transpose=+2 notes="capo 3" form=[Intro] [A|8]x2 [B|8] [A|8]
nrk:0.6
My Tune (Author)
…

Regole:

  • la riga item: si applica al blocco-brano che la segue immediatamente (il prossimo nrk:);
  • compare solo se c'è almeno un override; la sua assenza significa "nessun overlay";
  • è ammessa solo nelle playlist. Una riga item: in un nrk-book: è fuori contesto (W152).

6.1 Sintassi dei parametri

I parametri sono coppie chiave=valore separate da spazi. Il segno = distingue il parametro inline dalla direttiva di header (:), così nessun simbolo ha doppio ruolo.

Un valore:

  • è un token semplice (senza spazi), es. transpose=+2; oppure
  • è quotato "…" quando contiene spazi (testo letterale, quote-aware come nel resto di neumaRk), es. notes="capo 3".

Chiavi ammesse: transpose, notes, form. Una chiave sconosciuta è ignorata con diagnostica W153.

Chiave Tipo Significato
transpose intero con segno trasposizione di esecuzione (overlay, ±semitoni)
notes testo annotazione personale per questa voce
form grammatica FORM forma di esecuzione per questa occorrenza (§8)

6.2 form= consuma il resto della riga

Poiché la grammatica FORM usa essa stessa i container "…" per le label (vedi §8), il parametro form= non è quotato: consuma tutto ciò che segue fino a fine riga, verbatim. Per questo form=, se presente, è l'ultimo parametro della riga item:.

item: transpose=-1 form="Tema"[A|8]ff [B|4]x2 "Coda"[A|8]&fermata

Le virgolette qui sono interne alla grammatica FORM (label), non delimitatori del parametro. (Una forma su più righe è fuori scope in questa versione.)

Un transpose nullo (+0, no-op) e un notes vuoto non sono errori.


7. Modalità portabilità: dentro e fuori dal file

La collezione porta solo musica e struttura minima. Tutto ciò che è prodotto applicativo viene ricostruito all'import dai default, come quando si crea una collezione nuova.

Nel file (è neumaRk):

  • il tipo (nrk-book: / nrk-playlist:);
  • name: / desc:;
  • l'ordine dei brani (= ordine dei blocchi);
  • per le playlist: gli override transpose / notes / form su item:;
  • ogni brano per intero, come snapshot.

Fuori dal file (non è neumaRk):

  • copertina, tag curati, conteggio iscritti;
  • condivisione, ruoli, permessi;
  • identificatori di database dei brani;
  • la distinzione dynamic vs snapshot delle voci playlist (in export ogni voce è risolta a snapshot).

7.1 Il file è intenzionalmente anonimo

Per coerenza con il principio sopra, un file di collezione non porta alcuna identità d'origine: né identificatori di database, né chiavi di deduplicazione, né tag dell'host. È, di proposito, anonimo.

Conseguenza: l'identità di un brano (decidere se due brani sono "lo stesso") non è una proprietà del file — è una questione dell'host, e va derivata semmai dalla musica (vedi §7.2), mai da un'etichetta opaca incollata al file. Questa è una scelta deliberata: il formato non trasporta dati che esso stesso non comprende.

7.2 Semantica di import (comportamento dell'host)

Come un host re-importa una collezione non è definito da neumaRk — è comportamento applicativo. In leadsheet.app, dato che il file è anonimo (§7.1):

  • l'import di un book crea copie dei brani nella libreria dell'utente; l'import non è idempotente — reimportare lo stesso book ricrea le copie;
  • la deduplica ("ho già questo brano?") è rimandata e, quando arriverà, sarà basata sull'impronta melodica (proprietà della musica, condivisa con la ricerca) come advisory — non su una chiave esatta incollata al file. Una identità esatta e durevole, con un formato volutamente puro, non è ottenibile dal file; perciò la dedup nasce dalla musica, ed è per natura un suggerimento, non un automatismo.

7.3 Assorbimento dell'header all'import — modulo header-probe (lossless)

All'import, ogni brano viene separato in header (→ prop) e datapack (→ cont pulito), così il brano importato è coerente con quelli salvati da WRITE (header nelle property, corpo nel cont) e il round-trip export→import è lossless.

La separazione usa il vero parser (parseText, text→Music) esposto da un modulo WASM dedicato (createModuleHeader, sorgente wasm/src/neumark/header_probe.cppparseHeaderOnly), servito sotto assets/wasm/header/ e caricato lazy solo all'import. Il bundle WASM dell'app library resta volutamente snello (non include il parser nel boot normale); il costo del parser si paga una sola volta, alla prima importazione.

In view, che il parser lo bundla già, parseHeaderOnly è esposto sullo stesso modulo (createModuleView) e usato dal salvataggio persistito (collection-view.service), senza caricare un modulo separato.

Da parseHeaderOnly si ricavano:

  • prop: header completo nello shape DB (tit, crd{mus,lyr,arr,trs}, key, bpm, mtr, sty, sub, yr, alt[], lng[]) — valori dal Music parsato, presenza dai range di map.header;
  • cont: il datapack puro (righe dopo l'header, blank iniziali/finali rimossi).

Fallback: se il modulo non si carica, l'import ripiega sullo scan leggero (parse_block_header in collections.cpp, sottoinsieme dei campi) con l'header lasciato dentro cont, che WRITE normalizza al primo ri-salvataggio. Lo scan leggero resta quindi come rete di sicurezza, non più come comportamento primario.

Limite noto: la separazione richiede la riga vuota fra header e datapack (che generate_nrk emette sempre in export); su file scritti a mano senza quel separatore parseText non distingue header e corpo — stesso comportamento dell'import in WRITE.


8. FORM a livello di collezione

Il parametro form di una voce playlist (§6) usa la grammatica FORM) definita in neumaRk_play_and_form.md (box di sezione, durate informative |N, label, dinamiche, keyword, prosa libera).

8.1 Semantica: sostituzione

Se una voce playlist porta un form, esso sostituisce la sezione FORM) eventualmente contenuta nello snapshot del brano, per quella occorrenza.

Serve a dichiarare: "la forma con cui suoniamo questo brano stasera è questa, a prescindere dalla forma originale del brano."

  • override presente → l'item-form vince sulla FORM) del brano;
  • override assente → resta la FORM) del brano (se presente).

È una sostituzione, non una fusione.

8.2 Perché FORM e non PLAY

FORM) è posizione-indipendente (una sola, descrittiva, in fondo al documento; il player la ignora): si presta a essere dichiarata a livello di scaletta, dove esiste solo il riferimento a un brano.

PLAY) invece è posizionale/inline: il suo effetto dipende da dove è collocata fra i datapack (vedi neumaRk_play_and_form.md §2.1). A livello di scaletta non esiste una posizione interna al brano in cui iniettarla, quindi PLAY) non è esprimibile come override di voce. L'override di forma è perciò sempre descrittivo (FORM), mai eseguito: non altera il playback, ma ciò che i musicisti leggono come forma.

8.3 Risoluzione dei riferimenti

I box [NAME] dell'item-form si risolvono sui marker M) del brano della voce (lo snapshot che segue), con lo stesso modello di neumaRk_play_and_form.md §3.1. Un box che non corrisponde a nessun marker è reference broken: renderizzato come testo non eseguito, senza errore o warning (neumaRk_play_and_form.md §7.3). Questo consente forme di scaletta informali.

Le diagnostiche di box (W141 durata, W142 repeat, W143 keyword) restano applicabili; quelle di posizionamento del documento (W140 più FORM), W145 FORM) non in fondo) non si applicano all'item-form.


9. Regole di parsing

9.1 Split prima del parsing musicale

Il file di collezione viene suddiviso in blocchi alle righe nrk: prima di qualunque analisi musicale. Il parser di brano riceve solo il testo da una riga nrk: alla successiva.

Conseguenza: i token di collezione (nrk-book: / nrk-playlist:, name: / desc:, item:) vivono nei segmenti esterni ai blocchi e non possono collidere con il contenuto musicale.

9.2 Riconoscimento

  • prima riga nrk-book: → collezione book;
  • prima riga nrk-playlist: → collezione playlist;
  • prima riga nrk: → brano singolo.

L'entry-point del parser, che per i brani impone "nrk: deve essere la prima riga", accetta nrk-book: / nrk-playlist: come token di riga 1 alternativi. Sono prefissi letterali distinti: il riconoscimento è additivo e non ambiguo.

9.3 Righe vuote e versioni

  • Le righe vuote separano l'header di collezione dai blocchi e i blocchi tra loro, coerentemente con la delimitazione dei datapack (neumaRk_datapack.md §1).
  • Il blocco %%NAME … %%end (versioni del brano, neumaRk_versions.md) resta interno al singolo blocco-brano: è testo che il parser di brano vede, non un token di collezione. Lo strato collezione non usa %%.

10. Diagnostica

Codice Descrizione
W152 Riga item: in una collezione nrk-book: (i book non hanno override per-item)
W153 Chiave override sconosciuta in item: (ammessi transpose / notes / form)
W154 Riga item: non seguita da un blocco-brano (nrk:)
W155 Direttiva di header collezione (name: / desc:) dopo il primo blocco-brano
W156 Collezione priva di blocchi-brano

10.1 Non-errori

  • un blocco snapshot la cui FORM) è sostituita dall'item-form (§8.1);
  • un box [NAME] dell'item-form senza marker omonimo (reference broken, §8.3);
  • transpose=+0 (no-op) e notes vuoto;
  • righe vuote supplementari nell'header o fra i blocchi.

11. Esempi

11.1 Playlist con override

nrk-playlist:0.6
name: Friday Gig
desc: trio set

item: transpose=+2
nrk:0.6
Blue Bossa (Kenny Dorham)
Cm 140bpm
Cm| Fm| Dm7b5| G7| Cm|

item: form=[Intro|4] [A|16]x2 [Solos] [A|16] [Outro]&fermata
nrk:0.6
So What (Miles Davis)
M) [A] [Solos] [Outro]
…datapack…

La prima voce suona Blue Bossa trasposto +2; la seconda dichiara una forma di esecuzione che sostituisce quella del brano.

11.2 Book (nessun override)

nrk-book:0.6
name: My Real Book — Vol. 1

nrk:0.6
Autumn Leaves (J. Kosma)
…

nrk:0.6
All The Things You Are (J. Kern)
…

11.3 Collezione a un solo brano

nrk-playlist:0.6
name: Solo Spot

nrk:0.6
Naima (J. Coltrane)
…

Degrada in modo trasparente: una collezione con un solo blocco è valida.


12. Riassunto

Concetto Sintassi Sezione
Apertura book nrk-book:<v> §3
Apertura playlist nrk-playlist:<v> §3
Header collezione name: / desc: §4
Blocco-brano nrk:<v> … (snapshot) §5
Ordine ordine dei blocchi nel file §5
Override voce item: chiave=valore … (solo playlist) §6
Parametri voce transpose / notes / form §6.1
Forma di voce form= (grammatica FORM, rest-of-line) §6.2, §8
Sostituzione forma item-form sostituisce la FORM) del brano §8.1
Split alle righe nrk:, prima del parsing musicale §9.1
Portabilità tutto snapshot; copertina/tag/ruoli fuori §7

Questo documento definisce le collezioni (book e playlist) come strato contenitore portabile di neumaRk, costruito per riuso attorno ai documenti-brano e alla forma del brano (neumaRk_play_and_form.md).