Lyrics (L) line)¶
STATUS: SPEC. §1–§7 sealed 2026-05-23 (deduction semantics of the inline
L)line). §8 (section lyrics blockLYRICS)) is DESIGN 2026-05-27, sealed decisions, implementation in progress (§8.4.1 pickup syllables sealed 2026-05-28) — seedocs/feature-lyrics-sections-and-editions.md. Binding and order ofL)are not redefined here: seeneumaRk_datapack.md §2.1and §3. Sealing decisions and residual notes in §7; code-side follow-up indocs/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_continuesif there exists a non-empty syllable after it within the same token (or if a continuation token-Xfollows).
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:
- Syllable excess ✓ — emits W131 like
D)(was a silent drop; parser follow-up indocs/feature-deduction-spec-audit.md). - §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-laand the per-syllable formpa- -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 insideLYRICS)entries only; they are not syllable-grammar tokens elsewhere (L),D)).- Inside
<…>the syllable grammar is identical toL)(§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 onHT)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 theLYRICS)block. For an author-only edition the name IS that edition's lyricsBy. The old per-language creditHCL) … [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