Version v0.4.0 build 849bb84

Voice and part separation for scanned-score OMR: research notes

Scope: how to detect several voices or parts printed on one staff, mainly closed-score SATB hymns and chorales, and divisi, from page images. Covers when to split them into separate MusicXML <part>s, when to keep them as <voice>s in one part (piano grand staves, organ, local divisi), how lyrics follow the split, and how to handle uncertain cases. Written for an environment-agnostic JS OMR core that runs in the browser and in node.

Status: research and design input, compiled 2026-11-08. Sources are cited inline and listed in full in section 14. "Recommendation" marks this project's own engineering choices, as opposed to what a source says.


0. Summary

  1. On a printed staff, stem direction is the main evidence for voices. It beats pitch. Engraving convention puts the upper voice stems up and the lower voice stems down on a shared staff, with voice-1 rests moved up and voice-2 rests moved down (MuseScore handbook). Every image-based system examined uses stems, beams and ties first and treats pitch proximity as secondary: Audiveris (source), Jin & Raphael 2015 (paper) and BeadSolver 2026 (paper). Symbolic (MIDI-style) voice-separation algorithms have no stems to work with, so in OMR they serve only as fallbacks, for stemless whole notes or broken detections.
  2. The difficult part is consistency, not the first guess. In BeadSolver's piano test set, a greedy rule baseline that groups x-aligned columns and chains them forward gets only 55.1 % of measures with all voices matching and 50.5 % fully correct measures. Probability-guided search raises this to 92.4 % / 86.8 % (BeadSolver Table 7). On Rachmaninoff with corrected symbols, Jin & Raphael's graph optimisation (voice edges plus coincidence edges, hard bar-length constraints) interprets 355/371 = 95.7 % of system measures correctly (paper). Closed-score hymns are far easier than piano music, but the same tool applies: a hard constraint that each voice fills the bar exactly, plus repair search.
  3. Closed-score SATB statistics (measured here) over 368 four-part Bach chorales in the music21 10.5.2 corpus (script):
    • S/A unisons at 2.46 % of onset slots, T/B unisons at 3.58 %. Shared noteheads are common enough that they must be handled.
    • Voice crossings at only 0.24 % (S/A) and 0.78 % (T/B). Splitting by pitch order almost never contradicts the true voices, but stem direction should still decide.
    • At about 31 % of slots only one voice of a pair has an onset (passing notes, suspensions, melismas). Lyric copying therefore has to align by time slot, not by note index.
    • Lyrics appear only on the soprano part in 320/368 files, and never on A/T/B. Ground truth for lower-voice lyrics has to be derived.
  4. When to split:
    • Split a staff group into one part per voice when it is classified as vocal closed score. The strongest cues are lyrics between or under the staves, vocal labels ("S A", "T B", "Soprano/Alto", "Women/Men"), every voice monophonic (at most 2 heads per stem), two stem directions present throughout, and SATB ranges.
    • Never split a keyboard group. Cues: "Piano/Pno./Organ" labels, chords of 3 or more notes per stem, pedal marks, cross-staff beams, and lyrics present only on a different staff.
    • Braces are weak evidence because hymnals often brace their SATB staves (an observation; see §5). Brackets point towards separate performers.
    • Make the decision once per logical part for the whole piece, never per measure. Unison passages written with single stems are copied into both voices.
    • When the evidence is ambiguous, keep one part with <voice>s. This loses nothing. Show a warning and offer a one-click split (§11).
  5. MusicXML output:
    • Split vocal parts: one <score-part> per voice inside <part-group type="start"><group-symbol>bracket</group-symbol><group-barline>yes</group-barline>, each part monophonic in <voice>1</voice>, and lyrics copied to every voice that sings them.
    • Unsplit keyboard: one part with <staves>2</staves>, <staff> on every note, and <backup> between voices. Voice IDs 1–4 go on staff 1 and 5–8 on staff 2, the convention used by Audiveris (Voice.VoiceKind), Finale and MuseScore. Sibelius numbers voices per staff (MuseScore forum). <voice> is just a string with no defined semantics (W3C).
  6. Licensing:
    • Audiveris and homr are AGPL-3.0. Take ideas only and do not port code.
    • music21 (BSD-3), partitura (Apache-2.0), piano_svsep, vocsep_ijcai2023, voice-splitting and MV2H (all MIT), oemer (MIT) and OSMD (BSD-3) are all compatible.
    • OpenScore Lieder and OpenScore String Quartets are CC0, which makes them good negative and positive test sets.

1. Terms

Term Meaning here
Voice A monophonic (or homophonic-chord) sequence of events moving linearly in time. MusicXML: "A voice is a sequence of musical events (e.g. notes, chords, rests) that proceeds linearly in time" (W3C <voice>).
Part One performer or section, in MusicXML a <part> with a <score-part> entry. It may have several staves (<staves>) and several voices.
Staff group Staves joined by a brace or bracket and/or connected barlines within a system.
Closed (close) score Two or more vocal parts sharing a staff. SATB on two staves, "as in hymnals" (MuseScore 3 handbook). Merriam-Webster gives "close score" (M-W).
Open score One staff per part.
Divisi One section temporarily divided (S1/S2, Vln I div.), often on one staff with two stem directions or as a chord on one stem.
Slot A set of chords and rests on a system that share an onset time and are roughly x-aligned (Audiveris terminology, SlotsRetriever).
Logical part The same instrument followed across systems and pages, even when its physical staves differ per system (Audiveris "logical parts", handbook).

2. Visual evidence for several voices on one staff

2.1 Stem direction (primary)

2.2 Shared noteheads and unisons

2.3 Rests: displaced versus shared

2.4 Chord versus two voices

Visual Interpretation on a 2-voice (closed) staff On a keyboard staff
N heads on one stem Split: top head to upper voice, bottom head to lower voice. If N ≥ 3, the extra heads are divisi or an error: warn One chord in one voice
Two stems in opposite directions at the same x Two voices, by stem direction Two voices
One head with stems both sides Unison: both voices Two voices sharing a head
One stemmed head, no opposite-stem event during its span Unison: both voices (copy) One voice
Two stemless heads in one slot Pitch order: higher to upper voice Chord, or two voices

Symbolic research handles the same tension. Kilian & Hoos allow chords inside a voice and penalise "chords with a large pitch interval between the highest and the lowest note, as well as irregular chords containing notes with different onset times" (ISMIR 2002). Foscarin et al. cluster synchronous notes into chords with a learned chord-edge predictor before linking voices (arXiv 2407.21030). The chord-level model of Gray & Bunescu lets one note belong to several voices (convergence and divergence), which is exactly the unison case. On the Bach-chorale notes involved in such many-to-many links, an envelope baseline reaches only F = 56.7 against 87.2 for the chord-level model (arXiv 2011.03028).

2.5 Beams and ties within a voice

2.6 Rhythm as a global constraint

Jin & Raphael's hard constraints: the left barline is at onset 0 and the right barline at the meter. Onsets never decrease with x. Each vertex has a unique onset, so several paths into a vertex must agree. Coincident x implies the same onset (ISMIR 2015). Audiveris assigns voices slot by slot by mapping incoming "rookie" chords to active voices, using VoiceDistance and a NextInVoice blacklist. It assumes "all chords in a given narrow slot share the same time". When slot times disagree it "select[s] the smallest time value" and rejects the inconsistent mappings. Unmapped chords start new voices (MeasureRhythm.mapRookies). Its handbook warns that the engine "tries to reconcile different heuristics" and that "the time and voice algorithms are tightly coupled". In release 5.11 "a chord can be assigned to exactly one voice", and sharing rests between voices is still "debated" (Audiveris chords UI).

Recommendation: solve voices and onsets together per staff and measure. Use a small beam or branch-and-bound search over the ambiguous decisions: shared or private rests, unison copy, divisi head placement, ambiguous dots. Score each candidate by summed link costs plus a hard penalty when any voice's durations do not fill the bar (or the pickup length).

2.7 Known image-side failure modes


3. Closed-score SATB specifics


4. Divisi


5. Piano grand staff versus vocal parts; brace versus bracket

Discriminating features (weights are this project's recommendation; tune them on test data):

Feature (per staff group, aggregated over all systems) Weight
Vocal label tokens at system start (S, A, T, B, Soprano, Alto, Tenor, Bass, Sopran, Alt, Contralto, Basso, Cantus, Altus, Bassus, Women, Men, Choir, Chorus, Coro, Chor, SATB) +3
Lyric lines adjacent to the group (below staff 1, or between its staves) +2.5
Each staff: both stem directions co-occur in ≥ 30 % of measures +1.5 per staff
≥ 98 % of stems have ≤ 2 heads +1
Stream ranges fit SATB (S ≈ C4–A5, A ≈ F3–D5, T ≈ C3–G4, B ≈ E2–D4) +1
Treble-8 clef present +0.5
Bracket (rather than brace) +0.5
Keyboard label (Piano, Pno., Pf., Klavier, Organ, Org., Orgel, Manual, Ped., Harmonium, Harp, Accomp.) −3
Lyrics present in the system but only on a different staff (song or anthem accompaniment) −2.5
Pedal marks (Ped., ✱), arpeggio lines, fingering digits, cross-staff or kneed beams, 8va lines −2
≥ 5 % of stems with ≥ 3 heads; octave doublings in the bottom stream −1.5
Brace, and no lyrics anywhere near −0.5

Thresholds:

A typical hymn without labels scores about 2.5 + 3 + 1 + 1 − 0.5 = 7. A song accompaniment scores about −2.5 − 1.5 − 3 = −7.


6. Part naming from system-start labels


7. Lyrics: placement and which voices they apply to

7.1 What sources say

7.2 Recommended algorithm

  1. Lyric line to staff. Each lyric line (a band of OCR words at a common baseline) belongs to the nearest staff above it in the same system. If there is no staff above, or the gap to the staff above is more than twice the gap to the staff below, it belongs to the staff below. Stacked lines under one staff are verses 1…n, ordered top to bottom. Strip leading verse numbers ("1.", "2.") and use them as number. Audiveris also isolates initial numbers (source).
  2. Staff to voices.
    • A line under a 2-voice staff applies to both voices of that staff.
    • In a closed SATB group with lyrics only between the staves (nothing under the bass staff), the block applies to all four voices.
    • If a separate block exists under the bass staff, T and B take that block instead.
    • In open score each staff carries its own text.
  3. Syllables to time slots. Run a monotonic alignment (DP, like sequence alignment) between the syllables of one verse line and the onset slots of the reference voice, which is the upper voice of the staff above. Costs: x-distance between syllable centre and notehead centre (with a left-aligned variant for melisma starts); skipped onsets (melisma) are cheap after a syllable ending in an extender or hyphen and dearer otherwise; unmatched syllables are expensive. Alignment recovers from OCR splits and merges.
  4. Copy to each receiving voice.
    • If the voice has an onset at the syllable's slot, attach a copy with the same syllabic and verse number.
    • If the voice is sustaining a note there, it does not take that syllable. Count a conflict.
    • If the voice has onsets with no syllable after a syllable, those notes are a melisma for that voice: add <extend type="start"/> on the syllable note (after word-final syllables only) and <extend type="stop"/> on the last melisma note, or a plain <extend/> for maximum importer compatibility (W3C <lyric>).
    • Tied continuations never get syllables.
  5. Conflict check per voice. If more than 15 % of a verse's syllables conflict for a voice, that voice probably has its own text, or the text is not meant for it. Keep the lyric on the reference voice only and warn: "Lyrics not applied to Alto: rhythm differs in N places".
  6. Record provenance (read versus copied) per part, for the UI lyrics preview.

8. MusicXML representation

Reference points (MusicXML 4.0):

8.1 Output A: split choir (default for vocal closed score)

<part-list>
  <part-group number="1" type="start">
    <group-name>Choir</group-name>
    <group-symbol>bracket</group-symbol>
    <group-barline>yes</group-barline>
  </part-group>
  <score-part id="P1"><part-name>Soprano</part-name><part-abbreviation>S.</part-abbreviation></score-part>
  <score-part id="P2"><part-name>Alto</part-name><part-abbreviation>A.</part-abbreviation></score-part>
  <score-part id="P3"><part-name>Tenor</part-name><part-abbreviation>T.</part-abbreviation></score-part>
  <score-part id="P4"><part-name>Bass</part-name><part-abbreviation>B.</part-abbreviation></score-part>
  <part-group number="1" type="stop"/>
</part-list>

8.2 Output B: closed score kept (ambiguous or user choice)

Use two parts, each with one staff and voices 1 and 2. This mirrors the MuseScore closed-SATB convention (MuseScore 3 handbook).

<measure number="1">
  <attributes><divisions>2</divisions><time><beats>4</beats><beat-type>4</beat-type></time>
    <clef><sign>G</sign><line>2</line></clef></attributes>
  <note><pitch><step>E</step><octave>5</octave></pitch><duration>4</duration><voice>1</voice>
    <type>half</type><stem>up</stem>
    <lyric number="1"><syllabic>single</syllabic><text>Praise</text></lyric></note>
  <note><pitch><step>D</step><octave>5</octave></pitch><duration>4</duration><voice>1</voice>
    <type>half</type><stem>up</stem>
    <lyric number="1"><syllabic>single</syllabic><text>God</text></lyric></note>
  <backup><duration>8</duration></backup>
  <note><pitch><step>C</step><octave>5</octave></pitch><duration>4</duration><voice>2</voice>
    <type>half</type><stem>down</stem></note>
  <note><pitch><step>B</step><octave>4</octave></pitch><duration>4</duration><voice>2</voice>
    <type>half</type><stem>down</stem></note>
</measure>

8.3 Output C: keyboard grand staff (never split)

<attributes><divisions>2</divisions><staves>2</staves>
  <clef number="1"><sign>G</sign><line>2</line></clef>
  <clef number="2"><sign>F</sign><line>4</line></clef></attributes>
<!-- staff 1: voices 1 (stem up) and 2 (stem down) separated by <backup> -->
<!-- staff 2: voices 5 and 6 -->
<note>...<voice>5</voice><staff>2</staff></note>

9. Algorithms survey

9.1 Image-side OMR

System / paper How voices are formed Numbers Licence and use
Audiveris 5.x (code, handbook) (1) Chords into slots: shared stem → same slot, mirrored heads → same slot, same beam group on different stems → different slots, x only a hint. (2) Per slot, map new chords to active voices by VoiceDistance cost (stems, staff, rest bonus, pitch) with NextInVoice and SeparateVoices constraints. (3) Propagate through beams and ties. (4) Check slot times; take the lowest time on disagreement; unmapped chords start new voices. (5) Voice IDs 1–4 / 5–8 / 9–12 by starting staff. (6) Lyrics go to the nearest chord. (7) Parts come from staff counts, names from OCR PartName. No automatic splitting of voices into parts. Not published AGPL-3.0: ideas only
Jin & Raphael 2012/2015 (2015) Graph per system measure. Voice edges ("onset of receiver = sender onset + duration") and coincidence edges (same x → same onset). Hard constraints: bar length, x-monotonic onsets, unique onset per vertex. Soft: beams, stem direction, staff height. Search with re-interpretation (implicit tuplets) 355/371 (95.7 %) system measures on Rachmaninoff PC2. 100 % on a 180-measure score with triplets Paper only
BeadSolver 2026 (arXiv 2604.20522) Measure events as "beads" threaded into one chain (BOS…EOS per voice). A 16-layer Transformer (3.24 M parameters) proposes next node, division and dots. MCTS-like search with structural loss Greedy rule baseline: 55.09 % voice match, 50.54 % perfect measures. BeadSolver multipass: 92.42 % / 86.83 % (5,317 measures from a 107-score LilyPond test set, mostly piano: Bach, Chopin and Czerny are about 79 % of measures) Paper; code repo has no stated licence
SMT / SMT++ (Ríos-Vila et al., arXiv 2402.07596, 2405.12105) End to end, image to bekern (Humdrum spines encode voices) for pianoform and quartets CER 1.3 on quartets, 3.9 on GrandStaff (repo README) MIT (repo); needs a large model
LMX (Mayer et al. 2024, arXiv 2403.13763) End to end to linearized MusicXML (a depth-first token sequence close to MusicXML, so voices and staves survive into the output; see the paper for details). TEDn metric OpenScore Lieder test sets: 1,438/1,493 systems Paper and dataset
homr / oemer (homr) Segmentation (oemer UNet) plus a transformer (Polyphonic-TrOMR). Focus on pitch and rhythm — homr AGPL-3.0, oemer MIT

9.2 Symbolic voice separation (fallbacks and offline checks)

Method Idea Numbers Licence
Contig mapping, Chew & Wu 2004 (summary); partitura implementation (code) Cut the piece into contigs with a constant number of sounding notes. Inside a contig, voices are ordered by pitch. Seed from maximal contigs and connect fragments outward with a greedy minimum cost (pitch distance; −MAX for the same note, +MAX for skips). Chords are treated as their highest note 99.75 % fragment consistency, 94.50 % correct connections, 88.98 % average voice consistency partitura Apache-2.0
Better contig connection, Guiomard-Kagan et al. 2016 (ISMIR) Use the whole fragment content to score connections; leave doubtful ones unconnected Transition precision > 99 % on fugues if 25 % of links are left open (GA1-75 %) Paper
Voice vs. stream comparison (ISMIR 2015) Benchmark of voice and stream segmentation in one framework — CC-BY paper
VISA, Karydis/Cambouropoulos 2007 (ISMIR), revisited 2009 and VISA3 2016 Perceptual stream separation: a stream may contain synchronous notes (chords). Sweep line plus bipartite matching; the number of voices need not be known Precision 87.3–100 % on 10 works Paper
Kilian & Hoos 2002 (PDF) Stochastic local search over slices. Penalties for pitch distance, gaps, chord spread and irregular chords, and overlaps. Chords allowed inside a voice; user-tunable — Paper
Perceptual principles: Bregman ASA (Wikipedia), Huron 2001 (DOI) Pitch proximity, temporal continuity, avoid crossing, tonal fusion: the source of the cost terms above — —
HMM, McLeod & Steedman 2016 (PDF, code) States are sets of monophonic voices. Voice pitch is a weighted average of recent notes; beam search (b = 25) Average voice consistency 91.53 (Chew & Wu: 88.98). F-measure: WTC 0.97, Haydn 0.79 MIT
Neural greedy, Gray & Bunescu 2016 (ISMIR) One-hidden-layer network scoring note–voice pairs from perceptual features, applied left to right Bach chorales F = 95.47 (baseline 93.18); pop 91.03 Paper
Chord-level model, Gray & Bunescu 2020 (arXiv 2011.03028) A note may belong to several voices (convergence and divergence = unisons). Scores joint assignments for a whole chord Chorales F = 97.30; "Chords Many" F = 87.24 against a baseline of 56.72 Paper
DNN for lute and keyboard, de Valk & Weyde (ISMIR 2018) Feed-forward network with voice-entry heuristics Lute tablature voice prediction ≈ 80–90 % Paper
GNN link prediction, Karystinaios et al. IJCAI 2023 (arXiv, code) Heterogeneous GNN predicts note-to-next-note links, with a 1-in/1-out regulariser and linear assignment. Monophonic voices only Link F1: Inventions 0.997, WTC I 0.976, Haydn quartets 0.872 (McLeod: 0.781) MIT
piano_svsep, Foscarin et al. ISMIR 2024 (arXiv, code) GNN with chord clustering, voice edges and staff classifier; chord pooling plus linear assignment Voice F1 96.6 (J-Pop) / 89.9 (DCML); staff accuracy 96.3 / 91.0; rule baseline 85.4 / 78.2 MIT

9.3 Tools that split voices into parts in notation software

Tool Behaviour
music21 Stream.voicesToParts(separateById=False) / explode() (source, music21 10.5.2) For each measure, the k-th Voice goes to part k (<id>-v0, -v1, …). Missing voices get empty measures. Clefs, key and time are copied. With separateById=True voices are matched by id in order of first appearance. It does nothing for single-stem chords or unisons, and lyrics stay on whichever voice held them. explode() "currently just runs voicesToParts()". The inverse is Score.partsToVoices(voiceAllocation=2), the ideal generator for synthetic closed scores. makeVoices() isolates overlaps into voices; chordify() produces a reduction.
MuseScore Explode (handbook) Splits voices, or chord notes, of a selection onto the staves below; the staff count is the number of voices, or the largest chord. Extra notes are dropped when staves run out. Lyrics are not copied (forum).
Dorico condensing (docs) The opposite direction (open to closed) with divisi-aware layout. Lyrics are a known weak spot (forum).
LilyPond \partCombine (docs) Open to closed rules: a2, chords, apart, solo. Only two voices; it "only detects where notes start".

What this means for the project. No reviewed tool does what we need end to end: detect a closed score from an image, de-combine single-stem chords and unisons, split into parts, and copy lyrics by rhythm. Audiveris (no part splitting, nearest-chord lyrics) and music21 (voices only) each cover a piece. The pipeline in §10 combines Audiveris-style slot and voice building (re-implemented from scratch), Jin & Raphael-style bar-length constraints, partCombine-inverse de-combining, and the slot-based lyric copy from §7.


10. Recommended pipeline (environment-agnostic JS core)

Input is detected primitives per system: staves, barlines, brace/bracket glyphs, noteheads (filled, open, whole), stems with direction, beams, flags, dots, rests with y-offset, ties and slurs, accidentals, clefs, key and time, and OCR text boxes. The core runs in browser and node.

  1. Staff grouping. Group staves by brace/bracket glyphs and barline connectivity. Collate groups across systems into logical groups by staff count, clefs and labels (Audiveris-like collation).
  2. Chords and slots per system measure. A stem plus its heads makes a chord. Split heads with stems on both sides into two mirrored chords. Group whole notes by x. Build slots: a shared stem means the same slot; the same beam group on different stems means different slots; otherwise use an x-overlap tolerance of about 0.6 staff space.
  3. Per-staff voice assignment (two keys: U = upper, L = lower):
    • Stem up goes to U, stem down to L. Each beam group takes the majority direction of its stems.
    • On a 2-voice staff, a single-stem chord with ≥ 2 heads is split top → U, bottom → L. Middle heads are flagged divisi and attached as a chord to the nearer voice.
    • A single-stem single head with no opposite-stem event overlapping its span is a unison: copy to U and L.
    • Stemless heads: two in one slot go by pitch order. A single one goes to whichever voice lacks events in that span, otherwise to both.
    • Rests by y-offset: ≥ +1 space → U, ≤ −1 space → L, centred → shared unless one voice already covers that time.
    • Ties and beams keep the voice.
    • Keyboard staves use the same steps without de-combining chords. Allow up to 4 voices per staff; if a staff has no two-direction co-occurrence, it is one voice with chords.
  4. Rhythm solve and repair. Compute per-voice onsets from durations, with x-monotonic onsets and coincident slots forced equal. Each voice must sum to the bar (or pickup) length. When it fails, search over the ambiguous choices (shared or private rest, unison copy, divisi placement, dot or flag misreads, implicit triplets) with a budget of 200 states per measure. Still failing: pad with <forward>, warn with the measure number, and lower the confidence.
  5. Staff-group classification (§5 score) per logical group, aggregated over systems.
  6. Split decision and naming (§11, §6).
  7. Lyrics (§7).
  8. MusicXML emission (§8): output A, B or C per group, plus a provenance record per part (source staff, voice key, split reason, confidence, lyric source) for the UI's per-part summary.

Symbolic fallback: when stem detection on a staff is unreliable (stem confidence below threshold in more than 20 % of chords), run a contig or linear-assignment separator (pitch-proximity cost) on that staff's notes for k = 2 streams and compare its result with the stem-based one. Disagreement lowers confidence.


11. Decision procedure: split or keep, and uncertain cases

for each logical staff group G (whole piece):
  s = vocalScore(G)                       # §5 table, summed over systems
  if G.staves == 1:
     twoVoice = fraction of measures (where staff has notes) with ≥2 concurrent streams
     names    = labelNames(G)              # §6
     if names.count >= 2 and (s >= 1 or twoVoice >= 0.2):  split into names
     elif twoVoice >= 0.5 and allStreamsMonophonic and s >= 3: split into "<Name> 1", "<Name> 2"
     else: keep one part, voices 1/2 (warn if twoVoice > 0)
  elif G.staves == 2:
     if s >= 3:   SPLIT  -> per staff: k voices (k = max concurrent streams, usually 2; SAB → 2+1)
     elif s <= -1: KEEP as keyboard part, <staves>2</staves>, voices 1-4 / 5-8
     else:        KEEP as closed score (output B) + flag "ambiguous: split available"
  elif G.staves >= 3: KEEP (organ: 3 staves) unless every staff individually qualifies as vocal
  consistency: part count is fixed for the piece; measures with fewer visible voices
               duplicate unison material; never split per measure.
Uncertain case Action User-facing note
Hymn staves braced, no labels, lyrics between staves Split (score is high from lyrics plus stems) "Split from shared staves: lyrics between staves, opposite stems in N % of bars"
Single-stem 2-note chords throughout one passage of a closed staff De-combine top/bottom Silent unless a chord has ≥ 3 heads
3 or more heads on a closed staff (local divisi) Keep the extra head as a chord in the nearer voice "Divisi in bar N kept as chord in Alto"
Two-voice texture on a single open-score staff in < 50 % of bars, no label Keep one part with voices "Soprano has divisi in bars …"
Voice count differs between systems Use the maximum for the piece and fill with unison copies or rests —
Lyric rhythm conflicts in > 15 % of syllables for a voice Do not copy the lyrics to that voice "Lyrics not applied to Tenor (N conflicts)"
Bar-length failure after repair <forward> padding, confidence < 0.5 for the measure Bar highlighted in the overlay
Score in the ambiguous band (−1, 3) Output B (lossless), toggle offered "Could be piano or choir: currently kept together"
Vocal score with separate piano reduction below Choir group split; piano kept "Piano reduction kept as one part"
Instrumental shared staff labelled "1.2" or "a2" Keep by default; split only if labelled with two names and monophonic "Flutes 1.2 kept as one part (two voices)"

Report confidence as one number per output part: the minimum of the classification confidence (a sigmoid of the §5 score), the voice-assignment confidence (the share of measures passing the bar check without repair) and the stem-detection confidence.


12. Evaluation plan (node harness)


13. Licensing notes

Component Licence Use
Audiveris (repo) AGPL-3.0 Read for ideas; no code or constants ported. The algorithms are re-implemented from public descriptions
homr (repo) AGPL-3.0 Do not ship
oemer MIT Possible segmentation model source (check model weight terms separately)
music21 BSD-3-Clause Harness only (Python)
partitura Apache-2.0 Reference for the contig algorithm
piano_svsep, vocsep_ijcai2023, apmcleod/voice-splitting, MV2H MIT Reference or evaluation
SMT MIT Reference
OpenSheetMusicDisplay BSD-3-Clause Preview renderer
OpenScore Lieder / String Quartets CC0-1.0 Test data
BeadSolver "lotus" repo No licence stated Paper ideas only

14. References


Appendix A: chorale statistics script

Run with the project venv (.venv/bin/python, music21 10.5.2) over corpus.getComposer('bach'), keeping the 368 files that have exactly four parts. A "slot" is any onset time in either voice of the pair (S/A or T/B). Unison means both voices sound the same MIDI pitch at the slot. Crossing means the upper voice is below the lower. Rhythm-diverge means only one of the two voices has an onset at that slot.

Pair Slots Unison Crossing Rhythm diverges One voice resting
S/A 24,381 2.46 % 0.24 % 31.76 % 0.82 %
T/B 27,182 3.58 % 0.78 % 30.58 % 0.80 %

Lyrics present in part 0 (soprano) of 320 files and in no other part.

# chorale_stats.py (abridged): per pair, walk the union of onsets and compare sounding pitches
from music21 import corpus, note, chord
def events(part):
    out = []
    for n in part.recurse().notesAndRests:
        p = (max(x.midi for x in n.pitches) if isinstance(n, chord.Chord)
             else n.pitch.midi if isinstance(n, note.Note) else None)
        out.append((float(n.getOffsetInHierarchy(part)), float(n.quarterLength), p))
    return out
# for each (upper, lower) in ((S,A),(T,B)): for t in sorted onsets of both:
#   pa/pb = pitch sounding at t; count unison (pa==pb), crossing (pa<pb),
#   rhythm-diverge (exactly one voice has an onset at t)
v0.4.0 · · page built from 849bb84