E1 follow-up: slot-relative glyph remap (same-slot spacing preserved)

The spacing pass reserves a slot's full content extent in its advance, but
HorizontalRemap::glyphs moved every glyph independently by piecewise
interpolation — a same-slot companion whose absolute x crossed the next
slot's source was dragged by the wrong interval. E1 made it reachable: a
time signature after a morphed repeatLeft sits TIME_SIG_X + the sign's
right extension (~1.8sp) right of its barline, past the 1.6sp constrained
column step, collapsing the digits into the following note through the
real Engraver.

spacing::space_slots now returns each glyph-bearing slot's (source, target)
beside the interpolation control points; glyphs translate by their own
slot's rigid delta (intra-slot offsets survive verbatim), spanning strokes
keep endpoint interpolation, and rigid ledger strokes use the owning
glyph's slot delta exactly. Folded into ENGRAVER_VERSION 4 (unreleased this
push); scores without same-slot companions are byte-identical — only the
repeat fixture's engrave golden moved (volta digits).

Regression: time_signature_digits_ride_their_barline_slot_past_a_repeat_sign
(unbounded page so x-disjointness compares one line; verified to fail
against the interpolated remap).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Levi Neuwirth 2026-07-07 22:16:54 -04:00
parent 7a9bf42119
commit 6651ae5fce
4 changed files with 241 additions and 45 deletions

View File

@ -428,3 +428,20 @@ Repeat-free scores are byte-identical (the existing `ten_measure` /
- Volta bracket strokes raise their system's extent, so vertical stacking
and page overflow account for them with no engraver change (system height
is computed from every member box and stroke).
- **Same-slot spacing preservation (review follow-up, folded into version
4 before release).** The spacing pass reserves a slot's full content extent
(its companions included) in the slot's advance, but the remap moved every
glyph independently by piecewise interpolation — so a same-slot companion
whose absolute x crossed the next slot's *source* (E1 made this reachable:
a time signature after a morphed `repeatLeft` sits `TIME_SIG_X` + the
sign's right extension ≈ 1.8 staff spaces right of its barline, past the
1.6-space constrained column step) was dragged by the wrong interval and
collapsed into the following note. Glyphs now translate by **their own
slot's rigid delta** (`spacing::space_slots` returns the per-slot
`(source, target)` pairs beside the interpolation control points), which
is what honors the reservation; intra-slot offsets survive verbatim.
Spanning strokes keep endpoint interpolation; rigid (ledger) strokes now
translate by the owning glyph's slot delta exactly. Regression:
`time_signature_digits_ride_their_barline_slot_past_a_repeat_sign`
(unbounded page so x-disjointness compares one line; verified to fail
against the interpolated remap).

View File

