Conversation
…and unknown top-level tokens
… defs, measure-level elements - Fix MS2 format (.mscz v2.x): notes/rests are direct Measure children, not wrapped in <voice> elements. Parser now falls back to measureEl when no voice wrappers exist. - Extract default clef from Part/Staff definitions (<defaultClef>, <clef>) so bass/alto/tenor staves get correct initial clef instead of defaulting to treble. - Walk Measure-level children for KeySig/TimeSig/Tempo/Clef (MS3+ can place these outside <voice> elements). - Replace all firstElementChild/nextElementSibling iteration with childNodes-based helpers (forEachChildElement, toArray) for xmldom compatibility. - Add webmscore integration: vendored WASM library, Bun postinstall patch for WASM loading, CLI utility (scripts/webmscore.js), and comparison script that runs both parsers against all musescores/*.mscz files.
… mapping - Emit C-major key signature when no KeySig element present (implied) - Detect ties in MS2 format (<Tie id> + <endSpanner id>) in addition to MS3+ format (<Spanner type=Tie>) - Fix instrument <clef staff=N> attribution: match clef to correct staff index instead of always using first clef for staff 0 (was making piano treble staff default to bass) - Improve comparison script: handle grand-staff merging (split MusicXML events by staff number when our parser produces more staves)
Read tpc2 (written TPC) for transposing instruments like Bb clarinet, F horn, etc. Adjust concert MIDI pitch by transposeChromatic to derive correct written note name and octave for notation rendering.
…und in first measure Mid-score clef changes were incorrectly setting hadInitialClef=true, preventing the default clef from Part definitions from being inserted. This caused MS4 piano files to show bass clef on treble staff.
…clef changes - Support MuseScore v1.14 format: root <museScore> acts as Score element when no <Score> wrapper exists. Parse v1.x <cleflist><clef idx=N/> with numeric clef index mapping (from MuseScore v2 ClefType enum). - Handle v1.x <name> as fallback for <trackName> in Part definitions. - Fix hadInitialClef/KeySig/TimeSig: only set when found in first measure (mi===0), not on mid-score changes. This prevented default clef insertion when the only Clef element appeared later in the score.
- KeySig: v1.x uses <subtype>N</subtype> instead of <accidental>N</accidental>. Fix parseKeySigAccidentals to use xmlText presence check instead of xmlInt with undefined default (xmlInt returned 0 for missing elements, masking the fallback chain). - TimeSig: v1.x uses <nom1>/<den> instead of <sigN>/<sigD>. - KeyList: v1.x stores initial key sig in <Part><Staff><keylist><key idx=N/>, used as default when no KeySig appears in measure data. - Extract both helpers (parseKeySigAccidentals, parseTimeSigValues) to avoid code duplication between measure-level and voice-level handlers.
- Post-process tokens to infer tie ends: when a note has tie=1, the next note with matching name+octave gets tieEnd=1. Fixes v1.x format where tie ends have no explicit marker. - Comparison script: separate real diffs from info-only (voice2+ unsupported). Files with only voice2+ are marked [INFO] and count as passed.
…ng for MS2 vs MS3+
Parse voice 2+ content from <voice> elements and merge into the token stream at correct tick positions. Voice 2 rests advance the counter but aren't rendered. Voice 2+ notes are marked with _voice property and inserted by tick position for proper interleaving with voice 1 content.
Parser: - Parse voice 2+ content in MS3/MS4 files: iterate all <voice> elements, merge notes into token stream at correct tick positions - Voice 2 rests advance tick counter but aren't rendered Comparison: - Replace sequential note-by-note comparison with measure-based pitch inventory matching. Tolerant of voice interleaving and ordering differences. - Per-staff primary voice detection for split-staff comparisons
When a ref pitch doesn't match exactly, try matching with octave +-1 as a fallback. This accounts for ottava spans where webmscore exports sounding pitch while our parser correctly reads written pitch.
…teCount diffs - Allow +-2 octave shifts for 15ma and compound ottava+clef shifts - Only report noteCount diff when there are also pitch errors or >1% difference
Detect the most common octave shift in each measure and apply it before matching. Handles cases where 8va spans an entire measure (all notes shifted by the same amount). Falls back to per-note +-1 for residual mismatches.
…sks, test results
Expose speed/setSpeed() on PlaybackController, backed by local _speed state so changes made before lazy scheduler init are preserved. Wire a range slider (0.1x-3x) and number input (0.1x-4x) in the playback toolbar, kept in bidirectional sync.
Extract shared constants and helpers from musescore-parser.js into music-import-utils.js, then build a new MusicXML parser that produces the same canonical token format. Covers Phase 1+2: notes, rests, chords, clefs, key/time signatures, ties, articulations, dynamics, tempo, barlines, repeats, volta endings, pedal, and flow directions. Files: - src/music-import-utils.js — shared constants/helpers for XML importers - src/musicxml-import.js — MusicXML parser (format detection, .mxl ZIP extraction, score-partwise conversion, all element handlers) - src/musescore-parser.js — refactored to import from shared utils - src/main.js — format detection routing + interpreter skip for MusicXML - index.html — file input accepts .musicxml/.mxl/.xml - test/musicxml-import.test.js — 74 tests (unit + end-to-end + round-trip) - plans/musicxml-import.md — architecture & implementation plan Tests: 240 pass, 0 fail (166 existing + 74 new)
Remove voice filtering for single-staff parts — backup/forward already handle tick positioning for secondary voices. Previously voice 2+ notes were silently dropped (e.g., Libertango piano duet lost 78 notes). Result: 18/18 webmscore files now show exact note count match (100%).
…ice tests - Use 'staff N' naming instead of hardcoded treble/bass labels - Simplify skip-other-staff timing advance logic - Add end-to-end tests for grand staff parts and multi-voice with backup
Route .mscz/.mscx files through the vendored WebMscore WASM library to export MusicXML, then parse via our MusicXML importer. This gives full- fidelity MuseScore import including cross-staff notes, all voices, grace notes, and features the hand-written JS parser doesn't yet support. - src/webmscore-loader.js: lazy-loads WebMscore (~3MB WASM) on demand - src/main.js: new processMuseScoreViaWebMscore() with automatic fallback to the JS parser if WebMscore fails - index.html: toolbar dropdown to switch between 'WebMscore' and 'JS Parser' modes (persisted in localStorage) WebMscore is the default. If it fails (e.g., unsupported format), the JS parser is tried automatically with a console warning.
…NWC codes The audio tempo map expects beatDuration in whole-note fractions (0.25 = quarter, 0.5 = half, 0.125 = eighth). The MusicXML parser was setting it to DURATION_MAP values (4 = quarter, 2 = half, 8 = eighth), causing the formula quarterBpm = bpm * (4 / 0.25) = bpm * 16 — making playback 16x too fast. Fix: use 1/DURATION_MAP[beatUnit] to convert to fractions. Also added beatDuration to the MuseScore parser (previously omitted, worked by accident via the 0.25 default fallback). Added beat-unit-dot support for dotted tempo markings.
Page layout mode now supports four view arrangements:
- Single: vertical stack (existing behavior, default)
- Fit Width: single column with auto-zoom to fill viewport width,
recalculates on window resize
- Two-Up: side-by-side page pairs (book spread view)
- Horizontal: left-to-right horizontal scroll (single row)
Implementation:
- constants.js: pageViewMode state + getter/setter
- typeset.js: compute per-page {x,y} positions based on view mode,
apply per-system X offsets for non-vertical arrangements, update
page background drawing to use position array
- main.js: page view mode selector with localStorage persistence,
fit-width auto-zoom after render and on window resize
- index.html: dropdown selector visible only in page mode
… nav Separate concerns properly: Zoom controls (next to zoom slider): - Fit Width (W) and Fit Height (H) toggle buttons auto-zoom to fill viewport. Clicking again disengages. Dragging the zoom slider also disengages fit mode. Recalculates on window resize. Persisted in localStorage. Page view modes (dropdown, visible in Page layout): - Vertical: pages stacked vertically, free scrolling (default) - Single Page: one page at a time with prev/next navigation - Two-Up: side-by-side pairs (book spread view) - Horizontal: left-to-right horizontal scroll Single-page mode includes: - Page indicator (e.g. '3 / 12') - Prev/Next buttons that scroll to the target page - Nav controls visible only when in single-page mode Implementation: - constants.js: renamed 'single' to 'vertical', added zoomFitMode state (none/width/height) - main.js: onZoomSliderInput() disengages fit mode, applyZoomFit() for width/height, scrollToPage() for single-page nav - typeset.js: single-page layout uses same vertical positions (navigation works via scroll position) - index.html: W/H buttons next to zoom slider, page nav controls
The nwc2xml/ directory is gitignored so these tests fail in CI. Use describe.skip when the test data directory doesn't exist.
Complete the two remaining MusicXML import items: - score-timewise: refactor convertPart/detectStaffCount to accept measure element arrays, add convertScoreTimewise that collects <part> elements per part-id across timewise measures and feeds them to the shared pipeline (7 new tests, equivalence verified) - WebMscore loader: add 30s load timeout, script tag cleanup on failure, retry support, empty XML output detection, safe score.destroy(), and resetWebMscore() for recovery
Page mode enhancements: - Page numbers rendered at bottom center of each page - Copyright text drawn in bottom margin of page 1 (replaces DOM footer) - Page nav toolbar visible in all page view modes, not just single-page - Jump-to-page input: type a page number and press Enter to navigate - Scroll-based page tracking updates indicator as user scrolls - Viewport culling for page backgrounds (skip offscreen pages)
Extract TickTracker to src/layout/tick-tracker.js with dual-map alignment: barlineTicks stores pre-gap barline positions for vertical stacking, maxTicks stores post-gap positions for note alignment. Barlines use alignBarline() (first-barline-wins), notes use alignWithMax() with Math.max to prevent backward snapping past barline gaps. Barlines removed from tabbableTypes so they no longer drift tabCounter.
…okens Header tokens (Clef, KeySig, TimeSig) each advance tabCounter by 0.25. When staves have different numbers of headers (e.g. one omits a Clef), notes start at different tab offsets, causing barlines that should align across staves to have different tabValues. Add a post-processing pass in SightReader.read() that normalizes header offsets so all staves' musical content begins at the same tab time.
…alignment MusicXML and MuseScore imports emit a closing barline token at the end of the last measure while the layout code also draws one from the staff-level endingBar property, producing a duplicate barline and an extra bar number in scroll mode. - Add hasTrailingBarline() helper to detect token streams that already end with a barline, and skip the endingBar rendering in that case - Skip bar numbers for barlines that follow the last note in scroll mode - Reorder addStave/barline so the final stave segment extends to the ending barline rather than stopping before it, with a small nudge past the thick line's right edge for full visual coverage - Never justify the last system in wrap/page modes so stave lines end at the final barline instead of stretching to the page edge
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.