Skip to content

Fix #218 — one stem direction per beam group, every stem placed for it - #220

Merged
sbeitzel merged 2 commits into
developfrom
feature/fix_218
Oct 4, 2026
Merged

sbeitzel merged 2 commits into
developfrom
feature/fix_218

Conversation

@sbeitzel

@sbeitzel sbeitzel commented Oct 4, 2026

Copy link
Copy Markdown
Owner

Fix #218

Summary

A beam group now has one stem direction, and every stem in it is placed for that direction: on the notehead's right for up, its left for down. Before, each beamed note was placed for the direction it would have taken alone (stem x, and the stem's base from the stemUpSE / stemDownNW anchor), then drawn the way the group's first note went. In Gd the d got an upward stem on its notehead's left edge.

The direction follows abcm2ps 8.14 (checked against abcm2ps -g):

  • Down where the highest note is farther above the middle line than the lowest is below it.
  • Up in the opposite case.
  • A tie follows the part's previous stem, beamed or not (GB Ac → up, ce Ac → down); up if there is none.

A voice with a direction of its own (V: stem=, %%ceolkit:pipeformat, a shared staff's opposition) is unchanged.

How

  • SVGEmitter.beamGroupStemDirections(in:staffIndex:lastStemUp:) runs before a measure's events are drawn and returns a direction per beamed event. emitEvent takes it as stemOverride.
  • The previous stem per part (lastStemUp: [PartKey: Bool]) is threaded through pages, systems and measures alongside pendingTies / pendingSlurs, so the tie-break works across bar lines and system breaks.

Tests

  • New BeamGroupStemTests:
    • Gdf stems down, every stem on its stemDownNW anchor.
    • The directions of all 24 notes of GB Ac | ce Ac | ce Gd | GA Gd | GcBd | dGBc | match abcm2ps, each stem on its anchor.
    • A tie follows an unbeamed stem in the previous bar.
    • stem=down still wins.
  • Re-recorded snapshots: IntegrationTests jig (both modes) and Canzonetta. Each had a group drawn up with some stems at down-stem x positions. They now draw one consistent direction (the jig's GABcde stems down, as abcm2ps does).
  • Full suite passes (792 + 694 + 22).

Note

Independent of #217 and mergeable in either order. Together they render "Monsieur de la Miranda" (B2A G2d in 6/8) correctly. Until #217 lands, the still-beamed G2d can tip the tie-break for the group after it.

🤖 Generated with Claude Code

Each beamed note used to be placed — stem x, and the stem's base on the
notehead — for the direction it would have taken alone, and then drawn
the way the group's first note went.  In `Gd` the d was placed stem-down
at its notehead's left edge and drawn up to the beam.

- SVGEmitter decides each group's direction before drawing any of it, as
  abcm2ps does: down where the highest note is farther above the middle
  line than the lowest is below, up in the opposite case, and a group
  as far above as below follows the part's previous stem (up if none).
- That previous stem is threaded across bars, systems and pages
  alongside the pending ties and slurs.
- A voice with a direction of its own (stem=, pipeformat, a shared
  staff's opposition) is left as it was.
- The jig and Canzonetta snapshots are re-recorded: both had a group
  with stems on the wrong side of their noteheads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sbeitzel
sbeitzel merged commit 509feca into develop Oct 4, 2026
2 checks passed
@sbeitzel
sbeitzel deleted the feature/fix_218 branch October 4, 2026 22:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant