Datapack¶
1. The datapack concept¶
A datapack is the fundamental structural unit of musical content in neumaRk.
It represents a vertical block of musical information synchronized in time, typically corresponding to one or more aligned staves:
- markers
- chords
- notes
- fingering
- articulations
- dynamics
- lyrics
- formatting
Each datapack describes a coherent temporal sequence (one or more consecutive measures). Musical datapacks are separated from one another by one or more blank lines, which act as structural delimiters. The presence of a blank line implies the conclusion of the preceding datapack.
2. Line types¶
Each line of the datapack has a semantic type.
The type may be:
- explicit, via a marker
- implicit, deduced from the content and position
2.1 Explicit line markers¶
Explicit markers are:
- one, two, or three uppercase letters
- followed by
) - followed by a space
| Marker | Type |
|---|---|
M) |
Markers |
C) |
Chords |
$) |
Fingering |
A) |
Articulations |
N) |
Notes |
D) |
Dynamics |
L) |
Lyrics |
F) |
Format |
The markers are optional, except $), which is never deduced (§3.bis.1).
$) (fingering, neumaRk_fingering.md) is the only marker whose first
character is not an uppercase letter: it stays 2 characters wide like the
others, preserving the vertical-alignment invariant.
The N) marker admits two morphological 2-character variants,
explicitly provided for by the specification:
N+— new staff introduced in this datapack (§4.5);N2— second voice on the same staff (seeneumaRk_voices.md).
The N2 and N+ tokens stay 2 characters wide (the ) is absent),
preserving the vertical-alignment invariant.
Likewise, the C) marker admits a variant:
C+— line of alternate chords above the base line (neumaRk_chords.md§7; comment-label and line scope in §8).
C+ too stays 2 characters wide (the ) is absent), like N+,
preserving the same invariant.
2.2 Complete inventory of explicit lines¶
The table in §2.1 lists the markers used inside a datapack. For completeness, this is the inventory of all the lines the language recognizes by marker or by structure (independently of the implicit deduction of §3.bis, which applies only to lines without a marker).
Datapack line markers (inside a datapack; order in §3):
| Marker | Type | Ref. |
|---|---|---|
M) |
Markers | neumaRk_markers.md |
C) / C+ |
Chords / alternate chords | neumaRk_chords.md §7 |
$) |
Fingering (fingers, strings, fixed position) | neumaRk_fingering.md |
A) |
Articulations | neumaRk_articulations.md |
N) / N+ / N2 |
Notes / new staff / voice 2 | §4.5, neumaRk_voices.md |
D) |
Dynamics | neumaRk_dynamics.md |
L) |
Lyrics | neumaRk_lyrics.md |
F) |
Format | §4 |
Block markers (between datapacks, global):
| Marker | Type | Ref. |
|---|---|---|
PLAY) |
Imperative execution sub-program | neumaRk_play_and_form.md |
FORM) |
Descriptive form overview | neumaRk_play_and_form.md |
LYRICS) |
Per-section sung text | neumaRk_lyrics.md §8 |
FOOT) |
Footnote definitions | neumaRk_footnotes.md |
TEXT) |
Prose positioned in the flow | neumaRk_text_line.md |
%%NAME … %%end |
Version/arrangement blocks | neumaRk_versions.md |
Structural lines (carry no musical content, not deducible):
| Line | Role | Ref. |
|---|---|---|
nrk:MAJOR.MINOR |
Version line (line 1; mandatory in the exchanged file) | neumaRk_specification.md §8, neumaRk_header.md §2 |
H…) |
Header markers (HT / HCM / … / HV) |
neumaRk_header.md |
| blank line | Structural separator (end of header / between datapacks) | §3.bis.1 |
// … |
Comment (whole line or trailing) | §11 |
line starting with -, then only - / spaces / % |
Vertical margin between datapacks; % = possible page break (-%) |
§10 |
3. Logical order of lines (implicit deduction)¶
In the absence of explicit markers, the type of the lines is deduced following the logical order:
- Markers (at most one line, optional)
- Chords
- one base
C)line + up to 2 alternativeC+lines (§3.bis.7, limit E127) - a second base
C)line is error E129 - Note groups, each composed of:
- one Fingering line
$)(optional, first; explicit only, never deduced — seeneumaRk_fingering.md) - one Articulations line (optional)
- one Notes line (mandatory in the absence of chords)
- one Dynamics line (optional, after — see
neumaRk_dynamics.md) - one Lyrics line (optional, last)
A datapack may contain from 1 to 4 note groups: each group corresponds to a staff of the system (see §4). 4. Format (at most one line, optional, always final)
3.bis Line-type deduction (normative algorithm)¶
§3 gives the logical order of the types; this section formalizes its deduction algorithm: how, in the absence of an explicit marker, the type of a line is determined from content + position. The rule written here is normative — it is the language, not an implementation detail. An explicit marker (§2.1) always wins: the algorithm that follows applies only to lines without a marker.
3.bis.1 Datapack structure¶
Only the Markers block and the chord block are unique per datapack (shared by the whole system, §4.1); everything else is a repeated note group, once per (staff × voice). The chord block is unique but positionable: its position among the note groups determines the staff above which it is rendered (cardinal rule in §4.1).
datapack ::= [Markers]? Item{1..} [Format]?
Item ::= NoteGroup | ChordBlock // ChordBlock at most ONCE
NoteGroup ::= [$]? [A]? N [D]? [L]? ( [$]? [A]? N2 [D]? [L]? )? // one staff (voice 1 [+ voice 2])
ChordBlock ::= ( [A]? C+ ){0,2} [A]? C) // chord band (base + up to 2 alt.)
- Markers: unique, always the first line (§8), shared.
- ChordBlock (chord band): unique per datapack and shared by all
staves (§4.1). It contains a single base
C)line and up to 2 alternativeC+lines immediately above (§3.bis.7, limit E127); each line may be preceded by its ownA)articulating its chord-rhythm (neumaRk_chords.md §4) — for this reasonA)is admitted beforeC)/C+. The block appears at most once: two baseC)lines are error E129. - NoteGroup (one staff):
Nmandatory, with optional$)/A)/D)/L)and an optional voice 2N2(with its own retinue, including its own$)). It repeats up to 4 staves (§4); with voice 2 it reaches 8 voice-blocks overall (§4.6). Voice 2 is not implicitly deducible — there is no way to distinguish, from content alone, "voice 2 of the same staff" from "new staff": it must always be declared with the explicit markerN2. $)(fingering): never deduced — the cascade of §3.bis.4 never produces the Fingering type; the line exists only with the explicit$)marker. It fingers the first notes line below it (N)/N+/N2), with the group'sA), if any, in between (neumaRk_fingering.md§2).- Format: unique, always final (§9).
Implicit chord-detection = head-only. The implicit deduction of the Chords type stays head-only (§3.bis.4, "open head" guard): without a marker, a chord-shaped line is read as chords only before the first notes line. A
ChordBlockplaced after the notes (between staves or below the last one) requires the explicit markerC)— which always wins the deduction (§2.1) and is rendered at the position where it is written (§4.1). This keeps implicit classification unchanged (no existing piece reclassified, §3.bis.8), opening anchoring only on explicit request.
3.bis.2 Computation model¶
Deduction is a single-pass, per-datapack automaton that scans the lines in source order while maintaining two pieces of state:
- the last-row type — the type of the last line (initially Empty);
- the head-closed flag — false until the first Notes line of the datapack appears, then true forever. It marks the boundary between Head and Groups.
The last-row-type guards are the transition function of the automaton: they
encode both the order within a group, and the loop-back that opens the
next group (re-entry on A or on N). The head-closed flag prevents Chords from
reappearing once the staves have been entered.
3.bis.3 Pre-filter: decorative lines¶
Before the cascade, a purely decorative line — composed only of
simple barlines, :, compact repeat forms (neumaRk_flow_and_repeats.md §6.1),
dots, spaces, and tabs — is skipped and is not interpreted as musical content (it remains
structural; its barline decorations belong semantically to the
other lines). Exception: the >/^ rescue in §3.bis.6.
3.bis.4 Classification cascade (normative precedence)¶
On non-decorative lines without a marker, a first-match-wins cascade is applied. The order is binding: the content predicates are deliberately permissive and do not over-match only thanks to (a) this order and (b) the position guard. The triad that defines each type is predicate + guard + exclusions.
Scope. This cascade classifies only marker-less lines that carry musical content, and produces exactly the 6 types below. Explicit markers (§2.1) and structural lines — version line, header, blank lines, comments, margins (§10) — are determined by marker or position, not by this cascade: the complete inventory is in §2.2.
| # | Type | Predicate (content) | Guard (position) | Exclusions |
|---|---|---|---|---|
| 1 | Markers | only […] / barline / spaces |
is the first line of the datapack | — |
| 2 | Chords | ≥1 valid chord (neumaRk_chords.md §3) + barline / . / % / comment-label / spaces — or only % (TB1bis) |
head open (head not yet closed) | not slash-only (TB2); not rest-only r/! (TB1) |
| 3 | Articulations | charset of the A) vocabulary (neumaRk_articulations.md) |
last-row type ≠ Articulations | not ^-only (TB3); not a valid notes-line (TB4) |
| 4 | Dynamics | dynamics charset (< > c d f m p s z - . \| :) |
last-row type = Notes | — |
| 5 | Lyrics | word-char + . - _ ' \| : (the most permissive) |
last-row type ∈ | — |
| 6 | Notes | default / sink | any | — |
Notes on the guards:
- Markers (line 1): the implicit deduction of Markers occurs only
on the first line of the datapack. A markers-shaped line further down is not
Markers (an explicit
M)remains possible wherever the spec allows it). - Chords (line 2): the normative guard for implicit deduction is
head open. Without a marker, a chord-shaped line is read as chords
only before the first notes line: for implicit deduction alone there is
no chord-row after a notes-row. This condition holds only for implicit
deduction, though: an explicit
C)(§2.1) is admitted also after the notes and is rendered as a chord band at the position where it is written (§4.1). In either case the chords stay unique and shared by the system (§4.1); the chord block is unique per datapack (§3.bis.1) and a second baseC)line is error E129. - Lyrics (line 5): the predicate is the most permissive (it matches almost any text). This is intentional: the permissiveness is contained by the position guard, and in any case the default type is Notes. It is not narrowed (that would be non-monotonic and would risk changing the classification of existing songs).
- Notes default: line 6 is the sink — every line that reaches the bottom of the
cascade is a notes line. It is also the loop-back mechanism: two consecutive
N)(both sink) are two distinct staves. In the implicit form the second notes line does not reach the sink: after a Notes line, a line of words and barlines is Lyrics (line 5), soc d e f |underN) c4 d e f |is a text line. For two staves, write the marker (N)orN+).
3.bis.5 Tiebreakers¶
Three disambiguations resolve cases in which a permissive predicate would match the wrong type:
- TB1 — rest-only → Notes. A line composed only of rest-tokens
(
r/!), barline,.,%, and spaces belongs to the stave (it is a notes line of rests only), not to an empty chord-row. It applies after Chords/Alt and after Articulations:
A7 ← Chords | > . ← Articulations
r ← Notes (TB1) r ← Notes (TB1)
A single rule — "rest-only forces Notes": the line is intercepted before Chords and Articulations, regardless of the preceding row type. A guard requires a real rest-token
r/!or%, so| > |(anacrusis) remains handled by the rescue (§3.bis.6).
!counts here as a token of a notes line, but it is not a rest: it is the repetition of the preceding note (neumaRk_notes_and_durations.md§5). A line of only!with no note before (! !underC) A7) is therefore Notes and gives E018.
- TB1bis —
%-only without a chord-row → Chords. A line composed only of%(plus barline,., spaces — no rest-tokenr/!) with head open and in the absence of a real chord-row in the datapack is a chord-row of measure repeats only: each%repeats the last chord-measure (even from the preceding datapack — the measure-repeat lookback is cross-datapack). It is the exception to TB1:%without a rest-token is not forced to Notes, but falls into the Chords branch (which already accepts%).
| C7 | F7 | ← Chords
| a b c | … ← Notes (closes the head)
| % | % | ← Chords (TB1bis): repeats C7, F7 from the datapack above
| d e f | … ← Notes
Rationale: "chords that repeat while the melody changes" is the most common lead-sheet pattern. Two guards keep the case narrow:
- no rest-token — a line with
r/!always remains Notes (TB1): rests are unambiguously notes;- no real chord-row seen yet — if the head already has a chord-row with a real chord, a subsequent
%-only line remains Notes (it is a notes-row of repeats), because the datapack's chord-row is already defined.Escape-hatch for the opposite reading (a second notes staff of only
%): the explicit markerN+/N). Note: a datapack of only%without a valid source measure (neither in this datapack nor in the previous one, cross-datapack) emits E300 (measure-repeat with no source); declare it withN)if the intent is a standalone notes-row of repeats.
- TB2 — slash-only → Notes. A line of standalone
/only (plus barline,., spaces) is slash rhythm (neumaRk_notes_and_durations.md §10), never a chord-row:/on its own is syntactically a quasi-chord but semantically a note event.
| / | / | ← Notes (TB2)
- TB3 —
^-only → Notes. A line of^only (plus|and spaces) is a notes line with ties only (neumaRk_notes_and_durations.md §6), not Articulations.
| ^ | ← Notes (TB3)
- TB4 — valid notes-line → Notes. A line that is a valid notes-line
has priority over Articulations, even if it falls entirely within the charset
of the
A)vocabulary. Necessary because some letters of the vocabulary are also note-names/durations: in particularg(composesglglissando, §9) is also the note G, and the durations1–4are in the charset →g,g4,g g gwould be read as articulations and stolen from A).
A7 ← Chords
g ← Notes (TB4), not Articulations
Predicate: every token (split on spaces; the barlines
|/:at the edges are stripped) is a valid note-token according to the canonical notes grammar (a single source, so there is no divergence), and at least one token carries a real pitch[a-g]/r. Articulation-only tokens (>,tr,-,o,gl,~…) do not match that grammar → the line remains Articulations; a placeholder line with no real pitch that contains,(| . , ^ |) → remains Articulations. A line of only.is decorative instead and is skipped (§3.bis.3), and one of only^is Notes (TB3). The>/^/~rescue (§3.bis.6) also applies. Principle: in case of A)↔N) ambiguity, the note wins.
3.bis.6 > / ^ rescue¶
A line that the pre-filter (§3.bis.3) would have discarded as decorative — because
> is readable as an anacrusis attached to a barline — but which contains >
or ^ and is entirely articulations-charset, is recovered as
Articulations (here > is an accent, not an anacrusis):
| > | ← Articulations (rescue): accent on the first event of the measure
The rescue is a branch outside the cascade and always produces Articulations (
articulations may open a group in any position); it is subject to the
same constraint (last-row type ≠ Articulations) and the ^-only exclusion (TB3).
Line type ≠ coloring of the leading
>. The rescue decides only the type (Articulations); the| > |example has the>between barlines, not leading, and it is an accent. When instead anA)/D)/L)line aligned to the C/N carries a leading>in the upbeat column, that>is colored as the upbeat marker (red), not as an accent — seeneumaRk_notes_and_durations.md §7(Upbeat column in column-aligned lines). They are two distinct levels: line classification here, positional semantics of>there.First line of the datapack. The rescue presupposes a context above it. In first position (
line_id == 0) a line| > |is read as Markers/anacrusis, not as an accent: the Markers pre-filter (cascade line 1) takes precedence, and>is ambiguous between accent and upbeat when there is no notes line below it to disambiguate. It is a deliberate choice: in the absence of context, the anacrusis reading prevails.
3.bis.7 Post-pass: AlternateChords promotion¶
The base classification assigns Chords to all implicit chord-rows.
A subsequent pass relabels as AlternateChords (C+, alternate chords
above the base) the consecutive chord-rows that precede the last,
only if no chord-row of the datapack carries an explicit marker (if
the user has chosen the markers, their choice is respected). Limit: max 2 alternate
lines per datapack (E127). Full spec in neumaRk_chords.md §7.
3.bis.8 Derivations and invariant¶
- The articulation classification charset (line 3) derives from the
closed vocabulary of
neumaRk_articulations.md(single source): deduction and rendering must not diverge. - Invariant: the line→type classification defined here is normative; every conforming implementation must produce it exactly.
- Classification (TB1bis): a
%-only line with no rest-token, in an open head and without a real chord-row, is a chord-row of measure repeats (§3.bis.5 TB1bis). - Classification (TB4): a line that is a valid notes-line but falls in the
A)charset (typically note-only lines likeg:g,g4,g g g) is a notes-line — the note wins (§3.bis.5 TB4).
4. Multiple staves per datapack (multi-stave)¶
A datapack may represent a system with multiple aligned staves
(e.g. melody + bass). The number of staves is given by the number of note
groups ($?A?ND?L?) present, up to a maximum of 4.
The number of staves may vary from one datapack to another in the same song: one datapack may contain a single staff and the next two or more, without prior declarations.
4.1 Elements shared by the system¶
The following elements are common to all staves of the datapack:
- the Markers line
- the chord block (base
C)+ optional alternativesC+) - barlines and measure decorators (voltas, change of
meter/key,
$,@,DC,FINE, etc.) - the key and meter of the system
The measure barlines cross all staves vertically.
Cardinal rule — chord-block position (anchor). The chord block stays a
single harmony shared by the system, but it is rendered at the vertical
position where it is written relative to the note staves. Let k be the
number of note staves (N)) written before the chord block (N2 does not
count: it shares its parent N)'s staff) and n the total number of staves in
the system:
| Position in source | Rendering |
|---|---|
| block at the head (k = 0) | chord band above the first staff (default) |
| block between two staves (0 < k < n) | chord band in the interline, above staff k (2-staff lead sheet, symbols between melody and bass) |
| block after the last staff (k = n) | chord band below the last staff |
Position does not multiply or bind the chords to a staff: it only changes
the vertical rendering, not the harmony. Markers (section letters,
voltas) always stay at the top, independent of the anchor. Per-staff
independent harmonies (a different C) for each N)) are outside the
model: the system harmony is unique.
A) binding. An A) always articulates the line immediately below it:
inside the chord block it articulates the following C+/C) (chord-rhythm,
neumaRk_chords.md §4); above an N) it articulates that staff. So a chord's
A) does not "slip" onto the notes staff below.
An A) without its row below — written under the datapack's last N), or
followed by another A) — articulates nothing: it is ignored and the parser
reports W174 (non-blocking). The exception is a datapack with no note rows,
where the A) applies to the automatic rests.
Example (2-staff lead sheet, chords between melody and bass — Sher style):
N) c8 d e f | g a b c // staff 0 (melody)
C) | CMA7 | DbMA7 // chord block: k=1, n=2 → interline, above the bass
N) (@F) c,4 e g e | c e g e // staff 1 (bass)
4.2 Elements independent per staff¶
Each group ($?A?ND?L?) defines an independent staff with its own:
- Notes (mandatory)
- Fingering, Articulations, Dynamics, Lyrics (optional)
- clef declared via the inline directive
(@…)as the first token of the Notes line — seeneumaRk_notes_and_durations.md§9. In the absence of a directive, the last clef defined in the context applies (default: treble clef). - ties, beams, and tuplets (all contained within the staff)
4.3 Horizontal alignment¶
The vertical alignment between staves is temporal: measure n of a stave sits
above measure n of the others. If a stave (or a voice, neumaRk_voices.md)
has fewer measures than the others, its missing measures are completed with
measure rests, with no diagnostics: N) c d e f | g a b c | above
N+ c d e f | gives the second stave a rest in the second measure.
4.4 Example¶
C) | Gm6 |
N) | g | a | bb | a |
N) | (@F) g,4 bb8 d e4 d | g,4 bb8 d e4 d | g,4 bb8 d e4 d | g,4 bb8 d e4 d |
A 2-staff system: the first in treble clef (default), the second
in bass clef. The chord Gm6 is common to the system.
4.5 Staff continuity between datapacks (N+)¶
When a song has multiple datapacks, each staff (stave) has a persistent identity: the "first staff" of a datapack is the same "first staff" of the previous one — it inherits pitch context, the clef in effect, durCtx, and is rendered in the same vertical position.
The base identity is positional: the first N) of the datapack
corresponds to the first N) of the previous datapack, the second to the
second, and so on.
To add a new staff in a datapack without breaking its
identity with the previous ones, the N+ marker is used in place of N).
N+ <content of the new staff>
The + replaces the ) (it does not add to it): the prefix remains 2
characters as with N), so the vertical alignment of measures remains
identical between adjacent staves.
Rules¶
N)= continuation of the next staff of the previous datapack (FIFO, in source order). It inherits its context (pitch, clef, duration).- A datapack without note lines (an
M)on its own, a chords-onlyC)) does not count: the "previous datapack" of anN)is the last one that had staves. The automatic rests or slashes of the note-less datapack sit on the first of those staves, in its clef. N+= new staff introduced in this datapack. It starts with fresh context (the orientation reference depends on the initial clef, seeneumaRk_notes_and_durations.md§2.2).- Source order = visual order top→bottom. The
N+marker determines only the identity (new vs. continuation), not the position. N+in the first datapack of the song is admitted but redundant (in the absence of preceding datapacks all staves are necessarily new); it is treated as a normalN).- Unchanged limit: max 4 total staves per datapack (sum of
N) N+).
Errors¶
- E122 —
N) without match: the datapack has moreN)than the number of staves the previous one had.N+is missing (or there is oneN)too many). - W168 (non-blocking) —
N+redeclared: the datapack has at least oneN+and some stave of the previous one is left without anN)continuation. TypicallyN+was repeated in every datapack instead of being written only where the stave is born.N+does not consume the FIFO queue: the previous stave is orphaned and theN)that follows binds to the identity of the stave above, inheriting its clef and octave reference (typical symptom: the rests of a bass-clef stave drawn at treble positions, above the staff). Fix:N+only in the datapack that introduces the stave,N)in every later one.
Example¶
Datapack 1 (intro with 2 staves, treble + bass):
M) [intro]
C) G7
N) |: <d b>2 <e c>4 | <f d>2 <e c>4 :|
N) (@F) g,4. d'8 e d | f4. d8 e d
Datapack 2 (Theme): adds a third staff at the top (vocal melody), keeping the intro's treble and bass in the middle and at the bottom.
M) [Theme]
C) > | G7
N+ > d8 | b'^ | b2 r8 d,
A) > | . ! | . !
N) > |: <d b>2 <e c>4 | <f d>2 <e c>4 :|
N) > | g,4. d'8 e d | f4. d8 e d
Resulting mapping (for the engine, not visible in source):
| source row | type | identity |
|---|---|---|
1 (N+) |
new | new stave (top) |
2 (N)) |
cont. | = first intro staff (treble) |
3 (N)) |
cont. | = second intro staff (bass) |
The context (last_pitch, clef, durCtx) of staves 2 and 3 of the Theme comes from staves 1 and 2 of the intro; staff 1 of the Theme starts fresh.
Cases not covered¶
The N)/N+ syntax expresses the common situation "I add a
staff at the top/in the middle/at the bottom", but it does not cover:
- selective drop (a datapack that keeps the 2nd staff of the previous one but drops the 1st);
- reorder (swapping the visual order of existing staves while keeping their identity).
For these cases a future extension could introduce an explicit
reference to the staff's internal ID (e.g. N<n>)). Not spec'd in this
version.
4.6 Second voice per staff (N2)¶
Each staff may host an independent second voice, introduced
by the N2 line. Stave and voice are distinct concepts: the staff is the
pentagram, the voice is a musical stream within the staff.
Voice 2 shares the clef, key, meter, barlines, and chord line with voice 1, but maintains an independent persistent musical context. Voice 1 has stems up, voice 2 stems down.
The complete specification of syntax, context, binding, and diagnostics
is in neumaRk_voices.md.
5. Datapack validity rules¶
A datapack is valid if:
- it contains at least one Notes line or one Chords line
- it respects the logical order of the lines
- all musical lines are temporally alignable
It is invalid:
- a datapack with only text lines
A misplaced or duplicated Format line does not invalidate the datapack: it is corrected with W182 (§9).
6. Barlines and measures¶
All musical lines (Markers, Chords, Articulations, Notes, Dynamics, Lyrics):
- contain barlines — simple and compound — and any measure decorators (in the absence of barlines all content belongs to the first measure of the staff)
- implicitly define the division into measures
The Format line is the only exception: it does not admit barlines (neither simple nor compound) and no measure decorators (§9).
The first barline. The opening barline of a row is optional when only the row identifier and context objects come before it (§7.3: meter, key, clef, harmony in effect, pitch reference, also in a mixed group). Context does not create a measure: it holds for the measure that follows, as if it were glued to the entry barline. Anything else — a label, a note, a chord symbol, a text — makes the segment a measure, and the barline that closes it is a real barline.
C) (Bb) | Eb7 F | // one measure in B-flat: (Bb) is the key
N) | g2 a |
C) [Intro] | Eb7 F | // two measures: the first carries only the label
N) | r1 | g2 a |
N) (3/4) | c d e | // one 3/4 measure, like N) |(3/4) c d e |
6.1 Supported barlines¶
The admitted barlines are:
|simple barline||double barline|.or.|end|:repeat start:|repeat end
The final barline is written preceded by a space (c d e f |); the glued
form c d e f| is tolerated and read the same way.
6.2 What sits between quotes is text¶
A | between double quotes "…" is not a barline, and a > between quotes
is not a pickup: they are characters of the text. This holds for every quoted
container — chord comment-label, note annotation, M)/A)/D) label — so a
label may contain those signs without splitting the measure:
C) | C-7"a | b" | F7 | // two measures, the chord keeps its label
M) | "a > b" | // one measure, no pickup
Conditions, the same as for comments (§11):
\"is a literal quote and does not delimit;- an unclosed quote protects nothing: a stray quote must not swallow the barlines to the end of the line;
- exception: on sung
L)rows and insideLYRICS)blocks the quotes are word characters, so they do not protect there and a|splits the measure as usual.
7. Measure decorators¶
Measure decorators are elements adjacent to a barline and are divided into:
- BEGIN decorators (to the right of the barline)
- END decorators (to the left of the barline)
7.1 BEGIN decorators¶
Positioned immediately to the right of the barline.
Possible decorators:
- Measure-context object — see §7.3. Within parentheses, it declares meter, key, clef, the harmony in effect and a pitch reference:
|(3/4,Dm)
|([3+3+2]/8)
|(@F)
|(3/4,Ab,@F)
-
Volta endings
-
text within square brackets
- optional
+nfor the duration in measures
Example:
|[1.]+4
In the absence of +n, the volta closes automatically at the first :|
encountered within 4 measures (the typical case of [1.] endings).
If no :| appears within the following 4 measures, the volta indicates
an exit — use +n for non-standard cases.
$segno@coda
Order within the BEGIN group is free: |(3/4)[1] and |[1](3/4) are
equivalent. The canonical form — the one serialization
writes back — is |[1](3/4): flow touches the barline, context touches the music.
7.2 END decorators¶
Positioned immediately to the left of the barline.
Supported decorators:
DCDCal@DCalFINED$D$al@D$alFINEFINEal@- free text within square brackets (graphic annotation)
The $ and @ signs are BEGIN decorators (see §7.1): they identify the point where the segno or coda is located, not a jump toward them.
7.3 Measure-context object¶
The context that "runs" between measures — meter, key, clef in effect, the harmony that persists, the pitch reference and the current duration — is writable. You declare it with a parenthesised group at a measure boundary.
C) A7 |[1](@F7) !(2.) | G7 (@D7):open|
N) a |[1](3/4,F,@F,f@4_8) b c d | g2. (5/4,D,@G,d@2_2):open|
(:open| is the "open" repeat end, neumaRk_flow_and_repeats.md §6.1.)
Components¶
@ means set; what it sets is decided by the row it is written on.
| form | meaning | level |
|---|---|---|
3/4, [3+3+2]/8 |
meter (allowed forms in neumaRk_header.md §10.4) |
measure (shared across rows) |
Ab, Dm, X |
key | measure (shared across rows) |
@F, @G8vb, @C3 on an N) row |
clef | stave |
@F13, @NC on a C) row |
harmony in effect | stave (chord block) |
f@4_8 on an N) row |
pitch + duration reference | stave |
Separators: comma, space or both. Components may appear in any order; the canonical one is the order of the table (meter, key, clef, reference), and that is what serialization writes back.
f@4_8 is not new syntax: it is the absolute octave of
neumaRk_notes_and_durations.md §2.3 (@<n>_ plus a mandatory duration), used
here as a reference rather than as a note. It fixes pitch and duration for
the music that follows, without writing any note.
A clef is not allowed on a C) row (the clef belongs to the stave, and the
stave is declared by N)): there @ always means harmony. That is why (@F),
(@G) and (@C) on C) are chord symbols (F, G, C major), with no
diagnostics; a form that can only be a clef ((@F8vb), (@G8va), (@G8vb),
(@F8)) gives E135; (@C3) or (@F4) are read as chord symbols with an
unknown suffix (W103, neumaRk_chords.md). The pitch reference f@4_8
belongs to the stave and is written on N): it is not a component of the chord
row. On C) and C+, glued to the barline or before the first barline, it
gives E135 (E135.pitch_ref_on_secondary_row), as on the other rows that do
not carry the stave (below).
Also on C) the position decides, because the parenthesis slot inside the
measure is already taken by the optional chord (F13) and by chord rhythm, and
a bare key would be indistinguishable from a chord symbol:
- a group glued to the barline (
C) |(Bb) Eb7) or written before the first barline (C) (Bb) | Eb7, §6) is context:(Bb)is the key; - a group detached after the barline or inside the measure
(
C) | (Bb) Eb7,C) | Eb7 (Bb) F) is an optional chord; - a detached meter at a measure boundary is context on
C)too (C) | (3/4) G |,C) | G (3/4) | G |): a chord symbol never has the shape of a meter.
C) |(Bb) Eb7 F | (Bb) Eb7 F | // 1st measure: key of B-flat; 2nd: optional chord Bb
N) | g2 a | g2 a |
X as a context key means "no key signature", like C.
Position¶
Measure boundaries only. The barline may be explicit or implicit:
N) (@F) >g8 | … is valid — it is the entry boundary of the first measure of a
row that does not open with |.
The segment before the first barline of a row becomes a measure only if it
contains something that is not context (§6): N) | a is a single measure, and
so is N) (@F) | a — the (@F) is the entry context of the a measure,
exactly as in N) |(@F) a or N) (@F) a. In this position the group (or the
sequence of groups) is read as glued to the entry barline, with the same
vocabulary, on every measure row (N), N2, N+, C), C+, M), A), D),
L), $)). On a C) row (Bb) | Eb7 F | is therefore one measure in B-flat
and (@F13) | G one measure with the declared harmony, while (F13) | G — an
optional chord, i.e. music — stays two measures. A label makes the barline real:
C) [Intro] | Eb7 F | has two measures, the first with the label alone.
Free position. A detached group at a measure boundary, in a measure with no
notes (the M), A), D), L), $) rows, or a C) measure), follows the
adjacency rule below: before the content it is an entry, after it an exit that
holds from the next measure. A label counts as content: M) (3/4) [Intro] |
puts 3/4 on the label's measure, M) [Intro] (3/4) | from the measure after. On
the M), A), D), L), $) rows, only the meter is context in free
position: a detached key or pitch reference is not context, and the row reads it
with its own grammar (in L) | la (E) | (E) is a syllable). The M) grammar
has no place for them (neumaRk_markers.md §7): M) | (Bb) | gives W186
(W186.key_free_position) and the group is discarded; to change key, glue it to
the barline, M) |(Bb) |.
Rows that do not carry the stave. Clef and pitch reference belong to the
stave and are written on N) (the clef not on N2, neumaRk_voices.md). On
M), A), D), L) and $), glued to the barline or before the first
barline, also in a mixed group, they give E135
(E135.clef_on_secondary_row, E135.pitch_ref_on_secondary_row): the component
is discarded and the N) stave stays as it was; the other components of the
group hold (A) |(3/4,c@3_8) . declares 3/4). The pitch reference gives the
same E135 on C) and C+ too, with the chord row above or below N)
(C) |(3/4,c@3_8) G . . | declares 3/4, the N) notes do not change). Meter
and key belong to the measure and may be written on any row.
The adjacency rule, in one sentence: context touches the music, flow touches the barline.
- entry (music to the right):
|[1](3/4,@F) c4 - exit (music to the left):
d (@G,a@4_2):open|
The space sits on the music side only, and it is mandatory: glued to a note, a parenthesis is a note decoration (notehead, glissando, chord rhythm). A lone group in a measure with no music is an entry declaration: it has no music to its left to touch.
Mid-measure, only a clef change ((@F)) is allowed: that is not measure
context, it is a notational event falling on a note. Other components outside a
measure boundary are rejected → E136.
An invalid meter ((1/1), (5/3), (0/4), (3/32): the denominator must
be 2, 4, 8 or 16 and the numerator at least 1) is rejected with E136
(E136.context_meter_invalid): the component is discarded and the previous
meter stays. In a group with several components the others hold ((3/4,1/1)
means 3/4). The same criterion applies to the header meter
(neumaRk_header.md §10.4, E303).
The decorator may also be adjacent to an anacrusis barline > (besides
|): >(@F), >(3/4,Ab,@F). Glued >( … ) is disambiguated from the
reprise pickup (neumaRk_notes_and_durations.md §7.2) by the first
character after >(: a context-starter (@, digit, [, uppercase A–G) →
context object; a lowercase note / r / s → reprise pickup. (Keys use
uppercase, pickup notes lowercase: no collision.)
Semantics¶
- Measure-level components (meter, key) stay shared across the rows of
the datapack: the first row that writes wins. If a later row declares a
different meter or key for the same measure, its value is ignored with
W159 (
W159.context_conflictfor the meter,W159.context_key_conflictfor the key), and the rewritten document gives it the first row's value:C) |(3/4) G . . |aboveN) |(2/4) a4 b |gives a 3/4 measure. The same rule applies to flow decorators that differ between two rows of the same measure (segno, coda, jumps: W159, the row written first wins;neumaRk_markers.md§2.1). - Stave-level components (clef, harmony, reference) belong to the row they
are written on:
N+andN)in the same datapack each declare their own reference, with no conflict. - A component produces no sign by itself. Meter, key and clef are drawn
because they change something the reader must see; a reference and a harmony
have nothing to show. A clef is printed before the music that consumes it;
if the change falls on a system break, at the start of the new system — and,
when the courtesy announcement is on, also at the end of the previous system
(a renderer preference: where you write the object changes nothing).
Only the initial clef of the stave sets the octave reference
(
neumaRk_notes_and_durations.md§9); later changes are graphical only. - The object does not count a position for
A)andD)alignment: adding one does not shift the articulations of the measure (like the slash/, which counts as a single position). - Announced twice at the same instant (
…(5/4,D)|(3/4,F)…, also across a double barline(5/4,D)||(3/4,F)): W169, and the entry wins — it is the one closest to the music it affects. Two barlines separated by a space ((5/4,D)| |(3/4,F)) enclose an empty measure instead: they are two different instants, no warning. - An exit context on the last measure of the piece has no music to apply to: it has no effect, and the rewritten document does not keep it.
Context-only row and datapack¶
An empty measure carrying a declaration is still a measure:
N) c d e f | (@F) | c d e f | has three measures, exactly like
N) c d e f | | c d e f |. The clef change is printed at the start of the
second one, above its whole-measure rest.
What may go unprinted is the row:
- a row whose content is nothing but context objects (
N) (@F) |) is not printed; - a datapack whose rows are all like that takes no space in the score: no staff, no default rest, no bar number, no duration in the flow. What it declares applies to the music that follows, and is drawn there (clef, meter, key);
- in a datapack that does print, a context-only stave row (
N)/N+) does not appear in that system, in any position —N) c d e f |aboveN+ (@F) |gives a one-stave system, and the second stave appears from the datapack where it has music; the same withN) (@G) |aboveN) (@F) c e g e |. Chord symbols and markings of the other staves stay with their staves. If removing the context-only staves would leave none (anM) [A]aboveN) (@F) |), they print normally.
This is how you declare context without writing music: at a precise point in the
piece, or inside a %%NAME block that may not be rendered.
A context-only datapack that declares no staves (C) |(@F13) | on its own) does
not break stave continuity either: the N) of the next datapack continues the
staves from before.
A flow sign (repeat, volta, decorator), a marker or written harmony make a system exist even without notes: a datapack carrying them prints.
8. Markers line¶
The Markers line:
- contains markers enclosed within square brackets
- the markers refer to the start of the measure
If a marker is preceded by a barline:
- there must be a space between the barline and the marker
- to avoid ambiguity with the volta decorators: a glued
|[A]is read as a volta labelledA, not as a section
It is best practice to place the flow decorators (DC, D$, coda, etc.) on this line.
9. Format line¶
The Format line is always declared with the explicit marker F): there is
no implicit form, and a line of alignment marks only without F) (|*|) is a
notes line (invalid).
The Format line, if present, must be:
- unique per datapack;
- always the last line of the datapack;
- made of one alignment mark, with optional side content (§9.1) before and/or after it.
It contains alignment indications:
| Symbol | Alignment |
|---|---|
\|* |
LEFT |
*\| |
RIGHT |
\|*\| |
CENTER |
\|**\| |
JUSTIFIED (default) |
A mark counts only as a whole token, separated by spaces or by the edge of
the line: [A|8], **x**| or \|* are not marks. If the line carries more
than one, the first one holds.
9.1 Content beside the system¶
A system aligned left, right or center leaves free space. The F) line can
fill it: the content goes on the side of the mark where it is written.
F) |* On cue, D.S. al @ // system on the left, text on the right
F) Dal $ al @ poi [Coda]&fermata *| // text on the left, system on the right
F) Solo: |*| [B]x2 // centered, content on both sides
The content of each side is a FORM) body (neumaRk_play_and_form.md
§3–§6), with no quotes around it: markup-aware prose, section boxes with their
labels and postfixes, the $ @ &fermata tokens, ; to break the line,
footnote references [^…] and links [text=>url]. As in FORM), nothing is
executed: a $ or @ written here is not a flow sign, and a box that matches
no M) section is drawn as an unresolved reference, with no diagnostics. To
write an alignment mark as text, prefix it with \ (\|*). A // opens the
trailing comment, as on every line (§11).
The content is laid out in the free space, at a fixed distance from the system, and vertically centered on the system's staves. If it does not fit on one line, the system narrows (up to one fifth of its width, never below the minimum space of its measures) and then the content wraps.
The free space depends on the alignment: on the right with |*, on the left
with *|, on both sides with |*|; with |**| there is none.
9.2 Diagnostics¶
An F) line that breaks these rules emits W182 and is corrected:
- it is not the last: it is moved to the end (
W182.not_last); - there is more than one: the last one holds (
W182.duplicate); - it carries no mark as a whole token, or more than one: the line is ignored,
or everything from the second mark on (
W182.extra_content); - it carries content on the side the system occupies (
F) text |*,F) *| text) or with|**|: that content is ignored and the alignment holds (W182.no_room).
Boxes in side content have the same diagnostics as FORM) (W141, W142,
W143, E302).
10. Margins between datapacks¶
A line:
- that follows a blank line
- that begins with
- - that contains only
-, spaces, tabs, or%
indicates a vertical margin between datapacks.
The depth of the margin is given by the maximum number of consecutive -:
- - -→ margin 1- -- -→ margin 2--- -→ margin 3
The % symbol indicates a possible page break.
A line of only %, with no leading -, is not a margin: it is a chord row of
measure repeats only (§3.bis.5, TB1bis). The page break is written -%.
11. Comments¶
Single-line comments are permitted in the form // comment. The scope is the line: everything that follows // up to the newline is ignored by the parser, except in the two cases below. Comments may stand on their own line (above/below a datapack, or between the lines of a datapack) or trail any line of code, including the header lines of the free-text blocks (TEXT), INFO), PLAY), FORM), LYRICS), FOOT)).
The discriminant between the two forms is positional: if there is at least one non-whitespace character before //, the comment is trailing (the part of code before // remains valid); otherwise the entire line is a comment.
Two contexts protect a //, that is, make it text rather than the start of a comment:
- Inside a link
[text=>url]— including the escaped form\[text=>url]— the//is part of the address (https://…, seeneumaRk_text_markup.md§3quater). A//after the closing]is a comment again. - Between double quotes
"…"— chord comment-label, note annotation,M)/A)/D)label,INFO)label and body: what sits between quotes is text, as already holds for barlines (§6.2: a|between quotes does not split a measure).\"is a literal quote and does not delimit, and an unclosed quote protects nothing (a stray quote must not swallow the comment to the end of the line). Exception: on sungL)rows and insideLYRICS)blocks the quotes are word characters, so they do not protect there and the//opens a comment.
A comment is preserved where it is written: neither its position nor its form changes when the document is rewritten (a change of format, neumaRk_formats.md, or a rewrite by an editor). A comment on its own line inside a free-text block stays inside that block, on its line.
Multi-line comments (/* … */) are not supported: NRK is a column-sensitive language (the barline | align the lines of the datapack vertically) and block comments would break the alignment.
The sequence %% at the start of a line is not a comment: it is reserved for the song version blocks (see neumaRk_versions.md).
11.1 INFO) — a comment meant for the reader¶
// comment lives in the source only. INFO) is its second-level twin: it
carries text meant for the reader but not for engraving.
INFO) "Transcriber's note" Transcribed from the 1961 record;
the chord symbols follow the original lead sheet.
The body is not part of the score: it is not drawn among the signs and enters no
export (PDF, MusicXML, LilyPond). An interactive rendering may make it
available on request at the point it is anchored to; a static one omits it.
The text has no musical value: no construct references it, no execution reads it.
It does travel with the document, always: every .nrk file saved, exported
or shared keeps it.
Reference rendering (informative). When there is a body, the interactive rendering places a small ⓘ icon after the label (or on its own, if there is no label) that opens it; a label without a body has no icon. Static exports (PDF) drop the icon and keep the label.
Form. Marker at the start of the line; the prose may start on that line or
below. Following lines are continuation, markup-aware prose
(neumaRk_text_markup.md). The block ends at the first of the three things in
§13: an unescaped line marker, a blank line, the end of the document.
Label. Right after INFO) there may be a "…" container, which must close
on the same line: it is an engraved label, score in every respect —
printed, exported, part of the fit and the pagination. Whatever follows the
closing quote is already body; the two forms
INFO) "Studio version"
The studio take opens with …
INFO) "Studio version" The studio take opens with …
are equivalent. A body that must start with a quote writes it escaped (\",
neumaRk_text_markup.md §4). A label without a body is a legitimate use: a
short caption, with nothing to open.
Anchoring: the position in the source. There are no reference labels: where
you write INFO) decides what it refers to.
Where INFO) sits |
Anchor |
|---|---|
among the header lines (H…)), with no blank line in between |
the header |
| before the musical rows of a datapack | the head of that datapack |
| after the musical rows of a datapack | the tail of that datapack |
| alone, between two blank lines | standalone, where it stands |
at the head of a %%NAME block |
the version block (neumaRk_versions.md §2.4.3) |
The blank line ends INFO) precisely because that is how the author says what it
refers to: detached from the music below it stands alone, attached it is that
music's head. The body is prose: it breaks lines on the source newline, and ;
is a literal character in it (neumaRk_text_markup.md §3bis).
One per anchor: at most one INFO) in the header, one at the head and one at
the tail of each datapack; two standalone blocks separated by a blank line are
two distinct anchors. A second INFO) on the same anchor is ignored (W170,
the first one wins). A block with neither label nor body is ignored (W171). A
label not closed on the same line is not a label: the line is read as body
(W172). An INFO) between the musical rows of a datapack is an error
(E137): write it before the first row or after the last one.
Which one to use: an instruction about the music is an
M)annotation; laid-out prose isTEXT); saying what you are reading and why isINFO). Not to be confused with a description the host keeps about the song: that is organisation of the host, whileINFO)is content of the song and follows it everywhere.
12. Version blocks¶
Between one datapack and another, version blocks may appear, marked %%NAME … %%end, which enclose one or more alternate datapacks of the song. The blocks live standalone between datapacks, not inside them. See neumaRk_versions.md for the full spec.
13. End of free-text blocks¶
Free-text blocks — PLAY), FORM), LYRICS), FOOT), TEXT), INFO) —
continue on the lines after the line that opens them. They all share a single
end rule, defined here and referenced by their own specifications:
A free-text block ends at the first unescaped explicit line marker, or at the end of the document.
13.1 Line marker¶
A line is a line marker if, after leading whitespace, it does not start with
\ and starts with one of:
| Family | Markers |
|---|---|
| datapack lines | M) C) N) A) D) L) F) |
| 2-character variants | C+ N+ N2, followed by a space or the end of the line |
| block markers | PLAY) FORM) LYRICS) FOOT) TEXT) INFO) |
| versions | %% |
| header lines | H…) (HT) HC) … HV), neumaRk_header.md) |
A comment line (// …) is not a line marker: it does not end the block,
and it is preserved inside the block, on the line where it is written (§11).
These are the lines of the inventories in §2.2. An implicit marker row (a
line such as [Intro], without M)) does not end a block: inside PLAY),
FORM) and LYRICS) a [NAME] alone on a line is content (a section box, an
extended-form entry), and inside TEXT) it is prose. To end a block in front of
a marker row, write the explicit marker:
TEXT) some background prose
M) [Intro]
C) | Cm11 |
N) | c,4 g' c r |
The same holds for INFO): below an INFO) the marker row must be written
explicitly, M) [Intro], or [Intro] is absorbed as the body of the note.
N2O or C++ at the start of a prose line are not markers: the 2-character
variants are markers only when followed by a space or the end of the line.
13.2 Escape at the start of a line¶
A \ at the start of a line makes the following marker literal: the line is not
a marker and belongs to the block.
TEXT) The reprise is played
\N) as before, but softly.
- When rendered, the leading
\is removed: the line reads "N) as before, but softly.". - The source keeps it: on round-trip the line is written back with the
\. - If the character after
\is a markup metacharacter (\[,\*,\#, …), the markup escape applies (neumaRk_text_markup.md§4): the displayed result is the same, the character reads literally.
13.3 The blank line¶
| A blank line ends | A blank line does not end |
|---|---|
PLAY) FORM) FOOT) INFO) |
TEXT) LYRICS) |
A blank line does not end a block where it is content: in TEXT) and
LYRICS) it separates paragraphs or verses and is printed as vertical space.
It ends a block everywhere else, because there it is the separator between
datapacks (§1).
14. Diagnostics¶
| Code | Condition |
|---|---|
| E110 | More than 4 staves in the datapack (§4) |
| E122 | N) with no stave to continue in the previous datapack: use N+ (§4.5) |
| E127 | More than 2 alternate C+ rows (§3.bis.7) |
| E129 | More than one base C) row (§3.bis.1) |
| E135 | Clef on a C) row (E135.clef_on_chord_row), clef (E135.clef_on_secondary_row) on M)/A)/D)/L)/$), pitch reference (E135.pitch_ref_on_secondary_row) on M)/A)/D)/L)/$)/C)/C+: component discarded (§7.3) |
| E136 | Unrecognized context-object component, outside a measure boundary, or invalid meter (E136.context_meter_invalid) (§7.3) |
| E137 | INFO) among the musical rows of a datapack (§11.1) |
| E300 | Measure repeat % without a source (§3.bis.5, TB1bis) |
| W159 | Meter, key or flow decorators differing between rows of the same measure: the first row wins (§7.3) |
| W168 | N+ redeclared: a stave of the previous datapack is left without continuation (§4.5) |
| W169 | Context announced twice at the same instant (§7.3) |
| W170 | Second INFO) on the same anchor (§11.1) |
| W171 | Empty INFO) (§11.1) |
| W172 | INFO) label not closed on the same line (§11.1) |
| W174 | A) row without its row below: ignored (§4.1) |
| W182 | F) row not last, duplicated, without a mark or with content where there is no room (§9.2) |
| W186 | Key detached in free position on the M) row (W186.key_free_position): discarded (§7.3, neumaRk_markers.md §7) |