Inter-staff review fix: make staff attribution y-aware (stems tore off)

Self-review of the inter-staff solve found a real bug: stroke->staff attribution
reused component_glyph, whose fallback picks the nearest glyph by X ALONE. That
is correct for a SLOT — both staves of a system share their x columns, hence
their spring slots, so the horizontal delta is the same either way — but wrong
for a STAFF: it handed a lower-staff stem to the UPPER staff's notehead. The
stem then kept the wrong vertical shift and tore off its own head (measured
worst stem->notehead distance 5.837 on the two-staff fixture vs 1.150, the stem
x-inset, on the single-staff one), and it polluted the upper staff's content
extent, inflating the computed gap.

Fix: the staff attribution uses a 2-D nearest for that fallback (a ledger still
resolves via owning_glyph's shared Pitch source; a staff line via its Staff
source). component_glyph is unchanged and still serves the horizontal path.

Also corrected: staff-attributed primitives now contribute their y ONLY through
the shifted path (Extent::add_x for x, add_y for the shifted staff extent), so a
lower staff's UNSHIFTED content can no longer inflate a system's max_y. Dead
Extent::add removed.

The corrected attribution yields a smaller, more accurate separation (two-staff
view_box height 36.1 -> 31.1). Regression multi_staff_stems_stay_on_their_own_
staff (verified to fail at 5.837 without the fix). Only the two-staff engrave
golden churned; single-staff goldens byte-stable. 948 tests, clippy 0, docs
-D warnings, conformance 8/8.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Levi Neuwirth 2026-07-09 09:42:51 -04:00
parent 3978225e06
commit 4132a7ac05
5 changed files with 144 additions and 54 deletions

View File

@ -709,13 +709,29 @@ single-staff score, with no inter-staff pair, is byte-identical).
**Attribution (`vertical_band` + owning-glyph).** Every resolved primitive is
attributed to its owning staff: a glyph via its `vertical_band`
(`VerticalBandKind::Staff` → `StaffId`); a stem or ledger via its notehead
(`component_glyph` → that glyph's band); a staff line via its `Staff`
provenance source; a slur via the notehead nearest its start endpoint. Spacing
is horizontal-only, so a primitive's y is unchanged from the source frame the
attribution reads, and the resolved and source indices line up. (The geometric
`round(-y / pitch)` alternative was rejected: an extreme ledgered note on the
top or bottom staff rounds to a non-existent neighbour.)
(`VerticalBandKind::Staff` → `StaffId`); a ledger via its notehead
(`owning_glyph`, a shared `Pitch` source); a **stem** — which has no same-source
glyph — via the glyph nearest its BASE point *in two dimensions*; a staff line
via its `Staff` provenance source; a slur via the notehead nearest its start
endpoint. Spacing is horizontal-only, so a primitive's y is unchanged from the
source frame the attribution reads, and the resolved and source indices line up.
(The geometric `round(-y / pitch)` alternative was rejected: an extreme ledgered
note on the top or bottom staff rounds to a non-existent neighbour.)
**Why staff attribution must be y-aware (review fix).** The first cut reused
`component_glyph`, whose fallback picks the nearest glyph **by x alone**. That is
right for a *slot* — both staves of a system share their x columns, hence their
spring slots, so the horizontal delta is the same either way — but it is wrong
for a *staff*: it handed a lower-staff stem to the UPPER staff's notehead, so the
stem kept the wrong vertical shift and tore off its own head by several staff
spaces (and polluted the upper staff's content extent, inflating the computed
gap). The staff attribution now uses a 2-D nearest for that fallback.
`component_glyph` is unchanged and still serves the horizontal path. Locked by
`multi_staff_stems_stay_on_their_own_staff` (a stem's base sits at its notehead
on both the single- and multi-staff fixtures). A related correction: staff-
attributed primitives contribute their y ONLY through the shifted path
(`Extent::add_x` for x, `add_y` for the shifted staff extent), so a lower staff's
unshifted content can no longer inflate a system's `max_y`.
**The solve.** Per system, per staff, the real content y-extent is collected
(glyphs, strokes, curves — ledgers and slurs included, not just noteheads). The

View File

@ -81,7 +81,7 @@ use epiphany_layout_ir::{
SynthesisKind, SynthesisRegistryId, VerticalBand, VerticalBandId, VerticalBandKind,
};
use crate::{component_glyph, owning_glyph};
use crate::owning_glyph;
/// The registry id for the engraver's **system-continuation synthesis**: the
/// segment of a region-spanning stroke (a staff line) that casting-off places
@ -353,22 +353,23 @@ impl Extent {
}
}
fn add(&mut self, x0: f32, y0: f32, x1: f32, y1: f32) {
if [x0, y0, x1, y1].iter().all(|v| v.is_finite()) {
self.min_x = self.min_x.min(x0.min(x1));
self.max_x = self.max_x.max(x0.max(x1));
self.min_y = self.min_y.min(y0.min(y1));
self.max_y = self.max_y.max(y0.max(y1));
self.any = true;
}
}
/// Extend only the vertical extent (the inter-staff solve grows a system's
/// height by shifting staves apart, without touching its x-span).
fn add_y(&mut self, y0: f32, y1: f32) {
if y0.is_finite() && y1.is_finite() {
self.min_y = self.min_y.min(y0.min(y1));
self.max_y = self.max_y.max(y0.max(y1));
self.any = true;
}
}
/// Extend only the horizontal extent. Staff-attributed content contributes
/// its y through the inter-staff solve (SHIFTED), never here.
fn add_x(&mut self, x0: f32, x1: f32) {
if x0.is_finite() && x1.is_finite() {
self.min_x = self.min_x.min(x0.min(x1));
self.max_x = self.max_x.max(x0.max(x1));
self.any = true;
}
}
@ -651,12 +652,28 @@ pub(crate) fn cast_off(
.collect();
let glyph_band_staff = |g: &GlyphObject| band_to_staff.get(&g.vertical_band).copied();
let glyph_staff_of: Vec<Option<StaffId>> = input.glyphs.iter().map(glyph_band_staff).collect();
// The glyph nearest a point in TWO dimensions. Attribution to a *staff* must
// be y-aware: `component_glyph`'s horizontal fallback picks the nearest glyph
// by x alone, which is right for a slot (both staves of a system share their
// x columns, hence their slots) but would hand a stem to the ADJACENT staff.
let nearest_glyph = |x: f32, y: f32| -> Option<&GlyphObject> {
input.glyphs.iter().min_by(|a, b| {
let d = |g: &GlyphObject| (g.baseline.x.0 - x).powi(2) + (g.baseline.y.0 - y).powi(2);
d(a).total_cmp(&d(b))
})
};
let stroke_staff_of: Vec<Option<StaffId>> = input
.strokes
.iter()
.map(|s| match s.provenance.source {
// A staff line names its staff outright.
TypedObjectId::Staff(st) => Some(st),
_ => component_glyph(s, &input.glyphs).and_then(glyph_band_staff),
// A ledger shares its notehead's `Pitch` source (`owning_glyph`);
// a stem has no same-source glyph, so it takes the staff of the
// glyph nearest its BASE (`from`, which sits at the notehead).
_ => owning_glyph(s, &input.glyphs)
.or_else(|| nearest_glyph(s.from.x.0, s.from.y.0))
.and_then(glyph_band_staff),
})
.collect();
let curve_staff_of: Vec<Option<StaffId>> = input
@ -706,13 +723,14 @@ pub(crate) fn cast_off(
y + glyph.bounding_box.bottom.0,
y + glyph.bounding_box.top.0,
);
extents[s].add(
extents[s].add_x(
x + glyph.bounding_box.left.0,
lo_y,
x + glyph.bounding_box.right.0,
hi_y,
);
into_staff(&mut staff_ext, s, glyph_staff_of[g], lo_y, hi_y);
match glyph_staff_of[g] {
Some(_) => into_staff(&mut staff_ext, s, glyph_staff_of[g], lo_y, hi_y),
None => extents[s].add_y(lo_y, hi_y),
}
}
}
}
@ -727,8 +745,11 @@ pub(crate) fn cast_off(
};
for (s, from, to) in segs {
let (lo_y, hi_y) = (from.y.0.min(to.y.0) - half, from.y.0.max(to.y.0) + half);
extents[s].add(from.x.0 - half, lo_y, to.x.0 + half, hi_y);
into_staff(&mut staff_ext, s, staff, lo_y, hi_y);
extents[s].add_x(from.x.0 - half, to.x.0 + half);
match staff {
Some(_) => into_staff(&mut staff_ext, s, staff, lo_y, hi_y),
None => extents[s].add_y(lo_y, hi_y),
}
if is_staff_line {
if let Some(st) = staff {
staff_ref
@ -749,8 +770,11 @@ pub(crate) fn cast_off(
};
for (s, cp) in segs {
for p in cp {
extents[s].add(p.x.0 - half, p.y.0 - half, p.x.0 + half, p.y.0 + half);
into_staff(&mut staff_ext, s, staff, p.y.0 - half, p.y.0 + half);
extents[s].add_x(p.x.0 - half, p.x.0 + half);
match staff {
Some(_) => into_staff(&mut staff_ext, s, staff, p.y.0 - half, p.y.0 + half),
None => extents[s].add_y(p.y.0 - half, p.y.0 + half),
}
}
}
}

View File

@ -2233,6 +2233,56 @@ mod tests {
}
}
#[test]
fn multi_staff_stems_stay_on_their_own_staff() {
use epiphany_layout_ir::{to_constrained, to_logical};
// Review-found bug: the inter-staff attribution reused `component_glyph`,
// whose fallback picks the nearest glyph by X ALONE — correct for a slot
// (both staves of a system share their x columns, hence their slots) but
// it handed a stem to the ADJACENT staff, so the stem kept that staff's
// vertical shift and tore off its own notehead. Attribution is now
// y-aware. Every stem's base must still sit at its notehead, offset only
// by the stem's x inset — on the single- AND multi-staff fixtures.
let worst = |score: epiphany_core::Score| -> f32 {
let r = Engraver::default().solve(
&to_constrained(&to_logical(&score)),
&SolverConfig::default(),
);
let heads: Vec<(f32, f32)> = r
.layout
.glyphs
.iter()
.filter(|g| g.glyph.as_str().starts_with("notehead"))
.map(|g| (g.position.x.0, g.position.y.0))
.collect();
r.layout
.strokes
.iter()
.filter(|st| {
(st.from.x.0 - st.to.x.0).abs() < 1e-4 && (st.from.y.0 - st.to.y.0).abs() > 1e-3
})
.map(|st| {
heads
.iter()
.map(|(hx, hy)| {
((hx - st.from.x.0).powi(2) + (hy - st.from.y.0).powi(2)).sqrt()
})
.fold(f32::INFINITY, f32::min)
})
.fold(0.0_f32, f32::max)
};
let single = worst(epiphany_testkit::fixtures::ten_measure_single_staff(1));
let multi = worst(epiphany_testkit::fixtures::two_staff_close_content(1));
assert!(
single < 1.5,
"single-staff stems stay on their heads: {single}"
);
assert!(
multi < 1.5,
"multi-staff stems stay on their OWN staff's heads: {multi}"
);
}
#[test]
fn inter_staff_solve_separates_colliding_staves() {
use epiphany_layout_ir::{to_constrained, to_logical};

View File

@ -8,7 +8,7 @@ provenance_count=54
layer_count=1
hard_constraint_count=18
xml_well_formed=true
view_box=[5.5 -41.584576 17.379084 36.084]
view_box=[5.5 -36.58458 17.379084 31.084002]
class_counts:
barline=2
clef=2

View File

@ -1,28 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="173.7908" height="360.84" viewBox="0 0 17.3791 36.084">
<svg xmlns="http://www.w3.org/2000/svg" width="173.7908" height="310.84" viewBox="0 0 17.3791 31.084">
<!-- epiphany-render-svg: glyphs are genuine Bravura SMuFL outlines inlined as paths; geometry is the resolved layout verbatim, no engraving performed here; every glyph, stroke, and curve carries a data-prov trace to its score-graph source -->
<g transform="translate(-5.5 -5.5006) scale(1 -1)">
<g data-layer="0">
<line x1="8.6599" y1="-25.9526" x2="8.6599" y2="-25.9526" stroke="#000000" stroke-width="0" data-prov="5ff1785b2e5e554c42cff8d78dd57953" data-source-kind="6" data-kind="stroke"/>
<line x1="8.6599" y1="-12.8926" x2="8.6599" y2="-12.8926" stroke="#000000" stroke-width="0" data-prov="5ff1785b2e5e554c42cff8d78dd57953" data-source-kind="6" data-kind="stroke"/>
<line x1="7.565" y1="-12.8926" x2="20.2543" y2="-12.8926" stroke="#000000" stroke-width="0.13" data-prov="28cbe5009fab9990097b67e48c426c2b" data-source-kind="3" data-kind="stroke"/>
<line x1="7.565" y1="-11.8926" x2="20.2543" y2="-11.8926" stroke="#000000" stroke-width="0.13" data-prov="2dcbda48c9ae66371e9b618b4e0ff496" data-source-kind="3" data-kind="stroke"/>
<line x1="7.565" y1="-10.8926" x2="20.2543" y2="-10.8926" stroke="#000000" stroke-width="0.13" data-prov="5f47dfd6d0910fe3fa68504e3dc8b4cb" data-source-kind="3" data-kind="stroke"/>
<line x1="7.565" y1="-9.8926" x2="20.2543" y2="-9.8926" stroke="#000000" stroke-width="0.13" data-prov="2f1bc986dc62f2e24256b98e76338b40" data-source-kind="3" data-kind="stroke"/>
<line x1="7.565" y1="-8.8926" x2="20.2543" y2="-8.8926" stroke="#000000" stroke-width="0.13" data-prov="967277083788b3e3f9928206e546eb2d" data-source-kind="3" data-kind="stroke"/>
<line x1="7.565" y1="-37.9526" x2="20.2543" y2="-37.9526" stroke="#000000" stroke-width="0.13" data-prov="c6afc2060e4ee437d03c460c5e51edb0" data-source-kind="3" data-kind="stroke"/>
<line x1="7.565" y1="-36.9526" x2="20.2543" y2="-36.9526" stroke="#000000" stroke-width="0.13" data-prov="fc03fb2cca3c3f5d6276b60b1ff83b72" data-source-kind="3" data-kind="stroke"/>
<line x1="7.565" y1="-35.9526" x2="20.2543" y2="-35.9526" stroke="#000000" stroke-width="0.13" data-prov="4ac3502112246f2950bf9ba00346e839" data-source-kind="3" data-kind="stroke"/>
<line x1="7.565" y1="-34.9526" x2="20.2543" y2="-34.9526" stroke="#000000" stroke-width="0.13" data-prov="dbcbf5fb791e00972d5a014eb0897966" data-source-kind="3" data-kind="stroke"/>
<line x1="7.565" y1="-33.9526" x2="20.2543" y2="-33.9526" stroke="#000000" stroke-width="0.13" data-prov="0c940e546e084c0e0c76dc5b5d4cc29f" data-source-kind="3" data-kind="stroke"/>
<line x1="8.6599" y1="-25.9526" x2="8.6599" y2="-25.9526" stroke="#000000" stroke-width="0" data-prov="dec48b26ccf8d32aaef3b0d78c728ac1" data-source-kind="2" data-kind="stroke"/>
<line x1="13.0944" y1="-26.9526" x2="13.0944" y2="-23.4526" stroke="#000000" stroke-width="0.12" data-prov="2605e711923a6304409ca2b5627aed7e" data-source-kind="0" data-kind="stroke"/>
<line x1="7.565" y1="-32.9526" x2="20.2543" y2="-32.9526" stroke="#000000" stroke-width="0.13" data-prov="c6afc2060e4ee437d03c460c5e51edb0" data-source-kind="3" data-kind="stroke"/>
<line x1="7.565" y1="-31.9526" x2="20.2543" y2="-31.9526" stroke="#000000" stroke-width="0.13" data-prov="fc03fb2cca3c3f5d6276b60b1ff83b72" data-source-kind="3" data-kind="stroke"/>
<line x1="7.565" y1="-30.9526" x2="20.2543" y2="-30.9526" stroke="#000000" stroke-width="0.13" data-prov="4ac3502112246f2950bf9ba00346e839" data-source-kind="3" data-kind="stroke"/>
<line x1="7.565" y1="-29.9526" x2="20.2543" y2="-29.9526" stroke="#000000" stroke-width="0.13" data-prov="dbcbf5fb791e00972d5a014eb0897966" data-source-kind="3" data-kind="stroke"/>
<line x1="7.565" y1="-28.9526" x2="20.2543" y2="-28.9526" stroke="#000000" stroke-width="0.13" data-prov="0c940e546e084c0e0c76dc5b5d4cc29f" data-source-kind="3" data-kind="stroke"/>
<line x1="8.6599" y1="-12.8926" x2="8.6599" y2="-12.8926" stroke="#000000" stroke-width="0" data-prov="dec48b26ccf8d32aaef3b0d78c728ac1" data-source-kind="2" data-kind="stroke"/>
<line x1="13.0944" y1="-13.8926" x2="13.0944" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="2605e711923a6304409ca2b5627aed7e" data-source-kind="0" data-kind="stroke"/>
<line x1="11.6444" y1="-13.8926" x2="13.4251" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="9f71ea414173b35b44c2a526ba3572b7" data-source-kind="1" data-kind="stroke"/>
<line x1="15.1751" y1="-30.4526" x2="15.1751" y2="-26.9526" stroke="#000000" stroke-width="0.12" data-prov="b2df28c1e46d5eb33759d335032e7d07" data-source-kind="0" data-kind="stroke"/>
<line x1="15.1751" y1="-17.3926" x2="15.1751" y2="-13.8926" stroke="#000000" stroke-width="0.12" data-prov="b2df28c1e46d5eb33759d335032e7d07" data-source-kind="0" data-kind="stroke"/>
<line x1="13.7251" y1="-13.8926" x2="15.5058" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="609bda392c2721347d787ed7b85bab5a" data-source-kind="1" data-kind="stroke"/>
<line x1="13.7251" y1="-14.8926" x2="15.5058" y2="-14.8926" stroke="#000000" stroke-width="0.13" data-prov="6112bc164d0d03853f4fc44f55dcb57a" data-source-kind="1" data-kind="stroke"/>
<line x1="13.7251" y1="-15.8926" x2="15.5058" y2="-15.8926" stroke="#000000" stroke-width="0.13" data-prov="bef6e57fb87c56c2916a05fdb334f49c" data-source-kind="1" data-kind="stroke"/>
<line x1="13.7251" y1="-16.8926" x2="15.5058" y2="-16.8926" stroke="#000000" stroke-width="0.13" data-prov="15e0d12c71c4e3aa49c9145482409090" data-source-kind="1" data-kind="stroke"/>
<line x1="17.2558" y1="-33.9526" x2="17.2558" y2="-30.4526" stroke="#000000" stroke-width="0.12" data-prov="b625587ff114949abad6d0b181fbfd20" data-source-kind="0" data-kind="stroke"/>
<line x1="17.2558" y1="-20.8926" x2="17.2558" y2="-17.3926" stroke="#000000" stroke-width="0.12" data-prov="b625587ff114949abad6d0b181fbfd20" data-source-kind="0" data-kind="stroke"/>
<line x1="15.8058" y1="-13.8926" x2="17.5864" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="99a975983299cf3260278173c3867b94" data-source-kind="1" data-kind="stroke"/>
<line x1="15.8058" y1="-14.8926" x2="17.5864" y2="-14.8926" stroke="#000000" stroke-width="0.13" data-prov="a53017a9a527ee7597897591ec0e0974" data-source-kind="1" data-kind="stroke"/>
<line x1="15.8058" y1="-15.8926" x2="17.5864" y2="-15.8926" stroke="#000000" stroke-width="0.13" data-prov="250e0863d6d90879e1d890ec74b581c6" data-source-kind="1" data-kind="stroke"/>
@ -31,32 +31,32 @@
<line x1="15.8058" y1="-18.8926" x2="17.5864" y2="-18.8926" stroke="#000000" stroke-width="0.13" data-prov="74ef3fb044547118b3a45fa7ab21f91f" data-source-kind="1" data-kind="stroke"/>
<line x1="15.8058" y1="-19.8926" x2="17.5864" y2="-19.8926" stroke="#000000" stroke-width="0.13" data-prov="a3f1031ad8b73109c6c7fdebb5a82f88" data-source-kind="1" data-kind="stroke"/>
<line x1="15.8058" y1="-20.8926" x2="17.5864" y2="-20.8926" stroke="#000000" stroke-width="0.13" data-prov="14bc5e293e7e48821e34201207cedf7a" data-source-kind="1" data-kind="stroke"/>
<line x1="19.3364" y1="-30.4526" x2="19.3364" y2="-26.9526" stroke="#000000" stroke-width="0.12" data-prov="7cc4fe5d82ef7386da10c5cef6508856" data-source-kind="0" data-kind="stroke"/>
<line x1="19.3364" y1="-17.3926" x2="19.3364" y2="-13.8926" stroke="#000000" stroke-width="0.12" data-prov="7cc4fe5d82ef7386da10c5cef6508856" data-source-kind="0" data-kind="stroke"/>
<line x1="17.8864" y1="-13.8926" x2="19.6671" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="516a866745c72968322bf22e817109f3" data-source-kind="1" data-kind="stroke"/>
<line x1="17.8864" y1="-14.8926" x2="19.6671" y2="-14.8926" stroke="#000000" stroke-width="0.13" data-prov="faa92f6a10e55a97e9b2f7e359f09038" data-source-kind="1" data-kind="stroke"/>
<line x1="17.8864" y1="-15.8926" x2="19.6671" y2="-15.8926" stroke="#000000" stroke-width="0.13" data-prov="c7f132db91c48ecc8015b7e748f00819" data-source-kind="1" data-kind="stroke"/>
<line x1="17.8864" y1="-16.8926" x2="19.6671" y2="-16.8926" stroke="#000000" stroke-width="0.13" data-prov="867f712bbd9c4c178425fd0f53e01fa2" data-source-kind="1" data-kind="stroke"/>
<line x1="8.6599" y1="-37.9526" x2="8.6599" y2="-37.9526" stroke="#000000" stroke-width="0" data-prov="781ef3842ebea010cbefab514c733440" data-source-kind="2" data-kind="stroke"/>
<line x1="13.0944" y1="-38.9526" x2="13.0944" y2="-35.4526" stroke="#000000" stroke-width="0.12" data-prov="8fedf07e00164403db044570899f2550" data-source-kind="0" data-kind="stroke"/>
<line x1="11.6444" y1="-38.9526" x2="13.4251" y2="-38.9526" stroke="#000000" stroke-width="0.13" data-prov="32b5060f271a3aa21b504bfc3160af6d" data-source-kind="1" data-kind="stroke"/>
<line x1="15.1751" y1="-35.4526" x2="15.1751" y2="-31.9526" stroke="#000000" stroke-width="0.12" data-prov="19a0fc032dbfc94ac8b2801dfdeaa658" data-source-kind="0" data-kind="stroke"/>
<line x1="17.2558" y1="-31.9526" x2="17.2558" y2="-28.4526" stroke="#000000" stroke-width="0.12" data-prov="e4e5f1717e87111231d31ba493aaac57" data-source-kind="0" data-kind="stroke"/>
<line x1="15.8058" y1="-32.9526" x2="17.5864" y2="-32.9526" stroke="#000000" stroke-width="0.13" data-prov="1caed515110c4415278d31cc474bf065" data-source-kind="1" data-kind="stroke"/>
<line x1="15.8058" y1="-31.9526" x2="17.5864" y2="-31.9526" stroke="#000000" stroke-width="0.13" data-prov="1189a8cf5e7e1526a44f3cf89e0b160e" data-source-kind="1" data-kind="stroke"/>
<line x1="19.3364" y1="-35.4526" x2="19.3364" y2="-31.9526" stroke="#000000" stroke-width="0.12" data-prov="f230acdf26cd79ad3ba5cd02002fa1f9" data-source-kind="0" data-kind="stroke"/>
<line x1="8.6599" y1="-32.9526" x2="8.6599" y2="-32.9526" stroke="#000000" stroke-width="0" data-prov="781ef3842ebea010cbefab514c733440" data-source-kind="2" data-kind="stroke"/>
<line x1="13.0944" y1="-33.9526" x2="13.0944" y2="-30.4526" stroke="#000000" stroke-width="0.12" data-prov="8fedf07e00164403db044570899f2550" data-source-kind="0" data-kind="stroke"/>
<line x1="11.6444" y1="-33.9526" x2="13.4251" y2="-33.9526" stroke="#000000" stroke-width="0.13" data-prov="32b5060f271a3aa21b504bfc3160af6d" data-source-kind="1" data-kind="stroke"/>
<line x1="15.1751" y1="-30.4526" x2="15.1751" y2="-26.9526" stroke="#000000" stroke-width="0.12" data-prov="19a0fc032dbfc94ac8b2801dfdeaa658" data-source-kind="0" data-kind="stroke"/>
<line x1="17.2558" y1="-26.9526" x2="17.2558" y2="-23.4526" stroke="#000000" stroke-width="0.12" data-prov="e4e5f1717e87111231d31ba493aaac57" data-source-kind="0" data-kind="stroke"/>
<line x1="15.8058" y1="-27.9526" x2="17.5864" y2="-27.9526" stroke="#000000" stroke-width="0.13" data-prov="1caed515110c4415278d31cc474bf065" data-source-kind="1" data-kind="stroke"/>
<line x1="15.8058" y1="-26.9526" x2="17.5864" y2="-26.9526" stroke="#000000" stroke-width="0.13" data-prov="1189a8cf5e7e1526a44f3cf89e0b160e" data-source-kind="1" data-kind="stroke"/>
<line x1="19.3364" y1="-30.4526" x2="19.3364" y2="-26.9526" stroke="#000000" stroke-width="0.12" data-prov="f230acdf26cd79ad3ba5cd02002fa1f9" data-source-kind="0" data-kind="stroke"/>
<path d="M 14.8053 -20.1926 C 15.6723 -19.1259 16.5392 -19.1259 17.4062 -20.1926" fill="none" stroke="#000000" stroke-width="0.12" data-prov="0165993515166bea8f7cada46e27c86f" data-source-kind="11" data-kind="curve"/>
<path d="M1.504 1.66C1.496 1.708 1.504 1.712 1.528 1.736C1.96 2.14 2.288 2.648 2.288 3.26C2.288 3.608 2.192 3.952 2.028 4.192C1.968 4.28 1.864 4.392 1.82 4.392C1.764 4.392 1.64 4.288 1.56 4.2C1.264 3.872 1.168 3.372 1.168 2.956C1.168 2.724 1.196 2.464 1.224 2.3C1.232 2.252 1.236 2.244 1.188 2.204C0.612 1.728 0 1.156 0 0.348C0 -0.348 0.476 -1.008 1.456 -1.008C1.548 -1.008 1.652 -1 1.732 -0.984C1.776 -0.976 1.784 -0.972 1.792 -1.02C1.84 -1.288 1.9 -1.636 1.9 -1.824C1.9 -2.416 1.5 -2.488 1.264 -2.488C1.048 -2.488 0.944 -2.424 0.944 -2.372C0.944 -2.344 0.98 -2.332 1.072 -2.304C1.196 -2.268 1.34 -2.16 1.34 -1.928C1.34 -1.708 1.2 -1.52 0.956 -1.52C0.688 -1.52 0.528 -1.732 0.528 -1.98C0.528 -2.24 0.684 -2.632 1.288 -2.632C1.556 -2.632 2.076 -2.512 2.076 -1.832C2.076 -1.604 2.004 -1.224 1.96 -0.976C1.952 -0.928 1.956 -0.932 2.012 -0.908C2.416 -0.748 2.684 -0.408 2.684 0.044C2.684 0.556 2.308 1.008 1.72 1.008C1.616 1.008 1.616 1.008 1.604 1.08ZM1.88 3.772C2.012 3.772 2.12 3.664 2.12 3.444C2.12 3 1.74 2.64 1.424 2.364C1.396 2.34 1.38 2.344 1.372 2.396C1.356 2.5 1.348 2.636 1.348 2.764C1.348 3.388 1.636 3.772 1.88 3.772ZM1.444 1.048C1.456 0.972 1.456 0.976 1.384 0.952C1.032 0.832 0.804 0.516 0.804 0.176C0.804 -0.184 0.992 -0.44 1.264 -0.532C1.296 -0.544 1.344 -0.556 1.372 -0.556C1.404 -0.556 1.42 -0.536 1.42 -0.512C1.42 -0.484 1.388 -0.472 1.36 -0.46C1.192 -0.388 1.072 -0.216 1.072 -0.032C1.072 0.196 1.228 0.368 1.472 0.436C1.536 0.452 1.544 0.448 1.552 0.404L1.752 -0.788C1.76 -0.832 1.756 -0.832 1.696 -0.844C1.632 -0.856 1.552 -0.864 1.472 -0.864C0.772 -0.864 0.32 -0.476 0.32 0.08C0.32 0.316 0.36 0.632 0.692 1.008C0.932 1.276 1.116 1.424 1.304 1.576C1.344 1.608 1.352 1.604 1.36 1.56ZM1.72 0.412C1.712 0.46 1.716 0.472 1.764 0.468C2.088 0.44 2.356 0.168 2.356 -0.184C2.356 -0.436 2.204 -0.64 1.98 -0.752C1.932 -0.776 1.924 -0.776 1.916 -0.728Z" transform="translate(8.6599 -11.8926)" fill="#000000" data-prov="e81b0f9cc6ccb1d0209d51b1d9797db5" data-source-kind="4" data-glyph="gClef" data-class="clef"/>
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(11.9444 -13.8926)" fill="#000000" data-prov="9b3ac956eb2deb3e538d9b1942c6ae97" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(14.0251 -17.3926)" fill="#000000" data-prov="751a5a693a87d6c1a15a8dfc3eda8102" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(16.1058 -20.8926)" fill="#000000" data-prov="130e9eddac04d8c2fb7373aae2c3b458" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(18.1864 -17.3926)" fill="#000000" data-prov="e6f9d102cb9512c808d4bff5a3560b34" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
<path d="M1.504 1.66C1.496 1.708 1.504 1.712 1.528 1.736C1.96 2.14 2.288 2.648 2.288 3.26C2.288 3.608 2.192 3.952 2.028 4.192C1.968 4.28 1.864 4.392 1.82 4.392C1.764 4.392 1.64 4.288 1.56 4.2C1.264 3.872 1.168 3.372 1.168 2.956C1.168 2.724 1.196 2.464 1.224 2.3C1.232 2.252 1.236 2.244 1.188 2.204C0.612 1.728 0 1.156 0 0.348C0 -0.348 0.476 -1.008 1.456 -1.008C1.548 -1.008 1.652 -1 1.732 -0.984C1.776 -0.976 1.784 -0.972 1.792 -1.02C1.84 -1.288 1.9 -1.636 1.9 -1.824C1.9 -2.416 1.5 -2.488 1.264 -2.488C1.048 -2.488 0.944 -2.424 0.944 -2.372C0.944 -2.344 0.98 -2.332 1.072 -2.304C1.196 -2.268 1.34 -2.16 1.34 -1.928C1.34 -1.708 1.2 -1.52 0.956 -1.52C0.688 -1.52 0.528 -1.732 0.528 -1.98C0.528 -2.24 0.684 -2.632 1.288 -2.632C1.556 -2.632 2.076 -2.512 2.076 -1.832C2.076 -1.604 2.004 -1.224 1.96 -0.976C1.952 -0.928 1.956 -0.932 2.012 -0.908C2.416 -0.748 2.684 -0.408 2.684 0.044C2.684 0.556 2.308 1.008 1.72 1.008C1.616 1.008 1.616 1.008 1.604 1.08ZM1.88 3.772C2.012 3.772 2.12 3.664 2.12 3.444C2.12 3 1.74 2.64 1.424 2.364C1.396 2.34 1.38 2.344 1.372 2.396C1.356 2.5 1.348 2.636 1.348 2.764C1.348 3.388 1.636 3.772 1.88 3.772ZM1.444 1.048C1.456 0.972 1.456 0.976 1.384 0.952C1.032 0.832 0.804 0.516 0.804 0.176C0.804 -0.184 0.992 -0.44 1.264 -0.532C1.296 -0.544 1.344 -0.556 1.372 -0.556C1.404 -0.556 1.42 -0.536 1.42 -0.512C1.42 -0.484 1.388 -0.472 1.36 -0.46C1.192 -0.388 1.072 -0.216 1.072 -0.032C1.072 0.196 1.228 0.368 1.472 0.436C1.536 0.452 1.544 0.448 1.552 0.404L1.752 -0.788C1.76 -0.832 1.756 -0.832 1.696 -0.844C1.632 -0.856 1.552 -0.864 1.472 -0.864C0.772 -0.864 0.32 -0.476 0.32 0.08C0.32 0.316 0.36 0.632 0.692 1.008C0.932 1.276 1.116 1.424 1.304 1.576C1.344 1.608 1.352 1.604 1.36 1.56ZM1.72 0.412C1.712 0.46 1.716 0.472 1.764 0.468C2.088 0.44 2.356 0.168 2.356 -0.184C2.356 -0.436 2.204 -0.64 1.98 -0.752C1.932 -0.776 1.924 -0.776 1.916 -0.728Z" transform="translate(8.6599 -36.9526)" fill="#000000" data-prov="380c7e6f256e62d482135a6342d18380" data-source-kind="4" data-glyph="gClef" data-class="clef"/>
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(11.9444 -38.9526)" fill="#000000" data-prov="38b6271edbb50b409f119eff31d0faa9" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(14.0251 -35.4526)" fill="#000000" data-prov="b2c29b264d8593df8589608aee4d8465" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(16.1058 -31.9526)" fill="#000000" data-prov="a6892ec5562df412ce4bdf3bc118e5e9" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(18.1864 -35.4526)" fill="#000000" data-prov="d8123de74f57fff5bdc35478e8ec8e9e" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
<path d="M1.504 1.66C1.496 1.708 1.504 1.712 1.528 1.736C1.96 2.14 2.288 2.648 2.288 3.26C2.288 3.608 2.192 3.952 2.028 4.192C1.968 4.28 1.864 4.392 1.82 4.392C1.764 4.392 1.64 4.288 1.56 4.2C1.264 3.872 1.168 3.372 1.168 2.956C1.168 2.724 1.196 2.464 1.224 2.3C1.232 2.252 1.236 2.244 1.188 2.204C0.612 1.728 0 1.156 0 0.348C0 -0.348 0.476 -1.008 1.456 -1.008C1.548 -1.008 1.652 -1 1.732 -0.984C1.776 -0.976 1.784 -0.972 1.792 -1.02C1.84 -1.288 1.9 -1.636 1.9 -1.824C1.9 -2.416 1.5 -2.488 1.264 -2.488C1.048 -2.488 0.944 -2.424 0.944 -2.372C0.944 -2.344 0.98 -2.332 1.072 -2.304C1.196 -2.268 1.34 -2.16 1.34 -1.928C1.34 -1.708 1.2 -1.52 0.956 -1.52C0.688 -1.52 0.528 -1.732 0.528 -1.98C0.528 -2.24 0.684 -2.632 1.288 -2.632C1.556 -2.632 2.076 -2.512 2.076 -1.832C2.076 -1.604 2.004 -1.224 1.96 -0.976C1.952 -0.928 1.956 -0.932 2.012 -0.908C2.416 -0.748 2.684 -0.408 2.684 0.044C2.684 0.556 2.308 1.008 1.72 1.008C1.616 1.008 1.616 1.008 1.604 1.08ZM1.88 3.772C2.012 3.772 2.12 3.664 2.12 3.444C2.12 3 1.74 2.64 1.424 2.364C1.396 2.34 1.38 2.344 1.372 2.396C1.356 2.5 1.348 2.636 1.348 2.764C1.348 3.388 1.636 3.772 1.88 3.772ZM1.444 1.048C1.456 0.972 1.456 0.976 1.384 0.952C1.032 0.832 0.804 0.516 0.804 0.176C0.804 -0.184 0.992 -0.44 1.264 -0.532C1.296 -0.544 1.344 -0.556 1.372 -0.556C1.404 -0.556 1.42 -0.536 1.42 -0.512C1.42 -0.484 1.388 -0.472 1.36 -0.46C1.192 -0.388 1.072 -0.216 1.072 -0.032C1.072 0.196 1.228 0.368 1.472 0.436C1.536 0.452 1.544 0.448 1.552 0.404L1.752 -0.788C1.76 -0.832 1.756 -0.832 1.696 -0.844C1.632 -0.856 1.552 -0.864 1.472 -0.864C0.772 -0.864 0.32 -0.476 0.32 0.08C0.32 0.316 0.36 0.632 0.692 1.008C0.932 1.276 1.116 1.424 1.304 1.576C1.344 1.608 1.352 1.604 1.36 1.56ZM1.72 0.412C1.712 0.46 1.716 0.472 1.764 0.468C2.088 0.44 2.356 0.168 2.356 -0.184C2.356 -0.436 2.204 -0.64 1.98 -0.752C1.932 -0.776 1.924 -0.776 1.916 -0.728Z" transform="translate(8.6599 -31.9526)" fill="#000000" data-prov="380c7e6f256e62d482135a6342d18380" data-source-kind="4" data-glyph="gClef" data-class="clef"/>
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(11.9444 -33.9526)" fill="#000000" data-prov="38b6271edbb50b409f119eff31d0faa9" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(14.0251 -30.4526)" fill="#000000" data-prov="b2c29b264d8593df8589608aee4d8465" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(16.1058 -26.9526)" fill="#000000" data-prov="a6892ec5562df412ce4bdf3bc118e5e9" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(18.1864 -30.4526)" fill="#000000" data-prov="d8123de74f57fff5bdc35478e8ec8e9e" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
<path d="M0.144 0V4H0V0ZM0.912 4H0.412V0H0.912Z" transform="translate(19.9671 -12.8926)" fill="#000000" data-prov="ed49137c7e716c68d78a85b89b50500a" data-source-kind="9" data-glyph="barlineFinal" data-class="barline"/>
<path d="M0.144 0V4H0V0ZM0.912 4H0.412V0H0.912Z" transform="translate(19.9671 -37.9526)" fill="#000000" data-prov="7171ecca2c439b3c03c959bdad56cf08" data-source-kind="9" data-glyph="barlineFinal" data-class="barline"/>
<path d="M0.144 0V4H0V0ZM0.912 4H0.412V0H0.912Z" transform="translate(19.9671 -32.9526)" fill="#000000" data-prov="7171ecca2c439b3c03c959bdad56cf08" data-source-kind="9" data-glyph="barlineFinal" data-class="barline"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB