Skip to content

Lyrics (L) line)

STATUS: SPEC. §1–§7 sealed 2026-05-23 (deduction semantics of the inline L) line). §8 (section lyrics block LYRICS)) is DESIGN 2026-05-27, sealed decisions, implementation in progress (§8.4.1 pickup syllables sealed 2026-05-28) — see docs/feature-lyrics-sections-and-editions.md. Binding and order of L) are not redefined here: see neumaRk_datapack.md §2.1 and §3. Sealing decisions and residual notes in §7; code-side follow-up in docs/feature-deduction-spec-audit.md.

The Italian version (../it/neumaRk_lyrics.md) is the source of truth; this English version mirrors it.

1. Definition

The L) (Lyrics) line carries the lyrics, syllable by syllable, aligned to the events of the N) line it is bound to. It is a parallel layer: it does not alter rhythm or flow. Multiple consecutive L) lines of the same staff form distinct verses (multi-verse).

2. Structural position and binding

Marker, optionality and logical order are defined in neumaRk_datapack.md §2.1 and §3 (implicit order: L) is optional and last of the note group). The line binds to the N) of its own group/staff; voice 2 (N2) has its own L) lines (see neumaRk_voices.md).

3. Count-based alignment (notes only, not rests)

The correspondence is positional by count, left-to-right, but computed only on note-events: rests do not receive syllables.

N) | c4 d  r  e  |
L) | la la    sol |
     ↑  ↑  ↑   ↑
     la la (rest: none) sol
  • The syllable counter advances only on actual notes (isNote()); on a rest an empty padding is emitted (preserving positional alignment).
  • Token . = placeholder: no syllable on that note (the note stays without text, but the position is consumed).
  • Tokens in excess beyond the number of notes: emits W131 (like D); aligned 2026-05-23, parser follow-up).

4. Multiple verses (multi-verse)

Each consecutive L) line of the same staff is a successive verse:

N) | c  d  e  f |
L) | first verse syllables… |     ← verse 1
L) | second verse…          |     ← verse 2
  • Limit: 10 verses per measure/staff; lines beyond the tenth are silently dropped.
  • Padding between verses: for each verse, the notes without a token receive an empty syllable, so all notes have the same number of verses (vertical alignment between verses).

5. Syllable mechanics

Each text token is a word, optionally hyphenated with -:

Form Effect
parola one syllable (whole word) on the current note
pa-ro-la three syllables on three consecutive notes; the non-final syllables are marked word_continues (hyphenation dash in render)
_ melisma: extends the previous syllable onto the current note (no new syllable)
. placeholder: no syllable (see §3)
-coda token with leading -: continuation of the previous word — back-marks the last emitted syllable as word_continues and adds coda as the next syllable (per-syllable form produced by the serializer)

Robustness details (as per parser):

  • the empty syllables generated by spurious hyphens (-mar, mar--ti, isolated -) are discarded;
  • a syllable is word_continues if there exists a non-empty syllable after it within the same token (or if a continuation token -X follows).

6. Examples

N) | c4   d    e   f    |
L) | Ave-       _    ma- ri-a  |
Ave (with hyphenation dash) → _ extends Ave onto the 2nd note → ma/ri continue; a closes the word.

7. Decisions (closed 2026-05-23) and residual notes

Closed:

  1. Syllable excess ✓ — emits W131 like D) (was a silent drop; parser follow-up in docs/feature-deduction-spec-audit.md).
  2. §5 mechanics normative ✓ — hyphenation, melisma _, continuation - as described.

Residual notes (current behavior documented as normative pending revision, non-blocking):

  • Cap of 10 verses: beyond the tenth, silent drop. Limit confirmed; the behavior on overflow (silent vs warning) remains revisable.
  • Rests: they always interrupt the syllable count — the rest receives no text and the melisma _ does not "skip over" it. Normative.
  • Canonical hyphenation form of the serializer: the parser accepts both pa-ro-la and the per-syllable form pa- -ro- -la; which form the serializer emits is a separate output choice (it does not affect parsing), to be fixed when working on the serializer round-trip.

8. Section lyrics block (LYRICS) line)

DESIGN 2026-05-27, sealed decisions. Roadmap and rationale in docs/feature-lyrics-sections-and-editions.md.

8.1 Purpose and relationship to L)

The LYRICS) block is an alternative way to write the same verses that L) writes inline. Instead of aligning syllables to the notes of a single datapack, it carries the text per section and lets the engine distribute the syllables onto the notes of that section automatically.

LYRICS) does not introduce a different lyrics model: it feeds the very same per-note Lyric slots that L) feeds (§3, §5). The inline L) line remains fully valid; the two compose (§8.5).

The equivalence with L) holds only for rendering, though: the block carries information the inline cannot express — the text edition (language + author, §8.8). For this reason the round-trip serializer preserves the form the user chose: a LYRICS) block re-emits LYRICS) (verbatim, language included), an L) line re-emits L). The serializer does NOT automatically convert one into the other; the explicit conversion is a separate user action (WRITE context menu, not yet implemented). Implementation: Music::lyrics_blocks (raw block + position) + Lyric::from_section (suppresses the inline re-emission of verses that round-trip as a block).

8.2 Syntax

LYRICS)
[A] sillabe del verso…
[B] sillabe del verso…
[A] (a second [A] line = a second verse on A)
  • The block opens with a line whose first non-blank token is the marker LYRICS) (optionally followed by an edition tag [language] [<author>], §8.8).
  • Each entry starts with a section reference [NAME] (§8.3). The text after the ] on the same line, and/or the following non-blank lines that do not start with [, are the syllables of that entry.
  • The entry text may optionally begin with a pickup group <…> — syllables that bind to the notes preceding the section (anacrusi). See §8.4.1.
  • Blank lines inside the block are allowed (visual separators): they do not close the block.
  • The block extends until the first top-level marker (LYRICS), PLAY), FORM), %%…) or datapack prefix (M) N) C) A) D) L) F)).

Both compact and expanded forms are accepted and equivalent:

LYRICS)                          LYRICS)
[A] this is a text       ≡       [A]
                                 this is a text

The syllable grammar is identical to L) (§5): pa-ro-la, melisma _, placeholder ., leading-- continuation.

8.3 Section reference and structural position

[NAME] is the same reference-by-name used by PLAY) / FORM) (neumaRk_play_and_form.md §3.1): matching is by text-equality on NAME with markup stripped (neumaRk_text_markup.md §7.1). A LYRICS) block may appear anywhere between datapacks; position in the file does not affect which section an entry binds to (binding is by name, like %%versions).

A reference to a non-existent section is reference-broken: warning W157 (non-blocking, §8.9).

8.4 Cross-datapack section aggregation

This is the defining behaviour: the syllables of [A] are spread over all the notes of section [A], concatenated in flow order, crossing datapack / system / measure boundaries. (Inline L) is by contrast local to one datapack.)

A measure belongs to the section whose marker is the most recent […] section marker at or before that measure (annotations "…" do not open a section, neumaRk_markers.md §3.1, §4.1). A maximal run of consecutive measures with the same current section is an occurrence of that section.

M) [A]
N) a b c d

N) e f g a          ← same [A], next datapack, no marker

LYRICS)
[a] mol-te sil-la be su_un ri-go
     └─ distributed over a b c d  e f g a (8 notes, one occurrence, cross-datapack) ─┘

Within an occurrence, the notes (first staff, voice 1 — §8.7) are collected in flow order and the syllables are bound exactly as L) (§3): positional by count, rests receive no syllable, . placeholder, _ melisma.

Multiple occurrences (template). If [A] occurs more than once (e.g. AABA), the entry is a template applied to each occurrence: the same syllables are bound, independently, to every occurrence. The syllables are not spread across occurrences. (Per-occurrence different text — e.g. verse 1 / verse 2 across repeats — is out of scope; use distinct names [A1]/[A2], §8.10.)

8.4.1 Pickup syllables (anacrusi) — <…>

DESIGN 2026-05-28, sealed decisions. Closes the gap where lyrics need to start before a section boundary (anacrusi / pickup). Roadmap in docs/feature-lyrics-sections-and-editions.md §2.5.

An entry may begin with a pickup group delimited by <…>. The syllables inside the group bind to the last N notes that precede the section's occurrence in flow order, where N is the syllable count of the group (after grammar expansion: pa-ro-la counts 3, _ and . count 1 each). Syllables after > continue normally on the first note of the section's occurrence (§8.4).

M) [intro]      | a4 a a a |
M) [intro]      | h2 r4 c,8 d |
M) [A]          | e         |

LYRICS)
[A] <do re> mi
     │      └─ first note of [A] occurrence
     └──────── last 2 notes of the previous section ([intro]: c,8 d)

