A single article label (e.g. 'f') may emit on multiple `articles.<subtype>`
lines — `defaults` today, `overwrites` planned — with disjoint property
sets that apply at different stages. Model now merges those emissions:
{ name: 'f', properties: [{ name, value, scope: 'defaults'|'overwrites' }] }
Per-label entries in the AR pane show a property-count breakdown
(`f (1 defaults, 2 overwrites)`). The Article FO pane lists each property
with a `(O-) default` / `(-O) overwrite` toggle — click flips the scope
and marks the score dirty. ASCII glyph mimics a physical switch position
so the active scope is visible at a glance.
Fixture tests cover both the existing single-subtype shape and a synthetic
two-subtype merge (177 assertions total).
- Top-level dispatch switches on fqSlot (parentSlot.slot) so dotted top-level
slots (`stage.cone`, `stage.voice`, `articles.<subtype>`) are matched.
- `stage_voice` -> `stage.cone` (new, one per score) + `stage.voice` (list).
- `article` -> `articles.<subtype>` (subtype stored on the article entry;
first observed subtype is `defaults`).
- New `tuning.frequency_factors` child captured as `{ label, factors }`.
- buildModel un-nests preamble siblings that sompyler emits at depth 01
under `00 tuning` via `with deeper_level("articles")` / `deeper_level("stage")`.
Only `tuning`'s children are flattened; `instrument`'s implicit containers
(`character`, `VOLUMES`, `TIMBRE`, `FM`, `AM`) are left untouched.
- Fixture test extended with 18 preamble assertions (172 total now pass).
Pre-c3b51bc sompyler emits offset.stem_note/motif.stem_note/clause.note
while new sompyler emits line.stem_note/seq.note. Parser now accepts both
parentSlot values for stem_note, motif, and clause children so the app
works against both old and updated sompyler installations.
Also falls back to reading chain.clause directly from stem_note's children
when no stem_note.chain node is present (old format had no chain wrapper).
Sompyler commit c3b51bc renamed slots across the board:
- offset.stem_note → line.stem_note (parentSlot 'offset' → 'line')
- motif.stem_note → line.stem_note (parentSlot 'motif' → 'line')
- clause.note/pause/stack → seq.note/pause/stack (parentSlot 'clause' → 'seq')
- stem_note.chain is now a real hierarchical node (depth 04) between
line.stem_note and chain.clause; clauses are now its children, not
direct children of stem_note
Also adds:
- offset.motifs[] for line.motif invocations at tick positions
- stem_note.writeToName from stem_note.write_to positional
- adjacent prop already stored; new tests verify True/False/absent
PaneFO.js: show offset motif invocations inline (label + chord)
test-parser.mjs: update synthetic fixtures to new depths/slot names;
add tests for line.motif, stem_note.write_to, adjacent prop
- ast-parser: read for_value= prop for sub-variations (was always null,
causing fallback to positional "subvariation N" label)
- StatusPoller: calculate progress from currently_rendered_notes /
notes_in_total; show remaining_time string from status.json
- ShapeEditor/EnvelopeEditor: pass { undo } through onChange so callers
can revert in-place mutations
- PaneFO: makeChangeHandler stashes undo from info arg; discardEdit
calls undo() before clearing pendingEdit; depends_on handler provides
its own undo for the inline node.dependsOn mutation