From 4903baaa02d83db4f8284a0647b309c145e4d365 Mon Sep 17 00:00:00 2001 From: Jared Johnson Date: Sat, 9 May 2026 16:58:56 -0400 Subject: [PATCH] default empty first measure when fragmentizing --- src/parsing/musicxml/measure.ts | 6 +++++ .../vexml/empty_first_measure.musicxml | 25 ++++++++++++++++++ .../empty_first_measure_900px.png | Bin 0 -> 10802 bytes tests/integration/vexml.test.ts | 1 + 4 files changed, 32 insertions(+) create mode 100644 tests/__data__/vexml/empty_first_measure.musicxml create mode 100644 tests/integration/__image_snapshots__/empty_first_measure_900px.png diff --git a/src/parsing/musicxml/measure.ts b/src/parsing/musicxml/measure.ts index a72ad755b..3df82af2a 100644 --- a/src/parsing/musicxml/measure.ts +++ b/src/parsing/musicxml/measure.ts @@ -134,6 +134,12 @@ export class Measure { fragments.push(Fragment.create(config, log, signature, buffer, partIds)); } + // Ensure every measure has at least one fragment so the Part structure is preserved + // for empty measures (e.g. when and notes are missing). + if (fragments.length === 0) { + fragments.push(Fragment.create(config, log, signature, [], partIds)); + } + return fragments; } diff --git a/tests/__data__/vexml/empty_first_measure.musicxml b/tests/__data__/vexml/empty_first_measure.musicxml new file mode 100644 index 000000000..3a4872f4a --- /dev/null +++ b/tests/__data__/vexml/empty_first_measure.musicxml @@ -0,0 +1,25 @@ + + + + Pitches and accidentals + + + All pitches from G to c'''' in + ascending steps; First without accidentals, then with a sharp and then + with a flat accidental. Double alterations and cautionary accidentals + are tested at the end. + + + + + MusicXML Part + + + + + + + + + diff --git a/tests/integration/__image_snapshots__/empty_first_measure_900px.png b/tests/integration/__image_snapshots__/empty_first_measure_900px.png new file mode 100644 index 0000000000000000000000000000000000000000..4e85229f2c225c7b1dea48795d6653f173ea05fd GIT binary patch literal 10802 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 WF|j6Bd_Y|hh?lO)q+Gc5_x}NOwl?wr literal 0 HcmV?d00001 diff --git a/tests/integration/vexml.test.ts b/tests/integration/vexml.test.ts index 99406c370..21debc66a 100644 --- a/tests/integration/vexml.test.ts +++ b/tests/integration/vexml.test.ts @@ -42,6 +42,7 @@ describe('vexml', () => { { filename: 'tabs_vibrato.musicxml', width: 900 }, { filename: 'tabs_bends.musicxml', width: 900 }, { filename: 'chord_symbols.musicxml', width: 900 }, + { filename: 'empty_first_measure.musicxml', width: 900 }, ])(`$filename ($width px)`, async (t) => { const { document, vexmlDiv, screenshotElementSelector } = setup();