Where the pickup lands. For each occurrence of [NAME], the pickup scans backward from the first note of that occurrence over the contiguous run of notes whose current section is not [NAME]. The N last notes of that run receive the pickup syllables (rests are skipped, exactly as in §3 / §8.6). The scan stops at:

  • the first note belonging to [NAME] (i.e. it never crosses into the occurrence itself or into an earlier occurrence of [NAME]);
  • the start of the piece (no notes before [NAME]'s first occurrence).

Voltas. When the preceding run contains voltas (|[1.]|[2.] …, neumaRk_flow_and_repeats.md §6), only the last volta contributes notes to the pickup; measures inside earlier voltas are skipped by the backward scan. The intuition is that the anacrusi is the run-up into [NAME] along the path the music actually takes the last time, i.e. the path that ends in [NAME]. Non-volta measures contribute normally.

Multiple occurrences (template). As with the post-> syllables, the pickup group is a template: it is applied independently to each occurrence of [NAME], each looking back into its own preceding run of non-[NAME] notes. In AABA, every [A] gets its own pickup from the notes just before it.

Overflow / no preceding notes.

  • If the preceding run has zero notes (e.g. the occurrence is at the very start of the piece), the pickup is silently dropped for that occurrence — no warning. The post-> syllables are bound normally.
  • If 0 < available < N (partial overflow: a run exists but is shorter than the pickup), the available notes receive the trailing pickup syllables, the leading excess is dropped, and W158 is emitted once per affected occurrence (non-blocking, §8.9). The post-> syllables are bound normally regardless.

Verse accumulation (same slot, OVERRIDE). A pickup occupies the same verse slot as its own body on the pre-notes, replacing any syllables already present on that slot (sealed 2026-05-28, after testing on a real piece):

  • The body occupies the section's natural slot, verseCount[NAME] — the first free row of the section, like an entry without a pickup (§8.5).
  • The pickup occupies the same slot as its own body on the pre-notes: pickupSlot = bodySlot. If the pre-notes already carry syllables on that slot (e.g. the preceding section's body singing those notes), the pickup overwrites them. Otherwise the pickup writes directly (with implicit padding).

The musical effect matches traditional notation convention: the verse of [NAME] literally "steals" the last N notes of its predecessor from the predecessor's same-position verse (same language / same slot), and sings the anacrusis there.

Multilingual example (Portuguese = slot 0, English = slot 1): the last 2 notes of [B] carry two pickup syllables — <E vol> of [A2] Portuguese replaces the last 2 syllables of [B] Portuguese body (slot 0); <So I> of [A2] English replaces the last 2 of [B] English body (slot 1). The body of [A2] (both languages) stays on its natural slots (0 and 1), in continuity with [A1] and [B]. To achieve this replacement, the engine processes all bodies first, then all pickups (so each pickup sees and overwrites the body already written on the pre-notes).

Constraints.

  • Only one pickup group per entry, and it must come immediately after [NAME] (and optional whitespace), before any post-> syllable.
  • Empty <> is a silent no-op: it is consumed without effect, no warning. The post-> syllables bind as if the group were absent.
  • < and > are reserved inside LYRICS) entries only; they are not syllable-grammar tokens elsewhere (L), D)).
  • Inside <…> the syllable grammar is identical to L) (§5): pa-ro-la, _, ., leading-- continuation. A leading-- continuation as the first token of the pickup group is dropped (there is no prior syllable in the same entry to continue).
  • Staff/voice binding is the same as the entry body: first staff, voice 1 (§8.7).

8.5 Verse accumulation (L) inline ++ LYRICS) blocks)

For each section, the verses stack top-to-bottom in this order (sealed 2026-05-27):

verses(section) = [ inline L) verses, in order ] ++ [ [NAME] entries from LYRICS) blocks, in file order ]

Each [NAME] entry (one line) adds one verse. Two [A] entries (same block or different blocks) add two verses, in order of appearance.

To keep verses vertically aligned across a section that spans several datapacks (whose inline verse counts may differ), the engine first pads every note of the section to n_inline = max(note.lyrics.size()) over the whole section, then appends each block verse at a uniform index across all occurrences.

The three fragments below produce the same two-verse result on [A]:

// (a) all inline               // (b) mixed                  // (c) all block
N) ...                          N) ...                        N) ...
L) tes-to u-no                  L) tes-to u-no                LYRICS)
L) tes-to du-e                  LYRICS)                       [A] tes-to u-no
                                [A] tes-to du-e               [A] tes-to du-e

8.6 Alignment rules (reused from L))

Identical to §3 and §5: positional-by-count on notes, rests receive no syllable, pa-ro-la hyphenation (word_continues), _ melisma, . placeholder, leading-- continuation. Syllables in excess of the notes of an occurrence emit W131 (as L) / D)); notes in excess of the syllables receive an empty Lyric{} (padding).

8.7 Staff / voice binding

A LYRICS) entry always binds to the first staff, voice 1 (the main vocal line) — sealed 2026-05-27. Lyrics for other staves or for voice 2 (N2) use the inline L) line, which already has that granularity (neumaRk_voices.md). The block trades per-voice granularity for per-section convenience.

8.8 Text editions (LYRICS) [language] [<author>])

DESIGN 2026-05-30, sealed (Phase B). Generalizes multilingual lyrics (Phase 3): "language" is now a special case of a text edition. Roadmap and decisions in docs/feature-lyrics-sections-and-editions.md §9-§11.

An edition is a version of the sung text; a song may have more than one. The LYRICS) header may carry two optional elements, on distinct axes that are also syntactically separate:

  • a language token (LYRICS) pt-br): a code (it, en, pt-br, …; form [a-z]{2,3}(-region)?, case-insensitive, lowercased) — a translation;
  • an author in <…> (LYRICS) en <Frank Sinatra>): who wrote that text. Free text, may contain spaces (the brackets delimit it: no disambiguation heuristics).
Form Nature
bare LYRICS) / inline L) neutral (default, no language/author)
LYRICS) pt-br language
LYRICS) en <Frank Sinatra> language + author
LYRICS) <Al Jarreau> author-only (no language)

An edition is identified by the pair (language, author). Multiple editions in the same language with different authors are allowed (vocalese / competing translations: en <Jon Hendricks> + en <Eddie Jefferson>).

Default — Rule A

  • neutral (no language, no author) → default text;
  • language (has language, ± author) → intrinsic, contributes to the default;
  • author-only (author without language) → opt-in, never the default.

The default text is the neutral one (inline L) or bare LYRICS)) if present, otherwise the first language edition in file order. If no intrinsic edition exists (only <author>-only) the song opens instrumental: no default text, editions are activated from the selector.

Selection

The active edition is a scalar user preference (one per song, key = the language+author pair), selectable from a menu — same pattern as %%versions but scalar. It selects which edition feeds the per-note Lyric slots; entries of the other editions are not bound. If the active pair does not exist among the editions, the default is used (Rule A); if there is no default (instrumental), no block lyric is bound.

The inline L) line is the neutral default edition: it accumulates per §8.5 when the neutral edition is the shown text.

Metadata (title / author)

  • Title per language: one title per language (shared by all editions of that language — it is the translation of the song's name, it does not change with the lyricist), declared with the [language] tag on HT) titles (neumaRk_header.md §8.1). It lives in the metadata, not in the block text.
  • Text author: for the default → global lyricsBy (HCL)); for a tagged edition → the <…> group on the LYRICS) block. For an author-only edition the name IS that edition's lyricsBy. The old per-language credit HCL) … [language] is removed (neumaRk_header.md §9.2).

The edition axis is orthogonal to the %%versions axis: two independent user choices that compose. Version filtering (applyVersionFiltering) rewrites the flow first; the active edition's lyrics then bind by-name to the remaining notes. Available languages are deduced from the LYRICS) blocks + the [language] tags on titles; there is no declaration header (HL) stays out of scope, like the optional HV)).

8.8.1 Title ↔ language bridge

A title carrying a language tag (neumaRk_header.md §8.1, e.g. HT) No More Blues [EN]) is bound to that code. Opening the song as that title (e.g. from a search result) preselects the language for the session: the shown title and the lyrics language follow that choice. This is a session override (like a URL parameter): it does not overwrite a saved user preference, which stays settable from the menu.

8.9 Diagnostics

Code Severity Description
W157 WARNING LYRICS) [NAME] references a non-existent section (reference-broken)
W158 WARNING LYRICS) pickup group <…> overflows the available preceding-notes run (run shorter than N, leading excess dropped) — one per affected occurrence; silent skip if the run is empty (§8.4.1)
W131 WARNING syllables in excess of the notes of an occurrence (shared with L) / D))

Both non-blocking. (Cap of 10 verses per note, §4, applies to the combined inline+block total.)

8.10 Out of scope (MVP)

  • Per-occurrence text (AABA singing different words each pass of the same [A]): not covered — verses stack vertically, they are not per-occurrence. Workaround: distinct section names [A1]/[A2].
  • Voltas |[1.] / |[2.] with different text per ending.
  • Multilingual binding to alternative titles (Phase 3, §8.8).

8.11 Examples

Two verses on [A], one verse on [B], [Instrumental] left untexted:

M) [A]      | c4 d  e  f |
M) [Instrumental] | g a  b  c |
M) [B]      | c  b  a  g |

LYRICS)
[A] this is the first verse here
[A] and here the second verse goes
[B] the bridge has its own words now

Cross-datapack occurrence (one [A] over two datapacks):

M) [A]
N) c d e f

N) g a b c

LYRICS)
[A] eight syl-la-bles a-cross two rows