@ -122,8 +122,12 @@ pub struct Engraver {
/// when casting-off gained its widow-rebalance phase (a wrapping score's system
/// breaks — and so its baked geometry — differ again from version `2`'s pure
/// greedy first-fit), and to `4` when repeat barlines and volta brackets landed
/// (a repeat-bearing score's baked geometry differs from version `3`'s
/// invisible traced anchors; repeat-free scores are unchanged).
/// and the horizontal remap became **slot-relative** (a repeat-bearing score's
/// baked geometry differs from version `3`'s invisible traced anchors, and a
/// same-slot companion glyph — a time-signature digit, key-signature
/// accidental, or spelling accidental — now rides its slot's rigid delta
/// instead of drifting by interpolation; scores without such companions are
/// unchanged).
pub const ENGRAVER_VERSION: SolverVersion = SolverVersion(4);
impl Engraver {
@ -339,6 +343,14 @@ impl Engraver {
struct HorizontalRemap {
/// `(source_x, target_x)` control points, sorted by source, sources distinct.
points: Vec<(f32, f32)>,
/// Each glyph-bearing slot's rigid translation (`target source`). A
/// glyph moves by **its own slot's** delta — never by interpolation, which
/// would drag a same-slot companion (a time-signature digit after its
/// barline, a key-signature accidental after the clef) by a neighbouring
/// interval whenever its absolute x crosses the next slot's source. The
/// spacing pass already reserved the companion's extent in the slot's
/// advance; the rigid delta is what honors that reservation.
slot_delta: BTreeMap<SpringSlotId, f32>,
}
impl HorizontalRemap {
@ -346,8 +358,14 @@ impl HorizontalRemap {
// The control points are computed collision-aware (per-slot bearings) by
// the spacing pass; sources are globally monotonic because regions tile
// left-to-right.
let spaced = spacing::space_slots(input);
HorizontalRemap {
points: spacing::control_points(input),
points: spaced.points,
slot_delta: spaced
.by_slot
.into_iter()
.map(|(id, (source, target))| (id, target - source))
.collect(),
}
}
@ -373,20 +391,29 @@ impl HorizontalRemap {
}
}
/// Re-places each glyph at its mapped x, baseline `y` preserved; provenance,
/// glyph identity, bounds, style, and layer carried through.
/// Re-places each glyph by **its slot's rigid delta** (intra-slot offsets
/// preserved verbatim — see [`HorizontalRemap::slot_delta`]), baseline `y`
/// preserved; provenance, glyph identity, bounds, style, and layer carried
/// through. A glyph whose slot the spacing pass never placed
/// (out-of-pipeline input) falls back to the interpolated map.
fn glyphs(&self, input: &ConstrainedLayoutIR) -> Vec<ResolvedGlyph> {
input
.glyphs
.iter()
.map(|g| ResolvedGlyph {
provenance: g.provenance.clone(),
glyph: g.glyph.clone(),
position: Point::new(self.map(g.baseline.x.0), g.baseline.y.0),
transform: None,
bounding_box: g.bounding_box,
style: g.style,
layer: g.layer,
.map(|g| {
let x = match self.slot_delta.get(&g.horizontal_slot) {
Some(delta) => g.baseline.x.0 + delta,
None => self.map(g.baseline.x.0),
};
ResolvedGlyph {
provenance: g.provenance.clone(),
glyph: g.glyph.clone(),
position: Point::new(x, g.baseline.y.0),
transform: None,
bounding_box: g.bounding_box,
style: g.style,
layer: g.layer,
}
})
.collect()
}
@ -403,14 +430,19 @@ impl HorizontalRemap {
.iter()
.map(|s| {
let (from_x, to_x) = if epiphany_layout_ir::is_rigid_width_stroke(s) {
// Translate rigidly by the *owning glyph's* column delta — found by
// Translate rigidly by the *owning glyph's* slot delta — found by
// source, not the stroke's midpoint, which for a wide head could
// pick a neighbouring column and reintroduce drift. The glyph
// baseline is a control point, so `map(baseline) baseline` is its
// exact column delta; applying it keeps the stroke's offset from the
// glyph and its length.
// pick a neighbouring column and reintroduce drift. The slot delta
// is the exact column translation (the same one the glyph itself
// moves by), keeping the stroke's offset from its glyph and its
// length.
let delta = owning_glyph(s, &input.glyphs)
.map(|g| self.map(g.baseline.x.0) - g.baseline.x.0)
.map(|g| {
self.slot_delta
.get(&g.horizontal_slot)
.copied()
.unwrap_or_else(|| self.map(g.baseline.x.0) - g.baseline.x.0)
})
.unwrap_or(0.0);
(s.from.x.0 + delta, s.to.x.0 + delta)
} else {
@ -1712,6 +1744,120 @@ mod tests {
);
}
/// A repeat-morphed barline whose measure introduces a time signature: the
/// digits sit in the barline's **slot** but right of the *following note
/// column's source* (`TIME_SIG_X` plus the sign's right extension exceeds
/// the constrained column step), so a per-glyph interpolated remap would
/// drag them by the wrong interval and collapse them into the following
/// note. The slot-relative remap keeps them with their barline: resolved,
/// every digit clears both the repeat sign and every notehead, and every
/// same-slot pair keeps its exact constrained offset.
#[test]
fn time_signature_digits_ride_their_barline_slot_past_a_repeat_sign() {
use epiphany_core::{
BeatGroup, MusicalDuration, PowerOfTwo, RationalTime, TimeSignature,
TimeSignatureDisplay, TimeSignatureId, TypedObjectId,
};
use epiphany_layout_ir::{Margins, Size2D};
let mut score = epiphany_testkit::fixtures::ten_measure_with_repeats(0x000A_11CE);
let ts_id: TimeSignatureId = score.identity.mint();
let beat = || BeatGroup {
duration: MusicalDuration(RationalTime::new(1, 4).expect("nonzero")),
subdivision: None,
accent: 1,
};
score.time_signatures.push(
TimeSignature::new(
ts_id,
TimeSignatureDisplay::Standard {
numerator: 4,
denominator: PowerOfTwo::new(4).expect("4 is a power of two"),
},
MusicalDuration(RationalTime::new(1, 1).expect("nonzero")),
vec![beat(), beat(), beat(), beat()],
)
.expect("4/4 beat groups sum to a whole note"),
);
// Measure 2 is the fixture's repeatLeft morph (the first repeat's start).
score.canvas.regions[0]
.content
.staff_instances_mut()
.expect("the fixture is staff-based")[0]
.measures[1]
.time_signature = Some(ts_id);
let constrained = to_constrained(&to_logical(&score));
// An unbounded page keeps everything on one endless system, so
// x-disjointness below compares glyphs of the same line (casting
// restarts x per system, making cross-system x-overlap legitimate).
let layout = Engraver::with_geometry(PageGeometry {
size: Size2D::default(),
margins: Margins::default(),
})
.solve(&constrained, &SolverConfig::default())
.layout;
let interval = |position: f32, bounding_box: &epiphany_layout_ir::BoundingBox| {
(
position + bounding_box.left.0,
position + bounding_box.right.0,
)
};
let digits: Vec<_> = layout
.glyphs
.iter()
.filter(|g| {
g.glyph.as_str().starts_with("timeSig")
&& matches!(g.provenance.source, TypedObjectId::Measure(_))
})
.collect();
assert!(!digits.is_empty(), "the 4/4 draws digit glyphs");
let sign = layout
.glyphs
.iter()
.find(|g| {
g.glyph.as_str() == "repeatLeft"
&& matches!(g.provenance.source, TypedObjectId::Measure(_))
})
.expect("the morphed start sign is engraved");
let (_, sign_right) = interval(sign.position.x.0, &sign.bounding_box);
for digit in &digits {
let (digit_left, digit_right) = interval(digit.position.x.0, &digit.bounding_box);
assert!(
digit_left >= sign_right,
"a digit must clear the repeat sign's ink"
);
for head in layout
.glyphs
.iter()
.filter(|g| g.glyph.as_str().starts_with("notehead"))
{
let (head_left, head_right) = interval(head.position.x.0, &head.bounding_box);
assert!(
digit_right <= head_left || head_right <= digit_left,
"a digit must not cross a notehead's ink"
);
}
}
// The invariant behind the fix: same-slot companions keep their exact
// constrained offsets through the re-spacing.
let resolved: Vec<_> = constrained.glyphs.iter().zip(&layout.glyphs).collect();
for (a, ra) in &resolved {
for (b, rb) in &resolved {
if a.horizontal_slot == b.horizontal_slot {
let before = b.baseline.x.0 - a.baseline.x.0;
let after = rb.position.x.0 - ra.position.x.0;
assert!(
(before - after).abs() < 1e-4,
"intra-slot offsets must survive the re-spacing"
);
}
}
}
}
/// The editing-loop vertical slice (testkit's `run_edit_loop_with`) driven
/// through the **real Engraver**: click a notehead, sharpen its pitch, re-space
/// with the Engraver, and confirm the selection survives. The selection is the

View File

@ -30,14 +30,27 @@ use crate::owning_glyph;
/// the next slot's left content.
const SLOT_GAP: f32 = 0.3;
/// Horizontal coordinate-map control points `(source_x, target_x)`, one per
/// glyph-bearing slot, sorted left to right. The source is the slot's column
/// reference (its first member glyph's baseline); the target accumulates
/// collision-aware advances so neighbouring slots' content — including
/// left-overhanging accidentals — never overlaps, and a wide lead (clef + key
/// signature) reserves real space. Deterministic: a pure function of the glyphs
/// and their bounding boxes.
pub(crate) fn control_points(input: &ConstrainedLayoutIR) -> Vec<(f32, f32)> {
/// The spacing pass's output: the interpolation control points for spanning
/// strokes, and each glyph-bearing slot's exact `(source, target)` pair — the
/// rigid delta every member glyph translates by, so intra-slot offsets (a
/// time signature after its barline, key-signature accidentals after the
/// clef, an accidental left of its notehead) survive the re-spacing verbatim.
pub(crate) struct SpacedSlots {
/// `(source_x, target_x)` control points, sorted by source, sources
/// distinct — the piecewise-linear map for content that genuinely *spans*
/// columns (staff lines, brackets).
pub points: Vec<(f32, f32)>,
/// Each glyph-bearing slot's own `(source_x, target_x)`.
pub by_slot: BTreeMap<SpringSlotId, (f32, f32)>,
}
/// Spaces the glyph-bearing slots left to right. Each slot's source is its
/// column reference (its first member glyph's baseline); the target
/// accumulates collision-aware advances so neighbouring slots' content —
/// including left-overhanging accidentals — never overlaps, and a wide lead
/// (clef + key signature) reserves real space. Deterministic: a pure function
/// of the glyphs and their bounding boxes.
pub(crate) fn space_slots(input: &ConstrainedLayoutIR) -> SpacedSlots {
/// One slot's horizontal extent, from its member glyphs.
struct Extent {
/// Column reference x (the first member's baseline).
@ -94,28 +107,34 @@ pub(crate) fn control_points(input: &ConstrainedLayoutIR) -> Vec<(f32, f32)> {
}
}
let mut slots: Vec<Extent> = by_slot.into_values().collect();
let mut slots: Vec<(SpringSlotId, Extent)> = by_slot.into_iter().collect();
slots.sort_by(|a, b| {
a.source
.partial_cmp(&b.source)
a.1.source
.partial_cmp(&b.1.source)
.unwrap_or(std::cmp::Ordering::Equal)
});
let mut points = Vec::with_capacity(slots.len());
let mut placed: BTreeMap<SpringSlotId, (f32, f32)> = BTreeMap::new();
let mut target = 0.0_f32;
for i in 0..slots.len() {
points.push((slots[i].source, target));
let right_bearing = slots[i].max_right - slots[i].source;
let (id, extent) = &slots[i];
points.push((extent.source, target));
placed.insert(*id, (extent.source, target));
let right_bearing = extent.max_right - extent.source;
// The next slot's left overhang must be cleared by *this* slot's advance.
let next_left = slots
.get(i + 1)
.map(|next| next.source - next.min_left)
.map(|(_, next)| next.source - next.min_left)
.unwrap_or(0.0);
let advance = slots[i].preferred.max(right_bearing + SLOT_GAP + next_left);
let advance = extent.preferred.max(right_bearing + SLOT_GAP + next_left);
target += advance;
}
points.dedup_by(|a, b| a.0 == b.0);
points
SpacedSlots {
points,
by_slot: placed,
}
}
#[cfg(test)]
@ -127,12 +146,24 @@ mod tests {
#[test]
fn control_points_are_monotonic_in_source_and_target() {
let c = to_constrained(&to_logical(&valid_score_rich(7)));
let points = control_points(&c);
assert!(!points.is_empty());
for w in points.windows(2) {
let spaced = space_slots(&c);
assert!(!spaced.points.is_empty());
for w in spaced.points.windows(2) {
assert!(w[1].0 > w[0].0, "sources strictly increase");
assert!(w[1].1 > w[0].1, "targets strictly increase");
}
// The two views describe one spacing: every placed slot's pair is one
// of the control points (this fixture's slot sources are all distinct,
// so the equal-source dedup removes nothing).
for (source, target) in spaced.by_slot.values() {
assert!(
spaced
.points
.iter()
.any(|(s, t)| s == source && t == target),
"slot pair ({source}, {target}) must be a control point"
);
}
}
#[test]
@ -140,9 +171,9 @@ mod tests {
// A wide lead (clef) advances by more than a uniform note slot, so the
// engraved targets are not a copy of the source columns.
let c = to_constrained(&to_logical(&valid_score_rich(7)));
let points = control_points(&c);
let spaced = space_slots(&c);
assert!(
points.iter().any(|(s, t)| (s - t).abs() > 1e-3),
spaced.points.iter().any(|(s, t)| (s - t).abs() > 1e-3),
"targets must differ from sources (re-spacing happened)"
);
}
@ -150,6 +181,8 @@ mod tests {
#[test]
fn spacing_is_deterministic() {
let c = to_constrained(&to_logical(&valid_score_rich(3)));
assert_eq!(control_points(&c), control_points(&c));
let (a, b) = (space_slots(&c), space_slots(&c));
assert_eq!(a.points, b.points);
assert_eq!(a.by_slot, b.by_slot);
}
}

View File

@ -159,11 +159,11 @@
<path d="M0.7 0V4H0.556V0ZM1.468 4H0.968V0H1.468ZM0.204 2.72C0.092 2.72 0.004 2.632 0.004 2.52C0.004 2.412 0.092 2.32 0.204 2.32C0.312 2.32 0.404 2.412 0.404 2.52C0.404 2.632 0.312 2.72 0.204 2.72ZM0.204 1.712C0.092 1.712 0.004 1.624 0.004 1.512C0.004 1.4 0.092 1.312 0.204 1.312C0.312 1.312 0.404 1.4 0.404 1.512C0.404 1.624 0.312 1.712 0.204 1.712Z" transform="translate(17.8556 -25.1054)" fill="#000000" data-prov="f21f4e337050d50f36f7d905196db794" data-source-kind="9" data-glyph="repeatRight" data-class="repeat"/>
<path d="M0.144 0V4H0V0Z" transform="translate(31.107 -25.1054)" fill="#000000" data-prov="f4a3968aa56472207e4e921c44dfa1d7" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
<path d="M0.144 0V4H0V0ZM0.912 4H0.412V0H0.912Z" transform="translate(51.2776 -25.1054)" fill="#000000" data-prov="d99180b4838ec1269b3f4caee0201968" data-source-kind="9" data-glyph="barlineFinal" data-class="barline"/>
<path d="M0.096 0.052C0.096 0.052 0.08 0.028 0.08 0C0.08 -0.02 0.092 -0.044 0.124 -0.056C0.14 -0.06 0.156 -0.064 0.16 -0.064C0.2 -0.064 0.216 -0.028 0.216 -0.028C0.216 -0.028 0.388 0.248 0.432 0.324C0.448 0.352 0.464 0.364 0.472 0.364C0.488 0.364 0.496 0.332 0.496 0.308V-0.724C0.496 -0.816 0.404 -0.876 0.32 -0.876C0.292 -0.876 0.252 -0.888 0.252 -0.936C0.252 -0.98 0.288 -1 0.34 -1H1.192C1.256 -1 1.256 -0.936 1.256 -0.936C1.256 -0.936 1.256 -0.876 1.196 -0.876C1.14 -0.876 1.068 -0.804 1.068 -0.736V0.912C1.068 0.976 1.044 1 0.988 1.004C0.932 1.004 0.832 0.988 0.78 0.988C0.704 0.988 0.632 0.992 0.572 1C0.564 1 0.556 1.004 0.552 1.004C0.512 1.004 0.496 0.964 0.48 0.928Z" transform="translate(8.1442 -19.9054)" fill="#000000" data-prov="5d17b38b8aac6677412c7079c585d10a" data-source-kind="23" data-glyph="timeSig1" data-class="timeSig"/>
<path d="M1.684 -0.364C1.684 -0.316 1.664 -0.308 1.636 -0.308C1.604 -0.308 1.592 -0.324 1.584 -0.352L1.58 -0.356C1.54 -0.456 1.508 -0.532 1.424 -0.532C1.404 -0.532 1.384 -0.528 1.356 -0.52C1.304 -0.5 1.276 -0.496 1.236 -0.476C1.156 -0.444 0.968 -0.38 0.804 -0.38C0.752 -0.38 0.7 -0.388 0.656 -0.404C0.744 -0.26 1.084 -0.14 1.172 -0.116C1.452 -0.04 1.704 0.076 1.704 0.408C1.704 0.832 1.288 1.016 0.916 1.016C0.636 1.016 0.388 0.992 0.192 0.764C0.124 0.68 0.08 0.58 0.08 0.472C0.08 0.416 0.092 0.36 0.116 0.3C0.176 0.176 0.3 0.08 0.444 0.08C0.688 0.08 0.724 0.332 0.724 0.432C0.724 0.672 0.448 0.684 0.448 0.764C0.456 0.82 0.528 0.916 0.764 0.916C1.12 0.916 1.124 0.648 1.124 0.532C1.124 0.168 0.824 -0.108 0.536 -0.284C0.316 -0.424 0.16 -0.62 0.092 -0.88L0.088 -0.892C0.088 -0.944 0.132 -1.028 0.192 -1.028C0.28 -1.028 0.328 -0.784 0.564 -0.784C0.724 -0.784 0.784 -1 1.14 -1C1.312 -1 1.62 -0.984 1.684 -0.364Z" transform="translate(20.9386 -19.9054)" fill="#000000" data-prov="9f572f5af2bd7160313e5f31005e3f24" data-source-kind="23" data-glyph="timeSig2" data-class="timeSig"/>
<path d="M0.852 0.992C0.848 0.992 0.844 0.992 0.836 0.992L0.808 0.996C0.804 0.996 0.796 0.996 0.792 0.996C0.448 0.996 0.104 0.768 0.104 0.556C0.104 0.424 0.18 0.248 0.428 0.232H0.448C0.624 0.232 0.712 0.36 0.712 0.492V0.524C0.7 0.672 0.6 0.68 0.58 0.688C0.56 0.696 0.5 0.688 0.5 0.744V0.76C0.508 0.828 0.632 0.86 0.668 0.86C1.008 0.86 1.04 0.648 1.04 0.552V0.524C1.04 0.228 0.804 0.112 0.552 0.1C0.512 0.096 0.456 0.076 0.456 0.032C0.456 -0.016 0.524 -0.016 0.556 -0.016C1.016 -0.016 1.052 -0.328 1.052 -0.38C1.052 -0.804 0.836 -0.852 0.748 -0.852C0.732 -0.852 0.716 -0.848 0.712 -0.848C0.68 -0.844 0.604 -0.844 0.6 -0.784V-0.764C0.6 -0.676 0.688 -0.616 0.692 -0.5C0.692 -0.332 0.576 -0.212 0.404 -0.212C0.388 -0.212 0.376 -0.212 0.36 -0.216C0.292 -0.228 0.216 -0.268 0.168 -0.32C0.1 -0.38 0.08 -0.476 0.08 -0.564C0.088 -0.876 0.372 -0.996 0.764 -1.004H0.8C1.196 -1.004 1.604 -0.8 1.604 -0.448V-0.42C1.596 -0.304 1.568 -0.228 1.492 -0.14C1.468 -0.108 1.436 -0.08 1.396 -0.056L1.312 -0.008L1.18 0.028C1.16 0.032 1.148 0.032 1.14 0.048C1.136 0.056 1.136 0.06 1.136 0.068C1.136 0.084 1.14 0.1 1.152 0.104C1.196 0.116 1.24 0.12 1.276 0.14C1.436 0.216 1.52 0.32 1.52 0.504C1.52 0.872 1.02 0.98 0.852 0.992Z" transform="translate(23.2143 -19.9054)" fill="#000000" data-prov="ea315f82ff52d77f24cddcfbe048fb75" data-source-kind="23" data-glyph="timeSig3" data-class="timeSig"/>
<path d="M0.096 0.052C0.096 0.052 0.08 0.028 0.08 0C0.08 -0.02 0.092 -0.044 0.124 -0.056C0.14 -0.06 0.156 -0.064 0.16 -0.064C0.2 -0.064 0.216 -0.028 0.216 -0.028C0.216 -0.028 0.388 0.248 0.432 0.324C0.448 0.352 0.464 0.364 0.472 0.364C0.488 0.364 0.496 0.332 0.496 0.308V-0.724C0.496 -0.816 0.404 -0.876 0.32 -0.876C0.292 -0.876 0.252 -0.888 0.252 -0.936C0.252 -0.98 0.288 -1 0.34 -1H1.192C1.256 -1 1.256 -0.936 1.256 -0.936C1.256 -0.936 1.256 -0.876 1.196 -0.876C1.14 -0.876 1.068 -0.804 1.068 -0.736V0.912C1.068 0.976 1.044 1 0.988 1.004C0.932 1.004 0.832 0.988 0.78 0.988C0.704 0.988 0.632 0.992 0.572 1C0.564 1 0.556 1.004 0.552 1.004C0.512 1.004 0.496 0.964 0.48 0.928Z" transform="translate(7.98 -19.9054)" fill="#000000" data-prov="5d17b38b8aac6677412c7079c585d10a" data-source-kind="23" data-glyph="timeSig1" data-class="timeSig"/>
<path d="M1.684 -0.364C1.684 -0.316 1.664 -0.308 1.636 -0.308C1.604 -0.308 1.592 -0.324 1.584 -0.352L1.58 -0.356C1.54 -0.456 1.508 -0.532 1.424 -0.532C1.404 -0.532 1.384 -0.528 1.356 -0.52C1.304 -0.5 1.276 -0.496 1.236 -0.476C1.156 -0.444 0.968 -0.38 0.804 -0.38C0.752 -0.38 0.7 -0.388 0.656 -0.404C0.744 -0.26 1.084 -0.14 1.172 -0.116C1.452 -0.04 1.704 0.076 1.704 0.408C1.704 0.832 1.288 1.016 0.916 1.016C0.636 1.016 0.388 0.992 0.192 0.764C0.124 0.68 0.08 0.58 0.08 0.472C0.08 0.416 0.092 0.36 0.116 0.3C0.176 0.176 0.3 0.08 0.444 0.08C0.688 0.08 0.724 0.332 0.724 0.432C0.724 0.672 0.448 0.684 0.448 0.764C0.456 0.82 0.528 0.916 0.764 0.916C1.12 0.916 1.124 0.648 1.124 0.532C1.124 0.168 0.824 -0.108 0.536 -0.284C0.316 -0.424 0.16 -0.62 0.092 -0.88L0.088 -0.892C0.088 -0.944 0.132 -1.028 0.192 -1.028C0.28 -1.028 0.328 -0.784 0.564 -0.784C0.724 -0.784 0.784 -1 1.14 -1C1.312 -1 1.62 -0.984 1.684 -0.364Z" transform="translate(19.5798 -19.9054)" fill="#000000" data-prov="9f572f5af2bd7160313e5f31005e3f24" data-source-kind="23" data-glyph="timeSig2" data-class="timeSig"/>
<path d="M0.852 0.992C0.848 0.992 0.844 0.992 0.836 0.992L0.808 0.996C0.804 0.996 0.796 0.996 0.792 0.996C0.448 0.996 0.104 0.768 0.104 0.556C0.104 0.424 0.18 0.248 0.428 0.232H0.448C0.624 0.232 0.712 0.36 0.712 0.492V0.524C0.7 0.672 0.6 0.68 0.58 0.688C0.56 0.696 0.5 0.688 0.5 0.744V0.76C0.508 0.828 0.632 0.86 0.668 0.86C1.008 0.86 1.04 0.648 1.04 0.552V0.524C1.04 0.228 0.804 0.112 0.552 0.1C0.512 0.096 0.456 0.076 0.456 0.032C0.456 -0.016 0.524 -0.016 0.556 -0.016C1.016 -0.016 1.052 -0.328 1.052 -0.38C1.052 -0.804 0.836 -0.852 0.748 -0.852C0.732 -0.852 0.716 -0.848 0.712 -0.848C0.68 -0.844 0.604 -0.844 0.6 -0.784V-0.764C0.6 -0.676 0.688 -0.616 0.692 -0.5C0.692 -0.332 0.576 -0.212 0.404 -0.212C0.388 -0.212 0.376 -0.212 0.36 -0.216C0.292 -0.228 0.216 -0.268 0.168 -0.32C0.1 -0.38 0.08 -0.476 0.08 -0.564C0.088 -0.876 0.372 -0.996 0.764 -1.004H0.8C1.196 -1.004 1.604 -0.8 1.604 -0.448V-0.42C1.596 -0.304 1.568 -0.228 1.492 -0.14C1.468 -0.108 1.436 -0.08 1.396 -0.056L1.312 -0.008L1.18 0.028C1.16 0.032 1.148 0.032 1.14 0.048C1.136 0.056 1.136 0.06 1.136 0.068C1.136 0.084 1.14 0.1 1.152 0.104C1.196 0.116 1.24 0.12 1.276 0.14C1.436 0.216 1.52 0.32 1.52 0.504C1.52 0.872 1.02 0.98 0.852 0.992Z" transform="translate(20.8798 -19.9054)" fill="#000000" data-prov="ea315f82ff52d77f24cddcfbe048fb75" data-source-kind="23" data-glyph="timeSig3" data-class="timeSig"/>
<path d="M0.768 0H0.912V4H0.768ZM0 0H0.5V4H0ZM1.264 2.288C1.376 2.288 1.464 2.376 1.464 2.488C1.464 2.6 1.376 2.688 1.264 2.688C1.156 2.688 1.064 2.6 1.064 2.488C1.064 2.376 1.156 2.288 1.264 2.288ZM1.264 1.28C1.376 1.28 1.464 1.368 1.464 1.48C1.464 1.588 1.376 1.68 1.264 1.68C1.156 1.68 1.064 1.588 1.064 1.48C1.064 1.368 1.156 1.28 1.264 1.28Z" transform="translate(40.6297 -25.1054)" fill="#000000" data-prov="06532f5cbd726829ae54fa8326ac4880" data-source-kind="23" data-glyph="repeatLeft" data-class="repeat"/>
<path d="M0.2 2.68C0.088 2.68 0 2.592 0 2.48C0 2.372 0.088 2.28 0.2 2.28C0.308 2.28 0.4 2.372 0.4 2.48C0.4 2.592 0.308 2.68 0.2 2.68ZM0.2 1.672C0.088 1.672 0 1.584 0 1.472C0 1.36 0.088 1.272 0.2 1.272C0.308 1.272 0.4 1.36 0.4 1.472C0.4 1.584 0.308 1.672 0.2 1.672Z" transform="translate(50.8544 -25.1054)" fill="#000000" data-prov="ecb6833fd9d62fc4cd4aa529763c58db" data-source-kind="23" data-glyph="repeatDots" data-class="repeat"/>
<path d="M0.2 2.68C0.088 2.68 0 2.592 0 2.48C0 2.372 0.088 2.28 0.2 2.28C0.308 2.28 0.4 2.372 0.4 2.48C0.4 2.592 0.308 2.68 0.2 2.68ZM0.2 1.672C0.088 1.672 0 1.584 0 1.472C0 1.36 0.088 1.272 0.2 1.272C0.308 1.272 0.4 1.36 0.4 1.472C0.4 1.584 0.308 1.672 0.2 1.672Z" transform="translate(50.7172 -25.1054)" fill="#000000" data-prov="ecb6833fd9d62fc4cd4aa529763c58db" data-source-kind="23" data-glyph="repeatDots" data-class="repeat"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB