Agent I Phase 2-3: engrave recognizable notation from Score to SVG
Turn the Score -> layout-IR -> SVG pipeline from placeholder glyphs into real
music notation, rendered through the stub solver and the real Engraver alike.
to_constrained (the spacing pass) now dispatches each layout object to the
notation primitive that represents it, on a column-based spring model:
- Pitch -> notehead at its clef-relative staff position; chord pitches share
one column slot. StaffInstance -> clef glyph. Staff -> five staff-line
strokes (the bottom line its anchor, four synthesized). Pitched Event ->
stem stroke; Measure -> barline glyph; rest Event -> rest glyph.
- Phase 3 ornaments as synthesized glyphs: a spelling's full accidental stack
left of its notehead, a key signature's clef-relative sharp/flat zigzag in
the lead, and a measure's numerator/denominator time-signature digit pair.
- A tied decomposition draws one notehead/stem/rest per component (offsets
honored, not collapsed). Active clef and key resolve by time, not vector
order. The lead area reserves clef + key-signature width.
- Coverage/surjection preserved so the round-trip holds: each laid-out object
is covered by exactly one exact-provenance primitive, and derived primitives
(staff lines, components, accidentals, key/time glyphs) are synthesized from
a laid-out source. Engraving-coverage gaps (missing spelling, unbundled
glyph) are surfaced as ConstrainedLayoutIR diagnostics, not silently
defaulted. A measure depends on the time signature it displays.
The Engraver re-spaces glyphs AND the strokes that track them through one
collision-aware coordinate map (per-slot left/right bearings, pairwise
advances), so stems / barlines / staff lines stay attached and a note's
accidental never overlaps the previous note. validate() now rejects empty
spring slots -- the contract that map relies on -- and to_constrained never
emits one (slots are realized by glyph occupancy).
Bundle the genuine Bravura outlines and metrics for time-signature digits 0-9
(regenerated from the SHA-pinned font via tools/extract_bravura_outlines.py),
replacing an inconsistent hand-written placeholder.
Regions tile left-to-right (no page casting-off yet). Goldens regenerated into
recognizable notation. Full gate green: build, fmt, clippy, 574 tests,
conformance scale 1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
47a581a4de
commit
91c9f01bb5
|
|
@ -10,6 +10,11 @@
|
|||
/ALL_DECISIONS_AND_READMES.md
|
||||
/HANDOFF.md
|
||||
|
||||
# The render-svg demo writes its SVG here by convention
|
||||
# (`cargo run -p epiphany-render-svg --example render_fixture -- … > out.svg`);
|
||||
# it is a working artifact, not source.
|
||||
/out.svg
|
||||
|
||||
# LaTeX build intermediates (the spec PDF itself is tracked; these are not).
|
||||
spec/*.aux
|
||||
spec/*.fdb_latexmk
|
||||
|
|
|
|||
|
|
@ -16,8 +16,9 @@
|
|||
//! grows this crate into the real two-pass spring solver. This is the first
|
||||
//! increment: [`Engraver`] runs a genuine deterministic **horizontal spacing
|
||||
//! pass** (see [`spacing`]) — the first axis of the planned two-pass spring
|
||||
//! layout — placing each spring slot left-to-right by its preferred width
|
||||
//! instead of returning the input columns verbatim.
|
||||
//! layout — placing each glyph-bearing slot left-to-right by a collision-aware
|
||||
//! advance (its preferred width floored by the real glyph bearings) instead of
|
||||
//! returning the input columns verbatim.
|
||||
//!
|
||||
//! It does **not yet** run the vertical spring pass, the soft-constraint
|
||||
//! stretch/compress solve, or evaluate the IR's declared hard constraints. By the
|
||||
|
|
@ -41,13 +42,11 @@
|
|||
|
||||
mod spacing;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use epiphany_layout_ir::{
|
||||
all_available, BravuraCatalog, ConstrainedLayoutIR, ConstraintSolver, GlyphCatalog,
|
||||
InvalidationSet, Margins, QualityMetricVector, Rect, ResolvedGlyph, ResolvedLayoutIR,
|
||||
InvalidationSet, Margins, Point, QualityMetricVector, Rect, ResolvedGlyph, ResolvedLayoutIR,
|
||||
ResolvedPage, ResolvedSystem, Size2D, SolveReport, SolveStatus, SolverBudgetUsed, SolverConfig,
|
||||
SolverState, SolverTier, SolverVersion, SolverWarning, SolverWarningKind,
|
||||
SolverState, SolverTier, SolverVersion, SolverWarning, SolverWarningKind, Stroke,
|
||||
};
|
||||
|
||||
/// The Epiphany engraving solver (Chapter 9). See the crate docs for the phase
|
||||
|
|
@ -81,11 +80,17 @@ impl Engraver {
|
|||
// constraints is reported as not-yet-solvable rather than falsely Solved.
|
||||
let well_formed = structural_valid && catalog_valid && input.constraints.is_empty();
|
||||
|
||||
let glyphs: Vec<ResolvedGlyph> = if structural_valid {
|
||||
let positions = spacing::slot_positions(input);
|
||||
place_glyphs(input, &positions)
|
||||
// The horizontal spacing pass re-places each glyph by its spring slot.
|
||||
// The strokes that track those glyphs (stems, staff lines, barlines) must
|
||||
// ride the *same* horizontal map, or a re-spaced notehead would leave its
|
||||
// stem behind. Both gate on structural validity: a malformed input must
|
||||
// not leak geometry into the diagnostic layout (which reaches
|
||||
// canonical_bytes / the renderer).
|
||||
let (glyphs, strokes): (Vec<ResolvedGlyph>, Vec<Stroke>) = if structural_valid {
|
||||
let remap = HorizontalRemap::build(input);
|
||||
(remap.glyphs(input), remap.strokes(input))
|
||||
} else {
|
||||
Vec::new()
|
||||
(Vec::new(), Vec::new())
|
||||
};
|
||||
let resolved_glyphs = glyphs.len();
|
||||
|
||||
|
|
@ -136,6 +141,7 @@ impl Engraver {
|
|||
source: input.source,
|
||||
pages,
|
||||
glyphs,
|
||||
strokes,
|
||||
engraving_decisions: input.engraving_decisions.clone(),
|
||||
catalog: input.catalog.clone(),
|
||||
},
|
||||
|
|
@ -158,32 +164,92 @@ impl Engraver {
|
|||
}
|
||||
}
|
||||
|
||||
/// Copies each glyph to the `x` of its horizontal slot (its baseline `y`
|
||||
/// preserved), preserving provenance, glyph identity, bounds, style, and layer.
|
||||
/// A glyph whose slot has no computed position keeps its baseline `x`.
|
||||
fn place_glyphs(
|
||||
input: &ConstrainedLayoutIR,
|
||||
positions: &BTreeMap<epiphany_layout_ir::SpringSlotId, f32>,
|
||||
) -> Vec<ResolvedGlyph> {
|
||||
input
|
||||
.glyphs
|
||||
.iter()
|
||||
.map(|g| {
|
||||
let x = positions
|
||||
.get(&g.horizontal_slot)
|
||||
.copied()
|
||||
.unwrap_or(g.baseline.x.0);
|
||||
ResolvedGlyph {
|
||||
/// A monotonic piecewise-linear map from a constrained x to its spaced x. Each
|
||||
/// column's *source* x (the baseline its member glyphs share) maps to the
|
||||
/// *target* x the spacing pass assigns its slot; intermediate and outlying
|
||||
/// coordinates interpolate/extrapolate linearly. Applied to glyph baselines
|
||||
/// **and** stroke endpoints alike, so a stroke at (or near) a glyph's column
|
||||
/// moves with it instead of detaching — the fix for strokes being left at their
|
||||
/// constrained coordinates while glyphs re-space.
|
||||
struct HorizontalRemap {
|
||||
/// `(source_x, target_x)` control points, sorted by source, sources distinct.
|
||||
points: Vec<(f32, f32)>,
|
||||
}
|
||||
|
||||
impl HorizontalRemap {
|
||||
fn build(input: &ConstrainedLayoutIR) -> Self {
|
||||
// The control points are computed collision-aware (per-slot bearings) by
|
||||
// the spacing pass; sources are globally monotonic because regions tile
|
||||
// left-to-right.
|
||||
HorizontalRemap {
|
||||
points: spacing::control_points(input),
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps a constrained x to its spaced x.
|
||||
fn map(&self, x: f32) -> f32 {
|
||||
let p = &self.points;
|
||||
match p.len() {
|
||||
0 => x,
|
||||
// One column: a pure translation keeps relative offsets.
|
||||
1 => x + (p[0].1 - p[0].0),
|
||||
n => {
|
||||
if x <= p[0].0 {
|
||||
interp(p[0], p[1], x)
|
||||
} else if x >= p[n - 1].0 {
|
||||
interp(p[n - 2], p[n - 1], x)
|
||||
} else {
|
||||
p.windows(2)
|
||||
.find(|w| x >= w[0].0 && x <= w[1].0)
|
||||
.map(|w| interp(w[0], w[1], x))
|
||||
.unwrap_or(x)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Re-places each glyph at its mapped x, baseline `y` preserved; provenance,
|
||||
/// glyph identity, bounds, style, and layer carried through.
|
||||
fn glyphs(&self, input: &ConstrainedLayoutIR) -> Vec<ResolvedGlyph> {
|
||||
input
|
||||
.glyphs
|
||||
.iter()
|
||||
.map(|g| ResolvedGlyph {
|
||||
provenance: g.provenance.clone(),
|
||||
glyph: g.glyph.clone(),
|
||||
position: epiphany_layout_ir::Point::new(x, g.baseline.y.0),
|
||||
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,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Re-maps both endpoints of each stroke, so it tracks the glyphs it spans.
|
||||
fn strokes(&self, input: &ConstrainedLayoutIR) -> Vec<Stroke> {
|
||||
input
|
||||
.strokes
|
||||
.iter()
|
||||
.map(|s| Stroke {
|
||||
provenance: s.provenance.clone(),
|
||||
from: Point::new(self.map(s.from.x.0), s.from.y.0),
|
||||
to: Point::new(self.map(s.to.x.0), s.to.y.0),
|
||||
thickness: s.thickness,
|
||||
layer: s.layer,
|
||||
style: s.style,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Linear interpolation/extrapolation through two control points.
|
||||
fn interp((s0, t0): (f32, f32), (s1, t1): (f32, f32), x: f32) -> f32 {
|
||||
if (s1 - s0).abs() < f32::EPSILON {
|
||||
t0
|
||||
} else {
|
||||
t0 + (x - s0) * (t1 - t0) / (s1 - s0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConstraintSolver for Engraver {
|
||||
|
|
@ -254,6 +320,35 @@ mod tests {
|
|||
assert_eq!(report.metric_vector, QualityMetricVector::unmeasured());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_structurally_invalid_input_emits_no_strokes() {
|
||||
// Strokes are gated on the same structural validity as glyphs: an input
|
||||
// whose validation fails (here, an out-of-range stroke thickness) yields a
|
||||
// diagnostic layout with no glyphs *and* no strokes — the malformed stroke
|
||||
// must not leak into canonical_bytes or the renderer.
|
||||
let mut input = fixture();
|
||||
let provenance = input.glyphs[0].provenance.clone();
|
||||
input.strokes.push(epiphany_layout_ir::Stroke {
|
||||
provenance,
|
||||
from: epiphany_layout_ir::Point::new(0.0, 0.0),
|
||||
to: epiphany_layout_ir::Point::new(1.0, 0.0),
|
||||
thickness: epiphany_layout_ir::StaffSpace(f32::MAX),
|
||||
layer: 0,
|
||||
style: epiphany_layout_ir::GlyphStyle::default(),
|
||||
});
|
||||
assert!(
|
||||
input.validate().is_err(),
|
||||
"the out-of-range stroke is invalid"
|
||||
);
|
||||
let report = Engraver.solve(&input, &SolverConfig::default());
|
||||
assert_eq!(report.status, SolveStatus::InternalError);
|
||||
assert!(report.layout.glyphs.is_empty());
|
||||
assert!(
|
||||
report.layout.strokes.is_empty(),
|
||||
"a structurally invalid input emits no strokes (gated like glyphs)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn horizontal_spacing_differs_from_the_verbatim_stub() {
|
||||
// The whole point of the scaffold: it re-spaces horizontally rather than
|
||||
|
|
@ -279,6 +374,50 @@ mod tests {
|
|||
assert_eq!(engraved.glyphs.len(), stub.glyphs.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strokes_ride_the_same_coordinate_map_as_glyphs() {
|
||||
// The spacing pass re-places glyphs; the strokes that track them must move
|
||||
// by the same horizontal map, not stay at their constrained coordinates.
|
||||
let input = fixture();
|
||||
let engraved = Engraver.solve(&input, &SolverConfig::default()).layout;
|
||||
|
||||
// Constrained x -> engraved x, per glyph.
|
||||
let glyph_map: Vec<(f32, f32)> = input
|
||||
.glyphs
|
||||
.iter()
|
||||
.zip(&engraved.glyphs)
|
||||
.map(|(c, r)| (c.baseline.x.0, r.position.x.0))
|
||||
.collect();
|
||||
|
||||
// Every stroke endpoint coincident with a glyph's column lands at that
|
||||
// glyph's engraved x — they ride one map, so they stay attached.
|
||||
let mut checked = 0;
|
||||
for (c, r) in input.strokes.iter().zip(&engraved.strokes) {
|
||||
for (gx, ex) in &glyph_map {
|
||||
if (c.from.x.0 - gx).abs() < 1e-6 {
|
||||
assert!(
|
||||
(r.from.x.0 - ex).abs() < 1e-3,
|
||||
"a stroke at a glyph's column detached from it after spacing"
|
||||
);
|
||||
checked += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
checked > 0,
|
||||
"expected strokes coincident with glyph columns"
|
||||
);
|
||||
|
||||
// …and the strokes actually moved (the pass re-spaces, it does not echo).
|
||||
let moved = input.strokes.iter().zip(&engraved.strokes).any(|(c, r)| {
|
||||
(c.from.x.0 - r.from.x.0).abs() > 1e-4 || (c.to.x.0 - r.to.x.0).abs() > 1e-4
|
||||
});
|
||||
assert!(
|
||||
moved,
|
||||
"strokes must be re-spaced with the glyphs, not left behind"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn solve_is_deterministic_and_quantizable() {
|
||||
let input = fixture();
|
||||
|
|
@ -288,6 +427,205 @@ mod tests {
|
|||
assert_eq!(a.canonical_bytes(), b.canonical_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn engraver_reserves_an_accidental_against_the_previous_note() {
|
||||
// A note's accidental overhangs *left* of its notehead, into the previous
|
||||
// note's column. The spacing pass must reserve that overhang (against the
|
||||
// previous slot's advance), or the accidental overlaps the prior notehead.
|
||||
use epiphany_core::{
|
||||
AccidentalId, CmnNominal, EventId, MusicalPosition, PitchId, PitchSpelling,
|
||||
RationalTime, RegionId, StaffId, TypedObjectId,
|
||||
};
|
||||
use epiphany_layout_ir::{
|
||||
LayoutContent, LayoutObject, LayoutRegion, LocalCoordinateSystem, LogicalLayoutIR,
|
||||
MetricTimeAxis, NoteContent, NotePitch, Provenance, ScoreVersion, TimeAxisModel,
|
||||
TimePoint, VerticalExtent,
|
||||
};
|
||||
|
||||
let region = RegionId::from_raw(1);
|
||||
let staff = StaffId::from_raw(10);
|
||||
let plain = PitchId::from_raw(100);
|
||||
let sharped = PitchId::from_raw(101);
|
||||
let manifested = |src, content| {
|
||||
LayoutObject::from_projection_with_content(
|
||||
Provenance::manifested(src, region, vec![]),
|
||||
Some(staff),
|
||||
content,
|
||||
)
|
||||
};
|
||||
let at = |n, d| TimePoint::Musical(MusicalPosition(RationalTime::new(n, d).unwrap()));
|
||||
let note = |pid: PitchId, time: TimePoint, accidental: bool| {
|
||||
let mut spelling = PitchSpelling::cmn(CmnNominal::C, 5);
|
||||
if accidental {
|
||||
spelling.accidentals.push(AccidentalId::new("sharp"));
|
||||
}
|
||||
LayoutContent::Note(NoteContent {
|
||||
position: time,
|
||||
components: vec![],
|
||||
pitches: vec![NotePitch {
|
||||
pitch: pid,
|
||||
spelling: Some(spelling),
|
||||
}],
|
||||
})
|
||||
};
|
||||
let logical = LogicalLayoutIR {
|
||||
source: ScoreVersion::default(),
|
||||
regions: vec![LayoutRegion {
|
||||
provenance: Provenance::projected(TypedObjectId::Region(region), vec![]),
|
||||
coordinate_system: LocalCoordinateSystem::default(),
|
||||
time_axis: TimeAxisModel::Metric(MetricTimeAxis::default()),
|
||||
vertical_extent: VerticalExtent {
|
||||
staves: vec![staff],
|
||||
},
|
||||
objects: vec![
|
||||
// A plain note, then a note with a sharp a quarter later.
|
||||
manifested(
|
||||
TypedObjectId::Event(EventId::from_raw(1)),
|
||||
note(plain, at(0, 1), false),
|
||||
),
|
||||
manifested(TypedObjectId::Pitch(plain), LayoutContent::Structural),
|
||||
manifested(
|
||||
TypedObjectId::Event(EventId::from_raw(2)),
|
||||
note(sharped, at(1, 4), true),
|
||||
),
|
||||
manifested(TypedObjectId::Pitch(sharped), LayoutContent::Structural),
|
||||
],
|
||||
}],
|
||||
engraving_decisions: vec![],
|
||||
overrides: vec![],
|
||||
cross_region: vec![],
|
||||
};
|
||||
|
||||
let constrained = to_constrained(&logical);
|
||||
let engraved = Engraver
|
||||
.solve(&constrained, &SolverConfig::default())
|
||||
.layout;
|
||||
let mut noteheads: Vec<_> = engraved
|
||||
.glyphs
|
||||
.iter()
|
||||
.filter(|g| g.glyph.as_str() == "noteheadBlack")
|
||||
.collect();
|
||||
noteheads.sort_by(|a, b| a.position.x.0.partial_cmp(&b.position.x.0).unwrap());
|
||||
assert_eq!(noteheads.len(), 2, "two noteheads");
|
||||
let first_right = noteheads[0].position.x.0 + noteheads[0].bounding_box.right.0;
|
||||
let sharp = engraved
|
||||
.glyphs
|
||||
.iter()
|
||||
.find(|g| g.glyph.as_str() == "accidentalSharp")
|
||||
.expect("a sharp is drawn");
|
||||
let sharp_left = sharp.position.x.0 + sharp.bounding_box.left.0;
|
||||
assert!(
|
||||
sharp_left >= first_right,
|
||||
"the accidental ({sharp_left}) overlaps the previous notehead's right edge ({first_right})"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn engraver_preserves_key_signature_lead_spacing() {
|
||||
// The lead area (clef + key signature) is fixed-width content. The spacing
|
||||
// pass must reserve it via the lead slot's preferred width, or it compresses
|
||||
// the key signature back onto the clef. This drives the *real* engraver, not
|
||||
// just the verbatim stub.
|
||||
use epiphany_core::{
|
||||
CmnNominal, EventId, KeySignature, MusicalPosition, PitchId, PitchSpelling, RegionId,
|
||||
StaffId, StaffInstanceId, TypedObjectId,
|
||||
};
|
||||
use epiphany_layout_ir::{
|
||||
LayoutContent, LayoutObject, LayoutRegion, LocalCoordinateSystem, LogicalLayoutIR,
|
||||
MetricTimeAxis, NoteContent, NotePitch, PlacedKeySignature, Provenance, ScoreVersion,
|
||||
StaffContent, TimeAxisModel, TimePoint, VerticalExtent,
|
||||
};
|
||||
|
||||
let region = RegionId::from_raw(1);
|
||||
let staff = StaffId::from_raw(10);
|
||||
let pitch = PitchId::from_raw(100);
|
||||
let manifested = |src, content| {
|
||||
LayoutObject::from_projection_with_content(
|
||||
Provenance::manifested(src, region, vec![]),
|
||||
Some(staff),
|
||||
content,
|
||||
)
|
||||
};
|
||||
let logical = LogicalLayoutIR {
|
||||
source: ScoreVersion::default(),
|
||||
regions: vec![LayoutRegion {
|
||||
provenance: Provenance::projected(TypedObjectId::Region(region), vec![]),
|
||||
coordinate_system: LocalCoordinateSystem::default(),
|
||||
time_axis: TimeAxisModel::Metric(MetricTimeAxis::default()),
|
||||
vertical_extent: VerticalExtent {
|
||||
staves: vec![staff],
|
||||
},
|
||||
objects: vec![
|
||||
// A 3-sharp (A major) key signature, then a note.
|
||||
manifested(
|
||||
TypedObjectId::StaffInstance(StaffInstanceId::from_raw(1)),
|
||||
LayoutContent::Staff(StaffContent {
|
||||
clefs: vec![],
|
||||
keys: vec![PlacedKeySignature {
|
||||
time: TimePoint::Musical(MusicalPosition::origin()),
|
||||
key: KeySignature::new(3).expect("three sharps"),
|
||||
}],
|
||||
}),
|
||||
),
|
||||
manifested(
|
||||
TypedObjectId::Event(EventId::from_raw(1)),
|
||||
LayoutContent::Note(NoteContent {
|
||||
position: TimePoint::Musical(MusicalPosition::origin()),
|
||||
components: vec![],
|
||||
pitches: vec![NotePitch {
|
||||
pitch,
|
||||
spelling: Some(PitchSpelling::cmn(CmnNominal::C, 5)),
|
||||
}],
|
||||
}),
|
||||
),
|
||||
manifested(TypedObjectId::Pitch(pitch), LayoutContent::Structural),
|
||||
],
|
||||
}],
|
||||
engraving_decisions: vec![],
|
||||
overrides: vec![],
|
||||
cross_region: vec![],
|
||||
};
|
||||
|
||||
let constrained = to_constrained(&logical);
|
||||
let engraved = Engraver
|
||||
.solve(&constrained, &SolverConfig::default())
|
||||
.layout;
|
||||
let x_of = |name: &str| {
|
||||
engraved
|
||||
.glyphs
|
||||
.iter()
|
||||
.find(|g| g.glyph.as_str() == name)
|
||||
.map(|g| g.position.x.0)
|
||||
};
|
||||
let clef_x = x_of("gClef").expect("clef engraved");
|
||||
let note_x = x_of("noteheadBlack").expect("notehead engraved");
|
||||
let sharps: Vec<f32> = engraved
|
||||
.glyphs
|
||||
.iter()
|
||||
.filter(|g| g.glyph.as_str() == "accidentalSharp")
|
||||
.map(|g| g.position.x.0)
|
||||
.collect();
|
||||
assert_eq!(sharps.len(), 3, "a three-sharp signature");
|
||||
|
||||
// Not compressed into the clef: the lead clearly exceeds one note slot.
|
||||
assert!(
|
||||
note_x - clef_x > 3.0,
|
||||
"key signature compressed into the clef (lead width {})",
|
||||
note_x - clef_x
|
||||
);
|
||||
// The accidentals sit in the lead (clef..note), spread to distinct x.
|
||||
assert!(
|
||||
sharps.iter().all(|&x| x > clef_x && x < note_x),
|
||||
"accidentals must lie between the clef and the first note"
|
||||
);
|
||||
let mut sorted = sharps;
|
||||
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||
assert!(
|
||||
sorted[0] < sorted[1] && sorted[1] < sorted[2],
|
||||
"accidentals are spread out, not stacked at one x"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn incremental_is_observationally_equivalent_to_full() {
|
||||
let input = fixture();
|
||||
|
|
|
|||
|
|
@ -1,107 +1,132 @@
|
|||
//! The horizontal spacing pass — the first axis of the planned two-pass spring
|
||||
//! layout (`epiphany-engrave`'s DECISIONS.md, decision 1).
|
||||
//!
|
||||
//! A `ConstrainedLayoutIR` carries one horizontal spring slot per musical time
|
||||
//! column, each with a `preferred_width` in staff spaces. This pass walks the
|
||||
//! slots in their emitted left-to-right order and assigns each an absolute `x`
|
||||
//! by accumulating preferred widths, producing even, non-overlapping horizontal
|
||||
//! spacing (the stub solver, by contrast, returns the raw input columns
|
||||
//! verbatim). The vertical pass, the soft-spring stretch/compress solve, and
|
||||
//! constraint evaluation are deferred to the `Minimal`-tier work (next phase).
|
||||
//! A `ConstrainedLayoutIR` carries horizontal spring slots — one slot per
|
||||
//! *musical time column* (`to_constrained` groups simultaneous glyphs into a
|
||||
//! shared column slot, with the clef in a lead column and barlines in their own
|
||||
//! columns). This pass places each glyph-bearing slot left to right and yields
|
||||
//! the coordinate-map control points the caller ([`crate::HorizontalRemap`])
|
||||
//! applies to glyph baselines *and* the strokes that track them.
|
||||
//!
|
||||
//! The advance from one slot to the next is the larger of the slot's
|
||||
//! `preferred_width` (the spring's natural width — a uniform placeholder in v0)
|
||||
//! and a **collision minimum** derived from real glyph bounding boxes: the slot's
|
||||
//! right content extent, plus a gap, plus the *next* slot's left overhang (its
|
||||
//! accidental zone). Reserving the next slot's left overhang against *this* slot's
|
||||
//! advance is what protects a note's accidental from overlapping the previous
|
||||
//! note — a single per-slot `preferred_width` could only reserve space to the
|
||||
//! right of a slot's source. The vertical pass, the soft-spring stretch/compress
|
||||
//! solve, and constraint evaluation remain `Minimal`-tier work (next phase).
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use epiphany_layout_ir::{ConstrainedLayoutIR, SpringSlotId, StaffSpace};
|
||||
use epiphany_layout_ir::{ConstrainedLayoutIR, SpringSlotId};
|
||||
|
||||
/// The absolute `x` (in staff spaces) assigned to each spring slot, accumulated
|
||||
/// left-to-right over the slots' emitted order. Deterministic: a pure function
|
||||
/// of the slot sequence and their preferred widths.
|
||||
pub(crate) fn slot_positions(input: &ConstrainedLayoutIR) -> BTreeMap<SpringSlotId, f32> {
|
||||
let mut positions = BTreeMap::new();
|
||||
let mut cursor = 0.0_f32;
|
||||
for slot in &input.horizontal_slots {
|
||||
positions.insert(slot.id, cursor);
|
||||
// Advance by the slot's preferred width; a non-finite or negative width
|
||||
// would have been rejected by `ConstrainedLayoutIR::validate`, but clamp
|
||||
// defensively so the cursor stays finite and monotonic regardless.
|
||||
let StaffSpace(width) = slot.preferred_width;
|
||||
cursor += if width.is_finite() && width >= 0.0 {
|
||||
width
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
/// Inter-slot gap (staff spaces) reserved between one slot's right content and
|
||||
/// 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)> {
|
||||
/// One slot's horizontal extent, from its member glyphs.
|
||||
struct Extent {
|
||||
/// Column reference x (the first member's baseline).
|
||||
source: f32,
|
||||
/// Leftmost / rightmost content edge across the slot's glyphs.
|
||||
min_left: f32,
|
||||
max_right: f32,
|
||||
/// The spring's natural width.
|
||||
preferred: f32,
|
||||
}
|
||||
positions
|
||||
|
||||
let preferred_of: BTreeMap<SpringSlotId, f32> = input
|
||||
.horizontal_slots
|
||||
.iter()
|
||||
.map(|s| (s.id, s.preferred_width.0))
|
||||
.collect();
|
||||
let mut by_slot: BTreeMap<SpringSlotId, Extent> = BTreeMap::new();
|
||||
for glyph in &input.glyphs {
|
||||
let left = glyph.baseline.x.0 + glyph.bounding_box.left.0;
|
||||
let right = glyph.baseline.x.0 + glyph.bounding_box.right.0;
|
||||
by_slot
|
||||
.entry(glyph.horizontal_slot)
|
||||
.and_modify(|e| {
|
||||
e.min_left = e.min_left.min(left);
|
||||
e.max_right = e.max_right.max(right);
|
||||
})
|
||||
.or_insert(Extent {
|
||||
source: glyph.baseline.x.0,
|
||||
min_left: left,
|
||||
max_right: right,
|
||||
preferred: preferred_of
|
||||
.get(&glyph.horizontal_slot)
|
||||
.copied()
|
||||
.unwrap_or(0.0),
|
||||
});
|
||||
}
|
||||
|
||||
let mut slots: Vec<Extent> = by_slot.into_values().collect();
|
||||
slots.sort_by(|a, b| {
|
||||
a.source
|
||||
.partial_cmp(&b.source)
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
});
|
||||
|
||||
let mut points = Vec::with_capacity(slots.len());
|
||||
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;
|
||||
// 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)
|
||||
.unwrap_or(0.0);
|
||||
let advance = slots[i].preferred.max(right_bearing + SLOT_GAP + next_left);
|
||||
target += advance;
|
||||
}
|
||||
points.dedup_by(|a, b| a.0 == b.0);
|
||||
points
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use epiphany_core::generators::valid_score_rich;
|
||||
use epiphany_core::WallClockTime;
|
||||
use epiphany_layout_ir::{
|
||||
to_constrained, to_logical, GlyphCatalogIdentity, SpringSlot, TimePoint,
|
||||
};
|
||||
|
||||
fn slot(id: u128, preferred: f32) -> SpringSlot {
|
||||
SpringSlot {
|
||||
id: SpringSlotId(id),
|
||||
time: TimePoint::WallClock(WallClockTime(id as i64)),
|
||||
min_width: StaffSpace(preferred),
|
||||
preferred_width: StaffSpace(preferred),
|
||||
max_width: None,
|
||||
stretch_factor: 1.0,
|
||||
compress_factor: 1.0,
|
||||
members: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn ir_with_slots(slots: Vec<SpringSlot>) -> ConstrainedLayoutIR {
|
||||
ConstrainedLayoutIR {
|
||||
source: Default::default(),
|
||||
regions: vec![],
|
||||
horizontal_slots: slots,
|
||||
glyphs: vec![],
|
||||
vertical_bands: vec![],
|
||||
constraints: vec![],
|
||||
engraving_decisions: vec![],
|
||||
catalog: GlyphCatalogIdentity::default(),
|
||||
}
|
||||
}
|
||||
use epiphany_layout_ir::{to_constrained, to_logical};
|
||||
|
||||
#[test]
|
||||
fn slots_are_placed_left_to_right_by_accumulated_width() {
|
||||
fn control_points_are_monotonic_in_source_and_target() {
|
||||
let c = to_constrained(&to_logical(&valid_score_rich(7)));
|
||||
let positions = slot_positions(&c);
|
||||
// Every slot got a position equal to the running cursor, non-decreasing
|
||||
// in emitted order (preferred widths are non-negative).
|
||||
assert_eq!(positions.len(), c.horizontal_slots.len());
|
||||
let mut cursor = 0.0_f32;
|
||||
for slot in &c.horizontal_slots {
|
||||
let x = positions[&slot.id];
|
||||
assert!(x.is_finite());
|
||||
assert!(
|
||||
(x - cursor).abs() < 1e-3,
|
||||
"slot x must equal the running cursor"
|
||||
);
|
||||
cursor += slot.preferred_width.0;
|
||||
let points = control_points(&c);
|
||||
assert!(!points.is_empty());
|
||||
for w in points.windows(2) {
|
||||
assert!(w[1].0 > w[0].0, "sources strictly increase");
|
||||
assert!(w[1].1 > w[0].1, "targets strictly increase");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spacing_uses_preferred_widths_not_input_columns() {
|
||||
// Slots that each prefer 2.0 staff spaces lay out at 0, 2, 4 — a pure
|
||||
// function of widths, independent of any input baseline column.
|
||||
let input = ir_with_slots(vec![slot(1, 2.0), slot(2, 2.0), slot(3, 2.0)]);
|
||||
let p = slot_positions(&input);
|
||||
assert_eq!(p[&SpringSlotId(1)], 0.0);
|
||||
assert_eq!(p[&SpringSlotId(2)], 2.0);
|
||||
assert_eq!(p[&SpringSlotId(3)], 4.0);
|
||||
fn spacing_re_spaces_rather_than_echoing_sources() {
|
||||
// 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);
|
||||
assert!(
|
||||
points.iter().any(|(s, t)| (s - t).abs() > 1e-3),
|
||||
"targets must differ from sources (re-spacing happened)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spacing_is_deterministic() {
|
||||
let input = ir_with_slots(vec![slot(10, 1.5), slot(20, 1.5)]);
|
||||
assert_eq!(slot_positions(&input), slot_positions(&input));
|
||||
let c = to_constrained(&to_logical(&valid_score_rich(3)));
|
||||
assert_eq!(control_points(&c), control_points(&c));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,327 @@
|
|||
//! Music-theory engraving primitives — the pure mapping from notated content to
|
||||
//! SMuFL glyph names and staff positions, with no layout-IR plumbing.
|
||||
//!
|
||||
//! This is the "engraving decision" layer Chapter 7 assigns to the layout IR:
|
||||
//! which glyph notates a note value, where a pitch sits on the staff under a
|
||||
//! clef, which accidentals a spelling draws, and which accidentals a key
|
||||
//! signature places. The constrained-layout pass consumes these; the renderer
|
||||
//! never makes these choices (Chapter 7 §"Non-overreach").
|
||||
//!
|
||||
//! Every function here is total and deterministic. Glyph names are the SMuFL
|
||||
//! canonical names bundled in [`crate::glyph`].
|
||||
|
||||
use epiphany_core::{
|
||||
AccidentalId, Clef, ClefShape, CmnNominal, KeySignature, NoteValue, StemDirection,
|
||||
};
|
||||
|
||||
/// A staff position in **half-staff-space steps from the bottom staff line**:
|
||||
/// the bottom line is `0`, the space just above it is `1`, the second line is
|
||||
/// `2`, … (negative below the bottom line). One step is half a staff space, so
|
||||
/// the visual `y` of a position is `position as f32 * 0.5` staff spaces.
|
||||
///
|
||||
/// This is a **diatonic** position: it depends only on the nominal letter and
|
||||
/// octave, never on accidentals — a C-sharp sits on the same line as a
|
||||
/// C-natural. Accidentals are drawn to the *left* of the notehead, they do not
|
||||
/// move it vertically.
|
||||
pub type StaffStep = i32;
|
||||
|
||||
/// The diatonic index of a `(nominal, octave)`: `octave * 7 + letter`, with the
|
||||
/// nominal letter C=0 … B=6 ([`CmnNominal`]). Octaves are scientific-pitch
|
||||
/// (C4 = middle C), so each whole octave is exactly seven diatonic steps.
|
||||
fn diatonic_index(nominal: CmnNominal, octave: i8) -> i32 {
|
||||
octave as i32 * 7 + nominal as i32
|
||||
}
|
||||
|
||||
/// The diatonic index of a clef shape's reference pitch — the pitch the clef
|
||||
/// glyph fixes on its line: G clef → G4, F clef → F3, C clef → middle C (C4).
|
||||
/// A percussion clef has no diatonic reference.
|
||||
fn clef_reference_diatonic(shape: ClefShape) -> Option<i32> {
|
||||
match shape {
|
||||
ClefShape::G => Some(diatonic_index(CmnNominal::G, 4)),
|
||||
ClefShape::F => Some(diatonic_index(CmnNominal::F, 3)),
|
||||
ClefShape::C => Some(diatonic_index(CmnNominal::C, 4)),
|
||||
ClefShape::Percussion => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The staff position of a diatonic pitch under `clef` (see [`StaffStep`]).
|
||||
///
|
||||
/// The clef fixes its reference pitch on `clef.line` (line 1 = the bottom line);
|
||||
/// every diatonic step away from that pitch is one half-staff-space step.
|
||||
/// `clef.octave_shift` transposes the written position: a `+1` (8va) clef writes
|
||||
/// a sounding pitch an octave *lower* on the staff, a `-1` (8vb) clef an octave
|
||||
/// *higher*. A percussion clef has no diatonic mapping and reports its own line
|
||||
/// (a neutral mid-staff position) for any pitch.
|
||||
pub fn staff_position(nominal: CmnNominal, octave: i8, clef: &Clef) -> StaffStep {
|
||||
let reference_line_step = (clef.line as i32 - 1) * 2;
|
||||
match clef_reference_diatonic(clef.shape) {
|
||||
Some(reference_diatonic) => {
|
||||
reference_line_step + (diatonic_index(nominal, octave) - reference_diatonic)
|
||||
- clef.octave_shift as i32 * 7
|
||||
}
|
||||
None => reference_line_step,
|
||||
}
|
||||
}
|
||||
|
||||
/// The SMuFL notehead glyph for a note value: a hollow whole/half notehead, else
|
||||
/// the filled black notehead.
|
||||
pub fn notehead_glyph(value: NoteValue) -> &'static str {
|
||||
match value {
|
||||
NoteValue::Whole => "noteheadWhole",
|
||||
NoteValue::Half => "noteheadHalf",
|
||||
_ => "noteheadBlack",
|
||||
}
|
||||
}
|
||||
|
||||
/// The SMuFL rest glyph for a note value, if one is bundled. Only whole/half/
|
||||
/// quarter/eighth rests ship in the bundled metrics; a sixteenth-or-shorter rest
|
||||
/// reports `None` so the caller surfaces the missing glyph coverage rather than
|
||||
/// misrendering it as an eighth rest.
|
||||
pub fn rest_glyph(value: NoteValue) -> Option<&'static str> {
|
||||
Some(match value {
|
||||
NoteValue::Whole => "restWhole",
|
||||
NoteValue::Half => "restHalf",
|
||||
NoteValue::Quarter => "restQuarter",
|
||||
NoteValue::Eighth => "rest8th",
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Whether a note value is drawn with a stem — every value but the whole note.
|
||||
pub fn has_stem(value: NoteValue) -> bool {
|
||||
!matches!(value, NoteValue::Whole)
|
||||
}
|
||||
|
||||
/// The SMuFL flag glyph for an *unbeamed* stemmed note value, if one is bundled.
|
||||
/// Only the eighth-note flag ships in the bundled metrics; shorter values need
|
||||
/// their own flag glyphs (or beaming, deferred past I-1) and report `None`.
|
||||
pub fn flag_glyph(value: NoteValue, stem: StemDirection) -> Option<&'static str> {
|
||||
match value {
|
||||
NoteValue::Eighth => Some(match stem {
|
||||
StemDirection::Up => "flag8thUp",
|
||||
StemDirection::Down => "flag8thDown",
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The SMuFL clef glyph for a clef shape, if one is bundled. A percussion clef
|
||||
/// reports `None` until its glyph is bundled — returning a G clef for it would
|
||||
/// be a semantic false positive, so the caller surfaces the gap instead.
|
||||
pub fn clef_glyph(shape: ClefShape) -> Option<&'static str> {
|
||||
Some(match shape {
|
||||
ClefShape::G => "gClef",
|
||||
ClefShape::F => "fClef",
|
||||
ClefShape::C => "cClef",
|
||||
ClefShape::Percussion => return None,
|
||||
})
|
||||
}
|
||||
|
||||
/// The SMuFL accidental glyph for a spelling accidental, if one is bundled.
|
||||
/// `None` for an accidental the bundled metrics don't carry (e.g. microtonal),
|
||||
/// which the caller surfaces rather than papers over.
|
||||
pub fn accidental_glyph(accidental: &AccidentalId) -> Option<&'static str> {
|
||||
Some(match accidental.as_str() {
|
||||
"sharp" => "accidentalSharp",
|
||||
"flat" => "accidentalFlat",
|
||||
"natural" => "accidentalNatural",
|
||||
"doublesharp" | "double-sharp" => "accidentalDoubleSharp",
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
/// One accidental in a key signature: the SMuFL glyph and the staff position it
|
||||
/// occupies under the active clef.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct KeyAccidental {
|
||||
pub glyph: &'static str,
|
||||
pub position: StaffStep,
|
||||
}
|
||||
|
||||
// The conventional staff-step offsets, from the first accidental, of the
|
||||
// key-signature "zigzag" — invariant across clefs (only the start position is
|
||||
// clef-dependent). Sharp order F C G D A E B; flat order B E A D G C F.
|
||||
const SHARP_OFFSETS: [StaffStep; 7] = [0, -3, 1, -2, -5, -1, -4];
|
||||
const FLAT_OFFSETS: [StaffStep; 7] = [0, 3, -1, 2, -2, 1, -3];
|
||||
|
||||
/// The staff position of the first key-signature accidental of `letter` under
|
||||
/// `clef`: the octave of `letter` whose position is closest to the conventional
|
||||
/// band (sharps high, near the top line; flats mid, near the middle line),
|
||||
/// breaking ties toward the higher position. This reproduces the standard
|
||||
/// treble and bass placements; other clefs follow the same principle.
|
||||
fn first_accidental_position(letter: CmnNominal, clef: &Clef, sharp: bool) -> StaffStep {
|
||||
let target = if sharp { 8 } else { 4 };
|
||||
(-2i8..=10)
|
||||
.map(|octave| staff_position(letter, octave, clef))
|
||||
.min_by_key(|&p| ((p - target).abs(), -p))
|
||||
.unwrap_or(target)
|
||||
}
|
||||
|
||||
/// The ordered accidentals a key signature places under `clef`. A positive
|
||||
/// `key.fifths()` places that many sharps (order F C G D A E B); a negative one
|
||||
/// places that many flats (order B E A D G C F); `0` (and a percussion clef)
|
||||
/// places none. The count needs no clamping — [`KeySignature`] already
|
||||
/// guarantees `fifths` is within the conventional `-7..=7` (I-0 invariant).
|
||||
pub fn key_signature(key: KeySignature, clef: &Clef) -> Vec<KeyAccidental> {
|
||||
let fifths = key.fifths();
|
||||
if fifths == 0 || matches!(clef.shape, ClefShape::Percussion) {
|
||||
return Vec::new();
|
||||
}
|
||||
let count = fifths.unsigned_abs() as usize;
|
||||
let (glyph, start, offsets) = if fifths > 0 {
|
||||
(
|
||||
"accidentalSharp",
|
||||
first_accidental_position(CmnNominal::F, clef, true),
|
||||
&SHARP_OFFSETS,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
"accidentalFlat",
|
||||
first_accidental_position(CmnNominal::B, clef, false),
|
||||
&FLAT_OFFSETS,
|
||||
)
|
||||
};
|
||||
(0..count)
|
||||
.map(|i| KeyAccidental {
|
||||
glyph,
|
||||
position: start + offsets[i],
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn staff_position_is_diatonic_and_clef_relative() {
|
||||
let treble = Clef::treble();
|
||||
// Treble: E4 is the bottom line (0); G4 the second line (2); F5 the top
|
||||
// line (8); A4 the second space (3); C4 a ledger below (−2).
|
||||
assert_eq!(staff_position(CmnNominal::E, 4, &treble), 0);
|
||||
assert_eq!(staff_position(CmnNominal::G, 4, &treble), 2);
|
||||
assert_eq!(staff_position(CmnNominal::F, 5, &treble), 8);
|
||||
assert_eq!(staff_position(CmnNominal::A, 4, &treble), 3);
|
||||
assert_eq!(staff_position(CmnNominal::C, 4, &treble), -2);
|
||||
|
||||
// Bass: F3 the fourth line (6); G2 the bottom line (0); middle C4 the
|
||||
// first ledger above (10).
|
||||
let bass = Clef::bass();
|
||||
assert_eq!(staff_position(CmnNominal::F, 3, &bass), 6);
|
||||
assert_eq!(staff_position(CmnNominal::G, 2, &bass), 0);
|
||||
assert_eq!(staff_position(CmnNominal::C, 4, &bass), 10);
|
||||
|
||||
// Alto: middle C4 the middle line (4). Tenor: middle C4 the fourth line (6).
|
||||
assert_eq!(staff_position(CmnNominal::C, 4, &Clef::alto()), 4);
|
||||
assert_eq!(staff_position(CmnNominal::C, 4, &Clef::tenor()), 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accidentals_never_change_staff_position() {
|
||||
// The position is purely diatonic — the same nominal+octave lands on the
|
||||
// same line regardless of any accidental the spelling carries.
|
||||
let treble = Clef::treble();
|
||||
let c_natural = staff_position(CmnNominal::C, 5, &treble);
|
||||
let c_sharp = staff_position(CmnNominal::C, 5, &treble); // spelling differs, position must not
|
||||
assert_eq!(c_natural, c_sharp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn octave_shift_sign_is_explicit() {
|
||||
// 8va (+1) writes a sounding pitch an octave LOWER on the staff; 8vb (−1)
|
||||
// an octave HIGHER. A sounding G5 under treble-8va sits where G4 sits on
|
||||
// a plain treble clef (step 2); under treble-8vb, where G6 would (step 16).
|
||||
let plain = Clef::treble();
|
||||
let up = Clef {
|
||||
octave_shift: 1,
|
||||
..Clef::treble()
|
||||
};
|
||||
let down = Clef {
|
||||
octave_shift: -1,
|
||||
..Clef::treble()
|
||||
};
|
||||
let plain_g5 = staff_position(CmnNominal::G, 5, &plain); // 9
|
||||
assert_eq!(plain_g5, 9);
|
||||
assert_eq!(staff_position(CmnNominal::G, 5, &up), plain_g5 - 7);
|
||||
assert_eq!(staff_position(CmnNominal::G, 5, &down), plain_g5 + 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn note_value_glyphs() {
|
||||
assert_eq!(notehead_glyph(NoteValue::Whole), "noteheadWhole");
|
||||
assert_eq!(notehead_glyph(NoteValue::Half), "noteheadHalf");
|
||||
assert_eq!(notehead_glyph(NoteValue::Quarter), "noteheadBlack");
|
||||
assert_eq!(notehead_glyph(NoteValue::Sixteenth), "noteheadBlack");
|
||||
assert_eq!(rest_glyph(NoteValue::Whole), Some("restWhole"));
|
||||
assert_eq!(rest_glyph(NoteValue::Eighth), Some("rest8th"));
|
||||
assert_eq!(rest_glyph(NoteValue::Sixteenth), None);
|
||||
assert!(!has_stem(NoteValue::Whole));
|
||||
assert!(has_stem(NoteValue::Quarter));
|
||||
assert_eq!(flag_glyph(NoteValue::Quarter, StemDirection::Up), None);
|
||||
assert_eq!(
|
||||
flag_glyph(NoteValue::Eighth, StemDirection::Up),
|
||||
Some("flag8thUp")
|
||||
);
|
||||
assert_eq!(
|
||||
flag_glyph(NoteValue::Eighth, StemDirection::Down),
|
||||
Some("flag8thDown")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clef_and_accidental_glyphs() {
|
||||
assert_eq!(clef_glyph(ClefShape::G), Some("gClef"));
|
||||
assert_eq!(clef_glyph(ClefShape::F), Some("fClef"));
|
||||
assert_eq!(clef_glyph(ClefShape::C), Some("cClef"));
|
||||
assert_eq!(clef_glyph(ClefShape::Percussion), None);
|
||||
assert_eq!(
|
||||
accidental_glyph(&AccidentalId::new("sharp")),
|
||||
Some("accidentalSharp")
|
||||
);
|
||||
assert_eq!(
|
||||
accidental_glyph(&AccidentalId::new("flat")),
|
||||
Some("accidentalFlat")
|
||||
);
|
||||
assert_eq!(
|
||||
accidental_glyph(&AccidentalId::new("natural")),
|
||||
Some("accidentalNatural")
|
||||
);
|
||||
assert_eq!(accidental_glyph(&AccidentalId::new("quarter-sharp")), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_signature_positions_match_the_conventional_pattern() {
|
||||
let key = |fifths: i8| KeySignature::new(fifths).expect("fifths in range");
|
||||
|
||||
// C major / A minor: nothing.
|
||||
assert!(key_signature(key(0), &Clef::treble()).is_empty());
|
||||
|
||||
// Treble sharps F C G D A E B → steps 8 5 9 6 3 7 4.
|
||||
let treble_sharps: Vec<StaffStep> = key_signature(key(7), &Clef::treble())
|
||||
.iter()
|
||||
.map(|a| a.position)
|
||||
.collect();
|
||||
assert_eq!(treble_sharps, vec![8, 5, 9, 6, 3, 7, 4]);
|
||||
assert!(key_signature(key(7), &Clef::treble())
|
||||
.iter()
|
||||
.all(|a| a.glyph == "accidentalSharp"));
|
||||
|
||||
// Treble flats B E A D G C F → steps 4 7 3 6 2 5 1.
|
||||
let treble_flats: Vec<StaffStep> = key_signature(key(-7), &Clef::treble())
|
||||
.iter()
|
||||
.map(|a| a.position)
|
||||
.collect();
|
||||
assert_eq!(treble_flats, vec![4, 7, 3, 6, 2, 5, 1]);
|
||||
assert!(key_signature(key(-7), &Clef::treble())
|
||||
.iter()
|
||||
.all(|a| a.glyph == "accidentalFlat"));
|
||||
|
||||
// Bass: first sharp F#3 on the fourth line (6); first flat B♭2 on the
|
||||
// second line (2).
|
||||
assert_eq!(key_signature(key(1), &Clef::bass())[0].position, 6);
|
||||
assert_eq!(key_signature(key(-1), &Clef::bass())[0].position, 2);
|
||||
|
||||
// A two-sharp signature places exactly two accidentals.
|
||||
assert_eq!(key_signature(key(2), &Clef::treble()).len(), 2);
|
||||
}
|
||||
}
|
||||
|
|
@ -213,8 +213,20 @@ pub const BRAVURA_METRICS: &[GlyphMetric] = &[
|
|||
GlyphMetric::new("flag8thUp", 1007, [0, -84, 1007, 2607]),
|
||||
GlyphMetric::new("flag8thDown", 1007, [0, -2607, 1007, 84]),
|
||||
GlyphMetric::new("augmentationDot", 400, [0, -154, 308, 154]),
|
||||
GlyphMetric::new("timeSig4", 1280, [40, 0, 1240, 2048]),
|
||||
GlyphMetric::new("timeSigCommon", 1480, [80, 0, 1400, 2048]),
|
||||
// Time-signature digits and the common-time C, with their genuine Bravura
|
||||
// advances and tight bounding boxes (centred on the baseline, y ≈ ±1), from
|
||||
// `tools/extract_bravura_outlines.py` — kept consistent with the outlines.
|
||||
GlyphMetric::new("timeSig0", 1925, [82, -1024, 1843, 1028]),
|
||||
GlyphMetric::new("timeSig1", 1368, [82, -1024, 1286, 1028]),
|
||||
GlyphMetric::new("timeSig2", 1827, [82, -1053, 1745, 1040]),
|
||||
GlyphMetric::new("timeSig3", 1724, [82, -1028, 1642, 1020]),
|
||||
GlyphMetric::new("timeSig4", 1925, [82, -1024, 1843, 1028]),
|
||||
GlyphMetric::new("timeSig5", 1651, [82, -1028, 1569, 1008]),
|
||||
GlyphMetric::new("timeSig6", 1778, [82, -1020, 1696, 1028]),
|
||||
GlyphMetric::new("timeSig7", 1806, [82, -1024, 1724, 1020]),
|
||||
GlyphMetric::new("timeSig8", 1786, [82, -1061, 1704, 1061]),
|
||||
GlyphMetric::new("timeSig9", 1778, [82, -1020, 1696, 1028]),
|
||||
GlyphMetric::new("timeSigCommon", 1737, [20, -1020, 1737, 1028]),
|
||||
GlyphMetric::new("barlineSingle", 160, [0, -2048, 160, 2048]),
|
||||
GlyphMetric::new("barlineFinal", 620, [0, -2048, 620, 2048]),
|
||||
GlyphMetric::new("dynamicForte", 1480, [0, -706, 1480, 1565]),
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@
|
|||
pub mod barrier;
|
||||
pub mod cache;
|
||||
pub mod constrained;
|
||||
pub mod engrave_theory;
|
||||
pub mod engraving;
|
||||
pub mod glyph;
|
||||
pub mod logical;
|
||||
|
|
@ -91,7 +92,11 @@ pub use constrained::{
|
|||
to_constrained, try_to_constrained, Axis, BreakKind, ConstrainedLayoutIR,
|
||||
ConstrainedLayoutRegion, ConstrainedValidationError, ConstraintParameters,
|
||||
ConstraintRegistryId, GlyphObject, GlyphObjectId, GlyphStyle, LayoutConstraint,
|
||||
LayoutTransformError, SpringSlot,
|
||||
LayoutTransformError, SpringSlot, Stroke,
|
||||
};
|
||||
pub use engrave_theory::{
|
||||
accidental_glyph, clef_glyph, flag_glyph, has_stem, key_signature, notehead_glyph, rest_glyph,
|
||||
staff_position, KeyAccidental, StaffStep,
|
||||
};
|
||||
pub use engraving::{
|
||||
AuthorId, DecisionSource, EngravingDecision, EngravingDecisionId, EngravingDecisionKind,
|
||||
|
|
@ -104,11 +109,14 @@ pub use glyph::{
|
|||
GlyphRenderData, PathCommand, SemVer, SmuflVersion, BRAVURA_METRICS, BRAVURA_VERSION,
|
||||
};
|
||||
pub use logical::{
|
||||
to_logical, BarLineLayout, BeamGroupLayout, ChordLayout, ClefLayout, CompositeLayoutObject,
|
||||
CrossRegionObject, CueLayout, GraphicLayout, GroupLayout, KeySignatureLayout, LayoutObject,
|
||||
LayoutRegion, LocalCoordinateSystem, LogicalLayoutIR, MarkerLayout, MultimeasureRestLayout,
|
||||
NoteLayout, RestLayout, ScoreVersion, SlurLayout, SpannerLayout, StaffLayout, TextLayout,
|
||||
TieLayout, TimeSignatureDisplayLayout, TrajectoryLayout, TupletDisplayLayout, VerticalExtent,
|
||||
to_logical, BarLineLayout, BarlineKind, BeamGroupLayout, ChordLayout, ClefLayout,
|
||||
CompositeLayoutObject, CrossRegionObject, CueLayout, GraphicLayout, GroupLayout,
|
||||
KeySignatureLayout, LayoutContent, LayoutObject, LayoutRegion, LocalCoordinateSystem,
|
||||
LogicalLayoutIR, MarkerLayout, MeasureContent, MultimeasureRestLayout, NoteContent, NoteLayout,
|
||||
NotePitch, PlacedClef, PlacedComponent, PlacedKeySignature, RestContent, RestLayout,
|
||||
ScoreVersion, SlurLayout, SpannerLayout, StaffContent, StaffLayout, TextLayout, TieLayout,
|
||||
TimeSignatureContent, TimeSignatureDisplayLayout, TrajectoryLayout, TupletDisplayLayout,
|
||||
VerticalExtent,
|
||||
};
|
||||
pub use provenance::{
|
||||
manifestation_layout_id, stable_layout_id, synthesized_layout_id, LayoutObjectId, Provenance,
|
||||
|
|
|
|||
|
|
@ -13,26 +13,156 @@
|
|||
//! change should invalidate it, Chapter 7 §7.1's requirement), and that
|
||||
//! provenance survives the whole pipeline.
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use epiphany_core::{AnnotationAnchor, RegionId, Score, StaffId, TimeAnchor, TypedObjectId};
|
||||
use epiphany_core::prepass::{derive_annotations, DerivedAnnotations, PrePassProfile};
|
||||
use epiphany_core::{
|
||||
AleatoricAnchoringDiscipline, AnchorOffset, AnnotationAnchor, Clef, CoordinateDiscipline,
|
||||
Event, EventId, EventPosition, KeySignature, MeasurePosition, MusicalDuration, MusicalPosition,
|
||||
NotatedComponent, PitchId, PitchSpelling, Region, RegionEdge, RegionId, RegionTimeModel, Score,
|
||||
StaffId, StaffPosition, TimeAnchor, TimeSignatureDisplay, TupletId, TupletRatio, TypedObjectId,
|
||||
WallClockTime,
|
||||
};
|
||||
use epiphany_determinism::{DomainTag, Preimage};
|
||||
|
||||
use crate::engraving::{EngravingDecision, EngravingDecisionKind, EngravingOverride};
|
||||
use crate::provenance::{LayoutObjectId, Provenance};
|
||||
use crate::spatial::Transform2D;
|
||||
use crate::time_axis::{time_axis_of, TimeAxisModel};
|
||||
use crate::time_axis::{time_axis_of, TimeAxisModel, TimePoint};
|
||||
|
||||
/// A structural layout object before spacing (Chapter 7 §"Layout Objects"). v0
|
||||
/// carries its [`Provenance`] and the staff it belongs to (used to route it to
|
||||
/// the correct vertical band); the composite glyph content is materialized at
|
||||
/// the [`crate::ConstrainedLayoutIR`] stage.
|
||||
/// The engraving content of a layout object beyond its provenance and staff —
|
||||
/// the note value, spelled pitches, clef, key, or measure data the constrained
|
||||
/// pass needs to choose glyphs and compute staff positions (Chapter 7 §"Engraving
|
||||
/// Decisions": the decisions are recorded in the IR). Structural objects (staves,
|
||||
/// voices, the per-pitch back-references, cross-cutting structures) carry
|
||||
/// [`LayoutContent::Structural`]. This payload is *authoritative* for engraving;
|
||||
/// the [`LayoutObject`] variant remains the structural classification.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Default)]
|
||||
pub enum LayoutContent {
|
||||
/// No engraving content beyond provenance/staff.
|
||||
#[default]
|
||||
Structural,
|
||||
/// A staff instance's resolved clef and key-signature *sequences*; the
|
||||
/// constrained pass chooses the active clef/key per position and defaults to
|
||||
/// treble / C major when a sequence is empty.
|
||||
Staff(StaffContent),
|
||||
/// A note or chord: its note value and spelled pitches (one notehead each).
|
||||
Note(NoteContent),
|
||||
/// A rest: its note value and optional explicit staff position.
|
||||
Rest(RestContent),
|
||||
/// A measure: whether it ends the staff (a final barline) and the time
|
||||
/// signature in force, when this measure introduces one.
|
||||
Measure(MeasureContent),
|
||||
}
|
||||
|
||||
/// The clef and key-signature sequences in force across a staff instance,
|
||||
/// carried at resolved [`TimePoint`]s so the constrained pass can choose the
|
||||
/// *active* clef/key at any position without going back to the score graph.
|
||||
/// Empty sequences mean the score declares none — the constrained pass then
|
||||
/// defaults to treble / C major.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct StaffContent {
|
||||
pub clefs: Vec<PlacedClef>,
|
||||
pub keys: Vec<PlacedKeySignature>,
|
||||
}
|
||||
|
||||
/// A clef change with its score anchor resolved into the layout time axis.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct PlacedClef {
|
||||
pub time: TimePoint,
|
||||
pub clef: Clef,
|
||||
}
|
||||
|
||||
/// A key-signature change with its score anchor resolved into the layout time
|
||||
/// axis.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct PlacedKeySignature {
|
||||
pub time: TimePoint,
|
||||
pub key: KeySignature,
|
||||
}
|
||||
|
||||
/// A note or chord's notated content: its resolved start position, its placed
|
||||
/// notated components (one notehead/tie segment each, at successive offsets — a
|
||||
/// multi-component decomposition is *not* collapsed), and its spelled pitches.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct NoteContent {
|
||||
pub position: TimePoint,
|
||||
pub components: Vec<PlacedComponent>,
|
||||
pub pitches: Vec<NotePitch>,
|
||||
}
|
||||
|
||||
/// One notated component placed within a note or rest: its offset from the
|
||||
/// owning event's start position, the component itself (base value, dots, tuplet
|
||||
/// membership, tie), and the resolved tuplet ratio when it is in a tuplet (the
|
||||
/// `TupletId` inside the component does not carry the ratio).
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct PlacedComponent {
|
||||
pub offset: MusicalDuration,
|
||||
pub component: NotatedComponent,
|
||||
pub tuplet: Option<TupletRatio>,
|
||||
}
|
||||
|
||||
/// One pitch of a note — its identity (for the notehead's provenance) and its
|
||||
/// resolved spelling, or `None` when the pre-pass produced none. A `None`
|
||||
/// spelling is *preserved*, not dropped, so the constrained pass surfaces a
|
||||
/// missing-spelling diagnostic rather than silently losing musical content.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct NotePitch {
|
||||
pub pitch: PitchId,
|
||||
pub spelling: Option<PitchSpelling>,
|
||||
}
|
||||
|
||||
/// A rest's notated content: its resolved start position, its placed notated
|
||||
/// components, and any explicit vertical position.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct RestContent {
|
||||
pub position: TimePoint,
|
||||
pub components: Vec<PlacedComponent>,
|
||||
pub staff_position: Option<StaffPosition>,
|
||||
}
|
||||
|
||||
/// A measure's notated content: its resolved start position, which barline ends
|
||||
/// it, and the time signature it introduces, if any.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct MeasureContent {
|
||||
pub start: TimePoint,
|
||||
pub barline: BarlineKind,
|
||||
pub time_signature: Option<TimeSignatureContent>,
|
||||
}
|
||||
|
||||
/// Which barline ends a measure. A staff manifested across several regions
|
||||
/// continues at each region boundary, so only the last measure of the *last*
|
||||
/// region manifesting the staff is truly [`Final`](BarlineKind::Final).
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub enum BarlineKind {
|
||||
/// A measure within the staff's run.
|
||||
Interior,
|
||||
/// The last measure of this staff instance in this region; the staff
|
||||
/// continues in a later region.
|
||||
RegionEnd,
|
||||
/// The last measure of the last region manifesting this staff (the true end).
|
||||
Final,
|
||||
}
|
||||
|
||||
/// A time signature reduced to its displayed numerator and denominator.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub struct TimeSignatureContent {
|
||||
pub numerator: u16,
|
||||
pub denominator: u16,
|
||||
}
|
||||
|
||||
/// A structural layout object before spacing (Chapter 7 §"Layout Objects"). It
|
||||
/// carries its [`Provenance`], the staff it belongs to (used to route it to the
|
||||
/// correct vertical band), and its [`LayoutContent`] (the engraving payload the
|
||||
/// constrained pass materializes into glyphs).
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct CompositeLayoutObject {
|
||||
pub provenance: Provenance,
|
||||
/// The staff this object belongs to, or `None` for region-level and
|
||||
/// score-level (cross-cutting / free-graphic) objects.
|
||||
pub staff: Option<StaffId>,
|
||||
/// The engraving content materialized into glyphs at the constrained stage.
|
||||
pub content: LayoutContent,
|
||||
}
|
||||
|
||||
pub type NoteLayout = CompositeLayoutObject;
|
||||
|
|
@ -86,7 +216,11 @@ pub enum LayoutObject {
|
|||
|
||||
impl LayoutObject {
|
||||
pub fn from_projection(provenance: Provenance, staff: Option<StaffId>) -> Self {
|
||||
let payload = CompositeLayoutObject { provenance, staff };
|
||||
let payload = CompositeLayoutObject {
|
||||
provenance,
|
||||
staff,
|
||||
content: LayoutContent::Structural,
|
||||
};
|
||||
match payload.provenance.source {
|
||||
TypedObjectId::Event(_) | TypedObjectId::Pitch(_) => LayoutObject::Note(payload),
|
||||
TypedObjectId::Beam(_) => LayoutObject::BeamGroup(payload),
|
||||
|
|
@ -110,16 +244,32 @@ impl LayoutObject {
|
|||
}
|
||||
}
|
||||
|
||||
/// Projects an object and attaches its engraving content in one step.
|
||||
pub fn from_projection_with_content(
|
||||
provenance: Provenance,
|
||||
staff: Option<StaffId>,
|
||||
content: LayoutContent,
|
||||
) -> Self {
|
||||
let mut object = Self::from_projection(provenance, staff);
|
||||
object.payload_mut().content = content;
|
||||
object
|
||||
}
|
||||
|
||||
pub fn provenance(&self) -> &Provenance {
|
||||
self.payload().0
|
||||
&self.payload().provenance
|
||||
}
|
||||
|
||||
pub fn staff(&self) -> Option<StaffId> {
|
||||
self.payload().1
|
||||
self.payload().staff
|
||||
}
|
||||
|
||||
fn payload(&self) -> (&Provenance, Option<StaffId>) {
|
||||
let payload = match self {
|
||||
/// The engraving content of this object (authoritative over the variant).
|
||||
pub fn content(&self) -> &LayoutContent {
|
||||
&self.payload().content
|
||||
}
|
||||
|
||||
fn payload(&self) -> &CompositeLayoutObject {
|
||||
match self {
|
||||
LayoutObject::Note(value)
|
||||
| LayoutObject::Chord(value)
|
||||
| LayoutObject::Rest(value)
|
||||
|
|
@ -140,8 +290,32 @@ impl LayoutObject {
|
|||
| LayoutObject::Cue(value)
|
||||
| LayoutObject::Trajectory(value)
|
||||
| LayoutObject::Group(value) => value,
|
||||
};
|
||||
(&payload.provenance, payload.staff)
|
||||
}
|
||||
}
|
||||
|
||||
fn payload_mut(&mut self) -> &mut CompositeLayoutObject {
|
||||
match self {
|
||||
LayoutObject::Note(value)
|
||||
| LayoutObject::Chord(value)
|
||||
| LayoutObject::Rest(value)
|
||||
| LayoutObject::BeamGroup(value)
|
||||
| LayoutObject::TupletDisplay(value)
|
||||
| LayoutObject::Slur(value)
|
||||
| LayoutObject::Tie(value)
|
||||
| LayoutObject::Spanner(value)
|
||||
| LayoutObject::Marker(value)
|
||||
| LayoutObject::BarLine(value)
|
||||
| LayoutObject::Clef(value)
|
||||
| LayoutObject::KeySignature(value)
|
||||
| LayoutObject::TimeSignatureDisplay(value)
|
||||
| LayoutObject::Staff(value)
|
||||
| LayoutObject::Text(value)
|
||||
| LayoutObject::Graphic(value)
|
||||
| LayoutObject::MultimeasureRest(value)
|
||||
| LayoutObject::Cue(value)
|
||||
| LayoutObject::Trajectory(value)
|
||||
| LayoutObject::Group(value) => value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -222,54 +396,116 @@ pub fn to_logical(score: &Score) -> LogicalLayoutIR {
|
|||
let mut engraving_decisions = Vec::new();
|
||||
let mut cross_region = Vec::new();
|
||||
let mut seen: BTreeSet<LayoutObjectId> = BTreeSet::new();
|
||||
// The resolved spellings and decompositions the notation engraving consumes
|
||||
// (Agent H's pre-pass): which notehead a note draws, where its pitches sit,
|
||||
// and which accidentals its spelling carries. Recomputed deterministically
|
||||
// from the score with the default profile.
|
||||
let annotations = derive_annotations(score, &PrePassProfile::default());
|
||||
// The last region index that manifests each staff, so a measure can tell a
|
||||
// mid-staff region boundary (continuation) from the true final barline.
|
||||
let mut staff_last_region: BTreeMap<StaffId, usize> = BTreeMap::new();
|
||||
for (index, region) in score.canvas.regions.iter().enumerate() {
|
||||
for staff_id in ®ion.staff_extent.staves {
|
||||
staff_last_region.insert(*staff_id, index);
|
||||
}
|
||||
}
|
||||
|
||||
for region in &score.canvas.regions {
|
||||
for (region_index, region) in score.canvas.regions.iter().enumerate() {
|
||||
let region_id = region.id;
|
||||
let mut objects = Vec::new();
|
||||
let mut push =
|
||||
|source: TypedObjectId, dependencies: Vec<TypedObjectId>, staff: Option<StaffId>| {
|
||||
let provenance = Provenance::manifested(source, region_id, dependencies);
|
||||
if seen.insert(provenance.stable_id) {
|
||||
objects.push(LayoutObject::from_projection(provenance, staff));
|
||||
}
|
||||
};
|
||||
let mut push = |source: TypedObjectId,
|
||||
dependencies: Vec<TypedObjectId>,
|
||||
staff: Option<StaffId>,
|
||||
content: LayoutContent| {
|
||||
let provenance = Provenance::manifested(source, region_id, dependencies);
|
||||
if seen.insert(provenance.stable_id) {
|
||||
objects.push(LayoutObject::from_projection_with_content(
|
||||
provenance, staff, content,
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
// Staves manifested in this region (via the staff extent).
|
||||
for staff_id in ®ion.staff_extent.staves {
|
||||
push(TypedObjectId::Staff(*staff_id), vec![], Some(*staff_id));
|
||||
push(
|
||||
TypedObjectId::Staff(*staff_id),
|
||||
vec![],
|
||||
Some(*staff_id),
|
||||
LayoutContent::Structural,
|
||||
);
|
||||
}
|
||||
|
||||
// Staff instances, voices, and their events + pitches — all belong to
|
||||
// the instance's staff.
|
||||
// the instance's staff. The staff instance carries the clef/key in force.
|
||||
for si in region.staff_instances() {
|
||||
let staff = Some(si.staff);
|
||||
let si_src = TypedObjectId::StaffInstance(si.id);
|
||||
push(si_src, vec![TypedObjectId::Staff(si.staff)], staff);
|
||||
let mut si_deps = vec![TypedObjectId::Staff(si.staff)];
|
||||
si_deps.extend(
|
||||
si.clef_sequence
|
||||
.iter()
|
||||
.filter_map(|change| time_anchor_dep(&change.anchor)),
|
||||
);
|
||||
si_deps.extend(
|
||||
si.key_sequence
|
||||
.iter()
|
||||
.filter_map(|change| time_anchor_dep(&change.anchor)),
|
||||
);
|
||||
push(si_src, si_deps, staff, staff_content(score, si));
|
||||
for voice in &si.voices {
|
||||
let v_src = TypedObjectId::Voice(voice.id);
|
||||
push(v_src, vec![si_src], staff);
|
||||
push(v_src, vec![si_src], staff, LayoutContent::Structural);
|
||||
for eid in &voice.events {
|
||||
let e_src = TypedObjectId::Event(*eid);
|
||||
// The event's pitches become its invalidation dependencies.
|
||||
let pitches = identified_pitch_ids(score, *eid);
|
||||
let mut deps = vec![v_src];
|
||||
deps.extend(pitches.iter().copied().map(TypedObjectId::Pitch));
|
||||
push(e_src, deps, staff);
|
||||
// And the pitches themselves, as their own objects.
|
||||
// The event carries the notated content (note value + spelled
|
||||
// pitches); the per-pitch objects are structural provenance.
|
||||
push(e_src, deps, staff, event_content(score, *eid, &annotations));
|
||||
for pid in pitches {
|
||||
push(TypedObjectId::Pitch(pid), vec![e_src], staff);
|
||||
push(
|
||||
TypedObjectId::Pitch(pid),
|
||||
vec![e_src],
|
||||
staff,
|
||||
LayoutContent::Structural,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Measures, per staff instance (Chapter 5 §"Measures").
|
||||
// Measures, per staff instance (Chapter 5 §"Measures"). The last measure
|
||||
// of an instance ends this region's run; it is the true final barline only
|
||||
// when this is the last region manifesting the staff.
|
||||
for si in region.staff_instances() {
|
||||
for measure in &si.measures {
|
||||
let last = si.measures.len().saturating_sub(1);
|
||||
let staff_ends_here = staff_last_region.get(&si.staff) == Some(®ion_index);
|
||||
for (index, measure) in si.measures.iter().enumerate() {
|
||||
let barline = if index != last {
|
||||
BarlineKind::Interior
|
||||
} else if staff_ends_here {
|
||||
BarlineKind::Final
|
||||
} else {
|
||||
BarlineKind::RegionEnd
|
||||
};
|
||||
// The measure depends on its staff instance, the time signature it
|
||||
// displays (so a display change with the same id invalidates the
|
||||
// measure and its synthesized time-signature glyphs), and whatever
|
||||
// its start anchor resolves through.
|
||||
let mut measure_deps = vec![TypedObjectId::StaffInstance(si.id)];
|
||||
if let Some(time_signature) = measure.time_signature {
|
||||
measure_deps.push(TypedObjectId::TimeSignature(time_signature));
|
||||
}
|
||||
if let Some(anchor_dep) = time_anchor_dep(&measure.start) {
|
||||
measure_deps.push(anchor_dep);
|
||||
}
|
||||
push(
|
||||
TypedObjectId::Measure(measure.id),
|
||||
vec![TypedObjectId::StaffInstance(si.id)],
|
||||
measure_deps,
|
||||
Some(si.staff),
|
||||
measure_content(score, measure, barline),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -278,7 +514,12 @@ pub fn to_logical(score: &Score) -> LogicalLayoutIR {
|
|||
// Content"; Chapter 7 §"Region Uniformity"). These are region-level, not
|
||||
// staff-owned.
|
||||
for go in region.content.graphic_objects() {
|
||||
push(TypedObjectId::GraphicObject(go.id), vec![], None);
|
||||
push(
|
||||
TypedObjectId::GraphicObject(go.id),
|
||||
vec![],
|
||||
None,
|
||||
LayoutContent::Structural,
|
||||
);
|
||||
}
|
||||
|
||||
let r_src = TypedObjectId::Region(region.id);
|
||||
|
|
@ -393,6 +634,296 @@ fn derive_score_version(score: &Score) -> ScoreVersion {
|
|||
ScoreVersion(*preimage.finish().as_bytes())
|
||||
}
|
||||
|
||||
/// The clef and key-signature sequences of a staff instance, carried with
|
||||
/// resolved layout times. Empty sequences (a score that declares no clef/key)
|
||||
/// are carried as-is — the constrained pass defaults the *active* clef/key to
|
||||
/// treble / C major.
|
||||
fn staff_content(score: &Score, si: &epiphany_core::StaffInstance) -> LayoutContent {
|
||||
LayoutContent::Staff(StaffContent {
|
||||
clefs: si
|
||||
.clef_sequence
|
||||
.iter()
|
||||
.map(|change| PlacedClef {
|
||||
time: resolve_time_anchor(score, &change.anchor),
|
||||
clef: change.clef,
|
||||
})
|
||||
.collect(),
|
||||
keys: si
|
||||
.key_sequence
|
||||
.iter()
|
||||
.map(|change| PlacedKeySignature {
|
||||
time: resolve_time_anchor(score, &change.anchor),
|
||||
key: change.key,
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
||||
/// The notated content of an event: a note (its position, decomposition, and
|
||||
/// spelled pitches) for a pitched event, a rest for a rest, and structural for
|
||||
/// the kinds this Minimal slice does not yet engrave (unpitched / indeterminate
|
||||
/// / trajectory / graphic / cue). Every pitch is kept; an unspelled one carries
|
||||
/// `spelling: None` rather than being dropped.
|
||||
fn event_content(score: &Score, event: EventId, annotations: &DerivedAnnotations) -> LayoutContent {
|
||||
let Some(graph_event) = score.events.get(event) else {
|
||||
return LayoutContent::Structural;
|
||||
};
|
||||
let components = placed_components(score, components_of(annotations, event));
|
||||
match graph_event {
|
||||
Event::Pitched(pitched) => {
|
||||
let pitches = pitched
|
||||
.pitches
|
||||
.iter()
|
||||
.map(|identified| NotePitch {
|
||||
pitch: identified.id,
|
||||
spelling: annotations
|
||||
.spellings
|
||||
.get(&identified.id)
|
||||
.map(|resolved| resolved.spelling.clone()),
|
||||
})
|
||||
.collect();
|
||||
LayoutContent::Note(NoteContent {
|
||||
position: event_time(&pitched.position),
|
||||
components,
|
||||
pitches,
|
||||
})
|
||||
}
|
||||
Event::Rest(rest) => LayoutContent::Rest(RestContent {
|
||||
position: event_time(&rest.position),
|
||||
components,
|
||||
staff_position: rest.vertical_position,
|
||||
}),
|
||||
_ => LayoutContent::Structural,
|
||||
}
|
||||
}
|
||||
|
||||
/// An event's concrete position as a layout [`TimePoint`] (the two share the
|
||||
/// musical/wall-clock shape).
|
||||
fn event_time(position: &EventPosition) -> TimePoint {
|
||||
match position {
|
||||
EventPosition::Musical(p) => TimePoint::Musical(p.clone()),
|
||||
EventPosition::WallClock(t) => TimePoint::WallClock(*t),
|
||||
}
|
||||
}
|
||||
|
||||
/// Places each notated component at its successive offset from the event start,
|
||||
/// resolving its tuplet ratio. The offset of a component is the summed sounding
|
||||
/// duration of the components before it (base value × dot factor × tuplet
|
||||
/// scale), so a multi-component (e.g. tied-across-a-barline) note yields separate
|
||||
/// noteheads at the right positions.
|
||||
fn placed_components(score: &Score, components: Vec<NotatedComponent>) -> Vec<PlacedComponent> {
|
||||
let mut placed = Vec::with_capacity(components.len());
|
||||
let mut offset = MusicalDuration::zero();
|
||||
for component in components {
|
||||
let tuplet = component.tuplet.and_then(|id| tuplet_ratio(score, id));
|
||||
let duration = component_duration(&component, tuplet);
|
||||
placed.push(PlacedComponent {
|
||||
offset: offset.clone(),
|
||||
component,
|
||||
tuplet,
|
||||
});
|
||||
offset = offset + duration;
|
||||
}
|
||||
placed
|
||||
}
|
||||
|
||||
/// The resolved ratio of a tuplet, looked up by id in the score's cross-cutting
|
||||
/// registry.
|
||||
fn tuplet_ratio(score: &Score, id: TupletId) -> Option<TupletRatio> {
|
||||
score
|
||||
.cross_cutting
|
||||
.tuplets
|
||||
.iter()
|
||||
.find(|tuplet| tuplet.id == id)
|
||||
.map(|tuplet| tuplet.ratio)
|
||||
}
|
||||
|
||||
/// The sounding duration of a notated component. The core graph model owns the
|
||||
/// exact dotted-duration semantics, including large dot counts that require
|
||||
/// arbitrary precision, so layout delegates instead of duplicating the math.
|
||||
fn component_duration(
|
||||
component: &NotatedComponent,
|
||||
tuplet: Option<TupletRatio>,
|
||||
) -> MusicalDuration {
|
||||
component.sounding_duration(tuplet)
|
||||
}
|
||||
|
||||
/// Resolves a [`TimeAnchor`] to a concrete layout [`TimePoint`] for placement.
|
||||
/// Event anchors use the event's own region-local position plus the anchor
|
||||
/// offset; measure anchors recurse through the referenced measure boundary; and
|
||||
/// region anchors resolve to the referenced region edge in that region's local
|
||||
/// time discipline. Cycles, missing targets, unknown metric region ends, and
|
||||
/// clock-mismatched offsets fall back to the musical origin — surfaced as a
|
||||
/// Minimal-slice boundary rather than panicking or inventing a false coordinate.
|
||||
fn resolve_time_anchor(score: &Score, anchor: &TimeAnchor) -> TimePoint {
|
||||
const DEPTH: u8 = 16;
|
||||
resolve_time_anchor_inner(score, anchor, DEPTH).unwrap_or_else(origin_time)
|
||||
}
|
||||
|
||||
fn resolve_time_anchor_inner(score: &Score, anchor: &TimeAnchor, depth: u8) -> Option<TimePoint> {
|
||||
if depth == 0 {
|
||||
return None;
|
||||
}
|
||||
match anchor {
|
||||
TimeAnchor::WallClock { time } => Some(TimePoint::WallClock(*time)),
|
||||
TimeAnchor::Event { id, offset } => {
|
||||
let event = score.events.get(*id)?;
|
||||
apply_offset(event_time(event.position()), offset)
|
||||
}
|
||||
TimeAnchor::Measure {
|
||||
id,
|
||||
position,
|
||||
offset,
|
||||
} => {
|
||||
let base = measure_anchor_time(score, *id, *position, depth - 1)?;
|
||||
apply_offset(base, offset)
|
||||
}
|
||||
TimeAnchor::Region { id, edge, offset } => {
|
||||
let region = score
|
||||
.canvas
|
||||
.regions
|
||||
.iter()
|
||||
.find(|region| region.id == *id)?;
|
||||
let base = region_edge_time(region, *edge, offset)?;
|
||||
apply_offset(base, offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_offset(base: TimePoint, offset: &AnchorOffset) -> Option<TimePoint> {
|
||||
match (base, offset) {
|
||||
(base, AnchorOffset::Zero) => Some(base),
|
||||
(TimePoint::Musical(position), AnchorOffset::Musical(duration)) => {
|
||||
Some(TimePoint::Musical(position + duration.clone()))
|
||||
}
|
||||
(TimePoint::WallClock(time), AnchorOffset::WallClock(duration)) => time
|
||||
.0
|
||||
.checked_add(duration.0)
|
||||
.map(WallClockTime)
|
||||
.map(TimePoint::WallClock),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn measure_anchor_time(
|
||||
score: &Score,
|
||||
id: epiphany_core::MeasureId,
|
||||
position: MeasurePosition,
|
||||
depth: u8,
|
||||
) -> Option<TimePoint> {
|
||||
for (_, instance) in score.staff_instances() {
|
||||
let Some(index) = instance
|
||||
.measures
|
||||
.iter()
|
||||
.position(|measure| measure.id == id)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
return match position {
|
||||
MeasurePosition::Start => {
|
||||
resolve_time_anchor_inner(score, &instance.measures[index].start, depth)
|
||||
}
|
||||
MeasurePosition::End => instance
|
||||
.measures
|
||||
.get(index + 1)
|
||||
.and_then(|next| resolve_time_anchor_inner(score, &next.start, depth)),
|
||||
};
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn region_edge_time(region: &Region, edge: RegionEdge, offset: &AnchorOffset) -> Option<TimePoint> {
|
||||
match edge {
|
||||
RegionEdge::Start => Some(region_origin_time(region, offset)),
|
||||
RegionEdge::End => region_end_time(region),
|
||||
}
|
||||
}
|
||||
|
||||
fn region_origin_time(region: &Region, offset: &AnchorOffset) -> TimePoint {
|
||||
match offset {
|
||||
AnchorOffset::Musical(_) => TimePoint::Musical(MusicalPosition::origin()),
|
||||
AnchorOffset::WallClock(_) => TimePoint::WallClock(WallClockTime(0)),
|
||||
AnchorOffset::Zero => match region.time_model.coordinate_discipline() {
|
||||
CoordinateDiscipline::Musical => TimePoint::Musical(MusicalPosition::origin()),
|
||||
CoordinateDiscipline::WallClock => TimePoint::WallClock(WallClockTime(0)),
|
||||
CoordinateDiscipline::Aleatoric(AleatoricAnchoringDiscipline::WallClock) => {
|
||||
TimePoint::WallClock(WallClockTime(0))
|
||||
}
|
||||
CoordinateDiscipline::Aleatoric(_) => TimePoint::Musical(MusicalPosition::origin()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn region_end_time(region: &Region) -> Option<TimePoint> {
|
||||
match ®ion.time_model {
|
||||
RegionTimeModel::Proportional(model) => {
|
||||
Some(TimePoint::WallClock(WallClockTime(model.duration.0)))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The musical origin as a [`TimePoint`] (the placement fallback).
|
||||
fn origin_time() -> TimePoint {
|
||||
TimePoint::Musical(MusicalPosition::origin())
|
||||
}
|
||||
|
||||
/// The full notated decomposition of an event (base values, dots, tuplets, ties)
|
||||
/// from the pre-pass; empty when the event has no decomposition (non-metric or
|
||||
/// ineligible) — the constrained pass surfaces that rather than inventing a value.
|
||||
fn components_of(annotations: &DerivedAnnotations, event: EventId) -> Vec<NotatedComponent> {
|
||||
annotations
|
||||
.decompositions
|
||||
.get(&event)
|
||||
.map(|decomposition| decomposition.components.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The notated content of a measure: its start anchor, its ending barline, and
|
||||
/// the time signature it introduces, resolved to numerator/denominator when
|
||||
/// standard or irrational (compound / mixed / symbolic meters are not engraved
|
||||
/// in I-1).
|
||||
fn measure_content(
|
||||
score: &Score,
|
||||
measure: &epiphany_core::Measure,
|
||||
barline: BarlineKind,
|
||||
) -> LayoutContent {
|
||||
let time_signature = measure
|
||||
.time_signature
|
||||
.and_then(|id| time_signature_content(score, id));
|
||||
LayoutContent::Measure(MeasureContent {
|
||||
start: resolve_time_anchor(score, &measure.start),
|
||||
barline,
|
||||
time_signature,
|
||||
})
|
||||
}
|
||||
|
||||
/// Resolves a time-signature id to its displayed numerator/denominator, for the
|
||||
/// meter shapes I-1 engraves.
|
||||
fn time_signature_content(
|
||||
score: &Score,
|
||||
id: epiphany_core::TimeSignatureId,
|
||||
) -> Option<TimeSignatureContent> {
|
||||
let signature = score.time_signatures.iter().find(|t| t.id == id)?;
|
||||
match &signature.display {
|
||||
TimeSignatureDisplay::Standard {
|
||||
numerator,
|
||||
denominator,
|
||||
} => Some(TimeSignatureContent {
|
||||
numerator: *numerator,
|
||||
denominator: denominator.get(),
|
||||
}),
|
||||
TimeSignatureDisplay::Irrational {
|
||||
numerator,
|
||||
denominator,
|
||||
} => Some(TimeSignatureContent {
|
||||
numerator: *numerator,
|
||||
denominator: denominator.get(),
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The identified-pitch ids of an event, in arena order (empty if the event is
|
||||
/// absent or carries no pitches).
|
||||
pub(crate) fn identified_pitch_ids(
|
||||
|
|
@ -534,7 +1065,211 @@ pub(crate) fn cross_cutting_objects(score: &Score) -> Vec<(TypedObjectId, Vec<Ty
|
|||
mod tests {
|
||||
use super::*;
|
||||
use epiphany_core::generators::{valid_score, valid_score_rich};
|
||||
use epiphany_core::{AnchorOffset, RegionEdge, Spanner, SpannerId, TimeAnchor};
|
||||
use epiphany_core::{
|
||||
AnchorOffset, Clef, ClefChange, KeySignature, KeySignatureChange, NoteValue, RationalTime,
|
||||
RegionEdge, Spanner, SpannerId, TimeAnchor, WallClockTime,
|
||||
};
|
||||
|
||||
fn duration(numerator: i64, denominator: i64) -> MusicalDuration {
|
||||
MusicalDuration(RationalTime::new(numerator, denominator).expect("nonzero"))
|
||||
}
|
||||
|
||||
fn position(numerator: i64, denominator: i64) -> MusicalPosition {
|
||||
MusicalPosition(RationalTime::new(numerator, denominator).expect("nonzero"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_logical_enriches_notes_staves_and_measures() {
|
||||
let score = valid_score_rich(7);
|
||||
let ir = to_logical(&score);
|
||||
let objects: Vec<&LayoutObject> = ir
|
||||
.regions
|
||||
.iter()
|
||||
.flat_map(|region| region.objects.iter())
|
||||
.collect();
|
||||
|
||||
// A pitched event projects a note carrying its decomposition and at least
|
||||
// one spelled pitch (and its position is recorded).
|
||||
let spelled_note = objects.iter().any(|object| {
|
||||
matches!(object.content(), LayoutContent::Note(note)
|
||||
if !note.components.is_empty()
|
||||
&& note.pitches.iter().any(|pitch| pitch.spelling.is_some()))
|
||||
});
|
||||
assert!(
|
||||
spelled_note,
|
||||
"expected an enriched note with a decomposition and a spelled pitch"
|
||||
);
|
||||
|
||||
// Staff instances carry their clef/key sequences. valid_score declares no
|
||||
// clef, so the sequences are empty (the constrained pass defaults them to
|
||||
// treble / C major).
|
||||
let staves: Vec<&StaffContent> = objects
|
||||
.iter()
|
||||
.filter_map(|object| match object.content() {
|
||||
LayoutContent::Staff(staff) => Some(staff),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
assert!(!staves.is_empty(), "staff instances carry staff content");
|
||||
assert!(
|
||||
staves.iter().all(|staff| staff.clefs.is_empty()),
|
||||
"a score with no declared clef carries an empty clef sequence"
|
||||
);
|
||||
|
||||
// Measures project measure content carrying a start anchor; the last
|
||||
// region manifesting each staff ends with a true Final barline (never
|
||||
// every region end).
|
||||
let measures: Vec<&MeasureContent> = objects
|
||||
.iter()
|
||||
.filter_map(|object| match object.content() {
|
||||
LayoutContent::Measure(measure) => Some(measure),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
assert!(!measures.is_empty(), "measures project measure content");
|
||||
assert!(
|
||||
measures
|
||||
.iter()
|
||||
.any(|measure| measure.barline == BarlineKind::Final),
|
||||
"the staff's last region ends with a final barline"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn placed_components_accumulate_successive_offsets() {
|
||||
// A note notated as a quarter tied to an eighth: the second component
|
||||
// starts a quarter-note's duration after the first (offsets are summed,
|
||||
// not collapsed).
|
||||
let score = valid_score(1);
|
||||
let components = vec![
|
||||
NotatedComponent {
|
||||
base_value: NoteValue::Quarter,
|
||||
dots: 0,
|
||||
tuplet: None,
|
||||
tied_to_next: true,
|
||||
},
|
||||
NotatedComponent {
|
||||
base_value: NoteValue::Eighth,
|
||||
dots: 0,
|
||||
tuplet: None,
|
||||
tied_to_next: false,
|
||||
},
|
||||
];
|
||||
let placed = placed_components(&score, components);
|
||||
assert_eq!(placed.len(), 2);
|
||||
assert_eq!(placed[0].offset, MusicalDuration::zero());
|
||||
assert_eq!(placed[1].offset, duration(1, 4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn placed_components_uses_core_duration_for_large_dot_counts() {
|
||||
let score = valid_score(1);
|
||||
let component = NotatedComponent {
|
||||
base_value: NoteValue::SixtyFourth,
|
||||
dots: 80,
|
||||
tuplet: None,
|
||||
tied_to_next: true,
|
||||
};
|
||||
let placed = placed_components(&score, vec![component.clone(), component.clone()]);
|
||||
assert_eq!(placed.len(), 2);
|
||||
assert_eq!(placed[1].offset, component.sounding_duration(None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_time_anchor_applies_event_offsets() {
|
||||
let score = valid_score(1);
|
||||
let event = score
|
||||
.events
|
||||
.iter_canonical()
|
||||
.find(|event| matches!(event.position(), EventPosition::Musical(_)))
|
||||
.expect("valid_score contains musical events");
|
||||
let EventPosition::Musical(base) = event.position() else {
|
||||
unreachable!("filtered for musical events");
|
||||
};
|
||||
let offset = duration(1, 4);
|
||||
let resolved = resolve_time_anchor(
|
||||
&score,
|
||||
&TimeAnchor::Event {
|
||||
id: event.id(),
|
||||
offset: AnchorOffset::Musical(offset.clone()),
|
||||
},
|
||||
);
|
||||
assert_eq!(resolved, TimePoint::Musical(base.clone() + offset));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_time_anchor_uses_referenced_region_edge() {
|
||||
let score = valid_score_rich(7);
|
||||
let (region_id, duration_ns) = score
|
||||
.canvas
|
||||
.regions
|
||||
.iter()
|
||||
.find_map(|region| match ®ion.time_model {
|
||||
RegionTimeModel::Proportional(model) => Some((region.id, model.duration.0)),
|
||||
_ => None,
|
||||
})
|
||||
.expect("valid_score_rich contains a proportional region");
|
||||
|
||||
let resolved = resolve_time_anchor(
|
||||
&score,
|
||||
&TimeAnchor::Region {
|
||||
id: region_id,
|
||||
edge: RegionEdge::End,
|
||||
offset: AnchorOffset::Zero,
|
||||
},
|
||||
);
|
||||
assert_eq!(resolved, TimePoint::WallClock(WallClockTime(duration_ns)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn staff_content_resolves_clef_and_key_anchors() {
|
||||
let mut score = valid_score(1);
|
||||
let region_id = score.canvas.regions[0].id;
|
||||
let anchor = TimeAnchor::Region {
|
||||
id: region_id,
|
||||
edge: RegionEdge::Start,
|
||||
offset: AnchorOffset::Musical(duration(1, 4)),
|
||||
};
|
||||
let staff_instance = score.canvas.regions[0]
|
||||
.content
|
||||
.staff_instances_mut()
|
||||
.expect("valid_score is staff based")
|
||||
.first_mut()
|
||||
.expect("valid_score contains a staff instance");
|
||||
staff_instance.clef_sequence.push(ClefChange {
|
||||
anchor: anchor.clone(),
|
||||
clef: Clef::bass(),
|
||||
});
|
||||
staff_instance.key_sequence.push(KeySignatureChange {
|
||||
anchor,
|
||||
key: KeySignature::new(-3).expect("valid key signature"),
|
||||
});
|
||||
|
||||
let ir = to_logical(&score);
|
||||
let staff = ir
|
||||
.regions
|
||||
.iter()
|
||||
.flat_map(|region| region.objects.iter())
|
||||
.find_map(|object| match object.content() {
|
||||
LayoutContent::Staff(staff) if !staff.clefs.is_empty() => Some(staff),
|
||||
_ => None,
|
||||
})
|
||||
.expect("staff content carries clef/key changes");
|
||||
assert_eq!(
|
||||
staff.clefs[0],
|
||||
PlacedClef {
|
||||
time: TimePoint::Musical(position(1, 4)),
|
||||
clef: Clef::bass(),
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
staff.keys[0],
|
||||
PlacedKeySignature {
|
||||
time: TimePoint::Musical(position(1, 4)),
|
||||
key: KeySignature::new(-3).expect("valid key signature"),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn score_version_tracks_content_not_just_identifiers() {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
use crate::provenance::Provenance;
|
||||
use crate::resolved::ResolvedLayoutIR;
|
||||
use crate::spatial::{Point, ScaleContext};
|
||||
use crate::{BoundingBox, GlyphReference, GlyphStyle, Transform2D};
|
||||
use crate::{BoundingBox, GlyphReference, GlyphStyle, Stroke, Transform2D};
|
||||
|
||||
/// A single renderer primitive (Chapter 7 §"RenderIR"). Interface only — it
|
||||
/// carries just enough to prove the provenance-preservation contract: every
|
||||
|
|
@ -34,6 +34,9 @@ pub struct RenderPrimitive {
|
|||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub struct RenderIR {
|
||||
pub primitives: Vec<RenderPrimitive>,
|
||||
/// Non-glyph line primitives (staff lines, stems, barlines, …), traced like
|
||||
/// the glyph primitives so the round-trip recovers their sources too.
|
||||
pub strokes: Vec<Stroke>,
|
||||
}
|
||||
|
||||
/// The render target (Chapter 7 §"RenderIR": `RenderConfiguration.target`).
|
||||
|
|
@ -139,5 +142,6 @@ pub fn to_render(resolved: &ResolvedLayoutIR) -> RenderIR {
|
|||
layer: g.layer,
|
||||
})
|
||||
.collect(),
|
||||
strokes: resolved.strokes.clone(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
use epiphany_core::{MeasureId, StaffId, TypedObjectId};
|
||||
use epiphany_determinism::{CanonicalEncode, CanonicalF64, QuantizedCoord};
|
||||
|
||||
use crate::constrained::{GlyphObjectId, GlyphStyle};
|
||||
use crate::constrained::{GlyphObjectId, GlyphStyle, Stroke};
|
||||
use crate::engraving::{DecisionSource, EngravingDecision, EngravingDecisionKind};
|
||||
use crate::glyph::{GlyphCatalogIdentity, GlyphReference};
|
||||
use crate::logical::ScoreVersion;
|
||||
|
|
@ -87,6 +87,9 @@ pub struct ResolvedLayoutIR {
|
|||
pub source: ScoreVersion,
|
||||
pub pages: Vec<ResolvedPage>,
|
||||
pub glyphs: Vec<ResolvedGlyph>,
|
||||
/// Resolved non-glyph line primitives (staff lines, stems, barlines, …),
|
||||
/// positioned by the solver alongside the glyphs.
|
||||
pub strokes: Vec<Stroke>,
|
||||
pub engraving_decisions: Vec<EngravingDecision>,
|
||||
/// The catalog identity under which this layout was produced — required for
|
||||
/// any byte-equal conformance claim (Chapter 7 §7.3.2).
|
||||
|
|
@ -142,6 +145,19 @@ impl CanonicalEncode for ResolvedLayoutIR {
|
|||
out.extend_from_slice(&glyph.style.rgba.to_le_bytes());
|
||||
out.extend_from_slice(&glyph.layer.to_le_bytes());
|
||||
}
|
||||
push_u64(out, self.strokes.len() as u64);
|
||||
for stroke in &self.strokes {
|
||||
encode_provenance(out, &stroke.provenance);
|
||||
let (fx, fy) = quantize(stroke.from);
|
||||
fx.encode_canonical(out);
|
||||
fy.encode_canonical(out);
|
||||
let (tx, ty) = quantize(stroke.to);
|
||||
tx.encode_canonical(out);
|
||||
ty.encode_canonical(out);
|
||||
encode_staff_space(out, stroke.thickness);
|
||||
out.extend_from_slice(&stroke.style.rgba.to_le_bytes());
|
||||
out.extend_from_slice(&stroke.layer.to_le_bytes());
|
||||
}
|
||||
push_u64(out, self.engraving_decisions.len() as u64);
|
||||
for decision in &self.engraving_decisions {
|
||||
encode_decision(out, decision);
|
||||
|
|
@ -353,6 +369,7 @@ mod tests {
|
|||
source: ScoreVersion::default(),
|
||||
pages: vec![],
|
||||
glyphs,
|
||||
strokes: vec![],
|
||||
engraving_decisions: decisions,
|
||||
catalog: GlyphCatalogIdentity::default(),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,9 +64,13 @@ pub fn laid_out_object_ids(score: &Score) -> BTreeSet<TypedObjectId> {
|
|||
pub struct RoundTripReport {
|
||||
pub status: SolveStatus,
|
||||
pub logical_objects: usize,
|
||||
/// Glyph primitives (one per laid-out glyph).
|
||||
pub glyphs: usize,
|
||||
/// Stroke primitives (staff lines, stems, markers, …).
|
||||
pub render_strokes: usize,
|
||||
/// Total render primitives — glyphs **and** strokes.
|
||||
pub render_primitives: usize,
|
||||
/// Every score-graph source recovered from the RenderIR.
|
||||
/// Every score-graph source recovered from the RenderIR (glyphs + strokes).
|
||||
pub recovered_sources: BTreeSet<TypedObjectId>,
|
||||
}
|
||||
|
||||
|
|
@ -122,14 +126,27 @@ pub fn round_trip(score: &Score) -> RoundTripReport {
|
|||
})
|
||||
.chain(logical.cross_region.iter().map(|object| &object.provenance)),
|
||||
);
|
||||
// Every primitive — glyph *and* stroke — is provenance-tracked. The
|
||||
// constrained stage may carry *more* primitives than the logical stage has
|
||||
// objects: each logical object is covered by exactly one primitive carrying
|
||||
// its provenance, plus the engraver's synthesized derived primitives
|
||||
// (accidentals, staff lines, stems, …), whose `source` is constrained to a
|
||||
// laid-out object by the surjection below.
|
||||
let constrained_map = provenance_map(
|
||||
"constrained",
|
||||
constrained.glyphs.iter().map(|g| &g.provenance),
|
||||
);
|
||||
assert_eq!(
|
||||
logical_map, constrained_map,
|
||||
"provenance not preserved logical -> constrained"
|
||||
constrained
|
||||
.glyphs
|
||||
.iter()
|
||||
.map(|g| &g.provenance)
|
||||
.chain(constrained.strokes.iter().map(|s| &s.provenance)),
|
||||
);
|
||||
for (id, provenance) in &logical_map {
|
||||
assert_eq!(
|
||||
constrained_map.get(id),
|
||||
Some(provenance),
|
||||
"logical object {id:?} is not covered (with its exact provenance) in constrained"
|
||||
);
|
||||
}
|
||||
|
||||
let report = StubSolver.solve(&constrained, &SolverConfig::default());
|
||||
assert_eq!(
|
||||
|
|
@ -162,10 +179,20 @@ pub fn round_trip(score: &Score) -> RoundTripReport {
|
|||
assert_eq!(resolved_glyph.style, constrained_glyph.style);
|
||||
assert_eq!(resolved_glyph.layer, constrained_glyph.layer);
|
||||
}
|
||||
// Strokes likewise pass through the stub verbatim, in order.
|
||||
assert_eq!(
|
||||
report.layout.strokes, constrained.strokes,
|
||||
"stub solver must return the input strokes verbatim"
|
||||
);
|
||||
|
||||
let resolved_map = provenance_map(
|
||||
"resolved",
|
||||
report.layout.glyphs.iter().map(|g| &g.provenance),
|
||||
report
|
||||
.layout
|
||||
.glyphs
|
||||
.iter()
|
||||
.map(|g| &g.provenance)
|
||||
.chain(report.layout.strokes.iter().map(|s| &s.provenance)),
|
||||
);
|
||||
assert_eq!(
|
||||
constrained_map, resolved_map,
|
||||
|
|
@ -181,31 +208,43 @@ pub fn round_trip(score: &Score) -> RoundTripReport {
|
|||
assert_eq!(primitive.style, resolved_glyph.style);
|
||||
assert_eq!(primitive.layer, resolved_glyph.layer);
|
||||
}
|
||||
let render_map = provenance_map("render", render.primitives.iter().map(|p| &p.provenance));
|
||||
assert_eq!(
|
||||
render.strokes, report.layout.strokes,
|
||||
"render must carry the resolved strokes verbatim"
|
||||
);
|
||||
let render_map = provenance_map(
|
||||
"render",
|
||||
render
|
||||
.primitives
|
||||
.iter()
|
||||
.map(|p| &p.provenance)
|
||||
.chain(render.strokes.iter().map(|s| &s.provenance)),
|
||||
);
|
||||
assert_eq!(
|
||||
resolved_map, render_map,
|
||||
"provenance not preserved resolved -> render"
|
||||
);
|
||||
|
||||
// Provenance back to graph identity: the recovered source *set* is exactly
|
||||
// the set laid out (a surjection — every source recovered, nothing spurious),
|
||||
// while the primitive count equals the distinct-stable-id count, so each
|
||||
// layout object (including each manifestation of a multiply-manifested
|
||||
// source) is represented exactly once.
|
||||
// Provenance back to graph identity: the recovered source *set* — over every
|
||||
// primitive, glyph and stroke — is exactly the set laid out (a surjection:
|
||||
// every source recovered, nothing spurious), while the primitive count equals
|
||||
// the distinct-stable-id count, so each layout object (and each synthesized
|
||||
// derived primitive) is represented exactly once.
|
||||
let expected = laid_out_object_ids(score);
|
||||
let recovered: BTreeSet<TypedObjectId> = render
|
||||
.primitives
|
||||
.iter()
|
||||
.map(|p| p.provenance.source)
|
||||
.chain(render.strokes.iter().map(|s| s.provenance.source))
|
||||
.collect();
|
||||
assert_eq!(
|
||||
expected, recovered,
|
||||
"RenderIR sources do not match the laid-out graph objects"
|
||||
);
|
||||
assert_eq!(
|
||||
render.primitives.len(),
|
||||
render.primitives.len() + render.strokes.len(),
|
||||
render_map.len(),
|
||||
"render produced two objects with the same stable id"
|
||||
"render produced two primitives with the same stable id"
|
||||
);
|
||||
|
||||
RoundTripReport {
|
||||
|
|
@ -217,7 +256,8 @@ pub fn round_trip(score: &Score) -> RoundTripReport {
|
|||
.sum::<usize>()
|
||||
+ logical.cross_region.len(),
|
||||
glyphs: constrained.glyphs.len(),
|
||||
render_primitives: render.primitives.len(),
|
||||
render_strokes: render.strokes.len(),
|
||||
render_primitives: render.primitives.len() + render.strokes.len(),
|
||||
recovered_sources: recovered,
|
||||
}
|
||||
}
|
||||
|
|
@ -272,18 +312,26 @@ mod tests {
|
|||
assert_eq!(report.status, SolveStatus::Solved);
|
||||
let render = to_render(&report.layout);
|
||||
|
||||
// Two primitives for the shared staff (one per region), distinct ids.
|
||||
let staff_prims: Vec<_> = render
|
||||
.primitives
|
||||
// A staff engraves as stroke line primitives (its five staff lines); its
|
||||
// own provenance anchors the bottom line, the upper four are synthesized
|
||||
// from it. Two manifestations → two anchor lines with distinct ids.
|
||||
let staff_anchors: Vec<_> = render
|
||||
.strokes
|
||||
.iter()
|
||||
.filter(|p| p.provenance.source == TypedObjectId::Staff(staff))
|
||||
.filter(|s| {
|
||||
s.provenance.source == TypedObjectId::Staff(staff)
|
||||
&& s.provenance.synthesis.is_none()
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(
|
||||
staff_prims.len(),
|
||||
staff_anchors.len(),
|
||||
2,
|
||||
"both manifestations reach the render stage"
|
||||
);
|
||||
let ids: BTreeSet<_> = staff_prims.iter().map(|p| p.provenance.stable_id).collect();
|
||||
let ids: BTreeSet<_> = staff_anchors
|
||||
.iter()
|
||||
.map(|s| s.provenance.stable_id)
|
||||
.collect();
|
||||
assert_eq!(
|
||||
ids.len(),
|
||||
2,
|
||||
|
|
@ -369,8 +417,19 @@ mod tests {
|
|||
fn valid_scores_round_trip() {
|
||||
for seed in 0..64u64 {
|
||||
let report = round_trip(&valid_score(seed));
|
||||
assert_eq!(report.glyphs, report.render_primitives);
|
||||
assert_eq!(report.recovered_sources.len(), report.render_primitives);
|
||||
assert_eq!(
|
||||
report.render_primitives,
|
||||
report.glyphs + report.render_strokes
|
||||
);
|
||||
// Every laid-out source is recovered, nothing spurious (the
|
||||
// surjection). A source may back several primitives now — a staff's
|
||||
// five lines all trace to it — so the recovered *set* is no larger
|
||||
// than the primitive count, not equal to it.
|
||||
assert_eq!(
|
||||
report.recovered_sources,
|
||||
laid_out_object_ids(&valid_score(seed))
|
||||
);
|
||||
assert!(report.recovered_sources.len() <= report.render_primitives);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -204,9 +204,13 @@ pub struct ExtensionMetric {
|
|||
}
|
||||
|
||||
/// The quality metric vector for a layout (Chapter 9 §"Quality Metrics":
|
||||
/// `QualityMetricVector`). v0 carries the type but computes **no** values: the
|
||||
/// stub's vector is a placeholder all-`0.0` (nominal-best) vector with no
|
||||
/// conformance meaning (the normalization functions are deferred).
|
||||
/// `QualityMetricVector`). v0 carries the type but computes **no** values: an
|
||||
/// interface-only solver reports the conservative all-worst placeholder
|
||||
/// ([`QualityMetricVector::unmeasured`], every metric `1.0`), never a measured
|
||||
/// value, so a caller cannot mistake an unmeasured layout for a good one. (The
|
||||
/// derived [`Default`] is all-`0.0`/nominal-best and is *not* what the stub
|
||||
/// reports; the normalization functions of the Quality Metric Catalog are
|
||||
/// deferred.)
|
||||
#[derive(Clone, PartialEq, Debug, Default)]
|
||||
pub struct QualityMetricVector {
|
||||
pub collision_penalty: NormalizedMetric,
|
||||
|
|
@ -416,6 +420,13 @@ impl StubSolver {
|
|||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
// Strokes pass through verbatim (the stub resolves no geometry), gated on
|
||||
// the same structural validity as the glyphs.
|
||||
let strokes = if structural_valid {
|
||||
input.strokes.clone()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let resolved_glyphs = glyphs.len();
|
||||
let pages = input
|
||||
.regions
|
||||
|
|
@ -451,6 +462,7 @@ impl StubSolver {
|
|||
source: input.source,
|
||||
pages,
|
||||
glyphs,
|
||||
strokes,
|
||||
engraving_decisions: input.engraving_decisions.clone(),
|
||||
catalog: input.catalog.clone(),
|
||||
},
|
||||
|
|
@ -544,9 +556,11 @@ mod tests {
|
|||
members: glyphs.iter().map(GlyphObject::id).collect(),
|
||||
}],
|
||||
glyphs,
|
||||
strokes: vec![],
|
||||
vertical_bands: vec![band],
|
||||
constraints: vec![],
|
||||
engraving_decisions: vec![],
|
||||
diagnostics: vec![],
|
||||
catalog,
|
||||
}
|
||||
}
|
||||
|
|
@ -628,9 +642,11 @@ mod tests {
|
|||
members: vec![unknown.id()],
|
||||
}],
|
||||
glyphs: vec![unknown],
|
||||
strokes: vec![],
|
||||
vertical_bands: vec![band],
|
||||
constraints: vec![],
|
||||
engraving_decisions: vec![],
|
||||
diagnostics: vec![],
|
||||
catalog: GlyphCatalogIdentity::default(),
|
||||
};
|
||||
let report = StubSolver.solve(&input, &SolverConfig::default());
|
||||
|
|
@ -650,6 +666,34 @@ mod tests {
|
|||
assert!(report.warnings.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strokes_survive_the_solve_and_enter_the_canonical_bytes() {
|
||||
let mut input = constrained(vec![glyph("noteheadBlack")]);
|
||||
let baseline = StubSolver
|
||||
.solve(&input, &SolverConfig::default())
|
||||
.layout
|
||||
.canonical_bytes();
|
||||
input.strokes.push(crate::Stroke {
|
||||
provenance: input.glyphs[0].provenance.clone(),
|
||||
from: crate::Point::new(0.0, 0.0),
|
||||
to: crate::Point::new(1.5, 0.0),
|
||||
thickness: crate::StaffSpace(0.13),
|
||||
layer: 0,
|
||||
style: crate::GlyphStyle::default(),
|
||||
});
|
||||
let solved = StubSolver.solve(&input, &SolverConfig::default());
|
||||
assert_eq!(
|
||||
solved.layout.strokes.len(),
|
||||
1,
|
||||
"the stroke survives the solve"
|
||||
);
|
||||
assert_ne!(
|
||||
solved.layout.canonical_bytes(),
|
||||
baseline,
|
||||
"a stroke changes the resolved canonical bytes"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forged_catalog_metadata_is_rejected() {
|
||||
let mut input = constrained(vec![glyph("noteheadBlack")]);
|
||||
|
|
|
|||
|
|
@ -95,8 +95,9 @@ pub enum TimeAxisKind {
|
|||
}
|
||||
|
||||
/// Compares two [`TimePoint`]s of the *same* kind; mixed kinds are
|
||||
/// incomparable (`None`), which a uniform region never produces.
|
||||
fn time_cmp(a: &TimePoint, b: &TimePoint) -> Option<core::cmp::Ordering> {
|
||||
/// incomparable (`None`), which a uniform region never produces. Exact — over
|
||||
/// rational musical time and integer wall-clock, never a lossy `f64`.
|
||||
pub(crate) fn time_cmp(a: &TimePoint, b: &TimePoint) -> Option<core::cmp::Ordering> {
|
||||
match (a, b) {
|
||||
(TimePoint::Musical(x), TimePoint::Musical(y)) => Some(x.cmp(y)),
|
||||
(TimePoint::WallClock(x), TimePoint::WallClock(y)) => Some(x.cmp(y)),
|
||||
|
|
|
|||
|
|
@ -6,6 +6,12 @@
|
|||
//! (musical convention, positive y = higher pitch), relative to each glyph's
|
||||
//! origin, rounded to 4 decimals. The renderer applies a single y-flip wrapper.
|
||||
//!
|
||||
//! Source (pinned + SHA-256 verified on extraction): Bravura 1.392,
|
||||
//! steinbergmedia/bravura @ 301087ca0b0d30b65d81bc3e718ff64b613e2a9a
|
||||
//! (sha256 dca2d90c88437a701b1c2e71fa54e76f9fa41d7deee935d74dc871ea66ecfdd2);
|
||||
//! glyph names from w3c/smufl gh-pages @ 31a327a29640c313b12076739987bd7f25bdddde
|
||||
//! (sha256 1d05352599a20983d1c901635dc75d76f063c0987a7bee65f145325fc3e0d29f).
|
||||
//!
|
||||
//! Bravura is (c) Steinberg Media Technologies GmbH under the SIL Open Font
|
||||
//! License 1.1; these extracted outlines are redistributed under the same license
|
||||
//! (see `tools/OFL.txt`).
|
||||
|
|
@ -155,12 +161,66 @@ pub(crate) const BRAVURA_OUTLINES: &[BravuraOutline] = &[
|
|||
path: "M1.128 -0.436V-0.068C1.128 -0.008 1.08 0.036 1.024 0.036H0.104C0.044 0.036 0 -0.008 0 -0.068V-0.436C0 -0.492 0.044 -0.54 0.104 -0.54H1.024C1.08 -0.54 1.128 -0.492 1.128 -0.436Z",
|
||||
bbox: [0.0, -0.54, 1.128, 0.036],
|
||||
},
|
||||
BravuraOutline {
|
||||
name: "timeSig0",
|
||||
codepoint: 0xE080,
|
||||
path: "M1.8 0C1.8 0.556 1.416 1.004 0.94 1.004C0.464 1.004 0.08 0.556 0.08 0C0.08 -0.552 0.464 -1 0.94 -1C1.416 -1 1.8 -0.552 1.8 0ZM0.94 0.88C1.104 0.88 1.24 0.5 1.24 0.028C1.24 -0.44 1.104 -0.82 0.94 -0.82C0.772 -0.82 0.64 -0.44 0.64 0.028C0.64 0.5 0.772 0.88 0.94 0.88Z",
|
||||
bbox: [0.08, -1.0, 1.8, 1.004],
|
||||
},
|
||||
BravuraOutline {
|
||||
name: "timeSig1",
|
||||
codepoint: 0xE081,
|
||||
path: "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",
|
||||
bbox: [0.08, -1.0, 1.256, 1.004],
|
||||
},
|
||||
BravuraOutline {
|
||||
name: "timeSig2",
|
||||
codepoint: 0xE082,
|
||||
path: "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",
|
||||
bbox: [0.08, -1.028, 1.704, 1.016],
|
||||
},
|
||||
BravuraOutline {
|
||||
name: "timeSig3",
|
||||
codepoint: 0xE083,
|
||||
path: "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",
|
||||
bbox: [0.08, -1.004, 1.604, 0.996],
|
||||
},
|
||||
BravuraOutline {
|
||||
name: "timeSig4",
|
||||
codepoint: 0xE084,
|
||||
path: "M1.448 -0.296V0.56C1.448 0.592 1.444 0.628 1.4 0.628C1.364 0.628 1.344 0.62 1.32 0.592L0.94 0.132C0.924 0.112 0.904 0.088 0.904 0.04V-0.296H0.364C0.684 -0.024 1.324 0.884 1.336 0.932L1.34 0.944C1.34 0.98 1.312 1.004 1.28 1.004C1.244 1.004 1.08 0.996 1.008 0.996C0.936 0.996 0.756 1.004 0.724 1.004C0.688 1.004 0.632 0.992 0.632 0.928C0.632 0.432 0.24 -0.124 0.12 -0.292L0.096 -0.324C0.096 -0.324 0.096 -0.328 0.096 -0.328L0.092 -0.332C0.084 -0.352 0.08 -0.368 0.08 -0.38C0.08 -0.42 0.112 -0.448 0.16 -0.448H0.904V-0.7C0.904 -0.808 0.816 -0.84 0.744 -0.84C0.68 -0.84 0.652 -0.876 0.652 -0.916C0.652 -0.956 0.668 -1 0.728 -1H1.58C1.62 -1 1.66 -0.972 1.66 -0.916C1.66 -0.86 1.612 -0.836 1.572 -0.836C1.532 -0.836 1.448 -0.812 1.448 -0.684V-0.448H1.74C1.78 -0.448 1.8 -0.42 1.8 -0.372C1.8 -0.324 1.784 -0.296 1.74 -0.296Z",
|
||||
bbox: [0.08, -1.0, 1.8, 1.004],
|
||||
},
|
||||
BravuraOutline {
|
||||
name: "timeSig5",
|
||||
codepoint: 0xE085,
|
||||
path: "M0.304 0.236C0.304 0.236 0.32 0.46 0.324 0.496C0.328 0.528 0.348 0.548 0.384 0.548H0.4C0.44 0.54 0.628 0.512 0.792 0.512C1.348 0.512 1.368 0.832 1.368 0.896C1.368 0.948 1.356 0.98 1.312 0.98C1.26 0.98 0.948 0.944 0.82 0.944C0.692 0.944 0.348 0.976 0.28 0.984C0.208 0.984 0.188 0.948 0.184 0.916L0.14 0.028V0.02C0.14 -0.032 0.18 -0.04 0.22 -0.04C0.26 -0.04 0.264 -0.004 0.308 0.04C0.348 0.08 0.444 0.172 0.58 0.172C0.716 0.172 0.992 0.096 0.992 -0.348C0.992 -0.788 0.756 -0.844 0.652 -0.844C0.62 -0.844 0.588 -0.844 0.56 -0.832C0.54 -0.82 0.516 -0.804 0.512 -0.776C0.512 -0.748 0.54 -0.732 0.56 -0.72C0.652 -0.664 0.712 -0.564 0.712 -0.452C0.712 -0.276 0.572 -0.14 0.4 -0.14C0.184 -0.14 0.096 -0.296 0.084 -0.436C0.08 -0.46 0.08 -0.484 0.08 -0.508C0.08 -0.84 0.296 -1.004 0.788 -1.004C1.268 -1.004 1.532 -0.708 1.532 -0.348C1.532 0.016 1.236 0.312 0.872 0.312C0.64 0.312 0.468 0.272 0.34 0.196C0.332 0.192 0.324 0.192 0.32 0.192C0.304 0.192 0.304 0.208 0.304 0.22Z",
|
||||
bbox: [0.08, -1.004, 1.532, 0.984],
|
||||
},
|
||||
BravuraOutline {
|
||||
name: "timeSig6",
|
||||
codepoint: 0xE086,
|
||||
path: "M1.22 0.332C1.388 0.332 1.54 0.464 1.54 0.636C1.54 0.652 1.54 0.664 1.536 0.68C1.496 0.936 1.184 1.004 0.968 1.004C0.664 1 0.38 0.852 0.236 0.58C0.148 0.412 0.08 0.2 0.08 0.012V-0.004C0.084 -0.188 0.116 -0.396 0.204 -0.556C0.368 -0.852 0.564 -0.996 0.9 -0.996C1.08 -0.996 1.28 -0.968 1.424 -0.852C1.564 -0.74 1.656 -0.56 1.656 -0.38C1.656 -0.068 1.36 0.2 1.052 0.2C0.924 0.2 0.792 0.152 0.688 0.06C0.68 0.052 0.672 0.052 0.664 0.052C0.64 0.052 0.628 0.084 0.628 0.148C0.64 0.888 0.876 0.908 0.96 0.908C1.04 0.908 1.092 0.888 1.092 0.848C1.092 0.792 1.016 0.744 0.992 0.696C0.972 0.66 0.964 0.62 0.964 0.58C0.964 0.52 0.984 0.46 1.024 0.416C1.052 0.364 1.168 0.332 1.22 0.332ZM0.888 0.008C1.016 0.008 1.124 -0.192 1.124 -0.44C1.124 -0.688 1.016 -0.888 0.888 -0.888C0.76 -0.888 0.656 -0.688 0.656 -0.44C0.656 -0.192 0.76 0.008 0.888 0.008Z",
|
||||
bbox: [0.08, -0.996, 1.656, 1.004],
|
||||
},
|
||||
BravuraOutline {
|
||||
name: "timeSig7",
|
||||
codepoint: 0xE087,
|
||||
path: "M1.684 0.816C1.684 0.924 1.684 0.976 1.616 0.976C1.612 0.976 1.548 0.96 1.532 0.932C1.504 0.884 1.448 0.656 1.348 0.656C1.248 0.656 1.06 0.996 0.728 0.996C0.496 0.996 0.436 0.904 0.38 0.852C0.324 0.8 0.3 0.784 0.272 0.78C0.24 0.78 0.188 0.836 0.168 0.876C0.16 0.892 0.14 0.904 0.12 0.904C0.1 0.904 0.08 0.892 0.08 0.856V0.196C0.08 0.196 0.084 0.132 0.124 0.132C0.156 0.132 0.168 0.168 0.184 0.212C0.224 0.312 0.276 0.544 0.456 0.544C0.616 0.544 0.808 0.244 1.04 0.244C1.152 0.244 1.208 0.304 1.24 0.328C1.252 0.336 1.268 0.344 1.276 0.344C1.292 0.344 1.3 0.332 1.304 0.308C1.304 0.196 0.996 -0.028 0.756 -0.312C0.604 -0.488 0.48 -0.72 0.48 -0.876C0.48 -0.96 0.48 -1 0.556 -1C0.628 -1 0.716 -0.964 0.816 -0.964C0.916 -0.964 1.104 -1 1.144 -1C1.184 -1 1.208 -0.968 1.208 -0.852C1.208 -0.184 1.684 0.388 1.684 0.8Z",
|
||||
bbox: [0.08, -1.0, 1.684, 0.996],
|
||||
},
|
||||
BravuraOutline {
|
||||
name: "timeSig8",
|
||||
codepoint: 0xE088,
|
||||
path: "M1.336 0.144C1.48 0.236 1.576 0.368 1.576 0.568C1.576 0.976 0.988 1.036 0.88 1.036C0.416 1.036 0.1 0.824 0.1 0.488C0.1 0.212 0.256 0.064 0.448 -0.044C0.24 -0.144 0.08 -0.276 0.08 -0.528C0.08 -0.876 0.44 -1.036 0.836 -1.036C1.236 -1.036 1.664 -0.864 1.664 -0.324C1.664 -0.084 1.524 0.048 1.336 0.144ZM1.128 0.236C0.808 0.348 0.468 0.416 0.468 0.668C0.468 0.836 0.696 0.92 0.872 0.92C1 0.92 1.34 0.856 1.34 0.576C1.34 0.416 1.26 0.312 1.128 0.236ZM0.82 -0.904C0.552 -0.904 0.308 -0.768 0.308 -0.508C0.308 -0.348 0.448 -0.2 0.624 -0.132C0.916 -0.26 1.212 -0.344 1.212 -0.608C1.212 -0.768 1.088 -0.904 0.82 -0.904Z",
|
||||
bbox: [0.08, -1.036, 1.664, 1.036],
|
||||
},
|
||||
BravuraOutline {
|
||||
name: "timeSig9",
|
||||
codepoint: 0xE089,
|
||||
path: "M0.516 -0.324C0.348 -0.324 0.196 -0.456 0.196 -0.628C0.196 -0.644 0.196 -0.656 0.2 -0.672C0.24 -0.928 0.552 -0.996 0.768 -0.996C1.072 -0.992 1.356 -0.844 1.5 -0.572C1.588 -0.404 1.656 -0.192 1.656 -0.004V0.012C1.652 0.196 1.62 0.404 1.532 0.564C1.368 0.86 1.172 1.004 0.836 1.004C0.656 1.004 0.456 0.976 0.312 0.86C0.172 0.748 0.08 0.568 0.08 0.388C0.08 0.076 0.376 -0.192 0.684 -0.192C0.812 -0.192 0.944 -0.144 1.048 -0.052C1.056 -0.044 1.064 -0.044 1.072 -0.044C1.096 -0.044 1.108 -0.076 1.108 -0.14C1.096 -0.88 0.86 -0.9 0.776 -0.9C0.696 -0.9 0.644 -0.88 0.644 -0.84C0.644 -0.784 0.72 -0.736 0.744 -0.688C0.764 -0.652 0.772 -0.612 0.772 -0.572C0.772 -0.512 0.752 -0.452 0.712 -0.408C0.684 -0.356 0.568 -0.324 0.516 -0.324ZM0.848 0C0.72 0 0.612 0.2 0.612 0.448C0.612 0.696 0.72 0.896 0.848 0.896C0.976 0.896 1.08 0.696 1.08 0.448C1.08 0.2 0.976 0 0.848 0Z",
|
||||
bbox: [0.08, -0.996, 1.656, 1.004],
|
||||
},
|
||||
BravuraOutline {
|
||||
name: "timeSigCommon",
|
||||
codepoint: 0xE08A,
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
use std::collections::BTreeMap;
|
||||
use std::fmt::Write as _;
|
||||
|
||||
use epiphany_layout_ir::{BoundingBox, Provenance, ResolvedGlyph, ResolvedLayoutIR};
|
||||
use epiphany_layout_ir::{BoundingBox, Provenance, ResolvedGlyph, ResolvedLayoutIR, Transform2D};
|
||||
|
||||
use crate::outline::outline;
|
||||
use crate::xml::{check_well_formed, escape_attr};
|
||||
|
|
@ -154,13 +154,18 @@ pub struct RenderStats {
|
|||
pub path_count: usize,
|
||||
/// Fallback `<rect>` elements emitted (glyphs with no bundled outline).
|
||||
pub fallback_rect_count: usize,
|
||||
/// `<line>` elements emitted (one per resolved stroke: staff line, stem, …).
|
||||
pub stroke_count: usize,
|
||||
/// Elements carrying a `data-prov` trace back to a score-graph source.
|
||||
pub provenance_count: usize,
|
||||
/// Distinct layers, each rendered as one `<g>` group.
|
||||
pub layer_count: usize,
|
||||
/// Per-class glyph counts.
|
||||
pub class_counts: BTreeMap<GlyphClass, usize>,
|
||||
/// The content viewBox `[min_x, min_y, width, height]`, in staff spaces.
|
||||
/// The padded content bounds `[min_x, min_y, width, height]`, in staff
|
||||
/// spaces. Note this is the *content extent*, not the emitted `viewBox`
|
||||
/// attribute: the SVG is translated so its `viewBox` is always `0 0 W H`
|
||||
/// (the `min_x`/`min_y` here are folded into the y-flip group's translate).
|
||||
pub view_box: [f32; 4],
|
||||
}
|
||||
|
||||
|
|
@ -205,6 +210,7 @@ pub fn render(resolved: &ResolvedLayoutIR, options: &RenderOptions) -> RenderOut
|
|||
glyph_count: 0,
|
||||
path_count: 0,
|
||||
fallback_rect_count: 0,
|
||||
stroke_count: 0,
|
||||
provenance_count: 0,
|
||||
layer_count: 0,
|
||||
class_counts,
|
||||
|
|
@ -217,14 +223,26 @@ pub fn render(resolved: &ResolvedLayoutIR, options: &RenderOptions) -> RenderOut
|
|||
};
|
||||
let max_y = min_y + height;
|
||||
|
||||
// Group glyph indices by layer (ascending), preserving input order within.
|
||||
let mut layers: BTreeMap<i32, Vec<usize>> = BTreeMap::new();
|
||||
// Group glyphs and strokes by layer (ascending), preserving input order
|
||||
// within. Strokes draw before glyphs at the same layer, so a staff line sits
|
||||
// under the noteheads on it.
|
||||
let mut glyph_layers: BTreeMap<i32, Vec<usize>> = BTreeMap::new();
|
||||
for (i, g) in resolved.glyphs.iter().enumerate() {
|
||||
layers.entry(g.layer).or_default().push(i);
|
||||
glyph_layers.entry(g.layer).or_default().push(i);
|
||||
}
|
||||
let mut stroke_layers: BTreeMap<i32, Vec<usize>> = BTreeMap::new();
|
||||
for (i, stroke) in resolved.strokes.iter().enumerate() {
|
||||
stroke_layers.entry(stroke.layer).or_default().push(i);
|
||||
}
|
||||
let layer_ids: std::collections::BTreeSet<i32> = glyph_layers
|
||||
.keys()
|
||||
.chain(stroke_layers.keys())
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
let mut path_count = 0;
|
||||
let mut fallback_rect_count = 0;
|
||||
let mut stroke_count = 0;
|
||||
let mut provenance_count = 0;
|
||||
|
||||
let mut s = String::new();
|
||||
|
|
@ -250,53 +268,96 @@ pub fn render(resolved: &ResolvedLayoutIR, options: &RenderOptions) -> RenderOut
|
|||
num(max_y),
|
||||
);
|
||||
|
||||
for (layer, indices) in &layers {
|
||||
for layer in &layer_ids {
|
||||
let _ = writeln!(s, " <g data-layer=\"{}\">", layer);
|
||||
for &i in indices {
|
||||
let g = &resolved.glyphs[i];
|
||||
let name = g.glyph.as_str();
|
||||
let (x, y) = (g.position.x.0, g.position.y.0);
|
||||
let (fill, opacity) = colour(g.style.rgba);
|
||||
let prov = if options.emit_provenance {
|
||||
provenance_count += 1;
|
||||
provenance_attrs(&g.provenance, name, GlyphClass::of(name))
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
match outline(name) {
|
||||
Some(o) => {
|
||||
path_count += 1;
|
||||
let _ = writeln!(
|
||||
s,
|
||||
" <path d=\"{}\" transform=\"translate({} {})\" fill=\"{}\"{}{}/>",
|
||||
o.path,
|
||||
num(x),
|
||||
num(y),
|
||||
fill,
|
||||
opacity,
|
||||
prov,
|
||||
);
|
||||
|
||||
// Strokes (staff lines, stems, barlines, …) — drawn first so glyphs on
|
||||
// the same layer sit over them.
|
||||
if let Some(indices) = stroke_layers.get(layer) {
|
||||
for &i in indices {
|
||||
let stroke = &resolved.strokes[i];
|
||||
let (stroke_fill, opacity) = stroke_colour(stroke.style.rgba);
|
||||
let prov = if options.emit_provenance {
|
||||
provenance_count += 1;
|
||||
stroke_provenance_attrs(&stroke.provenance)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
stroke_count += 1;
|
||||
let _ = writeln!(
|
||||
s,
|
||||
" <line x1=\"{}\" y1=\"{}\" x2=\"{}\" y2=\"{}\" \
|
||||
stroke=\"{}\" stroke-width=\"{}\"{}{}/>",
|
||||
num(stroke.from.x.0),
|
||||
num(stroke.from.y.0),
|
||||
num(stroke.to.x.0),
|
||||
num(stroke.to.y.0),
|
||||
stroke_fill,
|
||||
num(stroke.thickness.0),
|
||||
opacity,
|
||||
prov,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(indices) = glyph_layers.get(layer) {
|
||||
for &i in indices {
|
||||
let g = &resolved.glyphs[i];
|
||||
let name = g.glyph.as_str();
|
||||
let (x, y) = (g.position.x.0, g.position.y.0);
|
||||
// The glyph's resolved transform (scale/rotate/skew about its
|
||||
// origin), composed after the placement translate. `None` ⇒ a bare
|
||||
// translate, the common case.
|
||||
let placement = placement_transform(x, y, &g.transform);
|
||||
if let Some(t) = &g.transform {
|
||||
if !is_affine(t) {
|
||||
diagnostics.push(Diagnostic {
|
||||
message: "non-affine (projective) glyph transform is not \
|
||||
representable in SVG; rendered its affine projection"
|
||||
.to_owned(),
|
||||
glyph: Some(name.to_owned()),
|
||||
});
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// No outline: surface it and draw the IR bounding box so the
|
||||
// missing glyph is visible, not silently absent.
|
||||
fallback_rect_count += 1;
|
||||
diagnostics.push(Diagnostic {
|
||||
message: "no bundled Bravura outline; drew bounding-box fallback"
|
||||
.to_owned(),
|
||||
glyph: Some(name.to_owned()),
|
||||
});
|
||||
let bb = g.bounding_box;
|
||||
let _ = writeln!(
|
||||
s,
|
||||
" <rect x=\"{}\" y=\"{}\" width=\"{}\" height=\"{}\" \
|
||||
fill=\"none\" stroke=\"#cc0000\" stroke-width=\"0.05\"{}/>",
|
||||
num(x + bb.left.0),
|
||||
num(y + bb.bottom.0),
|
||||
num((bb.right.0 - bb.left.0).max(0.0)),
|
||||
num((bb.top.0 - bb.bottom.0).max(0.0)),
|
||||
prov,
|
||||
);
|
||||
let (fill, opacity) = colour(g.style.rgba);
|
||||
let prov = if options.emit_provenance {
|
||||
provenance_count += 1;
|
||||
provenance_attrs(&g.provenance, name, GlyphClass::of(name))
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
match outline(name) {
|
||||
Some(o) => {
|
||||
path_count += 1;
|
||||
let _ = writeln!(
|
||||
s,
|
||||
" <path d=\"{}\" transform=\"{}\" fill=\"{}\"{}{}/>",
|
||||
o.path, placement, fill, opacity, prov,
|
||||
);
|
||||
}
|
||||
None => {
|
||||
// No outline: surface it and draw the IR bounding box so the
|
||||
// missing glyph is visible, not silently absent.
|
||||
fallback_rect_count += 1;
|
||||
diagnostics.push(Diagnostic {
|
||||
message: "no bundled Bravura outline; drew bounding-box fallback"
|
||||
.to_owned(),
|
||||
glyph: Some(name.to_owned()),
|
||||
});
|
||||
let bb = g.bounding_box;
|
||||
let _ = writeln!(
|
||||
s,
|
||||
" <rect x=\"{}\" y=\"{}\" width=\"{}\" height=\"{}\" \
|
||||
transform=\"{}\" fill=\"none\" stroke=\"#cc0000\" \
|
||||
stroke-width=\"0.05\"{}/>",
|
||||
num(bb.left.0),
|
||||
num(bb.bottom.0),
|
||||
num((bb.right.0 - bb.left.0).max(0.0)),
|
||||
num((bb.top.0 - bb.bottom.0).max(0.0)),
|
||||
placement,
|
||||
prov,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -311,8 +372,9 @@ pub fn render(resolved: &ResolvedLayoutIR, options: &RenderOptions) -> RenderOut
|
|||
glyph_count: resolved.glyphs.len(),
|
||||
path_count,
|
||||
fallback_rect_count,
|
||||
stroke_count,
|
||||
provenance_count,
|
||||
layer_count: layers.len(),
|
||||
layer_count: layer_ids.len(),
|
||||
class_counts,
|
||||
view_box: [num_f(min_x), num_f(min_y), num_f(width), num_f(height)],
|
||||
},
|
||||
|
|
@ -332,10 +394,16 @@ fn content_bounds(resolved: &ResolvedLayoutIR, margin: f32) -> Option<(f32, f32,
|
|||
for g in &resolved.glyphs {
|
||||
let bb = drawn_bbox(g);
|
||||
let (x, y) = (g.position.x.0, g.position.y.0);
|
||||
for (px, py) in [
|
||||
(x + bb.left.0, y + bb.bottom.0),
|
||||
(x + bb.right.0, y + bb.top.0),
|
||||
// All four bbox corners mapped through the *same* placement transform the
|
||||
// renderer applies — a scale/rotation can push drawn geometry past the
|
||||
// untransformed axis-aligned extent, which would crop it.
|
||||
for (lx, ly) in [
|
||||
(bb.left.0, bb.bottom.0),
|
||||
(bb.right.0, bb.bottom.0),
|
||||
(bb.left.0, bb.top.0),
|
||||
(bb.right.0, bb.top.0),
|
||||
] {
|
||||
let (px, py) = placed_point(x, y, &g.transform, lx, ly);
|
||||
if px.is_finite() && py.is_finite() {
|
||||
any = true;
|
||||
min_x = min_x.min(px);
|
||||
|
|
@ -345,6 +413,23 @@ fn content_bounds(resolved: &ResolvedLayoutIR, margin: f32) -> Option<(f32, f32,
|
|||
}
|
||||
}
|
||||
}
|
||||
// Strokes extend the bounds by a half-thickness box around each endpoint, so a
|
||||
// thick rule is not clipped perpendicular to its direction even at margin 0.
|
||||
for stroke in &resolved.strokes {
|
||||
let half = (stroke.thickness.0 * 0.5).max(0.0);
|
||||
for point in [stroke.from, stroke.to] {
|
||||
let (cx, cy) = (point.x.0, point.y.0);
|
||||
for (px, py) in [(cx - half, cy - half), (cx + half, cy + half)] {
|
||||
if px.is_finite() && py.is_finite() {
|
||||
any = true;
|
||||
min_x = min_x.min(px);
|
||||
min_y = min_y.min(py);
|
||||
max_x = max_x.max(px);
|
||||
max_y = max_y.max(py);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !any {
|
||||
return None;
|
||||
}
|
||||
|
|
@ -378,6 +463,87 @@ fn provenance_attrs(p: &Provenance, glyph: &str, class: GlyphClass) -> String {
|
|||
)
|
||||
}
|
||||
|
||||
/// `data-*` provenance attributes for a stroke (a non-glyph line primitive).
|
||||
fn stroke_provenance_attrs(p: &Provenance) -> String {
|
||||
format!(
|
||||
" data-prov=\"{:032x}\" data-source-kind=\"{}\" data-kind=\"stroke\"",
|
||||
p.stable_id.0,
|
||||
p.source.discriminant(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Whether a [`Transform2D`] is a pure 2-D affine — its bottom row is `[0, 0, 1]`
|
||||
/// (within an `f32` tolerance). SVG transforms are affine, so a non-affine
|
||||
/// (projective) transform cannot be represented; the renderer diagnoses it and
|
||||
/// renders its affine projection.
|
||||
fn is_affine(transform: &Transform2D) -> bool {
|
||||
let bottom = transform.matrix[2];
|
||||
bottom[0].abs() < 1e-6 && bottom[1].abs() < 1e-6 && (bottom[2] - 1.0).abs() < 1e-6
|
||||
}
|
||||
|
||||
/// A glyph-local point mapped to world space through the renderer's placement
|
||||
/// transform — the glyph's resolved affine applied about the origin, then
|
||||
/// translated to `(px, py)`. Matches [`placement_transform`]'s SVG output (it
|
||||
/// uses the affine projection, dropping any projective bottom row).
|
||||
fn placed_point(px: f32, py: f32, transform: &Option<Transform2D>, lx: f32, ly: f32) -> (f32, f32) {
|
||||
let (tx, ty) = match transform {
|
||||
None => (lx, ly),
|
||||
Some(t) => {
|
||||
let m = t.matrix;
|
||||
(
|
||||
m[0][0] * lx + m[0][1] * ly + m[0][2],
|
||||
m[1][0] * lx + m[1][1] * ly + m[1][2],
|
||||
)
|
||||
}
|
||||
};
|
||||
(px + tx, py + ty)
|
||||
}
|
||||
|
||||
/// The SVG `transform` placing a glyph at `(x, y)` and applying its resolved
|
||||
/// affine (`scale`/`rotate`/`skew` about the glyph origin) when present. The
|
||||
/// affine is the inner (rightmost) transform, so the glyph's local outline is
|
||||
/// transformed, then translated into place. `None` is the common case: a bare
|
||||
/// translate, byte-identical to the pre-transform output. A non-affine transform
|
||||
/// is rendered as its affine projection (the bottom row is dropped); the render
|
||||
/// loop emits a diagnostic for that case.
|
||||
fn placement_transform(x: f32, y: f32, transform: &Option<Transform2D>) -> String {
|
||||
match transform {
|
||||
None => format!("translate({} {})", num(x), num(y)),
|
||||
Some(t) => {
|
||||
let m = t.matrix;
|
||||
// SVG matrix(a b c d e f) is the affine [[a c e],[b d f],[0 0 1]];
|
||||
// map it from the row-major 3×3 (the bottom row is implicit).
|
||||
format!(
|
||||
"translate({} {}) matrix({} {} {} {} {} {})",
|
||||
num(x),
|
||||
num(y),
|
||||
num(m[0][0]),
|
||||
num(m[1][0]),
|
||||
num(m[0][1]),
|
||||
num(m[1][1]),
|
||||
num(m[0][2]),
|
||||
num(m[1][2]),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Splits an `0xRRGGBBAA` colour into an SVG `stroke` value and an optional
|
||||
/// `stroke-opacity` attribute (empty when fully opaque).
|
||||
fn stroke_colour(rgba: u32) -> (String, String) {
|
||||
let r = (rgba >> 24) & 0xff;
|
||||
let g = (rgba >> 16) & 0xff;
|
||||
let b = (rgba >> 8) & 0xff;
|
||||
let a = rgba & 0xff;
|
||||
let stroke = format!("#{r:02x}{g:02x}{b:02x}");
|
||||
let opacity = if a == 0xff {
|
||||
String::new()
|
||||
} else {
|
||||
format!(" stroke-opacity=\"{}\"", num(a as f32 / 255.0))
|
||||
};
|
||||
(stroke, opacity)
|
||||
}
|
||||
|
||||
/// Splits an `0xRRGGBBAA` colour into an SVG `fill` value and an optional
|
||||
/// `fill-opacity` attribute (empty when fully opaque).
|
||||
fn colour(rgba: u32) -> (String, String) {
|
||||
|
|
@ -419,8 +585,9 @@ fn num(v: f32) -> String {
|
|||
s
|
||||
}
|
||||
|
||||
/// Normalises a coordinate value: `-0.0` and tiny `±0` round to `0.0`; other
|
||||
/// values pass through. Keeps the formatted form and the stored stats agreeing.
|
||||
/// Normalises a coordinate value: `-0.0` (which compares equal to `0.0`) maps to
|
||||
/// `0.0`; every other value passes through unchanged. Keeps the formatted form
|
||||
/// and the stored stats agreeing.
|
||||
fn num_f(v: f32) -> f32 {
|
||||
if v == 0.0 {
|
||||
0.0
|
||||
|
|
@ -434,7 +601,8 @@ mod tests {
|
|||
use super::*;
|
||||
use epiphany_core::generators::valid_score_rich;
|
||||
use epiphany_layout_ir::{
|
||||
to_constrained, to_logical, ConstraintSolver, SolverConfig, StubSolver,
|
||||
to_constrained, to_logical, ConstraintSolver, GlyphStyle, Point, SolverConfig, StaffSpace,
|
||||
Stroke, StubSolver, Transform2D,
|
||||
};
|
||||
|
||||
fn stub_layout(seed: u64) -> ResolvedLayoutIR {
|
||||
|
|
@ -444,6 +612,120 @@ mod tests {
|
|||
.layout
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_stroke_renders_as_a_traced_line() {
|
||||
let mut layout = stub_layout(11);
|
||||
let glyph_count = layout.glyphs.len();
|
||||
// The engraver already emits strokes (staff lines, stems, …); this adds
|
||||
// one more and checks it renders and traces on top of them.
|
||||
let base_strokes = layout.strokes.len();
|
||||
layout.strokes.push(Stroke {
|
||||
provenance: layout.glyphs[0].provenance.clone(),
|
||||
from: Point::new(0.0, 0.0),
|
||||
to: Point::new(4.0, 0.0),
|
||||
thickness: StaffSpace(0.13),
|
||||
layer: -1,
|
||||
style: GlyphStyle { rgba: 0x0000_00ff },
|
||||
});
|
||||
let out = render(&layout, &RenderOptions::default());
|
||||
assert!(
|
||||
out.is_well_formed(),
|
||||
"SVG with a stroke must be well-formed"
|
||||
);
|
||||
assert_eq!(out.stats.stroke_count, base_strokes + 1);
|
||||
assert!(
|
||||
out.svg.contains("<line "),
|
||||
"the stroke is drawn as a <line>"
|
||||
);
|
||||
assert!(
|
||||
out.svg.contains("data-kind=\"stroke\""),
|
||||
"the stroke carries a provenance trace"
|
||||
);
|
||||
assert_eq!(
|
||||
out.stats.provenance_count,
|
||||
glyph_count + base_strokes + 1,
|
||||
"every glyph and stroke is traced"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_glyph_transform_is_applied_not_dropped() {
|
||||
let mut layout = stub_layout(11);
|
||||
// A 2× scale about the glyph origin: the renderer must emit it, not ignore
|
||||
// it (otherwise a future solver's transform would silently disappear).
|
||||
layout.glyphs[0].transform = Some(Transform2D {
|
||||
matrix: [[2.0, 0.0, 0.0], [0.0, 2.0, 0.0], [0.0, 0.0, 1.0]],
|
||||
});
|
||||
let out = render(&layout, &RenderOptions::default());
|
||||
assert!(out.is_well_formed());
|
||||
assert!(
|
||||
out.svg.contains("matrix(2 0 0 2 0 0)"),
|
||||
"the glyph's resolved transform is applied"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn glyph_transform_expands_the_view_box() {
|
||||
let base = stub_layout(11);
|
||||
let base_width = render(&base, &RenderOptions::default()).stats.view_box[2];
|
||||
let mut transformed = base.clone();
|
||||
// Translate one glyph far to the right via its transform; the bounds must
|
||||
// grow to contain it (untransformed bounds would crop it).
|
||||
transformed.glyphs[0].transform = Some(Transform2D {
|
||||
matrix: [[1.0, 0.0, 1000.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
|
||||
});
|
||||
let width = render(&transformed, &RenderOptions::default())
|
||||
.stats
|
||||
.view_box[2];
|
||||
assert!(
|
||||
width > base_width + 900.0,
|
||||
"a transformed glyph widens the viewBox ({width} vs {base_width})"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thick_stroke_expands_bounds_by_half_width() {
|
||||
let mut layout = stub_layout(11);
|
||||
let provenance = layout.glyphs[0].provenance.clone();
|
||||
layout.glyphs.clear();
|
||||
layout.strokes.push(Stroke {
|
||||
provenance,
|
||||
from: Point::new(0.0, 0.0),
|
||||
to: Point::new(4.0, 0.0),
|
||||
thickness: StaffSpace(2.0),
|
||||
layer: 0,
|
||||
style: GlyphStyle::default(),
|
||||
});
|
||||
let options = RenderOptions {
|
||||
margin: 0.0,
|
||||
..RenderOptions::default()
|
||||
};
|
||||
let height = render(&layout, &options).stats.view_box[3];
|
||||
// A horizontal rule of thickness 2 spans y ∈ [−1, 1]: a half-width each
|
||||
// side, so the perpendicular extent is at least the full thickness.
|
||||
assert!(
|
||||
height >= 2.0,
|
||||
"half-thickness expands the perpendicular extent (got {height})"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_affine_transform_is_diagnosed() {
|
||||
let mut layout = stub_layout(11);
|
||||
// A non-zero bottom row makes the transform projective, not affine.
|
||||
layout.glyphs[0].transform = Some(Transform2D {
|
||||
matrix: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.5, 0.0, 1.0]],
|
||||
});
|
||||
let out = render(&layout, &RenderOptions::default());
|
||||
assert!(out.is_well_formed());
|
||||
assert!(
|
||||
out.diagnostics
|
||||
.iter()
|
||||
.any(|d| d.message.contains("non-affine")),
|
||||
"a projective transform is surfaced as a diagnostic, not silently mis-rendered"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_well_formed_svg_with_one_path_per_glyph() {
|
||||
let layout = stub_layout(11);
|
||||
|
|
@ -454,7 +736,10 @@ mod tests {
|
|||
assert_eq!(out.stats.path_count, layout.glyphs.len());
|
||||
assert_eq!(out.stats.fallback_rect_count, 0);
|
||||
assert!(out.diagnostics.is_empty());
|
||||
assert_eq!(out.stats.provenance_count, layout.glyphs.len());
|
||||
assert_eq!(
|
||||
out.stats.provenance_count,
|
||||
layout.glyphs.len() + layout.strokes.len()
|
||||
);
|
||||
assert!(out.svg.contains("<svg"));
|
||||
assert!(out.svg.contains("data-prov="));
|
||||
}
|
||||
|
|
@ -474,6 +759,7 @@ mod tests {
|
|||
source: Default::default(),
|
||||
pages: vec![],
|
||||
glyphs: vec![],
|
||||
strokes: vec![],
|
||||
engraving_decisions: vec![],
|
||||
catalog: Default::default(),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ fn snapshot_text(fixture: &str, constrained: &ConstrainedLayoutIR, out: &RenderO
|
|||
"fallback_rect_count={}\n",
|
||||
out.stats.fallback_rect_count
|
||||
));
|
||||
s.push_str(&format!("stroke_count={}\n", out.stats.stroke_count));
|
||||
s.push_str(&format!(
|
||||
"provenance_count={}\n",
|
||||
out.stats.provenance_count
|
||||
|
|
@ -135,8 +136,9 @@ fn fixtures_render_to_golden_locked_svg_and_snapshot() {
|
|||
"{fixture}: every glyph must be drawn (path or fallback), none dropped"
|
||||
);
|
||||
assert_eq!(
|
||||
out.stats.provenance_count, out.stats.glyph_count,
|
||||
"{fixture}: every drawn glyph must carry a provenance trace"
|
||||
out.stats.provenance_count,
|
||||
out.stats.glyph_count + out.stats.stroke_count,
|
||||
"{fixture}: every drawn glyph and stroke must carry a provenance trace"
|
||||
);
|
||||
let class_sum: usize = out.stats.class_counts.values().sum();
|
||||
assert_eq!(
|
||||
|
|
@ -249,3 +251,45 @@ fn svg_validates_under_xmllint_when_available() {
|
|||
}
|
||||
let _ = std::fs::remove_file(&tmp);
|
||||
}
|
||||
|
||||
/// The renderer's contract is "consumes *any* solver's `ResolvedLayoutIR`", but
|
||||
/// the goldens above only exercise the stub. This drives Agent I's real
|
||||
/// `Engraver` — whose horizontal-spacing pass produces geometry that *differs*
|
||||
/// from the stub's verbatim columns — through the same renderer and asserts the
|
||||
/// output is still well-formed with every glyph drawn and traced, so an
|
||||
/// `Engraver` geometry regression cannot slip through unnoticed (the stub
|
||||
/// goldens would not catch it).
|
||||
#[test]
|
||||
fn engraver_output_renders_well_formed_with_every_glyph_drawn() {
|
||||
use epiphany_engrave::Engraver;
|
||||
use epiphany_layout_ir::SolveStatus;
|
||||
|
||||
for (fixture, score) in fixtures() {
|
||||
let constrained = to_constrained(&to_logical(&score));
|
||||
let report = Engraver.solve(&constrained, &SolverConfig::default());
|
||||
assert_eq!(
|
||||
report.status,
|
||||
SolveStatus::Solved,
|
||||
"{fixture}: engrave should solve the constraint-free stub pipeline"
|
||||
);
|
||||
let out = render(&report.layout, &RenderOptions::default());
|
||||
|
||||
check_well_formed(&out.svg)
|
||||
.unwrap_or_else(|e| panic!("{fixture}: engraver SVG is not well-formed: {e}"));
|
||||
assert!(out.stats.glyph_count > 0, "{fixture}: nothing was laid out");
|
||||
assert_eq!(
|
||||
out.stats.path_count + out.stats.fallback_rect_count,
|
||||
out.stats.glyph_count,
|
||||
"{fixture}: every engraver glyph must be drawn (path or fallback), none dropped"
|
||||
);
|
||||
assert_eq!(
|
||||
out.stats.provenance_count,
|
||||
out.stats.glyph_count + out.stats.stroke_count,
|
||||
"{fixture}: every drawn glyph and stroke must carry a provenance trace"
|
||||
);
|
||||
assert!(
|
||||
out.diagnostics.is_empty(),
|
||||
"{fixture}: stub-pipeline glyphs are all bundled, so no fallback is expected"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
fixture=ten_measure_single_staff solver=stub
|
||||
glyph_count=98
|
||||
path_count=98
|
||||
glyph_count=51
|
||||
path_count=51
|
||||
fallback_rect_count=0
|
||||
provenance_count=98
|
||||
stroke_count=51
|
||||
provenance_count=102
|
||||
layer_count=1
|
||||
hard_constraint_count=0
|
||||
xml_well_formed=true
|
||||
view_box=[-2 -5.2408 102.18 11.6328]
|
||||
view_box=[-3.065 -3.632 88.87701 11.632]
|
||||
class_counts:
|
||||
accidental=10
|
||||
clef=2
|
||||
flag=1
|
||||
notehead=83
|
||||
rest=2
|
||||
barline=10
|
||||
clef=1
|
||||
notehead=40
|
||||
|
|
|
|||
|
|
@ -1,106 +1,110 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1021.8" height="116.328" viewBox="0 0 102.18 11.6328">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="888.7701" height="116.32" viewBox="0 0 88.877 11.632">
|
||||
<!-- epiphany-render-svg: glyphs are genuine Bravura SMuFL outlines; geometry is the resolved layout verbatim, no engraving performed here -->
|
||||
<g transform="translate(2 6.392) scale(1 -1)">
|
||||
<g transform="translate(3.065 8) scale(1 -1)">
|
||||
<g data-layer="0">
|
||||
<path d="M0.92 1.928C0.92 1.984 0.892 2.012 0.836 2.012H0.832C0.776 2.012 0.748 1.984 0.748 1.928V-1.928C0.748 -1.984 0.776 -2.012 0.832 -2.012H0.836C0.892 -2.012 0.92 -1.984 0.92 -1.928V-0.176C0.92 -0.144 0.94 -0.148 0.956 -0.152C1.06 -0.18 1.228 -0.284 1.312 -0.736C1.324 -0.8 1.348 -0.836 1.388 -0.836C1.432 -0.836 1.452 -0.796 1.472 -0.728C1.524 -0.552 1.616 -0.356 1.9 -0.356C2.16 -0.356 2.232 -0.612 2.232 -1.136C2.232 -1.66 2.14 -1.896 1.808 -1.896C1.752 -1.896 1.468 -1.872 1.468 -1.788C1.468 -1.768 1.532 -1.744 1.576 -1.728C1.656 -1.7 1.736 -1.62 1.736 -1.468C1.736 -1.292 1.62 -1.192 1.464 -1.192C1.292 -1.192 1.156 -1.308 1.156 -1.52C1.156 -1.772 1.376 -2.024 1.852 -2.024C2.508 -2.024 2.796 -1.564 2.796 -1.148C2.796 -0.596 2.492 -0.212 1.96 -0.212C1.844 -0.212 1.768 -0.232 1.716 -0.248C1.676 -0.26 1.636 -0.268 1.6 -0.244C1.544 -0.208 1.456 -0.08 1.456 0C1.456 0.08 1.544 0.208 1.6 0.244C1.636 0.268 1.676 0.26 1.716 0.248C1.768 0.232 1.844 0.212 1.96 0.212C2.492 0.212 2.796 0.596 2.796 1.148C2.796 1.564 2.508 2.024 1.852 2.024C1.376 2.024 1.156 1.772 1.156 1.52C1.156 1.308 1.292 1.192 1.464 1.192C1.62 1.192 1.736 1.292 1.736 1.468C1.736 1.62 1.656 1.7 1.576 1.728C1.532 1.744 1.468 1.768 1.468 1.788C1.468 1.872 1.752 1.896 1.808 1.896C2.14 1.896 2.232 1.66 2.232 1.136C2.232 0.612 2.16 0.356 1.9 0.356C1.616 0.356 1.524 0.552 1.472 0.728C1.452 0.796 1.432 0.836 1.388 0.836C1.348 0.836 1.324 0.8 1.312 0.736C1.228 0.284 1.06 0.18 0.956 0.152C0.94 0.148 0.92 0.144 0.92 0.176ZM0.084 2.012C0.028 2.012 0 1.984 0 1.928V-1.928C0 -1.984 0.028 -2.012 0.084 -2.012H0.428C0.484 -2.012 0.512 -1.984 0.512 -1.928V1.928C0.512 1.984 0.484 2.012 0.428 2.012Z" transform="translate(0 0)" fill="#000000" data-prov="2ef8d2dba955aee38147023b72bdabb7" data-source-kind="6" data-glyph="cClef" data-class="clef"/>
|
||||
<path d="M0.036 0.62C0.012 0.62 0 0.604 0 0.588V-0.592C0 -0.608 0.012 -0.62 0.036 -0.62H0.088C0.108 -0.62 0.124 -0.608 0.124 -0.592V0.588C0.124 0.604 0.108 0.62 0.088 0.62ZM0.268 0.62C0.244 0.62 0.228 0.604 0.228 0.588V-0.592C0.228 -0.608 0.244 -0.62 0.268 -0.62H0.316C0.336 -0.62 0.356 -0.608 0.356 -0.592V0.588C0.356 0.604 0.336 0.62 0.316 0.62ZM2.076 0.62C2.056 0.62 2.036 0.604 2.036 0.588V-0.592C2.036 -0.608 2.056 -0.62 2.076 -0.62H2.128C2.148 -0.62 2.168 -0.608 2.168 -0.592V0.588C2.168 0.604 2.148 0.62 2.128 0.62ZM2.304 0.62C2.288 0.62 2.264 0.604 2.264 0.588V-0.592C2.264 -0.608 2.288 -0.62 2.304 -0.62H2.356C2.38 -0.62 2.396 -0.608 2.396 -0.592V0.588C2.396 0.604 2.38 0.62 2.356 0.62ZM1.216 0.5C0.688 0.5 0.36 0.28 0.36 0.004C0.36 -0.256 0.584 -0.5 1.176 -0.5C1.824 -0.5 2.036 -0.272 2.036 0.004C2.036 0.288 1.588 0.5 1.216 0.5ZM1.304 -0.404C1.136 -0.404 1.024 -0.312 0.92 -0.196C0.848 -0.104 0.788 0.032 0.788 0.16C0.788 0.364 0.924 0.408 1.112 0.408C1.384 0.408 1.604 0.116 1.604 -0.124C1.604 -0.32 1.48 -0.404 1.304 -0.404Z" transform="translate(1 0)" fill="#000000" data-prov="a2b28d85f329dbc90f5b6c27789b129b" data-source-kind="3" data-glyph="noteheadDoubleWhole" 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(2 0)" fill="#000000" data-prov="7979bb59896a39443f4602af4f2ee4fb" data-source-kind="4" data-glyph="gClef" data-class="clef"/>
|
||||
<path d="M0.864 0.5C0.332 0.5 0 0.28 0 0.008C0 -0.26 0.228 -0.5 0.824 -0.5C1.48 -0.5 1.688 -0.272 1.688 0.008C1.688 0.292 1.236 0.5 0.864 0.5ZM0.444 0.252C0.488 0.392 0.636 0.412 0.76 0.412C1.036 0.412 1.256 0.116 1.256 -0.124C1.256 -0.248 1.204 -0.36 1.072 -0.392C1.032 -0.404 0.988 -0.408 0.948 -0.408C0.804 -0.408 0.656 -0.312 0.572 -0.2C0.492 -0.108 0.432 0.028 0.432 0.156C0.432 0.188 0.436 0.22 0.444 0.252Z" transform="translate(3 0)" fill="#000000" data-prov="299bf2a93f24f45bbad8cd9368623750" data-source-kind="2" data-glyph="noteheadWhole" 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(4 0)" fill="#000000" data-prov="f180ab2f8fefa5fd114a522262781511" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(5 0)" fill="#000000" data-prov="b94bca553a7a2dbe815bef6d8b770dc8" data-source-kind="1" data-glyph="noteheadHalf" 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(6 0)" fill="#000000" data-prov="00cff43b791d393f0132b2b1fefa97fb" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(7 0)" fill="#000000" data-prov="a3820f9b52ab61a5c70d077f37b1dfbe" data-source-kind="1" data-glyph="noteheadHalf" 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(8 0)" fill="#000000" data-prov="ce8b8ed3f423f2233ee0349407382e86" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(9 0)" fill="#000000" data-prov="893ce7ac8d0b518dd85d53aae07485a6" data-source-kind="1" data-glyph="noteheadHalf" 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(10 0)" fill="#000000" data-prov="3454bf92ac286b4fa89f0711eb8406ab" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(11 0)" fill="#000000" data-prov="e0a27906823095e45f5f8acb6e5784ab" data-source-kind="1" data-glyph="noteheadHalf" 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(12 0)" fill="#000000" data-prov="78705817c33095fd68585149efa39693" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(13 0)" fill="#000000" data-prov="111d36d0544df9583dc71243a02cbace" data-source-kind="1" data-glyph="noteheadHalf" 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 0)" fill="#000000" data-prov="e7010481978598b50f2d16247c9e536f" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(15 0)" fill="#000000" data-prov="5abc05432af9047dd37a867f5508d366" data-source-kind="1" data-glyph="noteheadHalf" 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 0)" fill="#000000" data-prov="9d2e5b21bd83575f4a739897b23903f8" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(17 0)" fill="#000000" data-prov="cb42d5aafb0af3c41ab468790c214695" data-source-kind="1" data-glyph="noteheadHalf" 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 0)" fill="#000000" data-prov="4ae7a4d1adce3e02f9617f49301b790e" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(19 0)" fill="#000000" data-prov="463525d1acbd9e2cfeb6bf72b8a3bb2a" data-source-kind="1" data-glyph="noteheadHalf" 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(20 0)" fill="#000000" data-prov="8d1e19dc98ef35c36a61bf40c10e078a" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(21 0)" fill="#000000" data-prov="a673ee8395ee4b4d9652eebb4ded695b" data-source-kind="1" data-glyph="noteheadHalf" 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(22 0)" fill="#000000" data-prov="722042f4225d40465081128a28b9ba6d" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(23 0)" fill="#000000" data-prov="963cd4bce2dec2fc217d92c9691cfa10" data-source-kind="1" data-glyph="noteheadHalf" 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(24 0)" fill="#000000" data-prov="7979e5ca0cc41e36adf997ca73e3f11f" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(25 0)" fill="#000000" data-prov="3bff036aa7f994b09edf4cdea0bbd625" data-source-kind="1" data-glyph="noteheadHalf" 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(26 0)" fill="#000000" data-prov="6ea8506bc1beb9e26dd4f0a962b47e5c" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(27 0)" fill="#000000" data-prov="d63ffa0c7268bf23fcfeac0a1afde7f4" data-source-kind="1" data-glyph="noteheadHalf" 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(28 0)" fill="#000000" data-prov="3381c124bd495652c03209c9137ad1d6" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(29 0)" fill="#000000" data-prov="184c7ae4b9e86ee27d0bd85ff4b3ec7f" data-source-kind="1" data-glyph="noteheadHalf" 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(30 0)" fill="#000000" data-prov="c6bd0131d5d8b7f7084ce352fe03b9ee" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(31 0)" fill="#000000" data-prov="fb85357d0a96b2eb7a677d6edc58977e" data-source-kind="1" data-glyph="noteheadHalf" 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(32 0)" fill="#000000" data-prov="d1444672c538461a35616863866dd529" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(33 0)" fill="#000000" data-prov="c5bb48e08cef8b001fa7953f02590464" data-source-kind="1" data-glyph="noteheadHalf" 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(34 0)" fill="#000000" data-prov="36be7aa63cfa919f189daf8907b33414" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(35 0)" fill="#000000" data-prov="4f5cb48ae83cce69bc9907b49f2c55d8" data-source-kind="1" data-glyph="noteheadHalf" 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(36 0)" fill="#000000" data-prov="a9abf87e1178566e992ad27a605625db" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(37 0)" fill="#000000" data-prov="0cb38d85c0821f1a91f93d13cba6edc8" data-source-kind="1" data-glyph="noteheadHalf" 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(38 0)" fill="#000000" data-prov="663911388f594e9faf492ca1bef4424c" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(39 0)" fill="#000000" data-prov="4a111579a942ea99be4094d257b9229c" data-source-kind="1" data-glyph="noteheadHalf" 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(40 0)" fill="#000000" data-prov="e13d5950ccece94e0e78721af7a9b7ff" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(41 0)" fill="#000000" data-prov="edfdfb91c37ae76bfa51efd66c0f310a" data-source-kind="1" data-glyph="noteheadHalf" 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(42 0)" fill="#000000" data-prov="2d4b55ae05bb8a68647b9f53172d15f3" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(43 0)" fill="#000000" data-prov="6361d185baa8ed77f350287e10ed2176" data-source-kind="1" data-glyph="noteheadHalf" 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(44 0)" fill="#000000" data-prov="fabd84e3712a5226d704896a4711856d" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(45 0)" fill="#000000" data-prov="c2caa10ac0cf05f631b91d334ef24493" data-source-kind="1" data-glyph="noteheadHalf" 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(46 0)" fill="#000000" data-prov="254b610b6e96d8977e752e1e329c242c" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(47 0)" fill="#000000" data-prov="c16110f407e5c8ac507641d79d932fe6" data-source-kind="1" data-glyph="noteheadHalf" 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(48 0)" fill="#000000" data-prov="45aaeb27e4587d964fe054d9da91a55d" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(49 0)" fill="#000000" data-prov="cf10c843c33809009e9333526dd0879f" data-source-kind="1" data-glyph="noteheadHalf" 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(50 0)" fill="#000000" data-prov="9db9470ac3ce354bf774ef612c676c1f" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(51 0)" fill="#000000" data-prov="2aecf8993db2e9bf833e391d8824cac9" data-source-kind="1" data-glyph="noteheadHalf" 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(52 0)" fill="#000000" data-prov="b934ef5e3771b425d5e646d1ec905ba7" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(53 0)" fill="#000000" data-prov="b523a1a70a99069c4258e9a7f62744b8" data-source-kind="1" data-glyph="noteheadHalf" 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(54 0)" fill="#000000" data-prov="9ee165a1b754cf15a94af8ce1a2ed74a" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(55 0)" fill="#000000" data-prov="e970cb6a1cac78405c15ceaf6627f370" data-source-kind="1" data-glyph="noteheadHalf" 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(56 0)" fill="#000000" data-prov="e7c3cefbeaf0c7b182f61938f50b84b7" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(57 0)" fill="#000000" data-prov="e3eb76921ae215aa98331a9d1ad26804" data-source-kind="1" data-glyph="noteheadHalf" 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(58 0)" fill="#000000" data-prov="43269b2ba973f7d04f787e51fa30d2fe" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(59 0)" fill="#000000" data-prov="0fba3a760b42655a82fb25596d29fd02" data-source-kind="1" data-glyph="noteheadHalf" 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(60 0)" fill="#000000" data-prov="dbb189030d3fb922021f944ac8aa8c6d" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(61 0)" fill="#000000" data-prov="66fa0c6a9dfef2806751936f0360c782" data-source-kind="1" data-glyph="noteheadHalf" 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(62 0)" fill="#000000" data-prov="65ff34e6e9f8e9b1bbf795ffe3eaa2e6" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(63 0)" fill="#000000" data-prov="b546aac49da2b4624d759aed4527fe1d" data-source-kind="1" data-glyph="noteheadHalf" 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(64 0)" fill="#000000" data-prov="b00773cbb8db6a4218f1f956ebce9e25" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(65 0)" fill="#000000" data-prov="0275a564c27b5031f71cce54b0a8f830" data-source-kind="1" data-glyph="noteheadHalf" 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(66 0)" fill="#000000" data-prov="2d8c211fa0abbeb102ecafd986ec9f32" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(67 0)" fill="#000000" data-prov="a5b5cfea1a313032df813df1dcb06bc8" data-source-kind="1" data-glyph="noteheadHalf" 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(68 0)" fill="#000000" data-prov="b28b480792b411f3928a16c150a2a231" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(69 0)" fill="#000000" data-prov="6c3f374430ab9118c68bf368ccf35357" data-source-kind="1" data-glyph="noteheadHalf" 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(70 0)" fill="#000000" data-prov="67a80c74cc078028d23262082a6467af" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(71 0)" fill="#000000" data-prov="e95f370ea65c0f5c664b994bce3040ad" data-source-kind="1" data-glyph="noteheadHalf" 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(72 0)" fill="#000000" data-prov="c4f5b4a9375f3dcc6dd566865df0c92f" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(73 0)" fill="#000000" data-prov="c27abce6ad5e01170068a491b9424cbb" data-source-kind="1" data-glyph="noteheadHalf" 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(74 0)" fill="#000000" data-prov="ba2739c661b9acb3d32494f0f1adae98" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(75 0)" fill="#000000" data-prov="682d690f334af9f63b1a71a7d6cb10d8" data-source-kind="1" data-glyph="noteheadHalf" 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(76 0)" fill="#000000" data-prov="baea3329fdb0f32274fe3b9985ffe572" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(77 0)" fill="#000000" data-prov="b6f35f534dabfbb2ff75515cdd1713c5" data-source-kind="1" data-glyph="noteheadHalf" 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(78 0)" fill="#000000" data-prov="4dbd0d2bd320be9b9f333e3f8f4dad92" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(79 0)" fill="#000000" data-prov="5bc064cf9ed41d380fddb186477aeb78" data-source-kind="1" data-glyph="noteheadHalf" 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(80 0)" fill="#000000" data-prov="21e0c4e7089e613815b7a5d6bf2c103f" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(81 0)" fill="#000000" data-prov="8e06e0b817d3359c9e251afed6d58cee" data-source-kind="1" data-glyph="noteheadHalf" 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(82 0)" fill="#000000" data-prov="d9c5b62c4340d6bd9038a1a8ff081b3a" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(83 0)" fill="#000000" data-prov="86405f19f3e47252c21d628ae33e94c2" data-source-kind="1" data-glyph="noteheadHalf" data-class="notehead"/>
|
||||
<path d="M0.564 0.724C0.556 0.724 0.552 0.72 0.548 0.72C0.548 0.72 0.292 0.628 0.188 0.628C0.164 0.628 0.148 0.632 0.148 0.648V1.316C0.148 1.344 0.124 1.364 0.1 1.364H0.048C0.02 1.364 0 1.344 0 1.316V-0.744C0 -0.768 0.012 -0.78 0.036 -0.78L0.044 -0.776C0.048 -0.776 0.056 -0.776 0.06 -0.772C0.116 -0.748 0.34 -0.652 0.456 -0.652C0.496 -0.652 0.524 -0.664 0.524 -0.696V-1.292C0.524 -1.32 0.544 -1.34 0.572 -1.34H0.624C0.648 -1.34 0.672 -1.32 0.672 -1.292V0.716C0.672 0.736 0.656 0.748 0.64 0.748C0.636 0.748 0.628 0.748 0.624 0.744ZM0.148 0.156C0.148 0.212 0.392 0.316 0.488 0.316C0.512 0.316 0.524 0.312 0.524 0.296V-0.116C0.524 -0.188 0.296 -0.28 0.196 -0.28C0.168 -0.28 0.148 -0.272 0.148 -0.256Z" transform="translate(84 0)" fill="#000000" data-prov="913e510f5e3d36b6ab5a21a0bd531816" data-source-kind="9" data-glyph="accidentalNatural" data-class="accidental"/>
|
||||
<path d="M0.564 0.724C0.556 0.724 0.552 0.72 0.548 0.72C0.548 0.72 0.292 0.628 0.188 0.628C0.164 0.628 0.148 0.632 0.148 0.648V1.316C0.148 1.344 0.124 1.364 0.1 1.364H0.048C0.02 1.364 0 1.344 0 1.316V-0.744C0 -0.768 0.012 -0.78 0.036 -0.78L0.044 -0.776C0.048 -0.776 0.056 -0.776 0.06 -0.772C0.116 -0.748 0.34 -0.652 0.456 -0.652C0.496 -0.652 0.524 -0.664 0.524 -0.696V-1.292C0.524 -1.32 0.544 -1.34 0.572 -1.34H0.624C0.648 -1.34 0.672 -1.32 0.672 -1.292V0.716C0.672 0.736 0.656 0.748 0.64 0.748C0.636 0.748 0.628 0.748 0.624 0.744ZM0.148 0.156C0.148 0.212 0.392 0.316 0.488 0.316C0.512 0.316 0.524 0.312 0.524 0.296V-0.116C0.524 -0.188 0.296 -0.28 0.196 -0.28C0.168 -0.28 0.148 -0.272 0.148 -0.256Z" transform="translate(85 0)" fill="#000000" data-prov="e8f4fdf6d757a83af167a7d7b0d268b9" data-source-kind="9" data-glyph="accidentalNatural" data-class="accidental"/>
|
||||
<path d="M0.564 0.724C0.556 0.724 0.552 0.72 0.548 0.72C0.548 0.72 0.292 0.628 0.188 0.628C0.164 0.628 0.148 0.632 0.148 0.648V1.316C0.148 1.344 0.124 1.364 0.1 1.364H0.048C0.02 1.364 0 1.344 0 1.316V-0.744C0 -0.768 0.012 -0.78 0.036 -0.78L0.044 -0.776C0.048 -0.776 0.056 -0.776 0.06 -0.772C0.116 -0.748 0.34 -0.652 0.456 -0.652C0.496 -0.652 0.524 -0.664 0.524 -0.696V-1.292C0.524 -1.32 0.544 -1.34 0.572 -1.34H0.624C0.648 -1.34 0.672 -1.32 0.672 -1.292V0.716C0.672 0.736 0.656 0.748 0.64 0.748C0.636 0.748 0.628 0.748 0.624 0.744ZM0.148 0.156C0.148 0.212 0.392 0.316 0.488 0.316C0.512 0.316 0.524 0.312 0.524 0.296V-0.116C0.524 -0.188 0.296 -0.28 0.196 -0.28C0.168 -0.28 0.148 -0.272 0.148 -0.256Z" transform="translate(86 0)" fill="#000000" data-prov="aa6e5be72b5517b089a04363f42a1d1c" data-source-kind="9" data-glyph="accidentalNatural" data-class="accidental"/>
|
||||
<path d="M0.564 0.724C0.556 0.724 0.552 0.72 0.548 0.72C0.548 0.72 0.292 0.628 0.188 0.628C0.164 0.628 0.148 0.632 0.148 0.648V1.316C0.148 1.344 0.124 1.364 0.1 1.364H0.048C0.02 1.364 0 1.344 0 1.316V-0.744C0 -0.768 0.012 -0.78 0.036 -0.78L0.044 -0.776C0.048 -0.776 0.056 -0.776 0.06 -0.772C0.116 -0.748 0.34 -0.652 0.456 -0.652C0.496 -0.652 0.524 -0.664 0.524 -0.696V-1.292C0.524 -1.32 0.544 -1.34 0.572 -1.34H0.624C0.648 -1.34 0.672 -1.32 0.672 -1.292V0.716C0.672 0.736 0.656 0.748 0.64 0.748C0.636 0.748 0.628 0.748 0.624 0.744ZM0.148 0.156C0.148 0.212 0.392 0.316 0.488 0.316C0.512 0.316 0.524 0.312 0.524 0.296V-0.116C0.524 -0.188 0.296 -0.28 0.196 -0.28C0.168 -0.28 0.148 -0.272 0.148 -0.256Z" transform="translate(87 0)" fill="#000000" data-prov="ee1eea474c5d0d997d284a2610206922" data-source-kind="9" data-glyph="accidentalNatural" data-class="accidental"/>
|
||||
<path d="M0.564 0.724C0.556 0.724 0.552 0.72 0.548 0.72C0.548 0.72 0.292 0.628 0.188 0.628C0.164 0.628 0.148 0.632 0.148 0.648V1.316C0.148 1.344 0.124 1.364 0.1 1.364H0.048C0.02 1.364 0 1.344 0 1.316V-0.744C0 -0.768 0.012 -0.78 0.036 -0.78L0.044 -0.776C0.048 -0.776 0.056 -0.776 0.06 -0.772C0.116 -0.748 0.34 -0.652 0.456 -0.652C0.496 -0.652 0.524 -0.664 0.524 -0.696V-1.292C0.524 -1.32 0.544 -1.34 0.572 -1.34H0.624C0.648 -1.34 0.672 -1.32 0.672 -1.292V0.716C0.672 0.736 0.656 0.748 0.64 0.748C0.636 0.748 0.628 0.748 0.624 0.744ZM0.148 0.156C0.148 0.212 0.392 0.316 0.488 0.316C0.512 0.316 0.524 0.312 0.524 0.296V-0.116C0.524 -0.188 0.296 -0.28 0.196 -0.28C0.168 -0.28 0.148 -0.272 0.148 -0.256Z" transform="translate(88 0)" fill="#000000" data-prov="a353d60fd103f7a61e55a2b8aa95114e" data-source-kind="9" data-glyph="accidentalNatural" data-class="accidental"/>
|
||||
<path d="M0.564 0.724C0.556 0.724 0.552 0.72 0.548 0.72C0.548 0.72 0.292 0.628 0.188 0.628C0.164 0.628 0.148 0.632 0.148 0.648V1.316C0.148 1.344 0.124 1.364 0.1 1.364H0.048C0.02 1.364 0 1.344 0 1.316V-0.744C0 -0.768 0.012 -0.78 0.036 -0.78L0.044 -0.776C0.048 -0.776 0.056 -0.776 0.06 -0.772C0.116 -0.748 0.34 -0.652 0.456 -0.652C0.496 -0.652 0.524 -0.664 0.524 -0.696V-1.292C0.524 -1.32 0.544 -1.34 0.572 -1.34H0.624C0.648 -1.34 0.672 -1.32 0.672 -1.292V0.716C0.672 0.736 0.656 0.748 0.64 0.748C0.636 0.748 0.628 0.748 0.624 0.744ZM0.148 0.156C0.148 0.212 0.392 0.316 0.488 0.316C0.512 0.316 0.524 0.312 0.524 0.296V-0.116C0.524 -0.188 0.296 -0.28 0.196 -0.28C0.168 -0.28 0.148 -0.272 0.148 -0.256Z" transform="translate(89 0)" fill="#000000" data-prov="55d172d9d863190758f311e8b5c69c9b" data-source-kind="9" data-glyph="accidentalNatural" data-class="accidental"/>
|
||||
<path d="M0.564 0.724C0.556 0.724 0.552 0.72 0.548 0.72C0.548 0.72 0.292 0.628 0.188 0.628C0.164 0.628 0.148 0.632 0.148 0.648V1.316C0.148 1.344 0.124 1.364 0.1 1.364H0.048C0.02 1.364 0 1.344 0 1.316V-0.744C0 -0.768 0.012 -0.78 0.036 -0.78L0.044 -0.776C0.048 -0.776 0.056 -0.776 0.06 -0.772C0.116 -0.748 0.34 -0.652 0.456 -0.652C0.496 -0.652 0.524 -0.664 0.524 -0.696V-1.292C0.524 -1.32 0.544 -1.34 0.572 -1.34H0.624C0.648 -1.34 0.672 -1.32 0.672 -1.292V0.716C0.672 0.736 0.656 0.748 0.64 0.748C0.636 0.748 0.628 0.748 0.624 0.744ZM0.148 0.156C0.148 0.212 0.392 0.316 0.488 0.316C0.512 0.316 0.524 0.312 0.524 0.296V-0.116C0.524 -0.188 0.296 -0.28 0.196 -0.28C0.168 -0.28 0.148 -0.272 0.148 -0.256Z" transform="translate(90 0)" fill="#000000" data-prov="7552f781fca3b72788a57822d143ee69" data-source-kind="9" data-glyph="accidentalNatural" data-class="accidental"/>
|
||||
<path d="M0.564 0.724C0.556 0.724 0.552 0.72 0.548 0.72C0.548 0.72 0.292 0.628 0.188 0.628C0.164 0.628 0.148 0.632 0.148 0.648V1.316C0.148 1.344 0.124 1.364 0.1 1.364H0.048C0.02 1.364 0 1.344 0 1.316V-0.744C0 -0.768 0.012 -0.78 0.036 -0.78L0.044 -0.776C0.048 -0.776 0.056 -0.776 0.06 -0.772C0.116 -0.748 0.34 -0.652 0.456 -0.652C0.496 -0.652 0.524 -0.664 0.524 -0.696V-1.292C0.524 -1.32 0.544 -1.34 0.572 -1.34H0.624C0.648 -1.34 0.672 -1.32 0.672 -1.292V0.716C0.672 0.736 0.656 0.748 0.64 0.748C0.636 0.748 0.628 0.748 0.624 0.744ZM0.148 0.156C0.148 0.212 0.392 0.316 0.488 0.316C0.512 0.316 0.524 0.312 0.524 0.296V-0.116C0.524 -0.188 0.296 -0.28 0.196 -0.28C0.168 -0.28 0.148 -0.272 0.148 -0.256Z" transform="translate(91 0)" fill="#000000" data-prov="4e04c5515de885c96a377123738e0397" data-source-kind="9" data-glyph="accidentalNatural" data-class="accidental"/>
|
||||
<path d="M0.564 0.724C0.556 0.724 0.552 0.72 0.548 0.72C0.548 0.72 0.292 0.628 0.188 0.628C0.164 0.628 0.148 0.632 0.148 0.648V1.316C0.148 1.344 0.124 1.364 0.1 1.364H0.048C0.02 1.364 0 1.344 0 1.316V-0.744C0 -0.768 0.012 -0.78 0.036 -0.78L0.044 -0.776C0.048 -0.776 0.056 -0.776 0.06 -0.772C0.116 -0.748 0.34 -0.652 0.456 -0.652C0.496 -0.652 0.524 -0.664 0.524 -0.696V-1.292C0.524 -1.32 0.544 -1.34 0.572 -1.34H0.624C0.648 -1.34 0.672 -1.32 0.672 -1.292V0.716C0.672 0.736 0.656 0.748 0.64 0.748C0.636 0.748 0.628 0.748 0.624 0.744ZM0.148 0.156C0.148 0.212 0.392 0.316 0.488 0.316C0.512 0.316 0.524 0.312 0.524 0.296V-0.116C0.524 -0.188 0.296 -0.28 0.196 -0.28C0.168 -0.28 0.148 -0.272 0.148 -0.256Z" transform="translate(92 0)" fill="#000000" data-prov="65d223b9dca9579949a3a134b8d06182" data-source-kind="9" data-glyph="accidentalNatural" data-class="accidental"/>
|
||||
<path d="M0.564 0.724C0.556 0.724 0.552 0.72 0.548 0.72C0.548 0.72 0.292 0.628 0.188 0.628C0.164 0.628 0.148 0.632 0.148 0.648V1.316C0.148 1.344 0.124 1.364 0.1 1.364H0.048C0.02 1.364 0 1.344 0 1.316V-0.744C0 -0.768 0.012 -0.78 0.036 -0.78L0.044 -0.776C0.048 -0.776 0.056 -0.776 0.06 -0.772C0.116 -0.748 0.34 -0.652 0.456 -0.652C0.496 -0.652 0.524 -0.664 0.524 -0.696V-1.292C0.524 -1.32 0.544 -1.34 0.572 -1.34H0.624C0.648 -1.34 0.672 -1.32 0.672 -1.292V0.716C0.672 0.736 0.656 0.748 0.64 0.748C0.636 0.748 0.628 0.748 0.624 0.744ZM0.148 0.156C0.148 0.212 0.392 0.316 0.488 0.316C0.512 0.316 0.524 0.312 0.524 0.296V-0.116C0.524 -0.188 0.296 -0.28 0.196 -0.28C0.168 -0.28 0.148 -0.272 0.148 -0.256Z" transform="translate(93 0)" fill="#000000" data-prov="8f49273e29ce144556b73f0e914cf377" data-source-kind="9" data-glyph="accidentalNatural" data-class="accidental"/>
|
||||
<path d="M1.128 0.096V0.464C1.128 0.524 1.08 0.568 1.024 0.568H0.104C0.044 0.568 0 0.524 0 0.464V0.096C0 0.04 0.044 -0.008 0.104 -0.008H1.024C1.08 -0.008 1.128 0.04 1.128 0.096Z" transform="translate(94 0)" fill="#000000" data-prov="ad675e7f174a140139e2959e3ebdc8ad" data-source-kind="12" data-glyph="restHalf" data-class="rest"/>
|
||||
<path d="M0.536 0.428C0.536 0.576 0.416 0.696 0.268 0.696C0.12 0.696 0 0.576 0 0.428C0 0.344 0.048 0.272 0.108 0.224C0.172 0.18 0.248 0.156 0.324 0.156C0.38 0.156 0.436 0.168 0.48 0.184C0.536 0.2 0.572 0.216 0.624 0.244C0.632 0.248 0.64 0.248 0.644 0.248C0.66 0.248 0.664 0.232 0.664 0.212C0.664 0.2 0.664 0.184 0.66 0.168C0.648 0.108 0.36 -0.688 0.288 -0.952C0.288 -1 0.38 -1.004 0.404 -1.004C0.448 -1.004 0.504 -0.996 0.544 -0.964C0.556 -0.956 0.948 0.448 0.948 0.448C0.964 0.52 0.984 0.584 0.988 0.604C0.988 0.644 0.948 0.664 0.94 0.668C0.932 0.668 0.92 0.668 0.896 0.652C0.868 0.628 0.668 0.388 0.536 0.388Z" transform="translate(95 0)" fill="#000000" data-prov="14cf3b9c808d0467e40ee355ef4329a8" data-source-kind="14" data-glyph="rest8th" data-class="rest"/>
|
||||
<path d="M0.952 -3.16C0.952 -3.16 1.056 -2.78 1.056 -2.468C1.056 -1.968 0.848 -1.496 0.596 -1.096C0.392 -0.78 0.224 -0.436 0.16 -0.052C0.148 0.012 0.116 0.036 0.076 0.036C0.032 0.036 0 0.024 0 -0.024V-0.98C0.264 -1.028 0.644 -1.572 0.788 -1.912C0.848 -2.048 0.884 -2.276 0.884 -2.512C0.884 -2.692 0.856 -2.88 0.788 -3.06C0.78 -3.084 0.776 -3.104 0.776 -3.12C0.776 -3.184 0.816 -3.22 0.84 -3.236C0.864 -3.252 0.932 -3.228 0.952 -3.16Z" transform="translate(96 0)" fill="#000000" data-prov="82ca9c2a2208e28338167ec9151748d2" data-source-kind="15" data-glyph="flag8thUp" data-class="flag"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(97 0)" fill="#000000" data-prov="33b51b69dbfc551be0448fbb257675b2" data-source-kind="25" data-glyph="noteheadHalf" data-class="notehead"/>
|
||||
<line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="2ef8d2dba955aee38147023b72bdabb7" data-source-kind="6" data-kind="stroke"/>
|
||||
<line x1="-1" y1="0" x2="83.4" y2="0" stroke="#000000" stroke-width="0.13" data-prov="a2b28d85f329dbc90f5b6c27789b129b" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="-1" y1="1" x2="83.4" y2="1" stroke="#000000" stroke-width="0.13" data-prov="8cc8596297e2f64822b7d596f27b928a" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="-1" y1="2" x2="83.4" y2="2" stroke="#000000" stroke-width="0.13" data-prov="c802edfce37280f9b7a5280850fefb1f" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="-1" y1="3" x2="83.4" y2="3" stroke="#000000" stroke-width="0.13" data-prov="374ec294c78d169fdd371dafcc590a90" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="-1" y1="4" x2="83.4" y2="4" stroke="#000000" stroke-width="0.13" data-prov="1ba95e41d7bee35526357d6d68d7d885" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="299bf2a93f24f45bbad8cd9368623750" data-source-kind="2" data-kind="stroke"/>
|
||||
<line x1="5.75" y1="-1" x2="5.75" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="f180ab2f8fefa5fd114a522262781511" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="7.35" y1="-1" x2="7.35" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="00cff43b791d393f0132b2b1fefa97fb" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="8.95" y1="-1" x2="8.95" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="ce8b8ed3f423f2233ee0349407382e86" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="10.55" y1="-1" x2="10.55" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="3454bf92ac286b4fa89f0711eb8406ab" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="13.75" y1="-1" x2="13.75" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="78705817c33095fd68585149efa39693" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="15.35" y1="-1" x2="15.35" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="e7010481978598b50f2d16247c9e536f" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="16.95" y1="-1" x2="16.95" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="9d2e5b21bd83575f4a739897b23903f8" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="18.55" y1="-1" x2="18.55" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="4ae7a4d1adce3e02f9617f49301b790e" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="21.75" y1="-1" x2="21.75" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="8d1e19dc98ef35c36a61bf40c10e078a" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="23.35" y1="-1" x2="23.35" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="722042f4225d40465081128a28b9ba6d" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="24.95" y1="-1" x2="24.95" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="7979e5ca0cc41e36adf997ca73e3f11f" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="26.55" y1="-1" x2="26.55" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="6ea8506bc1beb9e26dd4f0a962b47e5c" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="29.75" y1="-1" x2="29.75" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="3381c124bd495652c03209c9137ad1d6" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="31.35" y1="-1" x2="31.35" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="c6bd0131d5d8b7f7084ce352fe03b9ee" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="32.95" y1="-1" x2="32.95" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="d1444672c538461a35616863866dd529" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="34.55" y1="-1" x2="34.55" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="36be7aa63cfa919f189daf8907b33414" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="37.75" y1="-1" x2="37.75" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="a9abf87e1178566e992ad27a605625db" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="39.35" y1="-1" x2="39.35" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="663911388f594e9faf492ca1bef4424c" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="40.95" y1="-1" x2="40.95" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="e13d5950ccece94e0e78721af7a9b7ff" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="42.55" y1="-1" x2="42.55" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="2d4b55ae05bb8a68647b9f53172d15f3" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="45.75" y1="-1" x2="45.75" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="fabd84e3712a5226d704896a4711856d" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="47.35" y1="-1" x2="47.35" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="254b610b6e96d8977e752e1e329c242c" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="48.95" y1="-1" x2="48.95" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="45aaeb27e4587d964fe054d9da91a55d" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="50.55" y1="-1" x2="50.55" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="9db9470ac3ce354bf774ef612c676c1f" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="53.75" y1="-1" x2="53.75" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="b934ef5e3771b425d5e646d1ec905ba7" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="55.35" y1="-1" x2="55.35" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="9ee165a1b754cf15a94af8ce1a2ed74a" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="56.95" y1="-1" x2="56.95" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="e7c3cefbeaf0c7b182f61938f50b84b7" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="58.55" y1="-1" x2="58.55" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="43269b2ba973f7d04f787e51fa30d2fe" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="61.75" y1="-1" x2="61.75" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="dbb189030d3fb922021f944ac8aa8c6d" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="63.35" y1="-1" x2="63.35" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="65ff34e6e9f8e9b1bbf795ffe3eaa2e6" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="64.95" y1="-1" x2="64.95" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="b00773cbb8db6a4218f1f956ebce9e25" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="66.55" y1="-1" x2="66.55" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="2d8c211fa0abbeb102ecafd986ec9f32" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="69.75" y1="-1" x2="69.75" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="b28b480792b411f3928a16c150a2a231" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="71.35" y1="-1" x2="71.35" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="67a80c74cc078028d23262082a6467af" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="72.95" y1="-1" x2="72.95" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="c4f5b4a9375f3dcc6dd566865df0c92f" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="74.55" y1="-1" x2="74.55" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="ba2739c661b9acb3d32494f0f1adae98" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="76.15" y1="-1" x2="76.15" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="baea3329fdb0f32274fe3b9985ffe572" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="77.75" y1="-1" x2="77.75" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="4dbd0d2bd320be9b9f333e3f8f4dad92" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="79.35" y1="-1" x2="79.35" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="21e0c4e7089e613815b7a5d6bf2c103f" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="80.95" y1="-1" x2="80.95" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="d9c5b62c4340d6bd9038a1a8ff081b3a" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="ad675e7f174a140139e2959e3ebdc8ad" data-source-kind="12" data-kind="stroke"/>
|
||||
<line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="14cf3b9c808d0467e40ee355ef4329a8" data-source-kind="14" data-kind="stroke"/>
|
||||
<line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="82ca9c2a2208e28338167ec9151748d2" data-source-kind="15" data-kind="stroke"/>
|
||||
<line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="33b51b69dbfc551be0448fbb257675b2" data-source-kind="25" data-kind="stroke"/>
|
||||
<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(0 1)" fill="#000000" data-prov="7979bb59896a39443f4602af4f2ee4fb" 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(4.6 -1)" fill="#000000" data-prov="b94bca553a7a2dbe815bef6d8b770dc8" 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(6.2 -1)" fill="#000000" data-prov="a3820f9b52ab61a5c70d077f37b1dfbe" 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(7.8 -1)" fill="#000000" data-prov="893ce7ac8d0b518dd85d53aae07485a6" 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(9.4 -1)" fill="#000000" data-prov="e0a27906823095e45f5f8acb6e5784ab" 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(12.6 -1)" fill="#000000" data-prov="111d36d0544df9583dc71243a02cbace" 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.2 -1)" fill="#000000" data-prov="5abc05432af9047dd37a867f5508d366" 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(15.8 -1)" fill="#000000" data-prov="cb42d5aafb0af3c41ab468790c214695" 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(17.4 -1)" fill="#000000" data-prov="463525d1acbd9e2cfeb6bf72b8a3bb2a" 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(20.6 -1)" fill="#000000" data-prov="a673ee8395ee4b4d9652eebb4ded695b" 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(22.2 -1)" fill="#000000" data-prov="963cd4bce2dec2fc217d92c9691cfa10" 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(23.8 -1)" fill="#000000" data-prov="3bff036aa7f994b09edf4cdea0bbd625" 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(25.4 -1)" fill="#000000" data-prov="d63ffa0c7268bf23fcfeac0a1afde7f4" 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(28.6 -1)" fill="#000000" data-prov="184c7ae4b9e86ee27d0bd85ff4b3ec7f" 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(30.2 -1)" fill="#000000" data-prov="fb85357d0a96b2eb7a677d6edc58977e" 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(31.8 -1)" fill="#000000" data-prov="c5bb48e08cef8b001fa7953f02590464" 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(33.4 -1)" fill="#000000" data-prov="4f5cb48ae83cce69bc9907b49f2c55d8" 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(36.6 -1)" fill="#000000" data-prov="0cb38d85c0821f1a91f93d13cba6edc8" 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(38.2 -1)" fill="#000000" data-prov="4a111579a942ea99be4094d257b9229c" 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(39.8 -1)" fill="#000000" data-prov="edfdfb91c37ae76bfa51efd66c0f310a" 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(41.4 -1)" fill="#000000" data-prov="6361d185baa8ed77f350287e10ed2176" 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(44.6 -1)" fill="#000000" data-prov="c2caa10ac0cf05f631b91d334ef24493" 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(46.2 -1)" fill="#000000" data-prov="c16110f407e5c8ac507641d79d932fe6" 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(47.8 -1)" fill="#000000" data-prov="cf10c843c33809009e9333526dd0879f" 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(49.4 -1)" fill="#000000" data-prov="2aecf8993db2e9bf833e391d8824cac9" 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(52.6 -1)" fill="#000000" data-prov="b523a1a70a99069c4258e9a7f62744b8" 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(54.2 -1)" fill="#000000" data-prov="e970cb6a1cac78405c15ceaf6627f370" 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(55.8 -1)" fill="#000000" data-prov="e3eb76921ae215aa98331a9d1ad26804" 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(57.4 -1)" fill="#000000" data-prov="0fba3a760b42655a82fb25596d29fd02" 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(60.6 -1)" fill="#000000" data-prov="66fa0c6a9dfef2806751936f0360c782" 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(62.2 -1)" fill="#000000" data-prov="b546aac49da2b4624d759aed4527fe1d" 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(63.8 -1)" fill="#000000" data-prov="0275a564c27b5031f71cce54b0a8f830" 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(65.4 -1)" fill="#000000" data-prov="a5b5cfea1a313032df813df1dcb06bc8" 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(68.6 -1)" fill="#000000" data-prov="6c3f374430ab9118c68bf368ccf35357" 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(70.2 -1)" fill="#000000" data-prov="e95f370ea65c0f5c664b994bce3040ad" 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(71.8 -1)" fill="#000000" data-prov="c27abce6ad5e01170068a491b9424cbb" 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(73.4 -1)" fill="#000000" data-prov="682d690f334af9f63b1a71a7d6cb10d8" 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(75 -1)" fill="#000000" data-prov="b6f35f534dabfbb2ff75515cdd1713c5" 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(76.6 -1)" fill="#000000" data-prov="5bc064cf9ed41d380fddb186477aeb78" 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(78.2 -1)" fill="#000000" data-prov="8e06e0b817d3359c9e251afed6d58cee" 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(79.8 -1)" fill="#000000" data-prov="86405f19f3e47252c21d628ae33e94c2" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.144 0V4H0V0Z" transform="translate(3 2)" fill="#000000" data-prov="913e510f5e3d36b6ab5a21a0bd531816" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
|
||||
<path d="M0.144 0V4H0V0Z" transform="translate(11 2)" fill="#000000" data-prov="e8f4fdf6d757a83af167a7d7b0d268b9" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
|
||||
<path d="M0.144 0V4H0V0Z" transform="translate(19 2)" fill="#000000" data-prov="aa6e5be72b5517b089a04363f42a1d1c" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
|
||||
<path d="M0.144 0V4H0V0Z" transform="translate(27 2)" fill="#000000" data-prov="ee1eea474c5d0d997d284a2610206922" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
|
||||
<path d="M0.144 0V4H0V0Z" transform="translate(35 2)" fill="#000000" data-prov="a353d60fd103f7a61e55a2b8aa95114e" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
|
||||
<path d="M0.144 0V4H0V0Z" transform="translate(43 2)" fill="#000000" data-prov="55d172d9d863190758f311e8b5c69c9b" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
|
||||
<path d="M0.144 0V4H0V0Z" transform="translate(51 2)" fill="#000000" data-prov="7552f781fca3b72788a57822d143ee69" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
|
||||
<path d="M0.144 0V4H0V0Z" transform="translate(59 2)" fill="#000000" data-prov="4e04c5515de885c96a377123738e0397" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
|
||||
<path d="M0.144 0V4H0V0Z" transform="translate(67 2)" fill="#000000" data-prov="65d223b9dca9579949a3a134b8d06182" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
|
||||
<path d="M0.144 0V4H0V0ZM0.912 4H0.412V0H0.912Z" transform="translate(82.9 2)" fill="#000000" data-prov="8f49273e29ce144556b73f0e914cf377" data-source-kind="9" data-glyph="barlineFinal" data-class="barline"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 25 KiB |
|
|
@ -1,16 +1,14 @@
|
|||
fixture=valid_score_rich solver=stub
|
||||
glyph_count=32
|
||||
path_count=32
|
||||
glyph_count=11
|
||||
path_count=11
|
||||
fallback_rect_count=0
|
||||
provenance_count=32
|
||||
stroke_count=33
|
||||
provenance_count=44
|
||||
layer_count=1
|
||||
hard_constraint_count=0
|
||||
xml_well_formed=true
|
||||
view_box=[-2 -5.2408 36.18 11.6328]
|
||||
view_box=[-3.065 -3.632 39.329998 11.632]
|
||||
class_counts:
|
||||
accidental=1
|
||||
clef=6
|
||||
dynamic=1
|
||||
flag=1
|
||||
notehead=21
|
||||
rest=2
|
||||
barline=1
|
||||
clef=3
|
||||
notehead=7
|
||||
|
|
|
|||
|
|
@ -1,40 +1,52 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="361.8" height="116.328" viewBox="0 0 36.18 11.6328">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="393.3" height="116.32" viewBox="0 0 39.33 11.632">
|
||||
<!-- epiphany-render-svg: glyphs are genuine Bravura SMuFL outlines; geometry is the resolved layout verbatim, no engraving performed here -->
|
||||
<g transform="translate(2 6.392) scale(1 -1)">
|
||||
<g transform="translate(3.065 8) scale(1 -1)">
|
||||
<g data-layer="0">
|
||||
<path d="M0.92 1.928C0.92 1.984 0.892 2.012 0.836 2.012H0.832C0.776 2.012 0.748 1.984 0.748 1.928V-1.928C0.748 -1.984 0.776 -2.012 0.832 -2.012H0.836C0.892 -2.012 0.92 -1.984 0.92 -1.928V-0.176C0.92 -0.144 0.94 -0.148 0.956 -0.152C1.06 -0.18 1.228 -0.284 1.312 -0.736C1.324 -0.8 1.348 -0.836 1.388 -0.836C1.432 -0.836 1.452 -0.796 1.472 -0.728C1.524 -0.552 1.616 -0.356 1.9 -0.356C2.16 -0.356 2.232 -0.612 2.232 -1.136C2.232 -1.66 2.14 -1.896 1.808 -1.896C1.752 -1.896 1.468 -1.872 1.468 -1.788C1.468 -1.768 1.532 -1.744 1.576 -1.728C1.656 -1.7 1.736 -1.62 1.736 -1.468C1.736 -1.292 1.62 -1.192 1.464 -1.192C1.292 -1.192 1.156 -1.308 1.156 -1.52C1.156 -1.772 1.376 -2.024 1.852 -2.024C2.508 -2.024 2.796 -1.564 2.796 -1.148C2.796 -0.596 2.492 -0.212 1.96 -0.212C1.844 -0.212 1.768 -0.232 1.716 -0.248C1.676 -0.26 1.636 -0.268 1.6 -0.244C1.544 -0.208 1.456 -0.08 1.456 0C1.456 0.08 1.544 0.208 1.6 0.244C1.636 0.268 1.676 0.26 1.716 0.248C1.768 0.232 1.844 0.212 1.96 0.212C2.492 0.212 2.796 0.596 2.796 1.148C2.796 1.564 2.508 2.024 1.852 2.024C1.376 2.024 1.156 1.772 1.156 1.52C1.156 1.308 1.292 1.192 1.464 1.192C1.62 1.192 1.736 1.292 1.736 1.468C1.736 1.62 1.656 1.7 1.576 1.728C1.532 1.744 1.468 1.768 1.468 1.788C1.468 1.872 1.752 1.896 1.808 1.896C2.14 1.896 2.232 1.66 2.232 1.136C2.232 0.612 2.16 0.356 1.9 0.356C1.616 0.356 1.524 0.552 1.472 0.728C1.452 0.796 1.432 0.836 1.388 0.836C1.348 0.836 1.324 0.8 1.312 0.736C1.228 0.284 1.06 0.18 0.956 0.152C0.94 0.148 0.92 0.144 0.92 0.176ZM0.084 2.012C0.028 2.012 0 1.984 0 1.928V-1.928C0 -1.984 0.028 -2.012 0.084 -2.012H0.428C0.484 -2.012 0.512 -1.984 0.512 -1.928V1.928C0.512 1.984 0.484 2.012 0.428 2.012Z" transform="translate(0 0)" fill="#000000" data-prov="bd56ad529a36a7bbf2b4c343886b55e5" data-source-kind="6" data-glyph="cClef" data-class="clef"/>
|
||||
<path d="M0.036 0.62C0.012 0.62 0 0.604 0 0.588V-0.592C0 -0.608 0.012 -0.62 0.036 -0.62H0.088C0.108 -0.62 0.124 -0.608 0.124 -0.592V0.588C0.124 0.604 0.108 0.62 0.088 0.62ZM0.268 0.62C0.244 0.62 0.228 0.604 0.228 0.588V-0.592C0.228 -0.608 0.244 -0.62 0.268 -0.62H0.316C0.336 -0.62 0.356 -0.608 0.356 -0.592V0.588C0.356 0.604 0.336 0.62 0.316 0.62ZM2.076 0.62C2.056 0.62 2.036 0.604 2.036 0.588V-0.592C2.036 -0.608 2.056 -0.62 2.076 -0.62H2.128C2.148 -0.62 2.168 -0.608 2.168 -0.592V0.588C2.168 0.604 2.148 0.62 2.128 0.62ZM2.304 0.62C2.288 0.62 2.264 0.604 2.264 0.588V-0.592C2.264 -0.608 2.288 -0.62 2.304 -0.62H2.356C2.38 -0.62 2.396 -0.608 2.396 -0.592V0.588C2.396 0.604 2.38 0.62 2.356 0.62ZM1.216 0.5C0.688 0.5 0.36 0.28 0.36 0.004C0.36 -0.256 0.584 -0.5 1.176 -0.5C1.824 -0.5 2.036 -0.272 2.036 0.004C2.036 0.288 1.588 0.5 1.216 0.5ZM1.304 -0.404C1.136 -0.404 1.024 -0.312 0.92 -0.196C0.848 -0.104 0.788 0.032 0.788 0.16C0.788 0.364 0.924 0.408 1.112 0.408C1.384 0.408 1.604 0.116 1.604 -0.124C1.604 -0.32 1.48 -0.404 1.304 -0.404Z" transform="translate(1 0)" fill="#000000" data-prov="f6b4640bbb87f4c52d2e8bb00667c1c4" data-source-kind="3" data-glyph="noteheadDoubleWhole" 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(2 0)" fill="#000000" data-prov="31186a3ef9c061378bfd5da443b88aa8" data-source-kind="4" data-glyph="gClef" data-class="clef"/>
|
||||
<path d="M0.864 0.5C0.332 0.5 0 0.28 0 0.008C0 -0.26 0.228 -0.5 0.824 -0.5C1.48 -0.5 1.688 -0.272 1.688 0.008C1.688 0.292 1.236 0.5 0.864 0.5ZM0.444 0.252C0.488 0.392 0.636 0.412 0.76 0.412C1.036 0.412 1.256 0.116 1.256 -0.124C1.256 -0.248 1.204 -0.36 1.072 -0.392C1.032 -0.404 0.988 -0.408 0.948 -0.408C0.804 -0.408 0.656 -0.312 0.572 -0.2C0.492 -0.108 0.432 0.028 0.432 0.156C0.432 0.188 0.436 0.22 0.444 0.252Z" transform="translate(3 0)" fill="#000000" data-prov="c9e2f7b92061893043380eabfe8fbcd2" data-source-kind="2" data-glyph="noteheadWhole" 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(4 0)" fill="#000000" data-prov="fb2b9c85e73e553a05ccc47d55af4811" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(5 0)" fill="#000000" data-prov="1c92743d8b3afa333dde4fc1bbe503ba" data-source-kind="1" data-glyph="noteheadHalf" 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(6 0)" fill="#000000" data-prov="109fe59f980a1b9bfa00a76e41b78dba" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(7 0)" fill="#000000" data-prov="56a954e9547cffd75e82fdad63afb684" data-source-kind="1" data-glyph="noteheadHalf" 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(8 0)" fill="#000000" data-prov="d4ef3689df957acec175a1396cebd27c" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(9 0)" fill="#000000" data-prov="290c3f76bff74030da13f651df94fd4b" data-source-kind="1" data-glyph="noteheadHalf" data-class="notehead"/>
|
||||
<path d="M0.564 0.724C0.556 0.724 0.552 0.72 0.548 0.72C0.548 0.72 0.292 0.628 0.188 0.628C0.164 0.628 0.148 0.632 0.148 0.648V1.316C0.148 1.344 0.124 1.364 0.1 1.364H0.048C0.02 1.364 0 1.344 0 1.316V-0.744C0 -0.768 0.012 -0.78 0.036 -0.78L0.044 -0.776C0.048 -0.776 0.056 -0.776 0.06 -0.772C0.116 -0.748 0.34 -0.652 0.456 -0.652C0.496 -0.652 0.524 -0.664 0.524 -0.696V-1.292C0.524 -1.32 0.544 -1.34 0.572 -1.34H0.624C0.648 -1.34 0.672 -1.32 0.672 -1.292V0.716C0.672 0.736 0.656 0.748 0.64 0.748C0.636 0.748 0.628 0.748 0.624 0.744ZM0.148 0.156C0.148 0.212 0.392 0.316 0.488 0.316C0.512 0.316 0.524 0.312 0.524 0.296V-0.116C0.524 -0.188 0.296 -0.28 0.196 -0.28C0.168 -0.28 0.148 -0.272 0.148 -0.256Z" transform="translate(10 0)" fill="#000000" data-prov="3111f6a4a6bc3100a78fa6f04b2e2f86" data-source-kind="9" data-glyph="accidentalNatural" data-class="accidental"/>
|
||||
<path d="M1.128 0.096V0.464C1.128 0.524 1.08 0.568 1.024 0.568H0.104C0.044 0.568 0 0.524 0 0.464V0.096C0 0.04 0.044 -0.008 0.104 -0.008H1.024C1.08 -0.008 1.128 0.04 1.128 0.096Z" transform="translate(11 0)" fill="#000000" data-prov="a647c465b9e490a52c23029a2c906cda" data-source-kind="12" data-glyph="restHalf" data-class="rest"/>
|
||||
<path d="M0.064 1.056C0.02 1.056 0 1.036 0 0.992C0 0.952 0.02 0.932 0.06 0.932H0.292C0.316 0.932 0.324 0.932 0.324 0.916C0.324 0.908 0.32 0.896 0.316 0.876L0.064 0C-0.048 -0.392 -0.12 -0.52 -0.28 -0.52C-0.332 -0.52 -0.352 -0.504 -0.352 -0.484C-0.352 -0.452 -0.316 -0.472 -0.256 -0.432C-0.208 -0.4 -0.176 -0.348 -0.176 -0.284C-0.176 -0.18 -0.248 -0.12 -0.356 -0.12C-0.476 -0.12 -0.564 -0.216 -0.564 -0.34C-0.564 -0.516 -0.432 -0.608 -0.264 -0.608C0.04 -0.608 0.228 -0.404 0.448 0.048C0.564 0.292 0.652 0.54 0.748 0.88L0.752 0.884C0.752 0.896 0.784 0.932 0.804 0.932H1.064C1.108 0.932 1.128 0.952 1.128 0.996C1.128 1.036 1.108 1.056 1.068 1.056H0.828C0.8 1.056 0.788 1.056 0.788 1.076C0.788 1.088 0.792 1.1 0.796 1.124C0.876 1.472 0.964 1.684 1.164 1.684C1.196 1.684 1.228 1.676 1.228 1.652C1.228 1.628 1.208 1.632 1.164 1.612C1.112 1.588 1.08 1.532 1.08 1.46C1.08 1.348 1.16 1.292 1.26 1.292C1.364 1.292 1.456 1.36 1.456 1.508C1.456 1.656 1.364 1.776 1.112 1.776C0.724 1.776 0.508 1.5 0.376 1.116C0.356 1.056 0.352 1.056 0.296 1.056Z" transform="translate(12 0)" fill="#000000" data-prov="8ed9d2ef46e84abf412a1b82e125d3a6" data-source-kind="22" data-glyph="dynamicForte" data-class="dynamic"/>
|
||||
<path d="M0.536 0.428C0.536 0.576 0.416 0.696 0.268 0.696C0.12 0.696 0 0.576 0 0.428C0 0.344 0.048 0.272 0.108 0.224C0.172 0.18 0.248 0.156 0.324 0.156C0.38 0.156 0.436 0.168 0.48 0.184C0.536 0.2 0.572 0.216 0.624 0.244C0.632 0.248 0.64 0.248 0.644 0.248C0.66 0.248 0.664 0.232 0.664 0.212C0.664 0.2 0.664 0.184 0.66 0.168C0.648 0.108 0.36 -0.688 0.288 -0.952C0.288 -1 0.38 -1.004 0.404 -1.004C0.448 -1.004 0.504 -0.996 0.544 -0.964C0.556 -0.956 0.948 0.448 0.948 0.448C0.964 0.52 0.984 0.584 0.988 0.604C0.988 0.644 0.948 0.664 0.94 0.668C0.932 0.668 0.92 0.668 0.896 0.652C0.868 0.628 0.668 0.388 0.536 0.388Z" transform="translate(13 0)" fill="#000000" data-prov="7b42d513243366bc1409bbb4cf418fac" data-source-kind="14" data-glyph="rest8th" data-class="rest"/>
|
||||
<path d="M0.952 -3.16C0.952 -3.16 1.056 -2.78 1.056 -2.468C1.056 -1.968 0.848 -1.496 0.596 -1.096C0.392 -0.78 0.224 -0.436 0.16 -0.052C0.148 0.012 0.116 0.036 0.076 0.036C0.032 0.036 0 0.024 0 -0.024V-0.98C0.264 -1.028 0.644 -1.572 0.788 -1.912C0.848 -2.048 0.884 -2.276 0.884 -2.512C0.884 -2.692 0.856 -2.88 0.788 -3.06C0.78 -3.084 0.776 -3.104 0.776 -3.12C0.776 -3.184 0.816 -3.22 0.84 -3.236C0.864 -3.252 0.932 -3.228 0.952 -3.16Z" transform="translate(14 0)" fill="#000000" data-prov="c8ad1fa9386570a1f3e49ccbf43b17f8" data-source-kind="15" data-glyph="flag8thUp" data-class="flag"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(15 0)" fill="#000000" data-prov="9f8b66aae47123317fae42a521a8ea1a" data-source-kind="25" data-glyph="noteheadHalf" data-class="notehead"/>
|
||||
<path d="M0.92 1.928C0.92 1.984 0.892 2.012 0.836 2.012H0.832C0.776 2.012 0.748 1.984 0.748 1.928V-1.928C0.748 -1.984 0.776 -2.012 0.832 -2.012H0.836C0.892 -2.012 0.92 -1.984 0.92 -1.928V-0.176C0.92 -0.144 0.94 -0.148 0.956 -0.152C1.06 -0.18 1.228 -0.284 1.312 -0.736C1.324 -0.8 1.348 -0.836 1.388 -0.836C1.432 -0.836 1.452 -0.796 1.472 -0.728C1.524 -0.552 1.616 -0.356 1.9 -0.356C2.16 -0.356 2.232 -0.612 2.232 -1.136C2.232 -1.66 2.14 -1.896 1.808 -1.896C1.752 -1.896 1.468 -1.872 1.468 -1.788C1.468 -1.768 1.532 -1.744 1.576 -1.728C1.656 -1.7 1.736 -1.62 1.736 -1.468C1.736 -1.292 1.62 -1.192 1.464 -1.192C1.292 -1.192 1.156 -1.308 1.156 -1.52C1.156 -1.772 1.376 -2.024 1.852 -2.024C2.508 -2.024 2.796 -1.564 2.796 -1.148C2.796 -0.596 2.492 -0.212 1.96 -0.212C1.844 -0.212 1.768 -0.232 1.716 -0.248C1.676 -0.26 1.636 -0.268 1.6 -0.244C1.544 -0.208 1.456 -0.08 1.456 0C1.456 0.08 1.544 0.208 1.6 0.244C1.636 0.268 1.676 0.26 1.716 0.248C1.768 0.232 1.844 0.212 1.96 0.212C2.492 0.212 2.796 0.596 2.796 1.148C2.796 1.564 2.508 2.024 1.852 2.024C1.376 2.024 1.156 1.772 1.156 1.52C1.156 1.308 1.292 1.192 1.464 1.192C1.62 1.192 1.736 1.292 1.736 1.468C1.736 1.62 1.656 1.7 1.576 1.728C1.532 1.744 1.468 1.768 1.468 1.788C1.468 1.872 1.752 1.896 1.808 1.896C2.14 1.896 2.232 1.66 2.232 1.136C2.232 0.612 2.16 0.356 1.9 0.356C1.616 0.356 1.524 0.552 1.472 0.728C1.452 0.796 1.432 0.836 1.388 0.836C1.348 0.836 1.324 0.8 1.312 0.736C1.228 0.284 1.06 0.18 0.956 0.152C0.94 0.148 0.92 0.144 0.92 0.176ZM0.084 2.012C0.028 2.012 0 1.984 0 1.928V-1.928C0 -1.984 0.028 -2.012 0.084 -2.012H0.428C0.484 -2.012 0.512 -1.984 0.512 -1.928V1.928C0.512 1.984 0.484 2.012 0.428 2.012Z" transform="translate(16 0)" fill="#000000" data-prov="311476ddcf5ddef3b76e9d807237a21a" data-source-kind="6" data-glyph="cClef" data-class="clef"/>
|
||||
<path d="M0.036 0.62C0.012 0.62 0 0.604 0 0.588V-0.592C0 -0.608 0.012 -0.62 0.036 -0.62H0.088C0.108 -0.62 0.124 -0.608 0.124 -0.592V0.588C0.124 0.604 0.108 0.62 0.088 0.62ZM0.268 0.62C0.244 0.62 0.228 0.604 0.228 0.588V-0.592C0.228 -0.608 0.244 -0.62 0.268 -0.62H0.316C0.336 -0.62 0.356 -0.608 0.356 -0.592V0.588C0.356 0.604 0.336 0.62 0.316 0.62ZM2.076 0.62C2.056 0.62 2.036 0.604 2.036 0.588V-0.592C2.036 -0.608 2.056 -0.62 2.076 -0.62H2.128C2.148 -0.62 2.168 -0.608 2.168 -0.592V0.588C2.168 0.604 2.148 0.62 2.128 0.62ZM2.304 0.62C2.288 0.62 2.264 0.604 2.264 0.588V-0.592C2.264 -0.608 2.288 -0.62 2.304 -0.62H2.356C2.38 -0.62 2.396 -0.608 2.396 -0.592V0.588C2.396 0.604 2.38 0.62 2.356 0.62ZM1.216 0.5C0.688 0.5 0.36 0.28 0.36 0.004C0.36 -0.256 0.584 -0.5 1.176 -0.5C1.824 -0.5 2.036 -0.272 2.036 0.004C2.036 0.288 1.588 0.5 1.216 0.5ZM1.304 -0.404C1.136 -0.404 1.024 -0.312 0.92 -0.196C0.848 -0.104 0.788 0.032 0.788 0.16C0.788 0.364 0.924 0.408 1.112 0.408C1.384 0.408 1.604 0.116 1.604 -0.124C1.604 -0.32 1.48 -0.404 1.304 -0.404Z" transform="translate(17 0)" fill="#000000" data-prov="ead033c623ec6fc4eab9fd3839b7caa9" data-source-kind="3" data-glyph="noteheadDoubleWhole" 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(18 0)" fill="#000000" data-prov="5a3ada73e6c7dab552b6c44a6aa8f9b6" data-source-kind="4" data-glyph="gClef" data-class="clef"/>
|
||||
<path d="M0.864 0.5C0.332 0.5 0 0.28 0 0.008C0 -0.26 0.228 -0.5 0.824 -0.5C1.48 -0.5 1.688 -0.272 1.688 0.008C1.688 0.292 1.236 0.5 0.864 0.5ZM0.444 0.252C0.488 0.392 0.636 0.412 0.76 0.412C1.036 0.412 1.256 0.116 1.256 -0.124C1.256 -0.248 1.204 -0.36 1.072 -0.392C1.032 -0.404 0.988 -0.408 0.948 -0.408C0.804 -0.408 0.656 -0.312 0.572 -0.2C0.492 -0.108 0.432 0.028 0.432 0.156C0.432 0.188 0.436 0.22 0.444 0.252Z" transform="translate(19 0)" fill="#000000" data-prov="c36e7a12ef652f2f8e8cf771489cdcdc" data-source-kind="2" data-glyph="noteheadWhole" 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(20 0)" fill="#000000" data-prov="e01815eeb89bc5fcd5157c32311ccf10" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(21 0)" fill="#000000" data-prov="23cf4d4c63aa6a23024a756ec4aabc6d" data-source-kind="1" data-glyph="noteheadHalf" 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(22 0)" fill="#000000" data-prov="119c53a4e3bf288ded2bd28178d6fcbe" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(23 0)" fill="#000000" data-prov="866d5e0219f737a3aa929bcff3d98833" data-source-kind="1" data-glyph="noteheadHalf" data-class="notehead"/>
|
||||
<path d="M0.92 1.928C0.92 1.984 0.892 2.012 0.836 2.012H0.832C0.776 2.012 0.748 1.984 0.748 1.928V-1.928C0.748 -1.984 0.776 -2.012 0.832 -2.012H0.836C0.892 -2.012 0.92 -1.984 0.92 -1.928V-0.176C0.92 -0.144 0.94 -0.148 0.956 -0.152C1.06 -0.18 1.228 -0.284 1.312 -0.736C1.324 -0.8 1.348 -0.836 1.388 -0.836C1.432 -0.836 1.452 -0.796 1.472 -0.728C1.524 -0.552 1.616 -0.356 1.9 -0.356C2.16 -0.356 2.232 -0.612 2.232 -1.136C2.232 -1.66 2.14 -1.896 1.808 -1.896C1.752 -1.896 1.468 -1.872 1.468 -1.788C1.468 -1.768 1.532 -1.744 1.576 -1.728C1.656 -1.7 1.736 -1.62 1.736 -1.468C1.736 -1.292 1.62 -1.192 1.464 -1.192C1.292 -1.192 1.156 -1.308 1.156 -1.52C1.156 -1.772 1.376 -2.024 1.852 -2.024C2.508 -2.024 2.796 -1.564 2.796 -1.148C2.796 -0.596 2.492 -0.212 1.96 -0.212C1.844 -0.212 1.768 -0.232 1.716 -0.248C1.676 -0.26 1.636 -0.268 1.6 -0.244C1.544 -0.208 1.456 -0.08 1.456 0C1.456 0.08 1.544 0.208 1.6 0.244C1.636 0.268 1.676 0.26 1.716 0.248C1.768 0.232 1.844 0.212 1.96 0.212C2.492 0.212 2.796 0.596 2.796 1.148C2.796 1.564 2.508 2.024 1.852 2.024C1.376 2.024 1.156 1.772 1.156 1.52C1.156 1.308 1.292 1.192 1.464 1.192C1.62 1.192 1.736 1.292 1.736 1.468C1.736 1.62 1.656 1.7 1.576 1.728C1.532 1.744 1.468 1.768 1.468 1.788C1.468 1.872 1.752 1.896 1.808 1.896C2.14 1.896 2.232 1.66 2.232 1.136C2.232 0.612 2.16 0.356 1.9 0.356C1.616 0.356 1.524 0.552 1.472 0.728C1.452 0.796 1.432 0.836 1.388 0.836C1.348 0.836 1.324 0.8 1.312 0.736C1.228 0.284 1.06 0.18 0.956 0.152C0.94 0.148 0.92 0.144 0.92 0.176ZM0.084 2.012C0.028 2.012 0 1.984 0 1.928V-1.928C0 -1.984 0.028 -2.012 0.084 -2.012H0.428C0.484 -2.012 0.512 -1.984 0.512 -1.928V1.928C0.512 1.984 0.484 2.012 0.428 2.012Z" transform="translate(24 0)" fill="#000000" data-prov="973e800421dc74ca8f5d1b5be644a433" data-source-kind="6" data-glyph="cClef" data-class="clef"/>
|
||||
<path d="M0.036 0.62C0.012 0.62 0 0.604 0 0.588V-0.592C0 -0.608 0.012 -0.62 0.036 -0.62H0.088C0.108 -0.62 0.124 -0.608 0.124 -0.592V0.588C0.124 0.604 0.108 0.62 0.088 0.62ZM0.268 0.62C0.244 0.62 0.228 0.604 0.228 0.588V-0.592C0.228 -0.608 0.244 -0.62 0.268 -0.62H0.316C0.336 -0.62 0.356 -0.608 0.356 -0.592V0.588C0.356 0.604 0.336 0.62 0.316 0.62ZM2.076 0.62C2.056 0.62 2.036 0.604 2.036 0.588V-0.592C2.036 -0.608 2.056 -0.62 2.076 -0.62H2.128C2.148 -0.62 2.168 -0.608 2.168 -0.592V0.588C2.168 0.604 2.148 0.62 2.128 0.62ZM2.304 0.62C2.288 0.62 2.264 0.604 2.264 0.588V-0.592C2.264 -0.608 2.288 -0.62 2.304 -0.62H2.356C2.38 -0.62 2.396 -0.608 2.396 -0.592V0.588C2.396 0.604 2.38 0.62 2.356 0.62ZM1.216 0.5C0.688 0.5 0.36 0.28 0.36 0.004C0.36 -0.256 0.584 -0.5 1.176 -0.5C1.824 -0.5 2.036 -0.272 2.036 0.004C2.036 0.288 1.588 0.5 1.216 0.5ZM1.304 -0.404C1.136 -0.404 1.024 -0.312 0.92 -0.196C0.848 -0.104 0.788 0.032 0.788 0.16C0.788 0.364 0.924 0.408 1.112 0.408C1.384 0.408 1.604 0.116 1.604 -0.124C1.604 -0.32 1.48 -0.404 1.304 -0.404Z" transform="translate(25 0)" fill="#000000" data-prov="5909bb98de8ef36fa41f0493dd500023" data-source-kind="3" data-glyph="noteheadDoubleWhole" 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(26 0)" fill="#000000" data-prov="c46ea45155710bbe02c7dc32cfe32bc2" data-source-kind="4" data-glyph="gClef" data-class="clef"/>
|
||||
<path d="M0.864 0.5C0.332 0.5 0 0.28 0 0.008C0 -0.26 0.228 -0.5 0.824 -0.5C1.48 -0.5 1.688 -0.272 1.688 0.008C1.688 0.292 1.236 0.5 0.864 0.5ZM0.444 0.252C0.488 0.392 0.636 0.412 0.76 0.412C1.036 0.412 1.256 0.116 1.256 -0.124C1.256 -0.248 1.204 -0.36 1.072 -0.392C1.032 -0.404 0.988 -0.408 0.948 -0.408C0.804 -0.408 0.656 -0.312 0.572 -0.2C0.492 -0.108 0.432 0.028 0.432 0.156C0.432 0.188 0.436 0.22 0.444 0.252Z" transform="translate(27 0)" fill="#000000" data-prov="3550b79c14ba8069d76656ff5347dc71" data-source-kind="2" data-glyph="noteheadWhole" 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(28 0)" fill="#000000" data-prov="2a1a8f5701737dac37ac7249833d1b7c" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(29 0)" fill="#000000" data-prov="7d6b27caefc21b2a04761b299840e44d" data-source-kind="1" data-glyph="noteheadHalf" 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(30 0)" fill="#000000" data-prov="ec08ad9ddf4f78c806a81632fe2ae1e4" data-source-kind="0" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C1.048 -0.5 1.18 0.036 1.18 0.168C1.18 0.372 1.016 0.5 0.784 0.5C0.188 0.5 0 0.04 0 -0.168C0 -0.38 0.168 -0.5 0.388 -0.5ZM0.3 -0.348C0.216 -0.348 0.168 -0.304 0.14 -0.256C0.128 -0.232 0.116 -0.204 0.116 -0.176C0.116 0.02 0.696 0.336 0.884 0.336C0.96 0.336 1.004 0.3 1.032 0.252C1.044 0.228 1.056 0.204 1.056 0.176C1.056 0.004 0.492 -0.348 0.3 -0.348Z" transform="translate(31 0)" fill="#000000" data-prov="8311e890285ee9cffb3e19c98277caa4" data-source-kind="1" data-glyph="noteheadHalf" data-class="notehead"/>
|
||||
<line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="bd56ad529a36a7bbf2b4c343886b55e5" data-source-kind="6" data-kind="stroke"/>
|
||||
<line x1="-1" y1="0" x2="9.8" y2="0" stroke="#000000" stroke-width="0.13" data-prov="f6b4640bbb87f4c52d2e8bb00667c1c4" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="-1" y1="1" x2="9.8" y2="1" stroke="#000000" stroke-width="0.13" data-prov="576ce5892fe554d096e5842d3d1fc1a2" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="-1" y1="2" x2="9.8" y2="2" stroke="#000000" stroke-width="0.13" data-prov="dd8a37a522ad3b0a5d5349dd123b2e54" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="-1" y1="3" x2="9.8" y2="3" stroke="#000000" stroke-width="0.13" data-prov="37fd5692db0c0904ede5a67d2f85feeb" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="-1" y1="4" x2="9.8" y2="4" stroke="#000000" stroke-width="0.13" data-prov="d8d766432ab5a036bcb136abc9353807" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="c9e2f7b92061893043380eabfe8fbcd2" data-source-kind="2" data-kind="stroke"/>
|
||||
<line x1="4.15" y1="-1" x2="4.15" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="fb2b9c85e73e553a05ccc47d55af4811" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="5.75" y1="-1" x2="5.75" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="109fe59f980a1b9bfa00a76e41b78dba" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="7.35" y1="-1" x2="7.35" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="d4ef3689df957acec175a1396cebd27c" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="a647c465b9e490a52c23029a2c906cda" data-source-kind="12" data-kind="stroke"/>
|
||||
<line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="8ed9d2ef46e84abf412a1b82e125d3a6" data-source-kind="22" data-kind="stroke"/>
|
||||
<line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="7b42d513243366bc1409bbb4cf418fac" data-source-kind="14" data-kind="stroke"/>
|
||||
<line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="c8ad1fa9386570a1f3e49ccbf43b17f8" data-source-kind="15" data-kind="stroke"/>
|
||||
<line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="9f8b66aae47123317fae42a521a8ea1a" data-source-kind="25" data-kind="stroke"/>
|
||||
<line x1="13.8" y1="0" x2="13.8" y2="0" stroke="#000000" stroke-width="0" data-prov="311476ddcf5ddef3b76e9d807237a21a" data-source-kind="6" data-kind="stroke"/>
|
||||
<line x1="12.8" y1="0" x2="22" y2="0" stroke="#000000" stroke-width="0.13" data-prov="ead033c623ec6fc4eab9fd3839b7caa9" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="12.8" y1="1" x2="22" y2="1" stroke="#000000" stroke-width="0.13" data-prov="878244625d3b30db37ee716c607b3131" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="12.8" y1="2" x2="22" y2="2" stroke="#000000" stroke-width="0.13" data-prov="33be23946d80fa61db93990bb8ce3090" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="12.8" y1="3" x2="22" y2="3" stroke="#000000" stroke-width="0.13" data-prov="497e490bbe54d5dffa7b6e498e561aba" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="12.8" y1="4" x2="22" y2="4" stroke="#000000" stroke-width="0.13" data-prov="0de0bf3d70a45dda0bcad9c291c8399a" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="13.8" y1="0" x2="13.8" y2="0" stroke="#000000" stroke-width="0" data-prov="c36e7a12ef652f2f8e8cf771489cdcdc" data-source-kind="2" data-kind="stroke"/>
|
||||
<line x1="17.95" y1="-1" x2="17.95" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="e01815eeb89bc5fcd5157c32311ccf10" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="19.55" y1="-0.5" x2="19.55" y2="3" stroke="#000000" stroke-width="0.12" data-prov="119c53a4e3bf288ded2bd28178d6fcbe" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="26" y1="0" x2="26" y2="0" stroke="#000000" stroke-width="0" data-prov="973e800421dc74ca8f5d1b5be644a433" data-source-kind="6" data-kind="stroke"/>
|
||||
<line x1="25" y1="0" x2="34.2" y2="0" stroke="#000000" stroke-width="0.13" data-prov="5909bb98de8ef36fa41f0493dd500023" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="25" y1="1" x2="34.2" y2="1" stroke="#000000" stroke-width="0.13" data-prov="e994e0b2b56dd52f254e7bbfa045973a" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="25" y1="2" x2="34.2" y2="2" stroke="#000000" stroke-width="0.13" data-prov="ef2e4986f9259c1be99015db776cc9cf" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="25" y1="3" x2="34.2" y2="3" stroke="#000000" stroke-width="0.13" data-prov="5de2776918f9997a484f5a80167007b1" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="25" y1="4" x2="34.2" y2="4" stroke="#000000" stroke-width="0.13" data-prov="2b697bcb6cc7418ce5ddbe47fdb84406" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="26" y1="0" x2="26" y2="0" stroke="#000000" stroke-width="0" data-prov="3550b79c14ba8069d76656ff5347dc71" data-source-kind="2" data-kind="stroke"/>
|
||||
<line x1="30.15" y1="-1" x2="30.15" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="2a1a8f5701737dac37ac7249833d1b7c" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="31.75" y1="-0.5" x2="31.75" y2="3" stroke="#000000" stroke-width="0.12" data-prov="ec08ad9ddf4f78c806a81632fe2ae1e4" data-source-kind="0" data-kind="stroke"/>
|
||||
<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(0 1)" fill="#000000" data-prov="31186a3ef9c061378bfd5da443b88aa8" 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(3 -1)" fill="#000000" data-prov="1c92743d8b3afa333dde4fc1bbe503ba" 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(4.6 -1)" fill="#000000" data-prov="56a954e9547cffd75e82fdad63afb684" 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(6.2 -1)" fill="#000000" data-prov="290c3f76bff74030da13f651df94fd4b" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.144 0V4H0V0ZM0.912 4H0.412V0H0.912Z" transform="translate(9.3 2)" fill="#000000" data-prov="3111f6a4a6bc3100a78fa6f04b2e2f86" data-source-kind="9" data-glyph="barlineFinal" data-class="barline"/>
|
||||
<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(13.8 1)" fill="#000000" data-prov="5a3ada73e6c7dab552b6c44a6aa8f9b6" 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(16.8 -1)" fill="#000000" data-prov="23cf4d4c63aa6a23024a756ec4aabc6d" 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.4 -0.5)" fill="#000000" data-prov="866d5e0219f737a3aa929bcff3d98833" 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(26 1)" fill="#000000" data-prov="c46ea45155710bbe02c7dc32cfe32bc2" 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(29 -1)" fill="#000000" data-prov="7d6b27caefc21b2a04761b299840e44d" 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(30.6 -0.5)" fill="#000000" data-prov="8311e890285ee9cffb3e19c98277caa4" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 14 KiB |
|
|
@ -14,26 +14,52 @@ The font is NOT vendored; only the generated Rust is committed. Bravura is
|
|||
© Steinberg Media Technologies GmbH under the SIL Open Font License 1.1; the
|
||||
extracted outlines are redistributed under the same license (see OFL.txt).
|
||||
"""
|
||||
import json, re, sys, urllib.request
|
||||
import hashlib, json, re, sys, urllib.request
|
||||
|
||||
FONT_URL = "https://raw.githubusercontent.com/steinbergmedia/bravura/master/redist/otf/Bravura.otf"
|
||||
NAMES_URL = "https://raw.githubusercontent.com/w3c/smufl/gh-pages/metadata/glyphnames.json"
|
||||
# Pinned, immutable sources. A moving branch (`master` / `gh-pages`) would make
|
||||
# regeneration non-reproducible: a future font update would silently change the
|
||||
# outlines. Both sources are pinned to a commit SHA and their bytes verified
|
||||
# against a recorded SHA-256, so a substituted or updated source is rejected
|
||||
# rather than quietly accepted. To deliberately move to a newer font, bump the
|
||||
# ref AND the checksum together (a reviewable change), then regenerate.
|
||||
FONT_TAG = "1.392" # steinbergmedia/bravura tag bravura-1.392
|
||||
FONT_REF = "301087ca0b0d30b65d81bc3e718ff64b613e2a9a"
|
||||
NAMES_REF = "31a327a29640c313b12076739987bd7f25bdddde" # w3c/smufl gh-pages
|
||||
FONT_URL = f"https://raw.githubusercontent.com/steinbergmedia/bravura/{FONT_REF}/redist/otf/Bravura.otf"
|
||||
NAMES_URL = f"https://raw.githubusercontent.com/w3c/smufl/{NAMES_REF}/metadata/glyphnames.json"
|
||||
FONT_SHA256 = "dca2d90c88437a701b1c2e71fa54e76f9fa41d7deee935d74dc871ea66ecfdd2"
|
||||
NAMES_SHA256 = "1d05352599a20983d1c901635dc75d76f063c0987a7bee65f145325fc3e0d29f"
|
||||
|
||||
# Exactly the glyph set the v0 layout pipeline can name (layout-ir BRAVURA_METRICS).
|
||||
NAMES = ["noteheadBlack","noteheadHalf","noteheadWhole","noteheadDoubleWhole",
|
||||
"gClef","fClef","cClef","accidentalSharp","accidentalFlat","accidentalNatural",
|
||||
"accidentalDoubleSharp","restWhole","restHalf","restQuarter","rest8th",
|
||||
"flag8thUp","flag8thDown","augmentationDot","timeSig4","timeSigCommon",
|
||||
"flag8thUp","flag8thDown","augmentationDot",
|
||||
"timeSig0","timeSig1","timeSig2","timeSig3","timeSig4","timeSig5","timeSig6",
|
||||
"timeSig7","timeSig8","timeSig9","timeSigCommon",
|
||||
"barlineSingle","barlineFinal","dynamicForte","dynamicPiano"]
|
||||
|
||||
def verify(data, expected, what):
|
||||
actual = hashlib.sha256(data).hexdigest()
|
||||
if actual != expected:
|
||||
sys.exit(f"{what} SHA-256 mismatch:\n expected {expected}\n actual {actual}\n"
|
||||
"the pinned source changed; refusing to regenerate against an unverified font "
|
||||
"(bump FONT_REF/NAMES_REF and the checksum deliberately if this is intended)")
|
||||
return data
|
||||
|
||||
def load():
|
||||
from fontTools.ttLib import TTFont
|
||||
import io
|
||||
if "--local" in sys.argv:
|
||||
font = TTFont("Bravura.otf"); names = json.load(open("glyphnames.json"))
|
||||
font_bytes = open("Bravura.otf", "rb").read()
|
||||
names_bytes = open("glyphnames.json", "rb").read()
|
||||
else:
|
||||
font = TTFont(io.BytesIO(urllib.request.urlopen(FONT_URL).read()))
|
||||
names = json.loads(urllib.request.urlopen(NAMES_URL).read())
|
||||
font_bytes = urllib.request.urlopen(FONT_URL).read()
|
||||
names_bytes = urllib.request.urlopen(NAMES_URL).read()
|
||||
verify(font_bytes, FONT_SHA256, "Bravura.otf")
|
||||
verify(names_bytes, NAMES_SHA256, "glyphnames.json")
|
||||
font = TTFont(io.BytesIO(font_bytes))
|
||||
names = json.loads(names_bytes)
|
||||
return font, names
|
||||
|
||||
def round_d(d, nd=4):
|
||||
|
|
@ -53,7 +79,9 @@ def main():
|
|||
scale = 1.0 / sp
|
||||
gs = font.getGlyphSet()
|
||||
cmap = font.getBestCmap()
|
||||
hmtx = font["hmtx"]
|
||||
rows = []
|
||||
metrics = [] # (name, advance, [l,b,r,t]) in 1/1024-staff-space integer units
|
||||
for name in NAMES:
|
||||
cp = int(glyphnames[name]["codepoint"].replace("U+", ""), 16)
|
||||
g = cmap.get(cp)
|
||||
|
|
@ -65,7 +93,16 @@ def main():
|
|||
bp = BoundsPen(gs); gs[g].draw(bp)
|
||||
l, b, r, t = ([round(v * scale, 4) for v in bp.bounds] if bp.bounds else [0, 0, 0, 0])
|
||||
rows.append((name, cp, d, (l, b, r, t)))
|
||||
# Companion metrics for layout-ir `BRAVURA_METRICS` (1/1024 staff space):
|
||||
# the glyph's advance (from hmtx) and tight bbox.
|
||||
adv1024 = round(hmtx[g][0] * scale * 1024)
|
||||
bbox1024 = [round(v * scale * 1024) for v in (bp.bounds or (0, 0, 0, 0))]
|
||||
metrics.append((name, adv1024, bbox1024))
|
||||
rows.sort()
|
||||
print("// --- BRAVURA_METRICS rows (advance, [l,b,r,t] in 1/1024 staff space) ---",
|
||||
file=sys.stderr)
|
||||
for name, adv1024, bbox1024 in sorted(metrics):
|
||||
print(f' GlyphMetric::new("{name}", {adv1024}, {bbox1024}),', file=sys.stderr)
|
||||
o = []
|
||||
o.append("//! GENERATED by `tools/extract_bravura_outlines.py` — do not edit by hand.")
|
||||
o.append("//!")
|
||||
|
|
@ -75,6 +112,12 @@ def main():
|
|||
o.append("//! (musical convention, positive y = higher pitch), relative to each glyph's")
|
||||
o.append("//! origin, rounded to 4 decimals. The renderer applies a single y-flip wrapper.")
|
||||
o.append("//!")
|
||||
o.append(f"//! Source (pinned + SHA-256 verified on extraction): Bravura {FONT_TAG},")
|
||||
o.append(f"//! steinbergmedia/bravura @ {FONT_REF}")
|
||||
o.append(f"//! (sha256 {FONT_SHA256});")
|
||||
o.append(f"//! glyph names from w3c/smufl gh-pages @ {NAMES_REF}")
|
||||
o.append(f"//! (sha256 {NAMES_SHA256}).")
|
||||
o.append("//!")
|
||||
o.append("//! Bravura is (c) Steinberg Media Technologies GmbH under the SIL Open Font")
|
||||
o.append("//! License 1.1; these extracted outlines are redistributed under the same license")
|
||||
o.append("//! (see `tools/OFL.txt`).")
|
||||
|
|
|
|||
|
|
@ -208,4 +208,47 @@ mod tests {
|
|||
assert_eq!(s.cross_cutting.markers.len(), 1);
|
||||
assert_eq!(s.cross_cutting.chord_symbols.len(), 1);
|
||||
}
|
||||
|
||||
/// A measure that references a time signature lists it (and its start
|
||||
/// anchor's target) among its invalidation dependencies, so a time-signature
|
||||
/// display change with an unchanged id invalidates the measure and its
|
||||
/// synthesized time-signature glyphs.
|
||||
#[test]
|
||||
fn a_measure_depends_on_its_time_signature_and_start_anchor() {
|
||||
use epiphany_core::{TimeSignatureId, TypedObjectId};
|
||||
use epiphany_layout_ir::to_logical;
|
||||
|
||||
let mut score = ten_measure_single_staff(1);
|
||||
let time_signature: TimeSignatureId = score.identity.mint();
|
||||
let (measure_id, region_id) = {
|
||||
let region = &mut score.canvas.regions[0];
|
||||
let region_id = region.id;
|
||||
let instance = region
|
||||
.content
|
||||
.staff_instances_mut()
|
||||
.expect("the fixture is staff-based")
|
||||
.first_mut()
|
||||
.expect("a staff instance");
|
||||
instance.measures[0].time_signature = Some(time_signature);
|
||||
(instance.measures[0].id, region_id)
|
||||
};
|
||||
|
||||
let logical = to_logical(&score);
|
||||
let measure = logical
|
||||
.regions
|
||||
.iter()
|
||||
.flat_map(|r| r.objects.iter())
|
||||
.find(|o| o.provenance().source == TypedObjectId::Measure(measure_id))
|
||||
.expect("measure 0 is projected");
|
||||
let deps = &measure.provenance().dependencies;
|
||||
assert!(
|
||||
deps.contains(&TypedObjectId::TimeSignature(time_signature)),
|
||||
"a measure depends on the time signature it displays"
|
||||
);
|
||||
// Each fixture measure is region-anchored, so the region is a dep too.
|
||||
assert!(
|
||||
deps.contains(&TypedObjectId::Region(region_id)),
|
||||
"a measure depends on its start anchor's target"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -118,11 +118,134 @@ pub fn gen_point(rng: &mut Rng) -> Point {
|
|||
Point::new(coord(rng), coord(rng))
|
||||
}
|
||||
|
||||
/// A logical layout object (provenance + an optional owning staff).
|
||||
/// A logical engraving-content payload — every [`LayoutContent`] variant, so the
|
||||
/// generator/fuzz surface exercises the enriched payloads, not just structural.
|
||||
pub fn gen_layout_content(rng: &mut Rng) -> LayoutContent {
|
||||
fn time(rng: &mut Rng) -> TimePoint {
|
||||
if rng.boolean() {
|
||||
TimePoint::Musical(epiphany_core::MusicalPosition(
|
||||
epiphany_core::RationalTime::new(rng.range(0, 16) as i64, 4).expect("nonzero"),
|
||||
))
|
||||
} else {
|
||||
TimePoint::WallClock(epiphany_core::WallClockTime(rng.range(0, 10_000) as i64))
|
||||
}
|
||||
}
|
||||
fn duration(numerator: i64, denominator: i64) -> epiphany_core::MusicalDuration {
|
||||
epiphany_core::MusicalDuration(
|
||||
epiphany_core::RationalTime::new(numerator, denominator).expect("nonzero"),
|
||||
)
|
||||
}
|
||||
fn component(rng: &mut Rng, offset: epiphany_core::MusicalDuration) -> PlacedComponent {
|
||||
PlacedComponent {
|
||||
offset,
|
||||
component: epiphany_core::NotatedComponent {
|
||||
base_value: *rng.choose(&[
|
||||
epiphany_core::NoteValue::Whole,
|
||||
epiphany_core::NoteValue::Half,
|
||||
epiphany_core::NoteValue::Quarter,
|
||||
epiphany_core::NoteValue::Eighth,
|
||||
epiphany_core::NoteValue::Sixteenth,
|
||||
]),
|
||||
dots: rng.range(0, 4) as u8,
|
||||
tuplet: None,
|
||||
tied_to_next: rng.boolean(),
|
||||
},
|
||||
tuplet: None,
|
||||
}
|
||||
}
|
||||
fn components(rng: &mut Rng) -> Vec<PlacedComponent> {
|
||||
let mut out = vec![component(rng, epiphany_core::MusicalDuration::zero())];
|
||||
if rng.boolean() {
|
||||
out.push(component(rng, duration(1, 4)));
|
||||
}
|
||||
out
|
||||
}
|
||||
fn clefs(rng: &mut Rng) -> Vec<PlacedClef> {
|
||||
if rng.boolean() {
|
||||
vec![
|
||||
PlacedClef {
|
||||
time: time(rng),
|
||||
clef: epiphany_core::Clef::treble(),
|
||||
},
|
||||
PlacedClef {
|
||||
time: time(rng),
|
||||
clef: epiphany_core::Clef::bass(),
|
||||
},
|
||||
]
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
fn keys(rng: &mut Rng) -> Vec<PlacedKeySignature> {
|
||||
if rng.boolean() {
|
||||
vec![PlacedKeySignature {
|
||||
time: time(rng),
|
||||
key: epiphany_core::KeySignature::new(rng.range(0, 14) as i8 - 7)
|
||||
.expect("generator stays in -7..=7"),
|
||||
}]
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
fn spelling(rng: &mut Rng) -> Option<epiphany_core::PitchSpelling> {
|
||||
rng.boolean().then(|| {
|
||||
let nominal = *rng.choose(&[
|
||||
epiphany_core::CmnNominal::C,
|
||||
epiphany_core::CmnNominal::D,
|
||||
epiphany_core::CmnNominal::E,
|
||||
epiphany_core::CmnNominal::F,
|
||||
epiphany_core::CmnNominal::G,
|
||||
epiphany_core::CmnNominal::A,
|
||||
epiphany_core::CmnNominal::B,
|
||||
]);
|
||||
epiphany_core::PitchSpelling::cmn(nominal, rng.range(2, 7) as i8)
|
||||
})
|
||||
}
|
||||
fn barline(rng: &mut Rng) -> BarlineKind {
|
||||
match rng.below(3) {
|
||||
0 => BarlineKind::Interior,
|
||||
1 => BarlineKind::RegionEnd,
|
||||
_ => BarlineKind::Final,
|
||||
}
|
||||
}
|
||||
match rng.below(5) {
|
||||
0 => LayoutContent::Structural,
|
||||
1 => LayoutContent::Staff(StaffContent {
|
||||
clefs: clefs(rng),
|
||||
keys: keys(rng),
|
||||
}),
|
||||
2 => LayoutContent::Note(NoteContent {
|
||||
position: time(rng),
|
||||
components: components(rng),
|
||||
pitches: vec![NotePitch {
|
||||
pitch: epiphany_core::PitchId::new(epiphany_core::ReplicaId(7), rng.next_u64()),
|
||||
spelling: spelling(rng),
|
||||
}],
|
||||
}),
|
||||
3 => LayoutContent::Rest(RestContent {
|
||||
position: time(rng),
|
||||
components: components(rng),
|
||||
staff_position: rng
|
||||
.boolean()
|
||||
.then(|| epiphany_core::StaffPosition(rng.range(0, 9) as i16)),
|
||||
}),
|
||||
_ => LayoutContent::Measure(MeasureContent {
|
||||
start: time(rng),
|
||||
barline: barline(rng),
|
||||
time_signature: rng.boolean().then(|| TimeSignatureContent {
|
||||
numerator: rng.range(1, 13) as u16,
|
||||
denominator: *rng.choose(&[2, 4, 8, 16]),
|
||||
}),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// A logical layout object (provenance + an optional owning staff + content).
|
||||
pub fn gen_layout_object(rng: &mut Rng) -> LayoutObject {
|
||||
LayoutObject::from_projection(
|
||||
LayoutObject::from_projection_with_content(
|
||||
gen_provenance(rng),
|
||||
rng.boolean().then(|| crate::generators::staff_id(rng)),
|
||||
gen_layout_content(rng),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -189,6 +312,21 @@ pub fn gen_logical_layout_ir(rng: &mut Rng) -> LogicalLayoutIR {
|
|||
}
|
||||
}
|
||||
|
||||
/// A non-glyph line primitive (a staff line / stem / barline), so the
|
||||
/// generator/fuzz surface exercises strokes alongside glyphs.
|
||||
pub fn gen_stroke(rng: &mut Rng) -> Stroke {
|
||||
Stroke {
|
||||
provenance: gen_provenance(rng),
|
||||
from: gen_point(rng),
|
||||
to: gen_point(rng),
|
||||
thickness: gen_staff_space(rng),
|
||||
layer: rng.range(0, 8) as i32 - 4,
|
||||
style: GlyphStyle {
|
||||
rgba: rng.next_u64() as u32,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// A constrained layout IR whose catalog hash covers exactly its glyph metrics
|
||||
/// (so the real stub solver accepts it as well-formed), with an **internally
|
||||
/// consistent** vertical band: every glyph names the band, and the band's
|
||||
|
|
@ -229,11 +367,15 @@ pub fn gen_constrained_layout_ir(rng: &mut Rng) -> ConstrainedLayoutIR {
|
|||
regions: vec![],
|
||||
horizontal_slots,
|
||||
glyphs,
|
||||
strokes: (0..rng.range_usize(0, 3))
|
||||
.map(|_| gen_stroke(rng))
|
||||
.collect(),
|
||||
vertical_bands: vec![band],
|
||||
constraints: vec![],
|
||||
engraving_decisions: (0..decisions)
|
||||
.map(|_| gen_engraving_decision(rng))
|
||||
.collect(),
|
||||
diagnostics: Vec::new(),
|
||||
catalog: GlyphCatalogIdentity {
|
||||
metrics_hash,
|
||||
..GlyphCatalogIdentity::default()
|
||||
|
|
@ -326,12 +468,15 @@ pub fn gen_round_trip_report(rng: &mut Rng) -> RoundTripReport {
|
|||
.primitives
|
||||
.iter()
|
||||
.map(|primitive| primitive.provenance.source)
|
||||
.chain(render.strokes.iter().map(|stroke| stroke.provenance.source))
|
||||
.collect();
|
||||
let total = render.primitives.len() + render.strokes.len();
|
||||
RoundTripReport {
|
||||
status: SolveStatus::Solved,
|
||||
logical_objects: render.primitives.len(),
|
||||
logical_objects: total,
|
||||
glyphs: render.primitives.len(),
|
||||
render_primitives: render.primitives.len(),
|
||||
render_strokes: render.strokes.len(),
|
||||
render_primitives: total,
|
||||
recovered_sources,
|
||||
}
|
||||
}
|
||||
|
|
@ -1305,7 +1450,10 @@ mod tests {
|
|||
let score = fixtures::ten_measure_single_staff(0xA11CE);
|
||||
let report = round_trip(&score);
|
||||
assert!(report.glyphs > 0);
|
||||
assert_eq!(report.glyphs, report.render_primitives);
|
||||
assert_eq!(
|
||||
report.render_primitives,
|
||||
report.glyphs + report.render_strokes
|
||||
);
|
||||
|
||||
let measures = report
|
||||
.recovered_sources
|
||||
|
|
|
|||
|
|
@ -214,7 +214,12 @@ fn criterion_6_layout_round_trip() {
|
|||
for seed in 0..128u64 {
|
||||
let report = layout_stub::round_trip(&fixtures::ten_measure_single_staff(seed));
|
||||
assert!(report.glyphs > 0);
|
||||
assert_eq!(report.glyphs, report.render_primitives);
|
||||
// The render IR carries glyph *and* stroke primitives; the round-trip
|
||||
// recovers a source for every one of them.
|
||||
assert_eq!(
|
||||
report.render_primitives,
|
||||
report.glyphs + report.render_strokes
|
||||
);
|
||||
|
||||
layout_stub::round_trip(&generators::graph::valid_score_rich(seed));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue