Schema major 2 Phase E1: repeat barlines + volta brackets render
The first repeat-structure ink, review-hardened (19 verified findings fixed pre-commit). Zero golden churn for repeat-free scores; four new goldens for the ten_measure_with_repeats fixture. layout-ir: repeats project LayoutContent::Repeat resolved at to_logical (RepeatPlacement::At/RegionEnd/Unresolved — honest, no origin fallback; zero-offsets judged by value; bare wall-clock boundaries draw no ink); deps now from anchor_sites(). Constrained: coinciding measure barlines morph into precomposed repeatLeft/Right/RightLeft (name-only, exact provenance kept); standalone signs synthesize under a semantic (site, staff) instance key; end repeats at the region close draw repeatDots beside a final barline (or the full sign on continuing staves); start marks there draw nothing; end-facing signs reserve their left reach via column overhang; time signatures clear morphed signs; volta brackets = 3 strokes above the top staff + timeSig digit numerals, kind-independent. engrave: ENGRAVER_VERSION 3 -> 4; casting classifies barline columns via is_barline_glyph AND direct Measure source, so standalone signs are never phantom break candidates or measure records; criterion-6 round-trip covers the repeat fixture. render-svg: repeatLeft/Right/RightLeft/Dots outlines + metrics + font subset regenerated from the SHA-pinned Bravura (fontTools 4.63.0); GlyphClass::Repeat. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
58eef41c7e
commit
7a9bf42119
|
|
@ -403,3 +403,28 @@ schema-major-1 track's Phase F (2026-07-06; core spec
|
|||
schema-major-1 tranche). Satisfaction is a predicate on the output layout, not
|
||||
the solver's spring state; casting-off evaluates the declared hard break
|
||||
constraints as part of its tier claim.
|
||||
|
||||
## ENGRAVER_VERSION 3 → 4: repeat barlines + volta brackets (E1, 2026-07-07)
|
||||
|
||||
The layout pipeline now draws repeat signs and volta brackets (layout-ir E1
|
||||
decision), so a repeat-bearing score's baked geometry differs from version 3's
|
||||
invisible traced anchors — a version bump per the constant's own rule.
|
||||
Repeat-free scores are byte-identical (the existing `ten_measure` /
|
||||
`valid_score_rich` render goldens passed unchanged; only the new
|
||||
`ten_measure_with_repeats` goldens were added). Casting-off changes:
|
||||
|
||||
- Barline-column classification routes through layout-ir's
|
||||
`is_barline_glyph` instead of the `"barline"` name prefix, **and now
|
||||
requires a directly-manifested `Measure` source** (no synthesis): a morphed
|
||||
repeat sign remains a break candidate and its measure keeps its record,
|
||||
while a repeat-synthesized standalone sign (a mid-measure boundary, a
|
||||
region edge without a final barline) and the `repeatDots` pair classify
|
||||
nothing — the casting contract breaks systems at measure boundaries, and a
|
||||
phantom candidate could tear off a degenerate lone-sign trailing system or
|
||||
split a measure. Locked by
|
||||
`repeat_signs_keep_measure_records_honest_and_raise_their_system`, and the
|
||||
criterion-6 round-trip now runs the repeat fixture through the real
|
||||
Engraver.
|
||||
- Volta bracket strokes raise their system's extent, so vertical stacking
|
||||
and page overflow account for them with no engraver change (system height
|
||||
is computed from every member box and stroke).
|
||||
|
|
|
|||
|
|
@ -79,11 +79,11 @@ use std::collections::{BTreeMap, BTreeSet};
|
|||
|
||||
use epiphany_core::{StaffId, TypedObjectId};
|
||||
use epiphany_layout_ir::{
|
||||
continuation_instance_key, is_rigid_width_stroke, synthesized_layout_id, BreakClass, BreakKind,
|
||||
ConstrainedLayoutIR, DecisionSource, EngravingDecision, EngravingDecisionKind,
|
||||
EngravingOverrideId, GlyphObjectId, LayoutConstraint, LayoutObjectId, Margins, Point,
|
||||
Provenance, Rect, ResolvedGlyph, ResolvedMeasure, ResolvedPage, ResolvedStaff, ResolvedSystem,
|
||||
Size2D, SpringSlotId, StaffSpace, Stroke, SynthesisInstanceKey, SynthesisKind,
|
||||
continuation_instance_key, is_barline_glyph, is_rigid_width_stroke, synthesized_layout_id,
|
||||
BreakClass, BreakKind, ConstrainedLayoutIR, DecisionSource, EngravingDecision,
|
||||
EngravingDecisionKind, EngravingOverrideId, GlyphObjectId, LayoutConstraint, LayoutObjectId,
|
||||
Margins, Point, Provenance, Rect, ResolvedGlyph, ResolvedMeasure, ResolvedPage, ResolvedStaff,
|
||||
ResolvedSystem, Size2D, SpringSlotId, StaffSpace, Stroke, SynthesisInstanceKey, SynthesisKind,
|
||||
SynthesisRegistryId, VerticalBand, VerticalBandId,
|
||||
};
|
||||
|
||||
|
|
@ -365,14 +365,22 @@ pub(crate) fn cast_off(
|
|||
entry.lo = entry.lo.min(lo);
|
||||
entry.hi = entry.hi.max(hi);
|
||||
entry.members.push(i);
|
||||
if name.starts_with("barline") {
|
||||
// Barline classification by the engraver's own name vocabulary (which
|
||||
// includes the composite repeat signs a repeat boundary morphs a
|
||||
// measure barline into) — but only for a **directly-manifested measure
|
||||
// barline**: the casting contract breaks systems at measure
|
||||
// boundaries, so a repeat-synthesized standalone sign (a mid-measure
|
||||
// boundary, a region edge without a final barline) must not become a
|
||||
// phantom break candidate that could tear off a degenerate lone-sign
|
||||
// trailing system or split a measure.
|
||||
if is_barline_glyph(name)
|
||||
&& glyph.provenance.synthesis.is_none()
|
||||
&& matches!(glyph.provenance.source, TypedObjectId::Measure(_))
|
||||
{
|
||||
entry.barline = true;
|
||||
if name == "barlineFinal" {
|
||||
entry.final_barline = true;
|
||||
} else if entry.measure_barline.is_none()
|
||||
&& glyph.provenance.synthesis.is_none()
|
||||
&& matches!(glyph.provenance.source, TypedObjectId::Measure(_))
|
||||
{
|
||||
} else if entry.measure_barline.is_none() {
|
||||
entry.measure_barline = Some(i);
|
||||
}
|
||||
}
|
||||
|
|
@ -1448,6 +1456,54 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repeat_signs_keep_measure_records_honest_and_raise_their_system() {
|
||||
use crate::Engraver;
|
||||
use epiphany_layout_ir::{to_constrained, to_logical, ConstraintSolver, SolverConfig};
|
||||
// The repeat fixture draws morphed repeat barlines, a standalone sign,
|
||||
// the final-barline dot pair, and volta brackets. None of that may
|
||||
// mint a phantom measure record (a standalone sign and the dot pair
|
||||
// are repeat-synthesized, not measure barlines) or lose one (a morphed
|
||||
// barline still marks its measure): both fixtures cast off to the same
|
||||
// nine records — one per measure-*start* barline column; the final
|
||||
// measure's barline closes the region and yields none, by convention.
|
||||
let solve = |score| {
|
||||
Engraver::default().solve(
|
||||
&to_constrained(&to_logical(&score)),
|
||||
&SolverConfig::default(),
|
||||
)
|
||||
};
|
||||
let plain = solve(epiphany_testkit::fixtures::ten_measure_single_staff(
|
||||
0x000A_11CE,
|
||||
));
|
||||
let repeats = solve(epiphany_testkit::fixtures::ten_measure_with_repeats(
|
||||
0x000A_11CE,
|
||||
));
|
||||
let measure_count = |report: &crate::SolveReport| -> usize {
|
||||
report
|
||||
.layout
|
||||
.pages
|
||||
.iter()
|
||||
.flat_map(|page| &page.systems)
|
||||
.map(|system| system.measures.len())
|
||||
.sum()
|
||||
};
|
||||
assert_eq!(measure_count(&plain), 9);
|
||||
assert_eq!(measure_count(&repeats), 9);
|
||||
// The volta brackets sit above the staff, so the system carrying them
|
||||
// is taller than any repeat-free system.
|
||||
let max_height = |report: &crate::SolveReport| -> f32 {
|
||||
report
|
||||
.layout
|
||||
.pages
|
||||
.iter()
|
||||
.flat_map(|page| &page.systems)
|
||||
.map(|system| system.bounding_box.size.height.0)
|
||||
.fold(0.0, f32::max)
|
||||
};
|
||||
assert!(max_height(&repeats) > max_height(&plain));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_widow_rebalance_evens_the_final_system() {
|
||||
use crate::Engraver;
|
||||
|
|
|
|||
|
|
@ -118,11 +118,13 @@ pub struct Engraver {
|
|||
/// The implementation version of this solver (Chapter 9: within a fixed version,
|
||||
/// identical input produces identical output). Distinct from the stub's `0`;
|
||||
/// bumped to `2` when the casting-off pass landed (the resolved geometry of a
|
||||
/// wrapping score differs from version `1`'s single endless system), and to `3`
|
||||
/// wrapping score differs from version `1`'s single endless system), to `3`
|
||||
/// when casting-off gained its widow-rebalance phase (a wrapping score's system
|
||||
/// breaks — and so its baked geometry — differ again from version `2`'s pure
|
||||
/// greedy first-fit).
|
||||
pub const ENGRAVER_VERSION: SolverVersion = SolverVersion(3);
|
||||
/// greedy first-fit), and to `4` when repeat barlines and volta brackets landed
|
||||
/// (a repeat-bearing score's baked geometry differs from version `3`'s
|
||||
/// invisible traced anchors; repeat-free scores are unchanged).
|
||||
pub const ENGRAVER_VERSION: SolverVersion = SolverVersion(4);
|
||||
|
||||
impl Engraver {
|
||||
/// An engraver casting off against the given page geometry.
|
||||
|
|
@ -1662,14 +1664,17 @@ mod tests {
|
|||
fn criterion_six_round_trips_through_the_engravers_respacing() {
|
||||
use epiphany_core::generators::valid_score;
|
||||
use epiphany_layout_ir::{round_trip_with, SolveStatus};
|
||||
use epiphany_testkit::fixtures::ten_measure_single_staff;
|
||||
use epiphany_testkit::fixtures::{ten_measure_single_staff, ten_measure_with_repeats};
|
||||
|
||||
for seed in 0..32u64 {
|
||||
// Mirror the criterion-6 hand-off gate's own fixtures — the 10-measure
|
||||
// single staff (measures + barlines) and the rich score (cross-cutting
|
||||
// tuplet/tie/spanner/marker) — and keep `valid_score` for added breadth.
|
||||
// single staff (measures + barlines), its repeat-bearing sibling
|
||||
// (morphed/standalone repeat signs, dot pair, volta brackets — all
|
||||
// re-spaced and cast off), and the rich score (cross-cutting
|
||||
// tuplet/tie/spanner/marker) — and keep `valid_score` for breadth.
|
||||
let scores = [
|
||||
ten_measure_single_staff(seed),
|
||||
ten_measure_with_repeats(seed),
|
||||
valid_score(seed),
|
||||
valid_score_rich(seed),
|
||||
];
|
||||
|
|
|
|||
|
|
@ -480,3 +480,79 @@ sanctioned (`req:solver:subconformant-report`). **I6** the implemented
|
|||
emission set (successive-notehead no-collision chains + per-glyph containment
|
||||
+ user-break constraints) is the normative Minimal-tier floor
|
||||
(`req:layoutir:constraint-floor`).
|
||||
|
||||
## Repeat barlines and volta brackets (schema major 2, E1, 2026-07-07)
|
||||
|
||||
The first repeat-structure ink (Chapter 5 `RepeatStructure` / `RepeatKind` /
|
||||
`Volta`, ratified by the major-2 Phase A). Rendering is spec-unconstrained
|
||||
(Ch7's `BarLine` payload is undefined and voltas have no layout variant), so
|
||||
these are E1 implementation decisions for the Phase-F ratification pass:
|
||||
|
||||
- **Kind → ink mapping.** `SimpleRepeat` and `Volta` draw repeat barlines at
|
||||
their boundaries; `DaCapo`/`DalSegno` draw **no Minimal-tier ink** (segno /
|
||||
coda / instruction marks need a text primitive — a later tranche) but keep
|
||||
their traced anchors. Volta brackets draw for the `voltas` list of **any**
|
||||
kind.
|
||||
- **Morph, standalone, or dots.** A boundary whose column carries a measure's
|
||||
own barline **morphs** that barline into the precomposed SMuFL sign
|
||||
(`repeatLeft` / `repeatRight` / `repeatRightLeft` when an end meets a
|
||||
start) — a *name* change only: the measure's exact provenance is preserved
|
||||
verbatim because the round-trip provenance floor compares it exactly;
|
||||
repeat-edit invalidation is carried by the `ScoreVersion` (v0 relayouts
|
||||
wholesale), and an incremental tranche would add the dependency at the
|
||||
logical stage where dependencies are established. A boundary with no
|
||||
coinciding measure barline stands alone as a repeat-synthesized sign at its
|
||||
own barline-role column (`REPEAT_BARLINE_SYNTHESIS`; one per (column,
|
||||
staff); coinciding structures merge into one sign whose synthesis owner is
|
||||
the smallest `(structure id, boundary site)` — a **semantic** instance key,
|
||||
`(site << 32) | staff index`, stable under unrelated edits where a
|
||||
positional column rank would re-derive). The **region-closing column**: an
|
||||
end repeat there adds the `repeatDots` pair beside a staff's final barline
|
||||
(the final barline never morphs, keeping the casting-off solver's
|
||||
final-barline classification truthful), or draws the full end sign on a
|
||||
staff whose run continues (no final barline there); a *start* repeat at the
|
||||
region close draws nothing on any staff — a sign after the close would
|
||||
misstate the structure.
|
||||
- **Source-geometry clearance.** An end-facing sign's ink reaches ~1.1–1.3
|
||||
staff spaces left of its column (its heavy line right-aligns to the plain
|
||||
barline's span), so the mark's column reserves that reach through the
|
||||
accidental-overhang mechanism and the source layout stays collision-free.
|
||||
A morphed measure's time-signature digits shift right by the sign's right
|
||||
extension (`repeatLeft`/`repeatRightLeft` are wider than the barline they
|
||||
replace); both adjustments are zero for the plain barlines, so repeat-free
|
||||
geometry is untouched.
|
||||
- **Honest placement.** Repeat boundaries resolve via `RepeatPlacement` at
|
||||
projection time (`to_constrained` has no `Score`): `At(time)`,
|
||||
`RegionEnd` (zero-offset anchors to an existing region's end edge or to the
|
||||
end of an instance's last measure — the *column* is knowable where the
|
||||
*time* is not; zero-ness is judged **by value**, so a `Musical(0)` offset
|
||||
earns the same verdict as the `Zero` variant), or `Unresolved`, which draws
|
||||
**no ink** — unlike `resolve_time_anchor`'s origin fallback, a repeat sign
|
||||
at a false position would misstate the musical structure. A bare
|
||||
**wall-clock boundary is `Unresolved`**: it references no graph object, so
|
||||
nothing pins it to the region it would draw in — the sign would land
|
||||
wherever its time happens to *sort* among that region's columns (repeat ink
|
||||
for wall-clock-synchronized material is a later tranche; wall-clock
|
||||
`TimePoint`s reached *through* an object anchor place normally).
|
||||
Cross-region repeats keep the traced anchor only (content is dropped on the
|
||||
cross-region path — a documented Minimal boundary until repeat ink learns
|
||||
to split). Repeat dependencies now come from
|
||||
`RepeatStructure::anchor_sites()` (THE single site-set walk), so volta
|
||||
spans and jump targets are real invalidation and region-membership
|
||||
evidence.
|
||||
- **Volta brackets.** Three strokes above the *top* staff (line at
|
||||
`VOLTA_Y = 6.5` staff spaces, two descending hooks) plus the ending numbers
|
||||
as `timeSig0..9` digit glyphs (the Minimal tier has no text primitive),
|
||||
all synthesized under `VOLTA_SYNTHESIS`, endings drawn verbatim in authored
|
||||
order. A reversed / zero-width / unresolvable span draws no bracket
|
||||
(advisory volta well-formedness is the authoring layer's jurisdiction).
|
||||
Bracket strokes are ordinary re-spaceable strokes, so the engraver's
|
||||
system-splitting (`StrokeFate::Split`) applies unchanged.
|
||||
- **Glyphs.** The precomposed Bravura signs over hand-compositing
|
||||
heavy/thin/dot primitives; metrics extracted from the same SHA-pinned
|
||||
`bravura-1.392` as the rest of the table. The heavy line is aligned to the
|
||||
plain barline's span by a box approximation (`repeat_sign_x`: start signs
|
||||
left-aligned, end signs right-aligned, the combined sign centred).
|
||||
`is_barline_glyph` is exported so the casting-off solver classifies
|
||||
measure-boundary columns from this crate's name vocabulary instead of a
|
||||
string prefix.
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ use std::collections::{BTreeMap, BTreeSet};
|
|||
|
||||
use epiphany_core::{
|
||||
Clef, EventId, KeySignature, MeasureId, MeasurePosition, MusicalDuration, NoteValue, PitchId,
|
||||
PitchSpelling, SpellingNominal, StaffId, TimeAnchor, TypedObjectId, WallClockTime,
|
||||
PitchSpelling, RepeatStructureId, SpellingNominal, StaffId, TimeAnchor, TypedObjectId,
|
||||
WallClockTime,
|
||||
};
|
||||
use epiphany_determinism::{DomainTag, Preimage};
|
||||
|
||||
|
|
@ -26,7 +27,7 @@ use crate::engraving::{EngravingDecision, OverrideKind, OverridePriority, Overri
|
|||
use crate::glyph::{metrics, BravuraCatalog, GlyphCatalog, GlyphCatalogIdentity, GlyphReference};
|
||||
use crate::logical::{
|
||||
apply_offset, BarlineKind, LayoutContent, LogicalLayoutIR, PlacedClef, PlacedKeySignature,
|
||||
ScoreVersion, StaffContent,
|
||||
RepeatContent, RepeatPlacement, ScoreVersion, StaffContent,
|
||||
};
|
||||
use crate::provenance::{
|
||||
manifestation_layout_id, LayoutObjectId, Provenance, SynthesisInstanceKey, SynthesisKind,
|
||||
|
|
@ -524,6 +525,15 @@ const KEY_SIG_START: f32 = 2.7; // x where a key signature begins (just after th
|
|||
const KEY_ACC_X: f32 = 0.9; // x advance per key-signature accidental
|
||||
const TIME_SIG_X: f32 = 0.5; // a time signature sits this far right of its barline
|
||||
const TIME_DIGIT_X: f32 = 0.8; // x advance per time-signature digit
|
||||
// Repeat/volta engraving defaults (Minimal tier; SMuFL engraving-default
|
||||
// neighborhood, not solver-negotiated).
|
||||
const REPEAT_DOTS_SEPARATION: f32 = 0.16; // gap between the dot pair and the barline it decorates
|
||||
const VOLTA_Y: f32 = 6.5; // the bracket line, above the top staff's bottom line
|
||||
const VOLTA_HOOK: f32 = 1.4; // the descending hook at each bracket end
|
||||
const VOLTA_LINE_THICKNESS: f32 = 0.16; // SMuFL repeatEndingLineThickness default
|
||||
const VOLTA_TEXT_X: f32 = 0.4; // the first ending digit sits this far right of the bracket start
|
||||
const VOLTA_TEXT_DROP: f32 = 1.3; // ending-digit baseline, below the bracket line
|
||||
const VOLTA_ENDING_GAP: f32 = 0.5; // extra gap between successive ending numbers
|
||||
|
||||
/// The horizontal half-reach of an emitted `PositionWithin` region, in staff
|
||||
/// spaces. The constrained stage performs no casting-off, so a region imposes
|
||||
|
|
@ -576,6 +586,26 @@ const KEY_SIG_SYNTHESIS: SynthesisRegistryId = SynthesisRegistryId(0x4B45_5953_4
|
|||
/// id, so they are synthesized from the measure.
|
||||
const TIME_SIG_SYNTHESIS: SynthesisRegistryId = SynthesisRegistryId(0x54_494D_4553_4947); // "TIMESIG"
|
||||
|
||||
/// The registry id for **repeat-barline synthesis**: a repeat sign drawn where
|
||||
/// no measure barline stands (a mid-measure boundary, a region edge without a
|
||||
/// final barline) or the dot pair beside a final barline. The repeat
|
||||
/// structure's own exact provenance stays on its traced anchor, so every ink
|
||||
/// primitive it owns is synthesized from it. The instance key is
|
||||
/// `(boundary site << 32) | staff index` — a **semantic** identity (site 0 =
|
||||
/// the owner's start boundary, 1 = its end; a structure has one of each, and
|
||||
/// each lands on one column), so the id survives unrelated edits where a
|
||||
/// positional column rank would re-derive.
|
||||
const REPEAT_BARLINE_SYNTHESIS: SynthesisRegistryId = SynthesisRegistryId(0x5245_5045_4154_424C); // "REPEATBL"
|
||||
|
||||
/// The registry id for **volta-bracket synthesis**: each bracket's three
|
||||
/// strokes and its ending-number digit glyphs, synthesized from the owning
|
||||
/// repeat structure. The instance key is `(volta index << 64) | element`,
|
||||
/// elements `0..=2` the strokes (line, start hook, end hook) and `3 +` the
|
||||
/// digits in drawing order — the element field is 64 bits wide so an
|
||||
/// adversarially long endings list cannot bleed into the volta-index bits
|
||||
/// (the same non-overlap discipline as [`ledger_line_key`]).
|
||||
const VOLTA_SYNTHESIS: SynthesisRegistryId = SynthesisRegistryId(0x564F_4C54_4142_524B); // "VOLTABRK"
|
||||
|
||||
/// Flattens [`LogicalLayoutIR`] into [`ConstrainedLayoutIR`], engraving each
|
||||
/// layout object into the notation primitive that represents it: a **glyph** for
|
||||
/// the SMuFL objects (a pitch's notehead at its clef-relative staff position, a
|
||||
|
|
@ -600,6 +630,14 @@ const TIME_SIG_SYNTHESIS: SynthesisRegistryId = SynthesisRegistryId(0x54_494D_45
|
|||
/// model does not contain. Structural objects with no Minimal-tier glyph
|
||||
/// (regions, voices, ties, slurs, beams, …) are carried as zero-extent traced
|
||||
/// anchors so provenance survives, pending their engraving in a higher tier.
|
||||
///
|
||||
/// **Repeat structures** draw real ink: a boundary of a barline-drawing kind
|
||||
/// morphs the coinciding measure barline into the composite SMuFL repeat sign
|
||||
/// (or stands alone at its own column when no measure barline coincides; an
|
||||
/// end repeat closing on the final barline adds the dot pair beside it), and
|
||||
/// each volta draws a bracket above the top staff with its ending numbers as
|
||||
/// digit glyphs. The structure's exact provenance stays on its traced anchor;
|
||||
/// all of its ink is synthesized from it.
|
||||
pub fn to_constrained(logical: &LogicalLayoutIR) -> ConstrainedLayoutIR {
|
||||
try_to_constrained(logical).expect("LogicalLayoutIR is malformed")
|
||||
}
|
||||
|
|
@ -698,6 +736,12 @@ pub fn try_to_constrained(
|
|||
// between clef and first note must fit it, so the first note column shifts
|
||||
// right by it (zero when no staff declares a key — layout unchanged).
|
||||
let mut key_sig_accs = 0usize;
|
||||
// This region's repeat structures (engraving content projected by the
|
||||
// logical stage), and every (column, staff) a measure's own barline
|
||||
// occupies — repeat signs replace a coinciding measure barline (pass 2
|
||||
// morphs its glyph) and stand alone elsewhere.
|
||||
let mut repeats: Vec<(RepeatStructureId, &RepeatContent)> = Vec::new();
|
||||
let mut measure_cols: BTreeSet<(ColumnKey, StaffId)> = BTreeSet::new();
|
||||
|
||||
for object in ®ion.objects {
|
||||
let staff = object.staff();
|
||||
|
|
@ -792,13 +836,32 @@ pub fn try_to_constrained(
|
|||
event_rests.insert(eid, segs);
|
||||
}
|
||||
(TypedObjectId::Measure(_), LayoutContent::Measure(measure)) => {
|
||||
keys.insert(measure_column(measure));
|
||||
let key = measure_column(measure);
|
||||
keys.insert(key.clone());
|
||||
if let Some(s) = staff {
|
||||
measure_cols.insert((key, s));
|
||||
}
|
||||
}
|
||||
(TypedObjectId::Measure(_), _) => {
|
||||
// Pass 2 renders malformed/missing measure content as a final
|
||||
// barline, so collect that fallback column here instead of
|
||||
// letting the fallible conversion panic.
|
||||
keys.insert(ColumnKey::End);
|
||||
if let Some(s) = staff {
|
||||
measure_cols.insert((ColumnKey::End, s));
|
||||
}
|
||||
}
|
||||
(TypedObjectId::RepeatStructure(id), LayoutContent::Repeat(content)) => {
|
||||
// A barline-drawing repeat's boundaries are real spacing
|
||||
// columns (a mid-measure boundary mints one of its own).
|
||||
if content.barlines {
|
||||
for placement in [&content.start, &content.end] {
|
||||
if let Some(key) = placement_column_key(placement) {
|
||||
keys.insert(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
repeats.push((id, content));
|
||||
}
|
||||
(TypedObjectId::StaffInstance(_), LayoutContent::Staff(content)) => {
|
||||
// The staff instance's clef glyph occupies the lead column. The
|
||||
|
|
@ -821,6 +884,58 @@ pub fn try_to_constrained(
|
|||
}
|
||||
}
|
||||
|
||||
// The repeat-barline marks: which columns carry a repeat boundary,
|
||||
// facing which way, owned by which structures. Marks from distinct
|
||||
// repeats merge (an end meeting a start draws the combined sign).
|
||||
let mut marks: BTreeMap<ColumnKey, RepeatMark> = BTreeMap::new();
|
||||
for (id, content) in &repeats {
|
||||
if !content.barlines {
|
||||
continue;
|
||||
}
|
||||
for (placement, is_start) in [(&content.start, true), (&content.end, false)] {
|
||||
let Some(key) = placement_column_key(placement) else {
|
||||
continue;
|
||||
};
|
||||
let mark = marks.entry(key).or_default();
|
||||
let site = if is_start {
|
||||
mark.start = true;
|
||||
0u8
|
||||
} else {
|
||||
mark.end = true;
|
||||
1u8
|
||||
};
|
||||
if !mark.sources.contains(id) {
|
||||
mark.sources.push(*id);
|
||||
}
|
||||
let candidate = (*id, site);
|
||||
mark.owner = Some(match mark.owner {
|
||||
None => candidate,
|
||||
Some(current) => current.min(candidate),
|
||||
});
|
||||
}
|
||||
}
|
||||
// An end-facing sign's ink reaches well left of its column (the plain
|
||||
// barline sits at the column; `repeat_sign_x` right-aligns the sign's
|
||||
// heavy line to it), so the column must clear the previous one by that
|
||||
// reach — the same separation mechanism accidentals use. Without it
|
||||
// the sign overlaps the preceding note column in the source geometry.
|
||||
for (key, mark) in &marks {
|
||||
if !mark.end || !matches!(key, ColumnKey::Timed(..)) {
|
||||
continue;
|
||||
}
|
||||
let name = repeat_sign_name(mark.start, mark.end);
|
||||
let left_reach = -(repeat_sign_x(name, 0.0)
|
||||
+ metrics(name)
|
||||
.expect("repeat sign metrics are bundled")
|
||||
.bounding_box()
|
||||
.left
|
||||
.0);
|
||||
if left_reach > 0.0 {
|
||||
let entry = column_overhang.entry(key.clone()).or_insert(0.0);
|
||||
*entry = entry.max(left_reach);
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 1b — turn the collected column keys into a table: each gets an x
|
||||
// (the lead at the clef, timed columns spread by rank, the final-barline
|
||||
// column at the right) and a spring slot. The table is sorted by
|
||||
|
|
@ -1142,17 +1257,30 @@ pub fn try_to_constrained(
|
|||
Some(LayoutContent::Measure(measure)) => measure_column(measure),
|
||||
_ => ColumnKey::End,
|
||||
};
|
||||
// A repeat boundary on this measure's own barline column
|
||||
// morphs the barline into the composite repeat sign — the
|
||||
// sign *replaces* the plain barline, keeping the measure's
|
||||
// exact provenance verbatim (the round-trip provenance
|
||||
// floor compares it exactly; repeat-edit invalidation is
|
||||
// carried by the score version, which any edit changes).
|
||||
// The final barline never morphs — an end repeat there
|
||||
// draws its dot pair beside it instead (emitted with the
|
||||
// standalone signs below), so the casting-off solver's
|
||||
// final-barline classification stays truthful.
|
||||
let name = if key == ColumnKey::End {
|
||||
"barlineFinal"
|
||||
} else {
|
||||
"barlineSingle"
|
||||
match marks.get(&key) {
|
||||
Some(mark) => repeat_sign_name(mark.start, mark.end),
|
||||
None => "barlineSingle",
|
||||
}
|
||||
};
|
||||
let info = column(&key);
|
||||
// The barline glyph's origin is its lower end — Bravura barlines
|
||||
// run 0..4 staff spaces *up* from the origin — so anchoring it at
|
||||
// the staff bottom (`yo`) makes it connect the bottom and top
|
||||
// staff lines rather than float above the midline.
|
||||
let baseline = Point::new(info.x, yo);
|
||||
let baseline = Point::new(repeat_sign_x(name, info.x), yo);
|
||||
emit.glyph(provenance, name, baseline, band_of(staff), staff, info.slot);
|
||||
// The time signature this measure introduces: numerator over
|
||||
// denominator, just right of the barline, each digit a
|
||||
|
|
@ -1161,7 +1289,9 @@ pub fn try_to_constrained(
|
|||
// representative subset).
|
||||
if let Some(LayoutContent::Measure(measure)) = content {
|
||||
if let Some(time_signature) = measure.time_signature {
|
||||
let center_x = info.x + TIME_SIG_X;
|
||||
// A morphed repeat sign's ink extends right of the
|
||||
// plain barline span; the time signature clears it.
|
||||
let center_x = info.x + TIME_SIG_X + repeat_sign_right_extension(name);
|
||||
// The digit glyphs are centred on their baseline, so the
|
||||
// numerator's baseline sits on the upper half of the
|
||||
// staff (≈ y 3) and the denominator's on the lower (≈ y 1).
|
||||
|
|
@ -1170,7 +1300,7 @@ pub fn try_to_constrained(
|
|||
(1u8, time_signature.denominator, yo + 1.0),
|
||||
];
|
||||
for (role, value, baseline_y) in lines {
|
||||
let digits = digits_of(value);
|
||||
let digits = digits_of(u32::from(value));
|
||||
let count = digits.len() as f32;
|
||||
for (i, digit) in digits.iter().enumerate() {
|
||||
let x =
|
||||
|
|
@ -1194,13 +1324,152 @@ pub fn try_to_constrained(
|
|||
}
|
||||
}
|
||||
}
|
||||
// Region, Voice, GraphicObject, and every cross-cutting structure
|
||||
// (ties, slurs, beams, tuplets, spanners, markers, …) have no
|
||||
// Minimal-tier glyph; a zero-extent traced anchor keeps them.
|
||||
TypedObjectId::RepeatStructure(_) => {
|
||||
// The structure's exact provenance rides its traced anchor
|
||||
// (uniform with every other cross-cutting structure); all
|
||||
// of its ink — the standalone signs below and the volta
|
||||
// brackets here — is synthesized from it.
|
||||
emit.stroke(anchor(provenance, Point::new(default_x, yo)));
|
||||
let Some(LayoutContent::Repeat(repeat)) = content else {
|
||||
continue;
|
||||
};
|
||||
// Volta brackets sit above the region's top staff: a
|
||||
// horizontal line with a descending hook at each end and
|
||||
// the ending numbers (time-signature digit glyphs — the
|
||||
// Minimal tier has no text primitive) under its left end.
|
||||
let top_staff = staff_order.first().copied();
|
||||
let yo_top = top_staff.map(&y_origin).unwrap_or(0.0);
|
||||
for (index, volta) in repeat.voltas.iter().enumerate() {
|
||||
let Some(start) = placement_column(&volta.start, &columns) else {
|
||||
continue;
|
||||
};
|
||||
let Some(end) = placement_column(&volta.end, &columns) else {
|
||||
continue;
|
||||
};
|
||||
if end.x <= start.x {
|
||||
// A reversed or zero-width span draws no bracket
|
||||
// (advisory volta well-formedness is the authoring
|
||||
// layer's jurisdiction, not the engraver's).
|
||||
continue;
|
||||
}
|
||||
let y = yo_top + VOLTA_Y;
|
||||
let volta_provenance = |element: u128| {
|
||||
Provenance::synthesized(
|
||||
provenance.source,
|
||||
SynthesisKind::Registered(VOLTA_SYNTHESIS),
|
||||
SynthesisInstanceKey(((index as u128) << 64) | element),
|
||||
provenance.dependencies.clone(),
|
||||
)
|
||||
};
|
||||
emit.stroke(line_stroke(
|
||||
volta_provenance(0),
|
||||
Point::new(start.x, y),
|
||||
Point::new(end.x, y),
|
||||
VOLTA_LINE_THICKNESS,
|
||||
));
|
||||
for (element, x) in [(1u128, start.x), (2u128, end.x)] {
|
||||
emit.stroke(line_stroke(
|
||||
volta_provenance(element),
|
||||
Point::new(x, y),
|
||||
Point::new(x, y - VOLTA_HOOK),
|
||||
VOLTA_LINE_THICKNESS,
|
||||
));
|
||||
}
|
||||
let mut cursor = start.x + VOLTA_TEXT_X;
|
||||
let mut element = 3u128;
|
||||
for ending in &volta.endings {
|
||||
for digit in digits_of(*ending) {
|
||||
emit.glyph(
|
||||
&volta_provenance(element),
|
||||
time_digit(digit),
|
||||
Point::new(cursor, y - VOLTA_TEXT_DROP),
|
||||
band_of(top_staff),
|
||||
top_staff,
|
||||
start.slot,
|
||||
);
|
||||
cursor += TIME_DIGIT_X;
|
||||
element += 1;
|
||||
}
|
||||
cursor += VOLTA_ENDING_GAP;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Region, Voice, GraphicObject, and every other cross-cutting
|
||||
// structure (ties, slurs, beams, tuplets, spanners, markers, …)
|
||||
// have no Minimal-tier glyph; a zero-extent traced anchor keeps
|
||||
// them.
|
||||
_ => emit.stroke(anchor(provenance, Point::new(default_x, yo))),
|
||||
}
|
||||
}
|
||||
|
||||
// The repeat signs the measures could not carry: a composite sign at a
|
||||
// column with no measure barline on that staff (a mid-measure boundary,
|
||||
// a region edge without a final barline), and the dot pair beside a
|
||||
// final barline an end repeat closes on. One sign per (column, staff),
|
||||
// synthesized from the mark's owner under its semantic
|
||||
// `(boundary site, staff)` instance key, depending on every structure
|
||||
// that shares the mark.
|
||||
for (key, info) in columns.iter() {
|
||||
let Some(mark) = marks.get(key) else {
|
||||
continue;
|
||||
};
|
||||
// At the region-closing column only an END repeat has ink — the
|
||||
// dot pair beside a final barline, or the full sign where no
|
||||
// final barline stands on that staff. A START boundary there
|
||||
// draws nothing on any staff: a sign after the region close
|
||||
// would misstate the structure.
|
||||
if *key == ColumnKey::End && !mark.end {
|
||||
continue;
|
||||
}
|
||||
let (owner, site) = mark
|
||||
.owner
|
||||
.expect("a repeat mark records at least one owning boundary");
|
||||
let deps: Vec<TypedObjectId> = mark
|
||||
.sources
|
||||
.iter()
|
||||
.map(|id| TypedObjectId::RepeatStructure(*id))
|
||||
.collect();
|
||||
for (staff_index, staff) in staff_order.iter().enumerate() {
|
||||
let covered = measure_cols.contains(&(key.clone(), *staff));
|
||||
let (name, x) = if *key == ColumnKey::End {
|
||||
if covered {
|
||||
let dots_width = metrics("repeatDots")
|
||||
.expect("repeatDots metrics are bundled")
|
||||
.bounding_box()
|
||||
.right
|
||||
.0;
|
||||
("repeatDots", info.x - REPEAT_DOTS_SEPARATION - dots_width)
|
||||
} else {
|
||||
// No final barline on this staff (its run continues in
|
||||
// a later region): the full end sign, never a
|
||||
// start-facing one.
|
||||
("repeatRight", repeat_sign_x("repeatRight", info.x))
|
||||
}
|
||||
} else {
|
||||
if covered {
|
||||
// The measure's own barline morphed into the sign.
|
||||
continue;
|
||||
}
|
||||
let name = repeat_sign_name(mark.start, mark.end);
|
||||
(name, repeat_sign_x(name, info.x))
|
||||
};
|
||||
let provenance = Provenance::synthesized(
|
||||
TypedObjectId::RepeatStructure(owner),
|
||||
SynthesisKind::Registered(REPEAT_BARLINE_SYNTHESIS),
|
||||
SynthesisInstanceKey(((site as u128) << 32) | staff_index as u128),
|
||||
deps.clone(),
|
||||
);
|
||||
emit.glyph(
|
||||
&provenance,
|
||||
name,
|
||||
Point::new(x, y_origin(*staff)),
|
||||
band_of(Some(*staff)),
|
||||
Some(*staff),
|
||||
info.slot,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let Emit {
|
||||
column_members,
|
||||
region_glyphs,
|
||||
|
|
@ -1696,6 +1965,93 @@ fn measure_column(measure: &crate::logical::MeasureContent) -> ColumnKey {
|
|||
}
|
||||
}
|
||||
|
||||
/// A repeat boundary landing on one spacing column: which way its sign faces
|
||||
/// (a coinciding end+start faces both) and the structures that own it, in
|
||||
/// region emission order.
|
||||
#[derive(Default)]
|
||||
struct RepeatMark {
|
||||
start: bool,
|
||||
end: bool,
|
||||
sources: Vec<RepeatStructureId>,
|
||||
/// The synthesis owner: the smallest `(structure id, boundary site)` that
|
||||
/// landed on this column, `site` 0 for the structure's start boundary and
|
||||
/// 1 for its end. A structure has one boundary of each site, so the pair
|
||||
/// is a **semantic** instance identity — stable under unrelated edits,
|
||||
/// where a positional (column-rank) key would re-derive whenever any
|
||||
/// earlier column appeared or disappeared.
|
||||
owner: Option<(RepeatStructureId, u8)>,
|
||||
}
|
||||
|
||||
/// The spacing column a repeat boundary's *sign* occupies, if placeable: the
|
||||
/// barline-role column at its resolved time (minting one is pass 1's job), or
|
||||
/// the region-closing column.
|
||||
fn placement_column_key(placement: &RepeatPlacement) -> Option<ColumnKey> {
|
||||
match placement {
|
||||
RepeatPlacement::At(time) => Some(ColumnKey::Timed(time.clone(), ColumnRole::Barline)),
|
||||
RepeatPlacement::RegionEnd => Some(ColumnKey::End),
|
||||
RepeatPlacement::Unresolved => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The realized column a volta boundary aligns with: the barline column at its
|
||||
/// resolved time when one exists, else the note column there, else the
|
||||
/// region-closing column for a region-end placement. `None` — the bracket
|
||||
/// draws no ink — when nothing at that time was laid out.
|
||||
fn placement_column<'a>(
|
||||
placement: &RepeatPlacement,
|
||||
columns: &'a BTreeMap<ColumnKey, ColumnInfo>,
|
||||
) -> Option<&'a ColumnInfo> {
|
||||
match placement {
|
||||
RepeatPlacement::At(time) => [ColumnRole::Barline, ColumnRole::Note]
|
||||
.iter()
|
||||
.find_map(|role| columns.get(&ColumnKey::Timed(time.clone(), *role))),
|
||||
RepeatPlacement::RegionEnd => columns.get(&ColumnKey::End),
|
||||
RepeatPlacement::Unresolved => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The composite SMuFL sign for a repeat boundary: a start opens the passage to
|
||||
/// its right (`repeatLeft`), an end closes the passage to its left
|
||||
/// (`repeatRight`), and a coinciding end+start draws the combined sign. The
|
||||
/// no-facing case is unreachable (a [`RepeatMark`] records at least one), but
|
||||
/// falls back to the plain barline rather than panicking on malformed input.
|
||||
fn repeat_sign_name(start: bool, end: bool) -> &'static str {
|
||||
match (start, end) {
|
||||
(true, false) => "repeatLeft",
|
||||
(false, true) => "repeatRight",
|
||||
(true, true) => "repeatRightLeft",
|
||||
(false, false) => "barlineSingle",
|
||||
}
|
||||
}
|
||||
|
||||
/// The baseline x aligning a repeat sign's **heavy line** with the boundary the
|
||||
/// plain barline marks (a Minimal approximation from the glyph boxes): a start
|
||||
/// sign's heavy line is its left edge, so it draws from the column; an end
|
||||
/// sign's is its right edge, so it right-aligns to the plain barline's span;
|
||||
/// the combined sign centers its shared heavy line on it.
|
||||
fn repeat_sign_x(name: &str, column_x: f32) -> f32 {
|
||||
let right = |name: &str| metrics(name).map_or(0.0, |m| m.bounding_box().right.0);
|
||||
match name {
|
||||
"repeatRight" => column_x + right("barlineSingle") - right("repeatRight"),
|
||||
"repeatRightLeft" => column_x + (right("barlineSingle") - right("repeatRightLeft")) / 2.0,
|
||||
_ => column_x,
|
||||
}
|
||||
}
|
||||
|
||||
/// How far a repeat sign's ink extends **right** of the plain-barline span it
|
||||
/// replaced, in staff spaces — zero for the plain barlines themselves, so
|
||||
/// repeat-free geometry is untouched. The morphed measure's time-signature
|
||||
/// digits shift right by this so they clear the sign.
|
||||
fn repeat_sign_right_extension(name: &str) -> f32 {
|
||||
match name {
|
||||
"repeatLeft" | "repeatRight" | "repeatRightLeft" => {
|
||||
let right = |name: &str| metrics(name).map_or(0.0, |m| m.bounding_box().right.0);
|
||||
(repeat_sign_x(name, 0.0) + right(name) - right("barlineSingle")).max(0.0)
|
||||
}
|
||||
_ => 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// The key signature in force at a staff's start, by resolved time (the same
|
||||
/// rule as the active clef), or `None` when the staff declares no key. Absence
|
||||
/// means no signature drawn — distinct from a declared C-major (which also draws
|
||||
|
|
@ -1723,8 +2079,9 @@ fn key_accidentals_for(content: &StaffContent) -> Vec<KeyAccidental> {
|
|||
}
|
||||
}
|
||||
|
||||
/// The decimal digits of a time-signature number, most significant first.
|
||||
fn digits_of(value: u16) -> Vec<u8> {
|
||||
/// The decimal digits of a displayed number (time-signature numerals, volta
|
||||
/// ending numbers), most significant first.
|
||||
fn digits_of(value: u32) -> Vec<u8> {
|
||||
if value == 0 {
|
||||
return vec![0];
|
||||
}
|
||||
|
|
@ -3312,4 +3669,404 @@ mod tests {
|
|||
Err(LayoutTransformError::RegionSourceIsNotRegion(_))
|
||||
));
|
||||
}
|
||||
|
||||
// --- Repeat barlines and volta brackets (schema-major-2 E1) ------------
|
||||
|
||||
use epiphany_core::{
|
||||
AnchorOffset, BeatGroup, MusicalDuration, PowerOfTwo, RationalTime, RegionEdge, RepeatKind,
|
||||
RepeatStructure, RepeatStructureId, Score, TimeSignature, TimeSignatureDisplay,
|
||||
TimeSignatureId, Volta,
|
||||
};
|
||||
|
||||
/// `valid_score_rich` with four extra measures appended to region A's first
|
||||
/// staff instance (region-anchored at whole-note offsets 1..=4), so repeat
|
||||
/// boundaries have real barline columns to land on. Returns the score and
|
||||
/// the five measure ids in order; the last measure's barline is the
|
||||
/// region-final one (region A's staff manifests nowhere else).
|
||||
fn repeat_ready_score(seed: u64) -> (Score, Vec<MeasureId>) {
|
||||
let mut score = valid_score_rich(seed);
|
||||
let region_id = score.canvas.regions[0].id;
|
||||
let extra: Vec<MeasureId> = (0..4).map(|_| score.identity.mint()).collect();
|
||||
let instance = &mut score.canvas.regions[0]
|
||||
.content
|
||||
.staff_instances_mut()
|
||||
.expect("region A is staff-based")[0];
|
||||
let mut ids = vec![instance.measures[0].id];
|
||||
for (index, id) in extra.iter().enumerate() {
|
||||
instance.measures.push(epiphany_core::Measure {
|
||||
id: *id,
|
||||
start: TimeAnchor::Region {
|
||||
id: region_id,
|
||||
edge: RegionEdge::Start,
|
||||
offset: AnchorOffset::Musical(MusicalDuration(
|
||||
RationalTime::new(index as i64 + 1, 1).expect("nonzero"),
|
||||
)),
|
||||
},
|
||||
time_signature: None,
|
||||
explicit_number: None,
|
||||
number_visibility: Default::default(),
|
||||
});
|
||||
}
|
||||
ids.extend(extra);
|
||||
(score, ids)
|
||||
}
|
||||
|
||||
fn measure_start(id: MeasureId) -> TimeAnchor {
|
||||
TimeAnchor::Measure {
|
||||
id,
|
||||
position: MeasurePosition::Start,
|
||||
offset: AnchorOffset::Zero,
|
||||
}
|
||||
}
|
||||
|
||||
fn named<'a>(constrained: &'a ConstrainedLayoutIR, name: &str) -> Vec<&'a GlyphObject> {
|
||||
constrained
|
||||
.glyphs
|
||||
.iter()
|
||||
.filter(|glyph| glyph.glyph.as_str() == name)
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repeat_boundaries_morph_their_measure_barlines_and_voltas_draw_brackets() {
|
||||
let (mut score, m) = repeat_ready_score(21);
|
||||
// The measure gaining the start sign also introduces a 4/4, so the
|
||||
// test covers the time signature clearing the wider sign's ink.
|
||||
let ts_id: TimeSignatureId = score.identity.mint();
|
||||
let beat = || BeatGroup {
|
||||
duration: MusicalDuration(RationalTime::new(1, 4).expect("nonzero")),
|
||||
subdivision: None,
|
||||
accent: 1,
|
||||
};
|
||||
score.time_signatures.push(
|
||||
TimeSignature::new(
|
||||
ts_id,
|
||||
TimeSignatureDisplay::Standard {
|
||||
numerator: 4,
|
||||
denominator: PowerOfTwo::new(4).expect("4 is a power of two"),
|
||||
},
|
||||
MusicalDuration(RationalTime::new(1, 1).expect("nonzero")),
|
||||
vec![beat(), beat(), beat(), beat()],
|
||||
)
|
||||
.expect("4/4 beat groups sum to a whole note"),
|
||||
);
|
||||
score.canvas.regions[0]
|
||||
.content
|
||||
.staff_instances_mut()
|
||||
.expect("region A is staff-based")[0]
|
||||
.measures
|
||||
.iter_mut()
|
||||
.find(|measure| measure.id == m[1])
|
||||
.expect("m1 exists")
|
||||
.time_signature = Some(ts_id);
|
||||
let a: RepeatStructureId = score.identity.mint();
|
||||
let b: RepeatStructureId = score.identity.mint();
|
||||
score.cross_cutting.repeats.push(RepeatStructure {
|
||||
id: a,
|
||||
start: measure_start(m[1]),
|
||||
end: measure_start(m[2]),
|
||||
kind: RepeatKind::SimpleRepeat { count: 2 },
|
||||
voltas: Vec::new(),
|
||||
});
|
||||
score.cross_cutting.repeats.push(RepeatStructure {
|
||||
id: b,
|
||||
start: measure_start(m[2]),
|
||||
end: measure_start(m[3]),
|
||||
kind: RepeatKind::Volta,
|
||||
voltas: vec![Volta {
|
||||
endings: vec![2, 3],
|
||||
start: measure_start(m[2]),
|
||||
end: measure_start(m[3]),
|
||||
}],
|
||||
});
|
||||
let constrained = to_constrained(&to_logical(&score));
|
||||
|
||||
// The three boundary columns morph their measure barlines: a start
|
||||
// sign, the combined sign where A's end meets B's start, and an end
|
||||
// sign — each keeping the measure's own exact provenance (a morph is a
|
||||
// name change, not a new primitive; the round-trip provenance floor
|
||||
// depends on that).
|
||||
for name in ["repeatLeft", "repeatRightLeft", "repeatRight"] {
|
||||
let signs = named(&constrained, name);
|
||||
assert_eq!(signs.len(), 1, "exactly one {name} sign");
|
||||
let sign = signs[0];
|
||||
assert!(
|
||||
matches!(sign.provenance.source, TypedObjectId::Measure(_)),
|
||||
"{name} is the measure's own (morphed) barline"
|
||||
);
|
||||
assert!(sign.provenance.synthesis.is_none());
|
||||
}
|
||||
// Exactly three plain barlines morphed away: five measures draw
|
||||
// m0..=m3 at their start columns and m4 at the region end.
|
||||
assert_eq!(named(&constrained, "barlineSingle").len(), 1);
|
||||
assert_eq!(named(&constrained, "barlineFinal").len(), 1);
|
||||
|
||||
// The volta bracket: three strokes (line + two hooks) above the staff,
|
||||
// synthesized from the repeat, spanning m2's column to m3's.
|
||||
let bracket: Vec<&Stroke> = constrained
|
||||
.strokes
|
||||
.iter()
|
||||
.filter(|stroke| {
|
||||
matches!(
|
||||
stroke.provenance.synthesis,
|
||||
Some(SynthesisKind::Registered(k)) if k == VOLTA_SYNTHESIS
|
||||
) && matches!(stroke.provenance.source, TypedObjectId::RepeatStructure(id) if id == b)
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(bracket.len(), 3, "bracket line plus two hooks");
|
||||
let line = bracket
|
||||
.iter()
|
||||
.find(|stroke| stroke.from.y == stroke.to.y)
|
||||
.expect("the bracket has a horizontal line");
|
||||
assert!(
|
||||
line.from.y.0 > STAFF_HEIGHT,
|
||||
"the bracket sits above the staff"
|
||||
);
|
||||
assert!(
|
||||
line.to.x.0 > line.from.x.0,
|
||||
"the bracket spans left to right"
|
||||
);
|
||||
for hook in bracket.iter().filter(|stroke| stroke.from.y != stroke.to.y) {
|
||||
assert_eq!(hook.from.x, hook.to.x, "hooks are vertical");
|
||||
assert!(hook.to.y.0 < hook.from.y.0, "hooks descend from the line");
|
||||
}
|
||||
// The ending numbers "2 3" draw as digit glyphs synthesized from the
|
||||
// repeat, under the bracket line.
|
||||
for digit in ["timeSig2", "timeSig3"] {
|
||||
let glyphs = named(&constrained, digit);
|
||||
let volta_digit = glyphs
|
||||
.iter()
|
||||
.find(|glyph| {
|
||||
matches!(glyph.provenance.source, TypedObjectId::RepeatStructure(id) if id == b)
|
||||
})
|
||||
.unwrap_or_else(|| panic!("volta ending digit {digit} is drawn"));
|
||||
assert!(volta_digit.baseline.y.0 > STAFF_HEIGHT);
|
||||
assert!(volta_digit.baseline.y.0 < line.from.y.0);
|
||||
}
|
||||
|
||||
// The 4/4 the morphed measure introduces clears the sign's ink: every
|
||||
// measure-owned digit starts right of the repeatLeft's right edge.
|
||||
let start_sign = named(&constrained, "repeatLeft")[0];
|
||||
let sign_right = start_sign.baseline.x.0 + start_sign.bounding_box.right.0;
|
||||
let measure_digits: Vec<&GlyphObject> = constrained
|
||||
.glyphs
|
||||
.iter()
|
||||
.filter(|glyph| {
|
||||
glyph.glyph.as_str().starts_with("timeSig")
|
||||
&& matches!(glyph.provenance.source, TypedObjectId::Measure(_))
|
||||
})
|
||||
.collect();
|
||||
assert!(!measure_digits.is_empty(), "the 4/4 draws digit glyphs");
|
||||
for digit in measure_digits {
|
||||
assert!(
|
||||
digit.baseline.x.0 + digit.bounding_box.left.0 >= sign_right,
|
||||
"time-signature digits clear the repeat sign's ink"
|
||||
);
|
||||
}
|
||||
|
||||
// The full pipeline round-trips: every source recovered exactly once,
|
||||
// every synthesized primitive with a distinct stable id.
|
||||
crate::roundtrip::round_trip(&score);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn off_grid_and_region_end_boundaries_stand_alone() {
|
||||
let (mut score, m) = repeat_ready_score(22);
|
||||
// Region A's triplet events sit at 0, 1/12, 2/12 — the second event is
|
||||
// mid-measure, off every barline column.
|
||||
let mid_measure_event = score.canvas.regions[0].staff_instances()[0].voices[0].events[1];
|
||||
let c: RepeatStructureId = score.identity.mint();
|
||||
score.cross_cutting.repeats.push(RepeatStructure {
|
||||
id: c,
|
||||
start: TimeAnchor::Event {
|
||||
id: mid_measure_event,
|
||||
offset: AnchorOffset::Zero,
|
||||
},
|
||||
end: TimeAnchor::Measure {
|
||||
id: m[4],
|
||||
position: MeasurePosition::End,
|
||||
offset: AnchorOffset::Zero,
|
||||
},
|
||||
kind: RepeatKind::SimpleRepeat { count: 2 },
|
||||
voltas: Vec::new(),
|
||||
});
|
||||
let constrained = to_constrained(&to_logical(&score));
|
||||
|
||||
// The mid-measure start mints its own column and stands alone,
|
||||
// synthesized from the repeat (no measure barline morphs).
|
||||
let left = named(&constrained, "repeatLeft");
|
||||
assert_eq!(left.len(), 1);
|
||||
assert!(matches!(left[0].provenance.source, TypedObjectId::RepeatStructure(id) if id == c));
|
||||
assert!(matches!(
|
||||
left[0].provenance.synthesis,
|
||||
Some(SynthesisKind::Registered(k)) if k == REPEAT_BARLINE_SYNTHESIS
|
||||
));
|
||||
|
||||
// The region-end close keeps the final barline and adds the dot pair
|
||||
// beside it (never a morph, never a second barline).
|
||||
let finals = named(&constrained, "barlineFinal");
|
||||
assert_eq!(finals.len(), 1, "the final barline stands");
|
||||
let dots = named(&constrained, "repeatDots");
|
||||
assert_eq!(dots.len(), 1, "one dot pair beside it");
|
||||
assert!(named(&constrained, "repeatRight").is_empty());
|
||||
assert!(
|
||||
dots[0].baseline.x.0 < finals[0].baseline.x.0,
|
||||
"the dots face the repeated passage, left of the final barline"
|
||||
);
|
||||
assert_eq!(
|
||||
dots[0].horizontal_slot, finals[0].horizontal_slot,
|
||||
"the dots share the region-closing column"
|
||||
);
|
||||
|
||||
crate::roundtrip::round_trip(&score);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_repeats_merge_one_standalone_sign_that_clears_the_notes() {
|
||||
let (mut score, m) = repeat_ready_score(24);
|
||||
// Region A's triplet events sit at 0, 1/12, 2/12; the second event is
|
||||
// an off-grid boundary two structures share: one ends there, the
|
||||
// other starts there.
|
||||
let mid_measure_event = score.canvas.regions[0].staff_instances()[0].voices[0].events[1];
|
||||
let event_anchor = TimeAnchor::Event {
|
||||
id: mid_measure_event,
|
||||
offset: AnchorOffset::Zero,
|
||||
};
|
||||
let g1: RepeatStructureId = score.identity.mint();
|
||||
let g2: RepeatStructureId = score.identity.mint();
|
||||
score.cross_cutting.repeats.push(RepeatStructure {
|
||||
id: g1,
|
||||
start: measure_start(m[1]),
|
||||
end: event_anchor.clone(),
|
||||
kind: RepeatKind::SimpleRepeat { count: 2 },
|
||||
voltas: Vec::new(),
|
||||
});
|
||||
score.cross_cutting.repeats.push(RepeatStructure {
|
||||
id: g2,
|
||||
start: event_anchor,
|
||||
end: measure_start(m[2]),
|
||||
kind: RepeatKind::SimpleRepeat { count: 2 },
|
||||
voltas: Vec::new(),
|
||||
});
|
||||
let constrained = to_constrained(&to_logical(&score));
|
||||
|
||||
// One merged combined sign, owned by the smallest structure id,
|
||||
// depending on both structures.
|
||||
let combined = named(&constrained, "repeatRightLeft");
|
||||
assert_eq!(combined.len(), 1, "the shared boundary draws one sign");
|
||||
let sign = combined[0];
|
||||
assert!(matches!(
|
||||
sign.provenance.synthesis,
|
||||
Some(SynthesisKind::Registered(k)) if k == REPEAT_BARLINE_SYNTHESIS
|
||||
));
|
||||
assert!(
|
||||
matches!(sign.provenance.source, TypedObjectId::RepeatStructure(id) if id == g1.min(g2))
|
||||
);
|
||||
for id in [g1, g2] {
|
||||
assert!(sign
|
||||
.provenance
|
||||
.dependencies
|
||||
.contains(&TypedObjectId::RepeatStructure(id)));
|
||||
}
|
||||
// The end-facing sign's leftward ink reserved its reach (the
|
||||
// accidental-overhang mechanism), so it crosses no notehead's ink.
|
||||
let sign_left = sign.baseline.x.0 + sign.bounding_box.left.0;
|
||||
let sign_right = sign.baseline.x.0 + sign.bounding_box.right.0;
|
||||
for head in constrained
|
||||
.glyphs
|
||||
.iter()
|
||||
.filter(|glyph| glyph.glyph.as_str().starts_with("notehead"))
|
||||
{
|
||||
let head_left = head.baseline.x.0 + head.bounding_box.left.0;
|
||||
let head_right = head.baseline.x.0 + head.bounding_box.right.0;
|
||||
assert!(
|
||||
sign_right <= head_left || head_right <= sign_left,
|
||||
"the repeat sign's ink must not cross a notehead's"
|
||||
);
|
||||
}
|
||||
|
||||
crate::roundtrip::round_trip(&score);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jump_kinds_and_unresolved_boundaries_draw_no_ink() {
|
||||
let (mut score, m) = repeat_ready_score(23);
|
||||
let replica = score.identity.replica_id;
|
||||
// A DalSegno repeat: barlines are a jump-mark tranche, not E1 — only
|
||||
// the traced anchor is emitted. Its volta bracket still draws: the
|
||||
// voltas list is kind-independent.
|
||||
let d: RepeatStructureId = score.identity.mint();
|
||||
score.cross_cutting.repeats.push(RepeatStructure {
|
||||
id: d,
|
||||
start: measure_start(m[1]),
|
||||
end: measure_start(m[3]),
|
||||
kind: RepeatKind::DalSegno {
|
||||
segno: measure_start(m[2]),
|
||||
end_target: measure_start(m[1]),
|
||||
},
|
||||
voltas: vec![Volta {
|
||||
endings: vec![1],
|
||||
start: measure_start(m[2]),
|
||||
end: measure_start(m[3]),
|
||||
}],
|
||||
});
|
||||
// A simple repeat whose start dangles (a decoded score may hold one);
|
||||
// the resolved end still morphs, the dangling side draws nothing.
|
||||
let e: RepeatStructureId = score.identity.mint();
|
||||
score.cross_cutting.repeats.push(RepeatStructure {
|
||||
id: e,
|
||||
start: TimeAnchor::Measure {
|
||||
id: MeasureId::new(replica, 9_999_999),
|
||||
position: MeasurePosition::Start,
|
||||
offset: AnchorOffset::Zero,
|
||||
},
|
||||
end: measure_start(m[2]),
|
||||
kind: RepeatKind::SimpleRepeat { count: 2 },
|
||||
voltas: Vec::new(),
|
||||
});
|
||||
// A bare wall-clock repeat: nothing pins it to a region, so it draws
|
||||
// no ink anywhere (its placements are Unresolved by rule).
|
||||
let f: RepeatStructureId = score.identity.mint();
|
||||
score.cross_cutting.repeats.push(RepeatStructure {
|
||||
id: f,
|
||||
start: TimeAnchor::WallClock {
|
||||
time: WallClockTime(5),
|
||||
},
|
||||
end: TimeAnchor::WallClock {
|
||||
time: WallClockTime(10),
|
||||
},
|
||||
kind: RepeatKind::SimpleRepeat { count: 2 },
|
||||
voltas: Vec::new(),
|
||||
});
|
||||
let constrained = to_constrained(&to_logical(&score));
|
||||
|
||||
assert!(named(&constrained, "repeatLeft").is_empty());
|
||||
assert!(named(&constrained, "repeatRightLeft").is_empty());
|
||||
assert!(named(&constrained, "repeatDots").is_empty());
|
||||
let right = named(&constrained, "repeatRight");
|
||||
assert_eq!(right.len(), 1, "the resolved end still closes");
|
||||
// The jump kind's volta bracket draws even though its barlines do not.
|
||||
let bracket_strokes = constrained
|
||||
.strokes
|
||||
.iter()
|
||||
.filter(|stroke| {
|
||||
matches!(
|
||||
stroke.provenance.synthesis,
|
||||
Some(SynthesisKind::Registered(k)) if k == VOLTA_SYNTHESIS
|
||||
)
|
||||
})
|
||||
.count();
|
||||
assert_eq!(bracket_strokes, 3, "the DalSegno's volta bracket draws");
|
||||
// All three structures keep their traced anchors.
|
||||
for id in [d, e, f] {
|
||||
assert!(
|
||||
constrained.strokes.iter().any(|stroke| {
|
||||
stroke.from == stroke.to
|
||||
&& matches!(stroke.provenance.source,
|
||||
TypedObjectId::RepeatStructure(s) if s == id)
|
||||
}),
|
||||
"repeat {id:?} keeps its zero-extent traced anchor"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -247,6 +247,14 @@ pub const BRAVURA_METRICS: &[GlyphMetric] = &[
|
|||
// spaces) *up* from it, spanning the staff when anchored at the bottom line.
|
||||
GlyphMetric::new("barlineSingle", 147, [0, 0, 148, 4096]),
|
||||
GlyphMetric::new("barlineFinal", 938, [0, 0, 934, 4096]),
|
||||
// Repeat signs: composite barline glyphs (heavy/thin lines plus dots),
|
||||
// staff-spanning like the barlines above; `repeatDots` is the bare dot
|
||||
// pair drawn beside a barline (its box sits in the middle two staff
|
||||
// spaces when anchored at the bottom line).
|
||||
GlyphMetric::new("repeatLeft", 1507, [0, 0, 1500, 4096]),
|
||||
GlyphMetric::new("repeatRight", 1503, [4, 0, 1504, 4096]),
|
||||
GlyphMetric::new("repeatRightLeft", 2490, [4, 0, 2491, 4096]),
|
||||
GlyphMetric::new("repeatDots", 410, [0, 1302, 410, 2745]),
|
||||
GlyphMetric::new("dynamicForte", 1491, [-578, -623, 1491, 1819]),
|
||||
GlyphMetric::new("dynamicPiano", 1495, [-365, -582, 1500, 1123]),
|
||||
];
|
||||
|
|
@ -261,6 +269,16 @@ pub fn all_available<'a>(names: impl IntoIterator<Item = &'a str>) -> bool {
|
|||
names.into_iter().all(|n| metrics(n).is_some())
|
||||
}
|
||||
|
||||
/// Whether a glyph name draws a **measure-boundary barline**: the plain and
|
||||
/// final barlines plus the composite repeat signs (which replace a plain
|
||||
/// barline at a repeat boundary). The casting-off solver classifies
|
||||
/// measure-boundary columns with this instead of hard-coding the engraver's
|
||||
/// name choices. `repeatDots` is decoration drawn *beside* a barline, not a
|
||||
/// barline itself, so it is excluded.
|
||||
pub fn is_barline_glyph(name: &str) -> bool {
|
||||
name.starts_with("barline") || matches!(name, "repeatLeft" | "repeatRight" | "repeatRightLeft")
|
||||
}
|
||||
|
||||
/// A glyph's rendering data (Chapter 7 §"Glyph Catalog Interface":
|
||||
/// `GlyphRenderData`). The full outline/bitmap vocabulary (`PathCommand`,
|
||||
/// `GlyphBitmap`) belongs to the out-of-core renderer; v0 carries an opaque
|
||||
|
|
@ -469,6 +487,25 @@ mod tests {
|
|||
assert_eq!(catalog.identity(&["runtimeGlyph"]).metrics_hash, [7; 32]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn barline_classification_covers_the_repeat_signs_but_not_the_dots() {
|
||||
for name in [
|
||||
"barlineSingle",
|
||||
"barlineFinal",
|
||||
"repeatLeft",
|
||||
"repeatRight",
|
||||
"repeatRightLeft",
|
||||
] {
|
||||
assert!(
|
||||
is_barline_glyph(name),
|
||||
"{name} is a measure-boundary barline"
|
||||
);
|
||||
}
|
||||
for name in ["repeatDots", "noteheadBlack", "timeSig2", "gClef"] {
|
||||
assert!(!is_barline_glyph(name), "{name} is not a barline");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_bundled_name_is_unique_and_resolvable() {
|
||||
let mut seen = BTreeSet::new();
|
||||
|
|
|
|||
|
|
@ -108,9 +108,10 @@ pub use engraving::{
|
|||
OverrideKind, OverrideOrigin, OverridePriority, OverrideTarget, PluginId, Timestamp,
|
||||
};
|
||||
pub use glyph::{
|
||||
all_available, bravura_catalog_identity, metrics, metrics_hash_for, BravuraCatalog, FontId,
|
||||
GlyphAnchor, GlyphBitmap, GlyphCatalog, GlyphCatalogIdentity, GlyphMetric, GlyphReference,
|
||||
GlyphRenderData, PathCommand, SemVer, SmuflVersion, BRAVURA_METRICS, BRAVURA_VERSION,
|
||||
all_available, bravura_catalog_identity, is_barline_glyph, metrics, metrics_hash_for,
|
||||
BravuraCatalog, FontId, GlyphAnchor, GlyphBitmap, GlyphCatalog, GlyphCatalogIdentity,
|
||||
GlyphMetric, GlyphReference, GlyphRenderData, PathCommand, SemVer, SmuflVersion,
|
||||
BRAVURA_METRICS, BRAVURA_VERSION,
|
||||
};
|
||||
pub use hittest::{HitRegion, HitShape, HitTestMap, PrimitiveRef};
|
||||
pub use logical::{
|
||||
|
|
@ -118,10 +119,10 @@ pub use logical::{
|
|||
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,
|
||||
NotePitch, PlacedClef, PlacedComponent, PlacedKeySignature, RepeatContent, RepeatPlacement,
|
||||
RestContent, RestLayout, ScoreVersion, SlurLayout, SpannerLayout, StaffContent, StaffLayout,
|
||||
TextLayout, TieLayout, TimeSignatureContent, TimeSignatureDisplayLayout, TrajectoryLayout,
|
||||
TupletDisplayLayout, VerticalExtent, VoltaContent,
|
||||
};
|
||||
pub use provenance::{
|
||||
continuation_instance_key, manifestation_layout_id, stable_layout_id, synthesized_layout_id,
|
||||
|
|
|
|||
|
|
@ -33,12 +33,14 @@ use crate::spatial::Transform2D;
|
|||
use crate::time_axis::{time_axis_of, TimeAxisModel, TimePoint};
|
||||
|
||||
/// 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.
|
||||
/// the note value, spelled pitches, clef, key, measure, or repeat 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, and the
|
||||
/// cross-cutting structures other than repeats) 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.
|
||||
|
|
@ -55,6 +57,10 @@ pub enum LayoutContent {
|
|||
/// A measure: whether it ends the staff (a final barline) and the time
|
||||
/// signature in force, when this measure introduces one.
|
||||
Measure(MeasureContent),
|
||||
/// A repeat structure: whether its kind draws repeat barlines, where its
|
||||
/// boundaries land, and its volta brackets — resolved to layout placements
|
||||
/// at projection time (the constrained pass has no score access).
|
||||
Repeat(RepeatContent),
|
||||
}
|
||||
|
||||
/// The clef and key-signature sequences in force across a staff instance,
|
||||
|
|
@ -153,6 +159,48 @@ pub struct TimeSignatureContent {
|
|||
pub denominator: u16,
|
||||
}
|
||||
|
||||
/// A repeat structure's engraving content (Chapter 5 §"Cross-Cutting
|
||||
/// Structures": `RepeatStructure`), with every boundary resolved to a
|
||||
/// [`RepeatPlacement`] so the constrained pass can place ink without going
|
||||
/// back to the score graph.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct RepeatContent {
|
||||
/// Whether the structure's kind draws repeat barlines at its boundaries
|
||||
/// (`SimpleRepeat` and `Volta`). The jump kinds (`DaCapo`/`DalSegno`) draw
|
||||
/// no Minimal-tier ink — their marks (segno, coda, instruction text) need
|
||||
/// a text primitive and belong to a later tranche.
|
||||
pub barlines: bool,
|
||||
pub start: RepeatPlacement,
|
||||
pub end: RepeatPlacement,
|
||||
/// The structure's volta brackets, in authored order.
|
||||
pub voltas: Vec<VoltaContent>,
|
||||
}
|
||||
|
||||
/// One volta bracket: its pass numbers and its resolved span.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct VoltaContent {
|
||||
/// The pass numbers this ending plays on (1-based; authored order kept).
|
||||
pub endings: Vec<u32>,
|
||||
pub start: RepeatPlacement,
|
||||
pub end: RepeatPlacement,
|
||||
}
|
||||
|
||||
/// Where a repeat boundary lands on its region's spacing axis.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub enum RepeatPlacement {
|
||||
/// At the spacing column of this resolved time.
|
||||
At(TimePoint),
|
||||
/// At the region's closing column (the final-barline position) — a
|
||||
/// zero-offset anchor to the region's end edge or to the end of its last
|
||||
/// measure, whose *time* the Minimal slice cannot resolve but whose
|
||||
/// *column* is exactly the region-closing one.
|
||||
RegionEnd,
|
||||
/// Not resolvable in the Minimal slice (dangling target, unknown metric
|
||||
/// region end, clock-mismatched offset). The boundary draws no ink; the
|
||||
/// structure keeps its traced anchor.
|
||||
Unresolved,
|
||||
}
|
||||
|
||||
/// 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
|
||||
|
|
@ -596,6 +644,19 @@ pub fn to_logical(score: &Score) -> LogicalLayoutIR {
|
|||
if !seen.insert(provenance.stable_id) {
|
||||
continue;
|
||||
}
|
||||
// A repeat structure carries its resolved engraving content (barline
|
||||
// verdict + placements). Every other cross-cutting object is
|
||||
// structural in this tier.
|
||||
let content = match src {
|
||||
TypedObjectId::RepeatStructure(id) => score
|
||||
.cross_cutting
|
||||
.repeats
|
||||
.iter()
|
||||
.find(|rp| rp.id == id)
|
||||
.map(|rp| repeat_content(score, rp))
|
||||
.unwrap_or_default(),
|
||||
_ => LayoutContent::Structural,
|
||||
};
|
||||
let mut anchored_regions = Vec::new();
|
||||
let mut anchored_staves = BTreeSet::new();
|
||||
for region in ®ions {
|
||||
|
|
@ -628,7 +689,9 @@ pub fn to_logical(score: &Score) -> LogicalLayoutIR {
|
|||
.expect("anchored region was collected from this vector");
|
||||
region
|
||||
.objects
|
||||
.push(LayoutObject::from_projection(provenance, staff));
|
||||
.push(LayoutObject::from_projection_with_content(
|
||||
provenance, staff, content,
|
||||
));
|
||||
}
|
||||
[] => {
|
||||
// Wall-clock-only annotations have no graph anchor from which
|
||||
|
|
@ -636,9 +699,15 @@ pub fn to_logical(score: &Score) -> LogicalLayoutIR {
|
|||
if let Some(first) = regions.first_mut() {
|
||||
first
|
||||
.objects
|
||||
.push(LayoutObject::from_projection(provenance, staff));
|
||||
.push(LayoutObject::from_projection_with_content(
|
||||
provenance, staff, content,
|
||||
));
|
||||
}
|
||||
}
|
||||
// A multi-region spanning object keeps no engraving content: the
|
||||
// cross-region path places a single traced anchor at its first
|
||||
// region (a Minimal-slice boundary — repeat ink across region
|
||||
// boundaries belongs to a later tranche).
|
||||
_ => cross_region.push(CrossRegionObject {
|
||||
provenance,
|
||||
regions: anchored_regions,
|
||||
|
|
@ -934,6 +1003,95 @@ fn origin_time() -> TimePoint {
|
|||
TimePoint::Musical(MusicalPosition::origin())
|
||||
}
|
||||
|
||||
/// A repeat structure's engraving content: its kind's barline verdict plus
|
||||
/// every boundary (structure and volta) resolved to a [`RepeatPlacement`].
|
||||
fn repeat_content(score: &Score, rp: &epiphany_core::RepeatStructure) -> LayoutContent {
|
||||
use epiphany_core::RepeatKind;
|
||||
LayoutContent::Repeat(RepeatContent {
|
||||
barlines: matches!(rp.kind, RepeatKind::SimpleRepeat { .. } | RepeatKind::Volta),
|
||||
start: repeat_placement(score, &rp.start),
|
||||
end: repeat_placement(score, &rp.end),
|
||||
voltas: rp
|
||||
.voltas
|
||||
.iter()
|
||||
.map(|volta| VoltaContent {
|
||||
endings: volta.endings.clone(),
|
||||
start: repeat_placement(score, &volta.start),
|
||||
end: repeat_placement(score, &volta.end),
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Resolves a repeat boundary anchor to a [`RepeatPlacement`]. Unlike
|
||||
/// [`resolve_time_anchor`], failure is **honest** (`Unresolved` draws no ink)
|
||||
/// rather than falling back to the origin — a repeat sign at a false position
|
||||
/// would misstate the musical structure. Two region-closing shapes resolve to
|
||||
/// [`RepeatPlacement::RegionEnd`] by *column* even though their *time* is
|
||||
/// unknowable in a metric region: a zero-offset anchor to an existing region's
|
||||
/// end edge, and a zero-offset anchor to the end of a staff instance's last
|
||||
/// measure. Offsets are tested for zero-ness **by value** (`Zero`,
|
||||
/// `Musical(0)`, `WallClock(0)` all qualify — value-equal decoded anchors must
|
||||
/// not render differently).
|
||||
///
|
||||
/// A bare **wall-clock** boundary is `Unresolved`: it references no graph
|
||||
/// object, so nothing ties it to the region it would draw in — the sign would
|
||||
/// land wherever the wall-clock time happens to *sort* among that region's
|
||||
/// columns (after every musical column, in a metric region), a false position.
|
||||
/// Repeat ink for wall-clock-synchronized material is a later tranche.
|
||||
/// (Wall-clock `TimePoint`s resolved *through* an event/measure/region anchor
|
||||
/// are fine — the referenced object pins the region and its clock.)
|
||||
fn repeat_placement(score: &Score, anchor: &TimeAnchor) -> RepeatPlacement {
|
||||
const DEPTH: u8 = 16;
|
||||
match anchor {
|
||||
TimeAnchor::Region {
|
||||
id,
|
||||
edge: RegionEdge::End,
|
||||
offset,
|
||||
} if offset_is_zero(offset)
|
||||
&& score.canvas.regions.iter().any(|region| region.id == *id) =>
|
||||
{
|
||||
RepeatPlacement::RegionEnd
|
||||
}
|
||||
TimeAnchor::Measure {
|
||||
id,
|
||||
position: MeasurePosition::End,
|
||||
offset,
|
||||
} if offset_is_zero(offset) && is_last_measure(score, *id) => RepeatPlacement::RegionEnd,
|
||||
TimeAnchor::WallClock { .. } => RepeatPlacement::Unresolved,
|
||||
_ => match resolve_time_anchor_inner(score, anchor, DEPTH) {
|
||||
Some(time) => RepeatPlacement::At(time),
|
||||
None => RepeatPlacement::Unresolved,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether an anchor offset is zero **by value**: the `Zero` variant or a
|
||||
/// zero-magnitude duration of either clock (both are representable and decode
|
||||
/// verbatim; the placement verdict must not depend on the representation).
|
||||
fn offset_is_zero(offset: &AnchorOffset) -> bool {
|
||||
match offset {
|
||||
AnchorOffset::Zero => true,
|
||||
AnchorOffset::Musical(duration) => *duration == MusicalDuration::zero(),
|
||||
AnchorOffset::WallClock(duration) => duration.0 == 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether `id` names the **last** measure of the staff instance that owns it
|
||||
/// (first-match walk, the same discipline as [`measure_anchor_time`]).
|
||||
fn is_last_measure(score: &Score, id: epiphany_core::MeasureId) -> bool {
|
||||
for (_, instance) in score.staff_instances() {
|
||||
if let Some(index) = instance
|
||||
.measures
|
||||
.iter()
|
||||
.position(|measure| measure.id == id)
|
||||
{
|
||||
return index + 1 == instance.measures.len();
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// 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.
|
||||
|
|
@ -1086,9 +1244,13 @@ pub(crate) fn cross_cutting_objects(score: &Score) -> Vec<(TypedObjectId, Vec<Ty
|
|||
));
|
||||
}
|
||||
for rp in &cc.repeats {
|
||||
let deps = [&rp.start, &rp.end]
|
||||
.iter()
|
||||
.filter_map(|a| time_anchor_dep(a))
|
||||
// THE single site-set walk (core `RepeatStructure::anchor_sites`):
|
||||
// volta spans and jump targets are real invalidation dependencies and
|
||||
// real region-membership evidence, exactly like start/end.
|
||||
let deps = rp
|
||||
.anchor_sites()
|
||||
.into_iter()
|
||||
.filter_map(time_anchor_dep)
|
||||
.collect();
|
||||
out.push((TypedObjectId::RepeatStructure(rp.id), deps));
|
||||
}
|
||||
|
|
@ -1473,4 +1635,198 @@ mod tests {
|
|||
.expect("tie must be in its events' region");
|
||||
assert_eq!(object.staff(), Some(expected_staff));
|
||||
}
|
||||
|
||||
// --- Repeat projection (schema-major-2 E1) ------------------------------
|
||||
|
||||
#[test]
|
||||
fn repeat_placements_resolve_honestly() {
|
||||
use epiphany_core::{EventId, Measure, MeasureId, MeasurePosition, RegionId};
|
||||
|
||||
let mut score = valid_score_rich(31);
|
||||
let region_a = score.canvas.regions[0].id;
|
||||
let replica = score.identity.replica_id;
|
||||
// Append a second measure so the generator's measure is not the last.
|
||||
let m1: MeasureId = score.identity.mint();
|
||||
let instance = &mut score.canvas.regions[0]
|
||||
.content
|
||||
.staff_instances_mut()
|
||||
.expect("region A is staff-based")[0];
|
||||
let m0 = instance.measures[0].id;
|
||||
instance.measures.push(Measure {
|
||||
id: m1,
|
||||
start: TimeAnchor::Region {
|
||||
id: region_a,
|
||||
edge: RegionEdge::Start,
|
||||
offset: AnchorOffset::Musical(duration(1, 1)),
|
||||
},
|
||||
time_signature: None,
|
||||
explicit_number: None,
|
||||
number_visibility: Default::default(),
|
||||
});
|
||||
|
||||
// A zero-offset end-edge anchor on an existing region closes on the
|
||||
// region-final column, whose *time* a metric region cannot state.
|
||||
assert_eq!(
|
||||
repeat_placement(
|
||||
&score,
|
||||
&TimeAnchor::Region {
|
||||
id: region_a,
|
||||
edge: RegionEdge::End,
|
||||
offset: AnchorOffset::Zero,
|
||||
}
|
||||
),
|
||||
RepeatPlacement::RegionEnd
|
||||
);
|
||||
// Zero-ness is a *value* judgement: a Musical(0) offset is the same
|
||||
// anchor and must earn the same verdict as the Zero variant.
|
||||
assert_eq!(
|
||||
repeat_placement(
|
||||
&score,
|
||||
&TimeAnchor::Region {
|
||||
id: region_a,
|
||||
edge: RegionEdge::End,
|
||||
offset: AnchorOffset::Musical(MusicalDuration::zero()),
|
||||
}
|
||||
),
|
||||
RepeatPlacement::RegionEnd
|
||||
);
|
||||
// A dangling region target never binds some other region's close.
|
||||
assert_eq!(
|
||||
repeat_placement(
|
||||
&score,
|
||||
&TimeAnchor::Region {
|
||||
id: RegionId::new(replica, 4_242_424),
|
||||
edge: RegionEdge::End,
|
||||
offset: AnchorOffset::Zero,
|
||||
}
|
||||
),
|
||||
RepeatPlacement::Unresolved
|
||||
);
|
||||
// The last measure's end closes on the region; an earlier measure's
|
||||
// end resolves to the next measure's start time.
|
||||
let measure_end = |id: MeasureId| TimeAnchor::Measure {
|
||||
id,
|
||||
position: MeasurePosition::End,
|
||||
offset: AnchorOffset::Zero,
|
||||
};
|
||||
assert_eq!(
|
||||
repeat_placement(&score, &measure_end(m1)),
|
||||
RepeatPlacement::RegionEnd
|
||||
);
|
||||
assert_eq!(
|
||||
repeat_placement(
|
||||
&score,
|
||||
&TimeAnchor::Measure {
|
||||
id: m1,
|
||||
position: MeasurePosition::End,
|
||||
offset: AnchorOffset::Musical(MusicalDuration::zero()),
|
||||
}
|
||||
),
|
||||
RepeatPlacement::RegionEnd
|
||||
);
|
||||
assert_eq!(
|
||||
repeat_placement(&score, &measure_end(m0)),
|
||||
RepeatPlacement::At(TimePoint::Musical(position(1, 1)))
|
||||
);
|
||||
// A dangling event target is honestly unresolved — never the origin
|
||||
// fallback, which would draw a repeat sign at a false position.
|
||||
assert_eq!(
|
||||
repeat_placement(
|
||||
&score,
|
||||
&TimeAnchor::Event {
|
||||
id: EventId::new(replica, 9_999_999),
|
||||
offset: AnchorOffset::Zero,
|
||||
}
|
||||
),
|
||||
RepeatPlacement::Unresolved
|
||||
);
|
||||
// A bare wall-clock boundary references no graph object, so nothing
|
||||
// pins it to a region — it draws no ink rather than landing wherever
|
||||
// the time happens to sort among a metric region's columns.
|
||||
assert_eq!(
|
||||
repeat_placement(
|
||||
&score,
|
||||
&TimeAnchor::WallClock {
|
||||
time: WallClockTime(7),
|
||||
}
|
||||
),
|
||||
RepeatPlacement::Unresolved
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repeats_project_content_and_volta_anchors_decide_region_membership() {
|
||||
use epiphany_core::{RepeatKind, RepeatStructure, RepeatStructureId, Volta};
|
||||
|
||||
let mut score = valid_score_rich(32);
|
||||
let region_a = score.canvas.regions[0].id;
|
||||
let region_b = score.canvas.regions[1].id;
|
||||
let edge = |id, edge| TimeAnchor::Region {
|
||||
id,
|
||||
edge,
|
||||
offset: AnchorOffset::Zero,
|
||||
};
|
||||
// r1: a volta repeat anchored wholly inside region A — it lands there
|
||||
// carrying resolved content.
|
||||
let r1: RepeatStructureId = score.identity.mint();
|
||||
score.cross_cutting.repeats.push(RepeatStructure {
|
||||
id: r1,
|
||||
start: edge(region_a, RegionEdge::Start),
|
||||
end: edge(region_a, RegionEdge::End),
|
||||
kind: RepeatKind::Volta,
|
||||
voltas: vec![Volta {
|
||||
endings: vec![1, 2],
|
||||
start: edge(region_a, RegionEdge::Start),
|
||||
end: edge(region_a, RegionEdge::End),
|
||||
}],
|
||||
});
|
||||
// r2: a DalSegno whose segno points into region B — the jump target is
|
||||
// real membership evidence, so the repeat spans regions (and the
|
||||
// cross-region path carries no engraving content).
|
||||
let r2: RepeatStructureId = score.identity.mint();
|
||||
score.cross_cutting.repeats.push(RepeatStructure {
|
||||
id: r2,
|
||||
start: edge(region_a, RegionEdge::Start),
|
||||
end: edge(region_a, RegionEdge::End),
|
||||
kind: RepeatKind::DalSegno {
|
||||
segno: edge(region_b, RegionEdge::Start),
|
||||
end_target: edge(region_a, RegionEdge::Start),
|
||||
},
|
||||
voltas: Vec::new(),
|
||||
});
|
||||
|
||||
let ir = to_logical(&score);
|
||||
let object = ir.regions[0]
|
||||
.objects
|
||||
.iter()
|
||||
.find(|object| object.provenance().source == TypedObjectId::RepeatStructure(r1))
|
||||
.expect("the in-region repeat lands in region A");
|
||||
let LayoutContent::Repeat(content) = object.content() else {
|
||||
panic!("the repeat carries resolved engraving content");
|
||||
};
|
||||
assert!(content.barlines, "a Volta-kind repeat draws barlines");
|
||||
assert_eq!(
|
||||
content.start,
|
||||
RepeatPlacement::At(TimePoint::Musical(MusicalPosition::origin()))
|
||||
);
|
||||
assert_eq!(content.end, RepeatPlacement::RegionEnd);
|
||||
assert_eq!(content.voltas.len(), 1);
|
||||
assert_eq!(content.voltas[0].endings, vec![1, 2]);
|
||||
|
||||
let spanning = ir
|
||||
.cross_region
|
||||
.iter()
|
||||
.find(|object| object.provenance.source == TypedObjectId::RepeatStructure(r2))
|
||||
.expect("the segno target pulls the repeat across regions");
|
||||
assert_eq!(spanning.regions, vec![region_a, region_b]);
|
||||
assert!(
|
||||
ir.regions.iter().all(|region| {
|
||||
region
|
||||
.objects
|
||||
.iter()
|
||||
.all(|o| o.provenance().source != TypedObjectId::RepeatStructure(r2))
|
||||
}),
|
||||
"a cross-region repeat is not also placed in a region"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,3 +103,14 @@ See `spec/PASS12_BATCH.md` (rows P12-I1, P12-I2, P12-I3). Most relevant here:
|
|||
`MUSCLOID` tag and `layout-ir` provenance routes through it. The renderer traces
|
||||
provenance by the (now `MUSCLOID`-tagged) `stable_id`; only the `data-prov` hex in
|
||||
the goldens changed (the ids are non-canonical).
|
||||
|
||||
## Repeat glyphs bundled + `repeat` glyph class (E1, 2026-07-07)
|
||||
|
||||
The E1 repeat tranche added `repeatLeft`, `repeatRight`, `repeatRightLeft`,
|
||||
and `repeatDots` to the extractor's `NAMES` set; outlines, companion
|
||||
layout-ir metrics, and the embedded font subset were regenerated from the
|
||||
same SHA-pinned `bravura-1.392` (fontTools 4.63.0 preserved, so the subset
|
||||
diff is content-only). `GlyphClass` gained a `Repeat` class (token
|
||||
`repeat`) so snapshots and `data-class` attributes separate repeat signs
|
||||
from plain barlines. Volta ending numerals arrive as `timeSig` digit glyphs
|
||||
(there is still no free-text primitive — unchanged).
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -137,6 +137,30 @@ pub(crate) const BRAVURA_OUTLINES: &[BravuraOutline] = &[
|
|||
path: "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",
|
||||
bbox: [0.0, -0.5, 1.688, 0.5],
|
||||
},
|
||||
BravuraOutline {
|
||||
name: "repeatDots",
|
||||
codepoint: 0xE043,
|
||||
path: "M0.2 2.68C0.088 2.68 0 2.592 0 2.48C0 2.372 0.088 2.28 0.2 2.28C0.308 2.28 0.4 2.372 0.4 2.48C0.4 2.592 0.308 2.68 0.2 2.68ZM0.2 1.672C0.088 1.672 0 1.584 0 1.472C0 1.36 0.088 1.272 0.2 1.272C0.308 1.272 0.4 1.36 0.4 1.472C0.4 1.584 0.308 1.672 0.2 1.672Z",
|
||||
bbox: [0.0, 1.272, 0.4, 2.68],
|
||||
},
|
||||
BravuraOutline {
|
||||
name: "repeatLeft",
|
||||
codepoint: 0xE040,
|
||||
path: "M0.768 0H0.912V4H0.768ZM0 0H0.5V4H0ZM1.264 2.288C1.376 2.288 1.464 2.376 1.464 2.488C1.464 2.6 1.376 2.688 1.264 2.688C1.156 2.688 1.064 2.6 1.064 2.488C1.064 2.376 1.156 2.288 1.264 2.288ZM1.264 1.28C1.376 1.28 1.464 1.368 1.464 1.48C1.464 1.588 1.376 1.68 1.264 1.68C1.156 1.68 1.064 1.588 1.064 1.48C1.064 1.368 1.156 1.28 1.264 1.28Z",
|
||||
bbox: [0.0, 0.0, 1.464, 4.0],
|
||||
},
|
||||
BravuraOutline {
|
||||
name: "repeatRight",
|
||||
codepoint: 0xE041,
|
||||
path: "M0.7 0V4H0.556V0ZM1.468 4H0.968V0H1.468ZM0.204 2.72C0.092 2.72 0.004 2.632 0.004 2.52C0.004 2.412 0.092 2.32 0.204 2.32C0.312 2.32 0.404 2.412 0.404 2.52C0.404 2.632 0.312 2.72 0.204 2.72ZM0.204 1.712C0.092 1.712 0.004 1.624 0.004 1.512C0.004 1.4 0.092 1.312 0.204 1.312C0.312 1.312 0.404 1.4 0.404 1.512C0.404 1.624 0.312 1.712 0.204 1.712Z",
|
||||
bbox: [0.004, 0.0, 1.468, 4.0],
|
||||
},
|
||||
BravuraOutline {
|
||||
name: "repeatRightLeft",
|
||||
codepoint: 0xE042,
|
||||
path: "M1.736 0H1.88V4H1.736ZM1.468 4H0.968V0H1.468ZM0.7 0V4H0.556V0ZM0.204 2.72C0.092 2.72 0.004 2.632 0.004 2.52C0.004 2.412 0.092 2.32 0.204 2.32C0.312 2.32 0.404 2.412 0.404 2.52C0.404 2.632 0.312 2.72 0.204 2.72ZM2.232 2.288C2.344 2.288 2.432 2.376 2.432 2.488C2.432 2.6 2.344 2.688 2.232 2.688C2.124 2.688 2.032 2.6 2.032 2.488C2.032 2.376 2.124 2.288 2.232 2.288ZM0.204 1.712C0.092 1.712 0.004 1.624 0.004 1.512C0.004 1.4 0.092 1.312 0.204 1.312C0.312 1.312 0.404 1.4 0.404 1.512C0.404 1.624 0.312 1.712 0.204 1.712ZM2.232 1.28C2.344 1.28 2.432 1.368 2.432 1.48C2.432 1.588 2.344 1.68 2.232 1.68C2.124 1.68 2.032 1.588 2.032 1.48C2.032 1.368 2.124 1.28 2.232 1.28Z",
|
||||
bbox: [0.004, 0.0, 2.432, 4.0],
|
||||
},
|
||||
BravuraOutline {
|
||||
name: "rest8th",
|
||||
codepoint: 0xE4E6,
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@ pub enum GlyphClass {
|
|||
Flag,
|
||||
TimeSignature,
|
||||
Barline,
|
||||
Repeat,
|
||||
Dynamic,
|
||||
AugmentationDot,
|
||||
Other,
|
||||
|
|
@ -129,6 +130,8 @@ impl GlyphClass {
|
|||
GlyphClass::TimeSignature
|
||||
} else if name.starts_with("barline") {
|
||||
GlyphClass::Barline
|
||||
} else if name.starts_with("repeat") {
|
||||
GlyphClass::Repeat
|
||||
} else if name.starts_with("dynamic") {
|
||||
GlyphClass::Dynamic
|
||||
} else if name == "augmentationDot" {
|
||||
|
|
@ -148,6 +151,7 @@ impl GlyphClass {
|
|||
GlyphClass::Flag => "flag",
|
||||
GlyphClass::TimeSignature => "timeSig",
|
||||
GlyphClass::Barline => "barline",
|
||||
GlyphClass::Repeat => "repeat",
|
||||
GlyphClass::Dynamic => "dynamic",
|
||||
GlyphClass::AugmentationDot => "augmentationDot",
|
||||
GlyphClass::Other => "other",
|
||||
|
|
|
|||
|
|
@ -37,6 +37,10 @@ fn fixtures() -> Vec<(&'static str, Score)> {
|
|||
"valid_score_rich",
|
||||
epiphany_core::generators::valid_score_rich(0x5EED),
|
||||
),
|
||||
(
|
||||
"ten_measure_with_repeats",
|
||||
epiphany_testkit::fixtures::ten_measure_with_repeats(0x000A_11CE),
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
fixture=ten_measure_with_repeats solver=engrave
|
||||
glyph_count=56
|
||||
path_count=56
|
||||
fallback_rect_count=0
|
||||
stroke_count=105
|
||||
provenance_count=161
|
||||
layer_count=1
|
||||
hard_constraint_count=95
|
||||
xml_well_formed=true
|
||||
view_box=[5.4999995 -28.60539 67.03889 23.104813]
|
||||
class_counts:
|
||||
barline=7
|
||||
clef=1
|
||||
notehead=40
|
||||
repeat=5
|
||||
timeSig=3
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="670.3889" height="231.0481" viewBox="0 0 67.0389 23.1048">
|
||||
<!-- epiphany-render-svg: glyphs are genuine Bravura SMuFL outlines inlined as paths; geometry is the resolved layout verbatim, no engraving performed here; every glyph and stroke carries a data-prov trace to its score-graph source -->
|
||||
<g transform="translate(-5.5 -5.5006) scale(1 -1)">
|
||||
<g data-layer="0">
|
||||
<line x1="8.5599" y1="-12.8926" x2="8.5599" y2="-12.8926" stroke="#000000" stroke-width="0" data-prov="01550dc9d118134419219044f188e28a" data-source-kind="6" data-kind="stroke"/>
|
||||
<line x1="7.565" y1="-12.8926" x2="70.4739" y2="-12.8926" stroke="#000000" stroke-width="0.13" data-prov="5235156954d3cdda987a3da1af222a55" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="7.565" y1="-11.8926" x2="70.4739" y2="-11.8926" stroke="#000000" stroke-width="0.13" data-prov="10cbe3e182e937b8e3d90a03397b2e97" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="7.565" y1="-10.8926" x2="70.4739" y2="-10.8926" stroke="#000000" stroke-width="0.13" data-prov="94825cd467fe6fb512d271e66b59674a" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="7.565" y1="-9.8926" x2="70.4739" y2="-9.8926" stroke="#000000" stroke-width="0.13" data-prov="bc2cd26cec8e4650eedcf04fbc91320f" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="7.565" y1="-8.8926" x2="70.4739" y2="-8.8926" stroke="#000000" stroke-width="0.13" data-prov="c46b9445a4c5ff909f50b13f9025c4a3" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="8.5599" y1="-12.8926" x2="8.5599" y2="-12.8926" stroke="#000000" stroke-width="0" data-prov="fe116eb75ff697f9f2451c10c05349c6" data-source-kind="2" data-kind="stroke"/>
|
||||
<line x1="14.5399" y1="-13.8926" x2="14.5399" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="dbd573fbc0c21f9b07a8537f658b048c" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="12.7444" y1="-13.8926" x2="14.5251" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="07ffb45f25e59b0955bd4e248522b2ef" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="16.6206" y1="-13.8926" x2="16.6206" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="276b6f72f2bcb818f84229b6833ce022" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="14.8251" y1="-13.8926" x2="16.6058" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="3539af5cd23dc6ca259a35fef4aa45c2" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="18.7012" y1="-13.8926" x2="18.7012" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="d1b0067ebb2b2b9115a77aaadc53bffe" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="16.9058" y1="-13.8926" x2="18.6864" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="9b43d5d2a83ce1086364f6f3ac4b5ba2" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="20.5663" y1="-13.8926" x2="20.5663" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="13303d184ca25b99835d71e21f3a96df" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="18.9864" y1="-13.8926" x2="20.7671" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="d6c7a9458a06532e1c7507e71363c0dd" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="24.6274" y1="-13.8926" x2="24.6274" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="aa33134cf72cebda65d1b2b427230541" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="22.8319" y1="-13.8926" x2="24.6126" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="20fbe0162b0bc4dd93e2c3e3da73bb83" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="26.7081" y1="-13.8926" x2="26.7081" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="01d06ae931cf05983e4b17744108297b" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="24.9126" y1="-13.8926" x2="26.6933" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="cd9abf9ed66ab876f68b9d88ce1e5199" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="28.7887" y1="-13.8926" x2="28.7887" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="47c9ce76466974ecfabe4a9cafb75c3c" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="26.9933" y1="-13.8926" x2="28.7739" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="7ade41165c11102f29e87cb09eb3f88f" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="30.6538" y1="-13.8926" x2="30.6538" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="e6d1c0a44d13be2700ad4381e1d518fa" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="29.0739" y1="-13.8926" x2="30.8546" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="32c2aa89808756c46e8b7afcfb2eb057" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="34.1501" y1="-13.8926" x2="34.1501" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="370a65aab516f6792e49a1ed28bd5cd9" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="32.3546" y1="-13.8926" x2="34.1352" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="0a3ae3c155d29c21e266e663a8b1b214" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="36.2307" y1="-13.8926" x2="36.2307" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="75b4bca159d5c28b10b2078bb457bb53" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="34.4352" y1="-13.8926" x2="36.2159" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="c27e3e835224dc54cef9b36818099cd0" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="38.3114" y1="-13.8926" x2="38.3114" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="c4531390972e55a43af2bd9f85e3c250" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="36.5159" y1="-13.8926" x2="38.2966" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="4b43990bc25bf4afffda3e2a7d4dcab7" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="40.1764" y1="-13.8926" x2="40.1764" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="888c6ef019fc4b0fc714672a4444c60c" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="38.5966" y1="-13.8926" x2="40.3772" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="8e120598410629bb86883f318071a6b5" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="43.6727" y1="-13.8926" x2="43.6727" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="6c70ad9006ed8fbb104d77eba3f2dbcb" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="41.8772" y1="-13.8926" x2="43.6579" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="6d2724bb96160f831b4e987ce6b4349a" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="45.7534" y1="-13.8926" x2="45.7534" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="debe82da2c34817a871d8a4cc5cc3fdd" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="43.9579" y1="-13.8926" x2="45.7386" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="7a9f84c8590c7e8156fd929e35153cf8" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="47.834" y1="-13.8926" x2="47.834" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="7b524c2e9e5657b68fd1a81337887c2e" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="46.0386" y1="-13.8926" x2="47.8192" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="c4a9602f838e0ea74a9fe68da12baff2" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="49.6994" y1="-13.8926" x2="49.6994" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="cb31fbbd4a3e3eb17ed06e65f40589e5" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="48.1192" y1="-13.8926" x2="49.8999" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="76860486e62be8520184bb3e8c284d94" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="54.7241" y1="-13.8926" x2="54.7241" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="bc1cc9bede7fc3c72827cfbf184cdb7e" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="52.9286" y1="-13.8926" x2="54.7093" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="a3fd2ae47f432e61881cb1e44a7108e6" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="56.8047" y1="-13.8926" x2="56.8047" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="95891bc2b58231a1618b5d422e6131b0" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="55.0093" y1="-13.8926" x2="56.7899" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="a9e2ee2be83c0a4978db517bf182efe9" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="58.8854" y1="-13.8926" x2="58.8854" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="ed21002861831422bdbb36e8f981d2a9" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="57.0899" y1="-13.8926" x2="58.8706" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="2bac60a88ddddcf403dd6edfb67b5bb0" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="60.7504" y1="-13.8926" x2="60.7504" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="e49748251746a6dbdf15aa8dd983e6e7" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="59.1706" y1="-13.8926" x2="60.9512" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="a592bc951f4afa64726aa30ccdd6ddfa" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="64.2467" y1="-13.8926" x2="64.2467" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="8d2938a01abfe451991829fbde6d2b94" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="62.4512" y1="-13.8926" x2="64.2319" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="c73f1aad686621adde07e7fe7b160ad7" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="66.3274" y1="-13.8926" x2="66.3274" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="88366a6edcce03db41d39d08b2616742" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="64.5319" y1="-13.8926" x2="66.3126" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="4e052448582a8fdc672f38b02dc205e1" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="68.408" y1="-13.8926" x2="68.408" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="77a10c7862f0ffce98d309bb71ce675f" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="66.6126" y1="-13.8926" x2="68.3932" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="68c691f2336e5a322116641b9f2f7054" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="70.2731" y1="-13.8926" x2="70.2731" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="cfa457e70519a1826f321a56cb3fd03b" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="68.6932" y1="-13.8926" x2="70.4739" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="3c3f0d6b5ab00374d7628cd6f9876bc9" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="11.3323" y1="-26.1054" x2="11.3323" y2="-22.6054" stroke="#000000" stroke-width="0.12" data-prov="273d04eeece605789035a29d80023c2c" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="9.5368" y1="-26.1054" x2="11.3175" y2="-26.1054" stroke="#000000" stroke-width="0.13" data-prov="9ec226573f8ea2320fc444c562163ddb" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="13.413" y1="-26.1054" x2="13.413" y2="-22.6054" stroke="#000000" stroke-width="0.12" data-prov="a23b09fda2784b566bf1de48e89a2baa" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="11.6175" y1="-26.1054" x2="13.3982" y2="-26.1054" stroke="#000000" stroke-width="0.13" data-prov="9e3345d5dddffbb9cf263a1708fc2589" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="15.4937" y1="-26.1054" x2="15.4937" y2="-22.6054" stroke="#000000" stroke-width="0.12" data-prov="edf9ca4611cfc17faace6241cae3b73d" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="13.6982" y1="-26.1054" x2="15.4788" y2="-26.1054" stroke="#000000" stroke-width="0.13" data-prov="ea06a5b83026202fbe3eed55ca3680c9" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="17.359" y1="-26.1054" x2="17.359" y2="-22.6054" stroke="#000000" stroke-width="0.12" data-prov="f1e3c0367633266d5f188e5bd6d98ad4" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="15.7788" y1="-26.1054" x2="17.5595" y2="-26.1054" stroke="#000000" stroke-width="0.13" data-prov="b8d324f0620ebb59f66346b61a4d214e" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="24.5798" y1="-26.1054" x2="24.5798" y2="-22.6054" stroke="#000000" stroke-width="0.12" data-prov="2bf824905032462ee8c9988b780feabf" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="22.7843" y1="-26.1054" x2="24.565" y2="-26.1054" stroke="#000000" stroke-width="0.13" data-prov="579f0f80918268f1ba03890d14832b56" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="26.6605" y1="-26.1054" x2="26.6605" y2="-22.6054" stroke="#000000" stroke-width="0.12" data-prov="08a65aebf8baaded74feb3ec7cc36d18" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="24.865" y1="-26.1054" x2="26.6456" y2="-26.1054" stroke="#000000" stroke-width="0.13" data-prov="f04f04c436808c53ab1c2b169d7f5a33" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="28.7411" y1="-26.1054" x2="28.7411" y2="-22.6054" stroke="#000000" stroke-width="0.12" data-prov="d155779804ad660f4466745153872349" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="26.9456" y1="-26.1054" x2="28.7263" y2="-26.1054" stroke="#000000" stroke-width="0.13" data-prov="3b9174fe7361dbfc454fbe17b9ec81b1" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="30.6062" y1="-26.1054" x2="30.6062" y2="-22.6054" stroke="#000000" stroke-width="0.12" data-prov="af73177552d5fa95b746caf62d000623" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="29.0263" y1="-26.1054" x2="30.807" y2="-26.1054" stroke="#000000" stroke-width="0.13" data-prov="46e6eac9f03c1e09a15cb0bb07628570" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="34.1025" y1="-26.1054" x2="34.1025" y2="-22.6054" stroke="#000000" stroke-width="0.12" data-prov="7c839e4fb5f2522c8fde3b48577eafae" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="32.307" y1="-26.1054" x2="34.0877" y2="-26.1054" stroke="#000000" stroke-width="0.13" data-prov="0e4ec9f37979b06c54f0e2d499df5bf1" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="36.1832" y1="-26.1054" x2="36.1832" y2="-22.6054" stroke="#000000" stroke-width="0.12" data-prov="0c8e0b8170c827494e606d4541ce2c89" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="34.3877" y1="-26.1054" x2="36.1683" y2="-26.1054" stroke="#000000" stroke-width="0.13" data-prov="61f1ed037baf82bf6594769a9205df07" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="38.2638" y1="-26.1054" x2="38.2638" y2="-22.6054" stroke="#000000" stroke-width="0.12" data-prov="42480ffc14ad129d8dec456e7bad317c" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="36.4683" y1="-26.1054" x2="38.249" y2="-26.1054" stroke="#000000" stroke-width="0.13" data-prov="adc82c9de5d3ed333e335c32f7690a6b" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="40.1289" y1="-26.1054" x2="40.1289" y2="-22.6054" stroke="#000000" stroke-width="0.12" data-prov="0f408610c63793003f04e3af5a03493f" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="38.549" y1="-26.1054" x2="40.3297" y2="-26.1054" stroke="#000000" stroke-width="0.13" data-prov="1dee0e17b4b9167cda8e51b45e2ab47c" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="44.19" y1="-26.1054" x2="44.19" y2="-22.6054" stroke="#000000" stroke-width="0.12" data-prov="99d50b32a7d1eac1019c14132a834144" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="42.3945" y1="-26.1054" x2="44.1752" y2="-26.1054" stroke="#000000" stroke-width="0.13" data-prov="c33f0e929fa3ea4230c07aca80f60c04" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="46.2707" y1="-26.1054" x2="46.2707" y2="-22.6054" stroke="#000000" stroke-width="0.12" data-prov="572e96450f21b18771e510c6c41444dc" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="44.4752" y1="-26.1054" x2="46.2559" y2="-26.1054" stroke="#000000" stroke-width="0.13" data-prov="67463b29bfb160641501f497f6134aa6" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="48.3514" y1="-26.1054" x2="48.3514" y2="-22.6054" stroke="#000000" stroke-width="0.12" data-prov="68935c410dbe357c3f80b01c6e6d3cb1" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="46.5559" y1="-26.1054" x2="48.3365" y2="-26.1054" stroke="#000000" stroke-width="0.13" data-prov="75f41055f9e23911ca51158dab6488df" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="49.805" y1="-26.1054" x2="49.805" y2="-22.6054" stroke="#000000" stroke-width="0.12" data-prov="9e9ba65603d31963bdc6d55dfacc5eba" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="48.6365" y1="-26.1054" x2="50.4172" y2="-26.1054" stroke="#000000" stroke-width="0.13" data-prov="66a37f88caecfe799ec34469d2302afd" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="8.5599" y1="-12.8926" x2="8.5599" y2="-12.8926" stroke="#000000" stroke-width="0" data-prov="40a9a55985c8bf8b1eb66b6d8c8e7b7a" data-source-kind="12" data-kind="stroke"/>
|
||||
<line x1="8.5599" y1="-12.8926" x2="8.5599" y2="-12.8926" stroke="#000000" stroke-width="0" data-prov="10eb9773d25f0d7d64f92b36f88e5b38" data-source-kind="14" data-kind="stroke"/>
|
||||
<line x1="8.5599" y1="-12.8926" x2="8.5599" y2="-12.8926" stroke="#000000" stroke-width="0" data-prov="86a982398112115027c54222505dd143" data-source-kind="15" data-kind="stroke"/>
|
||||
<line x1="8.5599" y1="-12.8926" x2="8.5599" y2="-12.8926" stroke="#000000" stroke-width="0" data-prov="64c3a33b80a715758aae1d6a9d040ab1" data-source-kind="23" data-kind="stroke"/>
|
||||
<line x1="8.5599" y1="-12.8926" x2="8.5599" y2="-12.8926" stroke="#000000" stroke-width="0" data-prov="d7735ee5abe9b94bafbd41d62910654c" data-source-kind="23" data-kind="stroke"/>
|
||||
<line x1="7.58" y1="-18.6054" x2="20.2234" y2="-18.6054" stroke="#000000" stroke-width="0.16" data-prov="1e8235a657015d71b99b656363b95d84" data-source-kind="23" data-kind="stroke"/>
|
||||
<line x1="7.58" y1="-18.6054" x2="7.58" y2="-20.0054" stroke="#000000" stroke-width="0.16" data-prov="be04b9451c60ce743c849918afbb0b2c" data-source-kind="23" data-kind="stroke"/>
|
||||
<line x1="20.2234" y1="-18.6054" x2="20.2234" y2="-20.0054" stroke="#000000" stroke-width="0.16" data-prov="106fbc3a3bd80e6314b3d1bdefa94034" data-source-kind="23" data-kind="stroke"/>
|
||||
<line x1="20.2234" y1="-18.6054" x2="31.107" y2="-18.6054" stroke="#000000" stroke-width="0.16" data-prov="7fcf5df56515bc3b1a5ce5ab26cc7d0b" data-source-kind="23" data-kind="stroke"/>
|
||||
<line x1="20.2234" y1="-18.6054" x2="20.2234" y2="-20.0054" stroke="#000000" stroke-width="0.16" data-prov="914c3e24403e2accbf62ab35de2fd55a" data-source-kind="23" data-kind="stroke"/>
|
||||
<line x1="31.107" y1="-18.6054" x2="31.107" y2="-20.0054" stroke="#000000" stroke-width="0.16" data-prov="97e0a3a2e8ac3234ae6cf8ac671604ab" data-source-kind="23" data-kind="stroke"/>
|
||||
<line x1="8.5599" y1="-12.8926" x2="8.5599" y2="-12.8926" stroke="#000000" stroke-width="0" data-prov="d4937e787c949d612f54b6d79ec79edf" data-source-kind="23" data-kind="stroke"/>
|
||||
<line x1="8.5599" y1="-12.8926" x2="8.5599" y2="-12.8926" stroke="#000000" stroke-width="0" data-prov="c183669ac91f35eb7a0040b48fde85ab" data-source-kind="25" data-kind="stroke"/>
|
||||
<line x1="7.58" y1="-25.1054" x2="51.6552" y2="-25.1054" stroke="#000000" stroke-width="0.13" data-prov="e5c54b1ebf58ed5292d1b98f73ad084e" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="7.58" y1="-24.1054" x2="51.6552" y2="-24.1054" stroke="#000000" stroke-width="0.13" data-prov="9ee2d26461a7bc2f28eaf20e07b55ee0" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="7.58" y1="-23.1054" x2="51.6552" y2="-23.1054" stroke="#000000" stroke-width="0.13" data-prov="c5feea54a17beefce1088337f84e39fc" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="7.58" y1="-22.1054" x2="51.6552" y2="-22.1054" stroke="#000000" stroke-width="0.13" data-prov="688349b3e25cac395b2dcca6e297dcfe" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="7.58" y1="-21.1054" x2="51.6552" y2="-21.1054" stroke="#000000" stroke-width="0.13" data-prov="c146782303bbe4c29c6228235090facf" data-source-kind="3" 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(8.5599 -11.8926)" fill="#000000" data-prov="2bc09f9490decea3e13f24459f1b64f4" 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(13.0444 -13.8926)" fill="#000000" data-prov="870b54f8730bed8251f4f891a3c4b233" 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.1251 -13.8926)" fill="#000000" data-prov="5833135b23eaf581bccad90222f64e7e" 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.2058 -13.8926)" fill="#000000" data-prov="3c653d764b31b22273540a079363370c" 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(19.2864 -13.8926)" fill="#000000" data-prov="dfd195901678557bdc1f1351634d2b68" 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.1319 -13.8926)" fill="#000000" data-prov="99d206152de1651afeef963c42d7984f" 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.2126 -13.8926)" fill="#000000" data-prov="1340dc8c93d5925410da02d683dd5916" 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(27.2933 -13.8926)" fill="#000000" data-prov="d9b7e874516d265e3c165a16887a22bd" 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(29.3739 -13.8926)" fill="#000000" data-prov="12ef29aff1cc56481133aec42c0d1d35" 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(32.6546 -13.8926)" fill="#000000" data-prov="6f2586548ccd7de6800f74edd852d03b" 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(34.7352 -13.8926)" fill="#000000" data-prov="6cd2fcd32ca7d1441b6534c025d7ae93" 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.8159 -13.8926)" fill="#000000" data-prov="e71f03f7d3d50a64ea72ce4bcdffb03e" 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.8966 -13.8926)" fill="#000000" data-prov="0227904e88f6312904444c17ae540b57" 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(42.1772 -13.8926)" fill="#000000" data-prov="6cce2821ccbb4f5769708cb9ca3c38a7" 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.2579 -13.8926)" fill="#000000" data-prov="9952a70d5e0f9911d93466e83edc252e" 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.3386 -13.8926)" fill="#000000" data-prov="1ead59d59c0c7cec46071003e7741905" 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(48.4192 -13.8926)" fill="#000000" data-prov="4b82f873de97ed4fa80748acbf368585" 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(53.2286 -13.8926)" fill="#000000" data-prov="a26098a444bb635ce82ff700f48491ed" 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.3092 -13.8926)" fill="#000000" data-prov="460c0831f9fd8de9b01a3a4bac641c8e" 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.3899 -13.8926)" fill="#000000" data-prov="50dc15968bfd55c7637d0dde7c2f7bb3" 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(59.4706 -13.8926)" fill="#000000" data-prov="d095c952d8865af497a68b9fb543c15e" 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.7512 -13.8926)" fill="#000000" data-prov="1e4cb41d3c7556e314182c82b7790bef" 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(64.8319 -13.8926)" fill="#000000" data-prov="9645397274cb84055aeba5639f8b06a5" 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(66.9126 -13.8926)" fill="#000000" data-prov="9578182658c00300531491532d13f66a" 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.9932 -13.8926)" fill="#000000" data-prov="0a4dcf79021a9ba0366091e34fab456c" 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.8368 -26.1054)" fill="#000000" data-prov="4b3905877c48341998d76f07cdb9eff5" 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(11.9175 -26.1054)" fill="#000000" data-prov="4582d540c93386289e704713ab2deeeb" 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(13.9982 -26.1054)" fill="#000000" data-prov="b6d88aa94cc5462fed11ef21a9b9d6df" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(16.0788 -26.1054)" fill="#000000" data-prov="88fff9d9a3f4097be33105eff4f0664a" 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.0843 -26.1054)" fill="#000000" data-prov="1c0a15caddb0e912285270f67ff23a3b" 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.165 -26.1054)" fill="#000000" data-prov="82e4c22515ed591ba37576873988586e" 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(27.2456 -26.1054)" fill="#000000" data-prov="6304bcb23912da87e33c77509f7985f4" 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(29.3263 -26.1054)" fill="#000000" data-prov="99878edbfc3bbb1e8eca463cccd584ce" 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(32.607 -26.1054)" fill="#000000" data-prov="d10593c1a375a3b07eb107dae9e897b3" 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(34.6877 -26.1054)" fill="#000000" data-prov="ba9722cd8f95336b0804751c84983573" 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.7683 -26.1054)" fill="#000000" data-prov="103fdcd52932b1c8f5fab0036c2dd002" 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.849 -26.1054)" fill="#000000" data-prov="fcd49faa62e91333932dbac75206c859" 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(42.6945 -26.1054)" fill="#000000" data-prov="cc13049cf24d89de78d5dcc3bed18d7f" 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.7752 -26.1054)" fill="#000000" data-prov="88a15f00dc6e06aaf9f2532f05f6197e" 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.8559 -26.1054)" fill="#000000" data-prov="cd16148313e9a4f391ab0cabfeb1ee4c" 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(48.9365 -26.1054)" fill="#000000" data-prov="df598a181d9acccffc346705fce5da9b" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.144 0V4H0V0Z" transform="translate(11.5444 -12.8926)" fill="#000000" data-prov="867b15e7cfaeccccfe301517166fe106" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
|
||||
<path d="M0.768 0H0.912V4H0.768ZM0 0H0.5V4H0ZM1.264 2.288C1.376 2.288 1.464 2.376 1.464 2.488C1.464 2.6 1.376 2.688 1.264 2.688C1.156 2.688 1.064 2.6 1.064 2.488C1.064 2.376 1.156 2.288 1.264 2.288ZM1.264 1.28C1.376 1.28 1.464 1.368 1.464 1.48C1.464 1.588 1.376 1.68 1.264 1.68C1.156 1.68 1.064 1.588 1.064 1.48C1.064 1.368 1.156 1.28 1.264 1.28Z" transform="translate(21.0671 -12.8926)" fill="#000000" data-prov="2d8913235b36cc0b1c41dc5238cba014" data-source-kind="9" data-glyph="repeatLeft" data-class="repeat"/>
|
||||
<path d="M0.144 0V4H0V0Z" transform="translate(31.1546 -12.8926)" fill="#000000" data-prov="b263edd8a4514fa237d4e7942c04be8c" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
|
||||
<path d="M0.144 0V4H0V0Z" transform="translate(40.6772 -12.8926)" fill="#000000" data-prov="abc1bd57e0116cfc47c4f84f0c22b2e6" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
|
||||
<path d="M1.736 0H1.88V4H1.736ZM1.468 4H0.968V0H1.468ZM0.7 0V4H0.556V0ZM0.204 2.72C0.092 2.72 0.004 2.632 0.004 2.52C0.004 2.412 0.092 2.32 0.204 2.32C0.312 2.32 0.404 2.412 0.404 2.52C0.404 2.632 0.312 2.72 0.204 2.72ZM2.232 2.288C2.344 2.288 2.432 2.376 2.432 2.488C2.432 2.6 2.344 2.688 2.232 2.688C2.124 2.688 2.032 2.6 2.032 2.488C2.032 2.376 2.124 2.288 2.232 2.288ZM0.204 1.712C0.092 1.712 0.004 1.624 0.004 1.512C0.004 1.4 0.092 1.312 0.204 1.312C0.312 1.312 0.404 1.4 0.404 1.512C0.404 1.624 0.312 1.712 0.204 1.712ZM2.232 1.28C2.344 1.28 2.432 1.368 2.432 1.48C2.432 1.588 2.344 1.68 2.232 1.68C2.124 1.68 2.032 1.588 2.032 1.48C2.032 1.368 2.124 1.28 2.232 1.28Z" transform="translate(50.196 -12.8926)" fill="#000000" data-prov="9f811a0578b546d35393e29bb06afe9b" data-source-kind="9" data-glyph="repeatRightLeft" data-class="repeat"/>
|
||||
<path d="M0.144 0V4H0V0Z" transform="translate(61.2512 -12.8926)" fill="#000000" data-prov="7c62f742d0a60d8a6177a714ef9241af" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
|
||||
<path d="M0.144 0V4H0V0Z" transform="translate(7.58 -25.1054)" fill="#000000" data-prov="0b1e63cbd07616159ee70000e3c76056" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
|
||||
<path d="M0.7 0V4H0.556V0ZM1.468 4H0.968V0H1.468ZM0.204 2.72C0.092 2.72 0.004 2.632 0.004 2.52C0.004 2.412 0.092 2.32 0.204 2.32C0.312 2.32 0.404 2.412 0.404 2.52C0.404 2.632 0.312 2.72 0.204 2.72ZM0.204 1.712C0.092 1.712 0.004 1.624 0.004 1.512C0.004 1.4 0.092 1.312 0.204 1.312C0.312 1.312 0.404 1.4 0.404 1.512C0.404 1.624 0.312 1.712 0.204 1.712Z" transform="translate(17.8556 -25.1054)" fill="#000000" data-prov="f21f4e337050d50f36f7d905196db794" data-source-kind="9" data-glyph="repeatRight" data-class="repeat"/>
|
||||
<path d="M0.144 0V4H0V0Z" transform="translate(31.107 -25.1054)" fill="#000000" data-prov="f4a3968aa56472207e4e921c44dfa1d7" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
|
||||
<path d="M0.144 0V4H0V0ZM0.912 4H0.412V0H0.912Z" transform="translate(51.2776 -25.1054)" fill="#000000" data-prov="d99180b4838ec1269b3f4caee0201968" data-source-kind="9" data-glyph="barlineFinal" data-class="barline"/>
|
||||
<path d="M0.096 0.052C0.096 0.052 0.08 0.028 0.08 0C0.08 -0.02 0.092 -0.044 0.124 -0.056C0.14 -0.06 0.156 -0.064 0.16 -0.064C0.2 -0.064 0.216 -0.028 0.216 -0.028C0.216 -0.028 0.388 0.248 0.432 0.324C0.448 0.352 0.464 0.364 0.472 0.364C0.488 0.364 0.496 0.332 0.496 0.308V-0.724C0.496 -0.816 0.404 -0.876 0.32 -0.876C0.292 -0.876 0.252 -0.888 0.252 -0.936C0.252 -0.98 0.288 -1 0.34 -1H1.192C1.256 -1 1.256 -0.936 1.256 -0.936C1.256 -0.936 1.256 -0.876 1.196 -0.876C1.14 -0.876 1.068 -0.804 1.068 -0.736V0.912C1.068 0.976 1.044 1 0.988 1.004C0.932 1.004 0.832 0.988 0.78 0.988C0.704 0.988 0.632 0.992 0.572 1C0.564 1 0.556 1.004 0.552 1.004C0.512 1.004 0.496 0.964 0.48 0.928Z" transform="translate(8.1442 -19.9054)" fill="#000000" data-prov="5d17b38b8aac6677412c7079c585d10a" data-source-kind="23" data-glyph="timeSig1" data-class="timeSig"/>
|
||||
<path d="M1.684 -0.364C1.684 -0.316 1.664 -0.308 1.636 -0.308C1.604 -0.308 1.592 -0.324 1.584 -0.352L1.58 -0.356C1.54 -0.456 1.508 -0.532 1.424 -0.532C1.404 -0.532 1.384 -0.528 1.356 -0.52C1.304 -0.5 1.276 -0.496 1.236 -0.476C1.156 -0.444 0.968 -0.38 0.804 -0.38C0.752 -0.38 0.7 -0.388 0.656 -0.404C0.744 -0.26 1.084 -0.14 1.172 -0.116C1.452 -0.04 1.704 0.076 1.704 0.408C1.704 0.832 1.288 1.016 0.916 1.016C0.636 1.016 0.388 0.992 0.192 0.764C0.124 0.68 0.08 0.58 0.08 0.472C0.08 0.416 0.092 0.36 0.116 0.3C0.176 0.176 0.3 0.08 0.444 0.08C0.688 0.08 0.724 0.332 0.724 0.432C0.724 0.672 0.448 0.684 0.448 0.764C0.456 0.82 0.528 0.916 0.764 0.916C1.12 0.916 1.124 0.648 1.124 0.532C1.124 0.168 0.824 -0.108 0.536 -0.284C0.316 -0.424 0.16 -0.62 0.092 -0.88L0.088 -0.892C0.088 -0.944 0.132 -1.028 0.192 -1.028C0.28 -1.028 0.328 -0.784 0.564 -0.784C0.724 -0.784 0.784 -1 1.14 -1C1.312 -1 1.62 -0.984 1.684 -0.364Z" transform="translate(20.9386 -19.9054)" fill="#000000" data-prov="9f572f5af2bd7160313e5f31005e3f24" data-source-kind="23" data-glyph="timeSig2" data-class="timeSig"/>
|
||||
<path d="M0.852 0.992C0.848 0.992 0.844 0.992 0.836 0.992L0.808 0.996C0.804 0.996 0.796 0.996 0.792 0.996C0.448 0.996 0.104 0.768 0.104 0.556C0.104 0.424 0.18 0.248 0.428 0.232H0.448C0.624 0.232 0.712 0.36 0.712 0.492V0.524C0.7 0.672 0.6 0.68 0.58 0.688C0.56 0.696 0.5 0.688 0.5 0.744V0.76C0.508 0.828 0.632 0.86 0.668 0.86C1.008 0.86 1.04 0.648 1.04 0.552V0.524C1.04 0.228 0.804 0.112 0.552 0.1C0.512 0.096 0.456 0.076 0.456 0.032C0.456 -0.016 0.524 -0.016 0.556 -0.016C1.016 -0.016 1.052 -0.328 1.052 -0.38C1.052 -0.804 0.836 -0.852 0.748 -0.852C0.732 -0.852 0.716 -0.848 0.712 -0.848C0.68 -0.844 0.604 -0.844 0.6 -0.784V-0.764C0.6 -0.676 0.688 -0.616 0.692 -0.5C0.692 -0.332 0.576 -0.212 0.404 -0.212C0.388 -0.212 0.376 -0.212 0.36 -0.216C0.292 -0.228 0.216 -0.268 0.168 -0.32C0.1 -0.38 0.08 -0.476 0.08 -0.564C0.088 -0.876 0.372 -0.996 0.764 -1.004H0.8C1.196 -1.004 1.604 -0.8 1.604 -0.448V-0.42C1.596 -0.304 1.568 -0.228 1.492 -0.14C1.468 -0.108 1.436 -0.08 1.396 -0.056L1.312 -0.008L1.18 0.028C1.16 0.032 1.148 0.032 1.14 0.048C1.136 0.056 1.136 0.06 1.136 0.068C1.136 0.084 1.14 0.1 1.152 0.104C1.196 0.116 1.24 0.12 1.276 0.14C1.436 0.216 1.52 0.32 1.52 0.504C1.52 0.872 1.02 0.98 0.852 0.992Z" transform="translate(23.2143 -19.9054)" fill="#000000" data-prov="ea315f82ff52d77f24cddcfbe048fb75" data-source-kind="23" data-glyph="timeSig3" data-class="timeSig"/>
|
||||
<path d="M0.768 0H0.912V4H0.768ZM0 0H0.5V4H0ZM1.264 2.288C1.376 2.288 1.464 2.376 1.464 2.488C1.464 2.6 1.376 2.688 1.264 2.688C1.156 2.688 1.064 2.6 1.064 2.488C1.064 2.376 1.156 2.288 1.264 2.288ZM1.264 1.28C1.376 1.28 1.464 1.368 1.464 1.48C1.464 1.588 1.376 1.68 1.264 1.68C1.156 1.68 1.064 1.588 1.064 1.48C1.064 1.368 1.156 1.28 1.264 1.28Z" transform="translate(40.6297 -25.1054)" fill="#000000" data-prov="06532f5cbd726829ae54fa8326ac4880" data-source-kind="23" data-glyph="repeatLeft" data-class="repeat"/>
|
||||
<path d="M0.2 2.68C0.088 2.68 0 2.592 0 2.48C0 2.372 0.088 2.28 0.2 2.28C0.308 2.28 0.4 2.372 0.4 2.48C0.4 2.592 0.308 2.68 0.2 2.68ZM0.2 1.672C0.088 1.672 0 1.584 0 1.472C0 1.36 0.088 1.272 0.2 1.272C0.308 1.272 0.4 1.36 0.4 1.472C0.4 1.584 0.308 1.672 0.2 1.672Z" transform="translate(50.8544 -25.1054)" fill="#000000" data-prov="ecb6833fd9d62fc4cd4aa529763c58db" data-source-kind="23" data-glyph="repeatDots" data-class="repeat"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 42 KiB |
|
|
@ -0,0 +1,16 @@
|
|||
fixture=ten_measure_with_repeats solver=stub
|
||||
glyph_count=56
|
||||
path_count=56
|
||||
fallback_rect_count=0
|
||||
stroke_count=100
|
||||
provenance_count=156
|
||||
layer_count=1
|
||||
hard_constraint_count=95
|
||||
xml_well_formed=true
|
||||
view_box=[-3.065 -3.632 92.937454 12.212]
|
||||
class_counts:
|
||||
barline=7
|
||||
clef=1
|
||||
notehead=40
|
||||
repeat=5
|
||||
timeSig=3
|
||||
|
|
@ -0,0 +1,164 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="929.3745" height="122.12" viewBox="0 0 92.9375 12.212">
|
||||
<!-- epiphany-render-svg: glyphs are genuine Bravura SMuFL outlines inlined as paths; geometry is the resolved layout verbatim, no engraving performed here; every glyph and stroke carries a data-prov trace to its score-graph source -->
|
||||
<g transform="translate(3.065 8.58) scale(1 -1)">
|
||||
<g data-layer="0">
|
||||
<line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="01550dc9d118134419219044f188e28a" data-source-kind="6" data-kind="stroke"/>
|
||||
<line x1="-1" y1="0" x2="87.4604" y2="0" stroke="#000000" stroke-width="0.13" data-prov="5235156954d3cdda987a3da1af222a55" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="-1" y1="1" x2="87.4604" y2="1" stroke="#000000" stroke-width="0.13" data-prov="10cbe3e182e937b8e3d90a03397b2e97" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="-1" y1="2" x2="87.4604" y2="2" stroke="#000000" stroke-width="0.13" data-prov="94825cd467fe6fb512d271e66b59674a" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="-1" y1="3" x2="87.4604" y2="3" stroke="#000000" stroke-width="0.13" data-prov="bc2cd26cec8e4650eedcf04fbc91320f" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="-1" y1="4" x2="87.4604" y2="4" stroke="#000000" stroke-width="0.13" data-prov="c46b9445a4c5ff909f50b13f9025c4a3" data-source-kind="3" data-kind="stroke"/>
|
||||
<line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="fe116eb75ff697f9f2451c10c05349c6" 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="dbd573fbc0c21f9b07a8537f658b048c" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="4.3" y1="-1" x2="6.0807" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="07ffb45f25e59b0955bd4e248522b2ef" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="7.35" y1="-1" x2="7.35" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="276b6f72f2bcb818f84229b6833ce022" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="5.9" y1="-1" x2="7.6807" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="3539af5cd23dc6ca259a35fef4aa45c2" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="8.95" y1="-1" x2="8.95" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="d1b0067ebb2b2b9115a77aaadc53bffe" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="7.5" y1="-1" x2="9.2807" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="9b43d5d2a83ce1086364f6f3ac4b5ba2" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="10.55" y1="-1" x2="10.55" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="13303d184ca25b99835d71e21f3a96df" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="9.1" y1="-1" x2="10.8807" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="d6c7a9458a06532e1c7507e71363c0dd" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="13.75" y1="-1" x2="13.75" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="aa33134cf72cebda65d1b2b427230541" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="12.3" y1="-1" x2="14.0807" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="20fbe0162b0bc4dd93e2c3e3da73bb83" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="15.35" y1="-1" x2="15.35" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="01d06ae931cf05983e4b17744108297b" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="13.9" y1="-1" x2="15.6807" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="cd9abf9ed66ab876f68b9d88ce1e5199" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="16.95" y1="-1" x2="16.95" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="47c9ce76466974ecfabe4a9cafb75c3c" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="15.5" y1="-1" x2="17.2807" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="7ade41165c11102f29e87cb09eb3f88f" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="18.55" y1="-1" x2="18.55" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="e6d1c0a44d13be2700ad4381e1d518fa" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="17.1" y1="-1" x2="18.8807" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="32c2aa89808756c46e8b7afcfb2eb057" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="21.75" y1="-1" x2="21.75" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="370a65aab516f6792e49a1ed28bd5cd9" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="20.3" y1="-1" x2="22.0807" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="0a3ae3c155d29c21e266e663a8b1b214" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="23.35" y1="-1" x2="23.35" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="75b4bca159d5c28b10b2078bb457bb53" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="21.9" y1="-1" x2="23.6807" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="c27e3e835224dc54cef9b36818099cd0" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="24.95" y1="-1" x2="24.95" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="c4531390972e55a43af2bd9f85e3c250" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="23.5" y1="-1" x2="25.2807" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="4b43990bc25bf4afffda3e2a7d4dcab7" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="26.55" y1="-1" x2="26.55" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="888c6ef019fc4b0fc714672a4444c60c" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="25.1" y1="-1" x2="26.8807" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="8e120598410629bb86883f318071a6b5" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="29.75" y1="-1" x2="29.75" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="6c70ad9006ed8fbb104d77eba3f2dbcb" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="28.3" y1="-1" x2="30.0807" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="6d2724bb96160f831b4e987ce6b4349a" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="31.35" y1="-1" x2="31.35" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="debe82da2c34817a871d8a4cc5cc3fdd" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="29.9" y1="-1" x2="31.6807" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="7a9f84c8590c7e8156fd929e35153cf8" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="32.95" y1="-1" x2="32.95" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="7b524c2e9e5657b68fd1a81337887c2e" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="31.5" y1="-1" x2="33.2807" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="c4a9602f838e0ea74a9fe68da12baff2" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="34.55" y1="-1" x2="34.55" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="cb31fbbd4a3e3eb17ed06e65f40589e5" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="33.1" y1="-1" x2="34.8807" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="76860486e62be8520184bb3e8c284d94" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="38.8901" y1="-1" x2="38.8901" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="bc1cc9bede7fc3c72827cfbf184cdb7e" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="37.4401" y1="-1" x2="39.2208" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="a3fd2ae47f432e61881cb1e44a7108e6" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="40.4901" y1="-1" x2="40.4901" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="95891bc2b58231a1618b5d422e6131b0" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="39.0401" y1="-1" x2="40.8208" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="a9e2ee2be83c0a4978db517bf182efe9" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="42.0901" y1="-1" x2="42.0901" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="ed21002861831422bdbb36e8f981d2a9" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="40.6401" y1="-1" x2="42.4208" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="2bac60a88ddddcf403dd6edfb67b5bb0" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="43.6901" y1="-1" x2="43.6901" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="e49748251746a6dbdf15aa8dd983e6e7" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="42.2401" y1="-1" x2="44.0208" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="a592bc951f4afa64726aa30ccdd6ddfa" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="46.8901" y1="-1" x2="46.8901" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="8d2938a01abfe451991829fbde6d2b94" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="45.4401" y1="-1" x2="47.2208" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="c73f1aad686621adde07e7fe7b160ad7" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="48.4901" y1="-1" x2="48.4901" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="88366a6edcce03db41d39d08b2616742" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="47.0401" y1="-1" x2="48.8208" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="4e052448582a8fdc672f38b02dc205e1" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="50.0901" y1="-1" x2="50.0901" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="77a10c7862f0ffce98d309bb71ce675f" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="48.6401" y1="-1" x2="50.4208" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="68c691f2336e5a322116641b9f2f7054" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="51.6901" y1="-1" x2="51.6901" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="cfa457e70519a1826f321a56cb3fd03b" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="50.2401" y1="-1" x2="52.0208" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="3c3f0d6b5ab00374d7628cd6f9876bc9" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="54.8901" y1="-1" x2="54.8901" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="273d04eeece605789035a29d80023c2c" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="53.4401" y1="-1" x2="55.2208" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="9ec226573f8ea2320fc444c562163ddb" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="56.4901" y1="-1" x2="56.4901" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="a23b09fda2784b566bf1de48e89a2baa" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="55.0401" y1="-1" x2="56.8208" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="9e3345d5dddffbb9cf263a1708fc2589" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="58.0901" y1="-1" x2="58.0901" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="edf9ca4611cfc17faace6241cae3b73d" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="56.6401" y1="-1" x2="58.4208" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="ea06a5b83026202fbe3eed55ca3680c9" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="59.6901" y1="-1" x2="59.6901" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="f1e3c0367633266d5f188e5bd6d98ad4" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="58.2401" y1="-1" x2="60.0208" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="b8d324f0620ebb59f66346b61a4d214e" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="64.2104" y1="-1" x2="64.2104" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="2bf824905032462ee8c9988b780feabf" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="62.7604" y1="-1" x2="64.5411" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="579f0f80918268f1ba03890d14832b56" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="65.8104" y1="-1" x2="65.8104" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="08a65aebf8baaded74feb3ec7cc36d18" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="64.3604" y1="-1" x2="66.1411" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="f04f04c436808c53ab1c2b169d7f5a33" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="67.4104" y1="-1" x2="67.4104" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="d155779804ad660f4466745153872349" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="65.9604" y1="-1" x2="67.7411" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="3b9174fe7361dbfc454fbe17b9ec81b1" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="69.0104" y1="-1" x2="69.0104" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="af73177552d5fa95b746caf62d000623" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="67.5604" y1="-1" x2="69.3411" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="46e6eac9f03c1e09a15cb0bb07628570" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="72.2104" y1="-1" x2="72.2104" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="7c839e4fb5f2522c8fde3b48577eafae" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="70.7604" y1="-1" x2="72.5411" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="0e4ec9f37979b06c54f0e2d499df5bf1" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="73.8104" y1="-1" x2="73.8104" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="0c8e0b8170c827494e606d4541ce2c89" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="72.3604" y1="-1" x2="74.1411" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="61f1ed037baf82bf6594769a9205df07" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="75.4104" y1="-1" x2="75.4104" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="42480ffc14ad129d8dec456e7bad317c" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="73.9604" y1="-1" x2="75.7411" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="adc82c9de5d3ed333e335c32f7690a6b" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="77.0104" y1="-1" x2="77.0104" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="0f408610c63793003f04e3af5a03493f" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="75.5604" y1="-1" x2="77.3411" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="1dee0e17b4b9167cda8e51b45e2ab47c" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="80.2104" y1="-1" x2="80.2104" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="99d50b32a7d1eac1019c14132a834144" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="78.7604" y1="-1" x2="80.5411" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="c33f0e929fa3ea4230c07aca80f60c04" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="81.8104" y1="-1" x2="81.8104" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="572e96450f21b18771e510c6c41444dc" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="80.3604" y1="-1" x2="82.1411" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="67463b29bfb160641501f497f6134aa6" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="83.4104" y1="-1" x2="83.4104" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="68935c410dbe357c3f80b01c6e6d3cb1" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="81.9604" y1="-1" x2="83.7411" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="75f41055f9e23911ca51158dab6488df" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="85.0104" y1="-1" x2="85.0104" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="9e9ba65603d31963bdc6d55dfacc5eba" data-source-kind="0" data-kind="stroke"/>
|
||||
<line x1="83.5604" y1="-1" x2="85.3411" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="66a37f88caecfe799ec34469d2302afd" data-source-kind="1" data-kind="stroke"/>
|
||||
<line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="40a9a55985c8bf8b1eb66b6d8c8e7b7a" data-source-kind="12" data-kind="stroke"/>
|
||||
<line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="10eb9773d25f0d7d64f92b36f88e5b38" data-source-kind="14" data-kind="stroke"/>
|
||||
<line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="86a982398112115027c54222505dd143" data-source-kind="15" data-kind="stroke"/>
|
||||
<line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="64c3a33b80a715758aae1d6a9d040ab1" data-source-kind="23" data-kind="stroke"/>
|
||||
<line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="d7735ee5abe9b94bafbd41d62910654c" data-source-kind="23" data-kind="stroke"/>
|
||||
<line x1="52.1401" y1="6.5" x2="61.4604" y2="6.5" stroke="#000000" stroke-width="0.16" data-prov="1e8235a657015d71b99b656363b95d84" data-source-kind="23" data-kind="stroke"/>
|
||||
<line x1="52.1401" y1="6.5" x2="52.1401" y2="5.1" stroke="#000000" stroke-width="0.16" data-prov="be04b9451c60ce743c849918afbb0b2c" data-source-kind="23" data-kind="stroke"/>
|
||||
<line x1="61.4604" y1="6.5" x2="61.4604" y2="5.1" stroke="#000000" stroke-width="0.16" data-prov="106fbc3a3bd80e6314b3d1bdefa94034" data-source-kind="23" data-kind="stroke"/>
|
||||
<line x1="61.4604" y1="6.5" x2="69.4604" y2="6.5" stroke="#000000" stroke-width="0.16" data-prov="7fcf5df56515bc3b1a5ce5ab26cc7d0b" data-source-kind="23" data-kind="stroke"/>
|
||||
<line x1="61.4604" y1="6.5" x2="61.4604" y2="5.1" stroke="#000000" stroke-width="0.16" data-prov="914c3e24403e2accbf62ab35de2fd55a" data-source-kind="23" data-kind="stroke"/>
|
||||
<line x1="69.4604" y1="6.5" x2="69.4604" y2="5.1" stroke="#000000" stroke-width="0.16" data-prov="97e0a3a2e8ac3234ae6cf8ac671604ab" data-source-kind="23" data-kind="stroke"/>
|
||||
<line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="d4937e787c949d612f54b6d79ec79edf" data-source-kind="23" data-kind="stroke"/>
|
||||
<line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="c183669ac91f35eb7a0040b48fde85ab" 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="2bc09f9490decea3e13f24459f1b64f4" 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="870b54f8730bed8251f4f891a3c4b233" 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="5833135b23eaf581bccad90222f64e7e" 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="3c653d764b31b22273540a079363370c" 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="dfd195901678557bdc1f1351634d2b68" 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="99d206152de1651afeef963c42d7984f" 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="1340dc8c93d5925410da02d683dd5916" 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="d9b7e874516d265e3c165a16887a22bd" 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="12ef29aff1cc56481133aec42c0d1d35" 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="6f2586548ccd7de6800f74edd852d03b" 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="6cd2fcd32ca7d1441b6534c025d7ae93" 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="e71f03f7d3d50a64ea72ce4bcdffb03e" 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="0227904e88f6312904444c17ae540b57" 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="6cce2821ccbb4f5769708cb9ca3c38a7" 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="9952a70d5e0f9911d93466e83edc252e" 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="1ead59d59c0c7cec46071003e7741905" 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="4b82f873de97ed4fa80748acbf368585" 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(37.7401 -1)" fill="#000000" data-prov="a26098a444bb635ce82ff700f48491ed" 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.3401 -1)" fill="#000000" data-prov="460c0831f9fd8de9b01a3a4bac641c8e" 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(40.9401 -1)" fill="#000000" data-prov="50dc15968bfd55c7637d0dde7c2f7bb3" 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(42.5401 -1)" fill="#000000" data-prov="d095c952d8865af497a68b9fb543c15e" 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(45.7401 -1)" fill="#000000" data-prov="1e4cb41d3c7556e314182c82b7790bef" 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.3401 -1)" fill="#000000" data-prov="9645397274cb84055aeba5639f8b06a5" 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(48.9401 -1)" fill="#000000" data-prov="9578182658c00300531491532d13f66a" 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(50.5401 -1)" fill="#000000" data-prov="0a4dcf79021a9ba0366091e34fab456c" 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(53.7401 -1)" fill="#000000" data-prov="4b3905877c48341998d76f07cdb9eff5" 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.3401 -1)" fill="#000000" data-prov="4582d540c93386289e704713ab2deeeb" 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(56.9401 -1)" fill="#000000" data-prov="b6d88aa94cc5462fed11ef21a9b9d6df" 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(58.5401 -1)" fill="#000000" data-prov="88fff9d9a3f4097be33105eff4f0664a" 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.0604 -1)" fill="#000000" data-prov="1c0a15caddb0e912285270f67ff23a3b" 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(64.6604 -1)" fill="#000000" data-prov="82e4c22515ed591ba37576873988586e" 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(66.2604 -1)" fill="#000000" data-prov="6304bcb23912da87e33c77509f7985f4" 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(67.8604 -1)" fill="#000000" data-prov="99878edbfc3bbb1e8eca463cccd584ce" 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.0604 -1)" fill="#000000" data-prov="d10593c1a375a3b07eb107dae9e897b3" 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(72.6604 -1)" fill="#000000" data-prov="ba9722cd8f95336b0804751c84983573" 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(74.2604 -1)" fill="#000000" data-prov="103fdcd52932b1c8f5fab0036c2dd002" 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.8604 -1)" fill="#000000" data-prov="fcd49faa62e91333932dbac75206c859" 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.0604 -1)" fill="#000000" data-prov="cc13049cf24d89de78d5dcc3bed18d7f" 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(80.6604 -1)" fill="#000000" data-prov="88a15f00dc6e06aaf9f2532f05f6197e" 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(82.2604 -1)" fill="#000000" data-prov="cd16148313e9a4f391ab0cabfeb1ee4c" 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(83.8604 -1)" fill="#000000" data-prov="df598a181d9acccffc346705fce5da9b" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
|
||||
<path d="M0.144 0V4H0V0Z" transform="translate(3 0)" fill="#000000" data-prov="867b15e7cfaeccccfe301517166fe106" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
|
||||
<path d="M0.768 0H0.912V4H0.768ZM0 0H0.5V4H0ZM1.264 2.288C1.376 2.288 1.464 2.376 1.464 2.488C1.464 2.6 1.376 2.688 1.264 2.688C1.156 2.688 1.064 2.6 1.064 2.488C1.064 2.376 1.156 2.288 1.264 2.288ZM1.264 1.28C1.376 1.28 1.464 1.368 1.464 1.48C1.464 1.588 1.376 1.68 1.264 1.68C1.156 1.68 1.064 1.588 1.064 1.48C1.064 1.368 1.156 1.28 1.264 1.28Z" transform="translate(11 0)" fill="#000000" data-prov="2d8913235b36cc0b1c41dc5238cba014" data-source-kind="9" data-glyph="repeatLeft" data-class="repeat"/>
|
||||
<path d="M0.144 0V4H0V0Z" transform="translate(19 0)" fill="#000000" data-prov="b263edd8a4514fa237d4e7942c04be8c" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
|
||||
<path d="M0.144 0V4H0V0Z" transform="translate(27 0)" fill="#000000" data-prov="abc1bd57e0116cfc47c4f84f0c22b2e6" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
|
||||
<path d="M1.736 0H1.88V4H1.736ZM1.468 4H0.968V0H1.468ZM0.7 0V4H0.556V0ZM0.204 2.72C0.092 2.72 0.004 2.632 0.004 2.52C0.004 2.412 0.092 2.32 0.204 2.32C0.312 2.32 0.404 2.412 0.404 2.52C0.404 2.632 0.312 2.72 0.204 2.72ZM2.232 2.288C2.344 2.288 2.432 2.376 2.432 2.488C2.432 2.6 2.344 2.688 2.232 2.688C2.124 2.688 2.032 2.6 2.032 2.488C2.032 2.376 2.124 2.288 2.232 2.288ZM0.204 1.712C0.092 1.712 0.004 1.624 0.004 1.512C0.004 1.4 0.092 1.312 0.204 1.312C0.312 1.312 0.404 1.4 0.404 1.512C0.404 1.624 0.312 1.712 0.204 1.712ZM2.232 1.28C2.344 1.28 2.432 1.368 2.432 1.48C2.432 1.588 2.344 1.68 2.232 1.68C2.124 1.68 2.032 1.588 2.032 1.48C2.032 1.368 2.124 1.28 2.232 1.28Z" transform="translate(34.9961 0)" fill="#000000" data-prov="9f811a0578b546d35393e29bb06afe9b" data-source-kind="9" data-glyph="repeatRightLeft" data-class="repeat"/>
|
||||
<path d="M0.144 0V4H0V0Z" transform="translate(44.1401 0)" fill="#000000" data-prov="7c62f742d0a60d8a6177a714ef9241af" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
|
||||
<path d="M0.144 0V4H0V0Z" transform="translate(52.1401 0)" fill="#000000" data-prov="0b1e63cbd07616159ee70000e3c76056" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
|
||||
<path d="M0.7 0V4H0.556V0ZM1.468 4H0.968V0H1.468ZM0.204 2.72C0.092 2.72 0.004 2.632 0.004 2.52C0.004 2.412 0.092 2.32 0.204 2.32C0.312 2.32 0.404 2.412 0.404 2.52C0.404 2.632 0.312 2.72 0.204 2.72ZM0.204 1.712C0.092 1.712 0.004 1.624 0.004 1.512C0.004 1.4 0.092 1.312 0.204 1.312C0.312 1.312 0.404 1.4 0.404 1.512C0.404 1.624 0.312 1.712 0.204 1.712Z" transform="translate(60.1362 0)" fill="#000000" data-prov="f21f4e337050d50f36f7d905196db794" data-source-kind="9" data-glyph="repeatRight" data-class="repeat"/>
|
||||
<path d="M0.144 0V4H0V0Z" transform="translate(69.4604 0)" fill="#000000" data-prov="f4a3968aa56472207e4e921c44dfa1d7" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
|
||||
<path d="M0.144 0V4H0V0ZM0.912 4H0.412V0H0.912Z" transform="translate(86.9604 0)" fill="#000000" data-prov="d99180b4838ec1269b3f4caee0201968" data-source-kind="9" data-glyph="barlineFinal" data-class="barline"/>
|
||||
<path d="M0.096 0.052C0.096 0.052 0.08 0.028 0.08 0C0.08 -0.02 0.092 -0.044 0.124 -0.056C0.14 -0.06 0.156 -0.064 0.16 -0.064C0.2 -0.064 0.216 -0.028 0.216 -0.028C0.216 -0.028 0.388 0.248 0.432 0.324C0.448 0.352 0.464 0.364 0.472 0.364C0.488 0.364 0.496 0.332 0.496 0.308V-0.724C0.496 -0.816 0.404 -0.876 0.32 -0.876C0.292 -0.876 0.252 -0.888 0.252 -0.936C0.252 -0.98 0.288 -1 0.34 -1H1.192C1.256 -1 1.256 -0.936 1.256 -0.936C1.256 -0.936 1.256 -0.876 1.196 -0.876C1.14 -0.876 1.068 -0.804 1.068 -0.736V0.912C1.068 0.976 1.044 1 0.988 1.004C0.932 1.004 0.832 0.988 0.78 0.988C0.704 0.988 0.632 0.992 0.572 1C0.564 1 0.556 1.004 0.552 1.004C0.512 1.004 0.496 0.964 0.48 0.928Z" transform="translate(52.5401 5.2)" fill="#000000" data-prov="5d17b38b8aac6677412c7079c585d10a" data-source-kind="23" data-glyph="timeSig1" data-class="timeSig"/>
|
||||
<path d="M1.684 -0.364C1.684 -0.316 1.664 -0.308 1.636 -0.308C1.604 -0.308 1.592 -0.324 1.584 -0.352L1.58 -0.356C1.54 -0.456 1.508 -0.532 1.424 -0.532C1.404 -0.532 1.384 -0.528 1.356 -0.52C1.304 -0.5 1.276 -0.496 1.236 -0.476C1.156 -0.444 0.968 -0.38 0.804 -0.38C0.752 -0.38 0.7 -0.388 0.656 -0.404C0.744 -0.26 1.084 -0.14 1.172 -0.116C1.452 -0.04 1.704 0.076 1.704 0.408C1.704 0.832 1.288 1.016 0.916 1.016C0.636 1.016 0.388 0.992 0.192 0.764C0.124 0.68 0.08 0.58 0.08 0.472C0.08 0.416 0.092 0.36 0.116 0.3C0.176 0.176 0.3 0.08 0.444 0.08C0.688 0.08 0.724 0.332 0.724 0.432C0.724 0.672 0.448 0.684 0.448 0.764C0.456 0.82 0.528 0.916 0.764 0.916C1.12 0.916 1.124 0.648 1.124 0.532C1.124 0.168 0.824 -0.108 0.536 -0.284C0.316 -0.424 0.16 -0.62 0.092 -0.88L0.088 -0.892C0.088 -0.944 0.132 -1.028 0.192 -1.028C0.28 -1.028 0.328 -0.784 0.564 -0.784C0.724 -0.784 0.784 -1 1.14 -1C1.312 -1 1.62 -0.984 1.684 -0.364Z" transform="translate(61.8604 5.2)" fill="#000000" data-prov="9f572f5af2bd7160313e5f31005e3f24" data-source-kind="23" data-glyph="timeSig2" data-class="timeSig"/>
|
||||
<path d="M0.852 0.992C0.848 0.992 0.844 0.992 0.836 0.992L0.808 0.996C0.804 0.996 0.796 0.996 0.792 0.996C0.448 0.996 0.104 0.768 0.104 0.556C0.104 0.424 0.18 0.248 0.428 0.232H0.448C0.624 0.232 0.712 0.36 0.712 0.492V0.524C0.7 0.672 0.6 0.68 0.58 0.688C0.56 0.696 0.5 0.688 0.5 0.744V0.76C0.508 0.828 0.632 0.86 0.668 0.86C1.008 0.86 1.04 0.648 1.04 0.552V0.524C1.04 0.228 0.804 0.112 0.552 0.1C0.512 0.096 0.456 0.076 0.456 0.032C0.456 -0.016 0.524 -0.016 0.556 -0.016C1.016 -0.016 1.052 -0.328 1.052 -0.38C1.052 -0.804 0.836 -0.852 0.748 -0.852C0.732 -0.852 0.716 -0.848 0.712 -0.848C0.68 -0.844 0.604 -0.844 0.6 -0.784V-0.764C0.6 -0.676 0.688 -0.616 0.692 -0.5C0.692 -0.332 0.576 -0.212 0.404 -0.212C0.388 -0.212 0.376 -0.212 0.36 -0.216C0.292 -0.228 0.216 -0.268 0.168 -0.32C0.1 -0.38 0.08 -0.476 0.08 -0.564C0.088 -0.876 0.372 -0.996 0.764 -1.004H0.8C1.196 -1.004 1.604 -0.8 1.604 -0.448V-0.42C1.596 -0.304 1.568 -0.228 1.492 -0.14C1.468 -0.108 1.436 -0.08 1.396 -0.056L1.312 -0.008L1.18 0.028C1.16 0.032 1.148 0.032 1.14 0.048C1.136 0.056 1.136 0.06 1.136 0.068C1.136 0.084 1.14 0.1 1.152 0.104C1.196 0.116 1.24 0.12 1.276 0.14C1.436 0.216 1.52 0.32 1.52 0.504C1.52 0.872 1.02 0.98 0.852 0.992Z" transform="translate(63.1604 5.2)" fill="#000000" data-prov="ea315f82ff52d77f24cddcfbe048fb75" data-source-kind="23" data-glyph="timeSig3" data-class="timeSig"/>
|
||||
<path d="M0.768 0H0.912V4H0.768ZM0 0H0.5V4H0ZM1.264 2.288C1.376 2.288 1.464 2.376 1.464 2.488C1.464 2.6 1.376 2.688 1.264 2.688C1.156 2.688 1.064 2.6 1.064 2.488C1.064 2.376 1.156 2.288 1.264 2.288ZM1.264 1.28C1.376 1.28 1.464 1.368 1.464 1.48C1.464 1.588 1.376 1.68 1.264 1.68C1.156 1.68 1.064 1.588 1.064 1.48C1.064 1.368 1.156 1.28 1.264 1.28Z" transform="translate(77.4604 0)" fill="#000000" data-prov="06532f5cbd726829ae54fa8326ac4880" data-source-kind="23" data-glyph="repeatLeft" data-class="repeat"/>
|
||||
<path d="M0.2 2.68C0.088 2.68 0 2.592 0 2.48C0 2.372 0.088 2.28 0.2 2.28C0.308 2.28 0.4 2.372 0.4 2.48C0.4 2.592 0.308 2.68 0.2 2.68ZM0.2 1.672C0.088 1.672 0 1.584 0 1.472C0 1.36 0.088 1.272 0.2 1.272C0.308 1.272 0.4 1.36 0.4 1.472C0.4 1.584 0.308 1.672 0.2 1.672Z" transform="translate(86.4001 0)" fill="#000000" data-prov="ecb6833fd9d62fc4cd4aa529763c58db" data-source-kind="23" data-glyph="repeatDots" data-class="repeat"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 39 KiB |
|
|
@ -37,7 +37,8 @@ NAMES = ["noteheadBlack","noteheadHalf","noteheadWhole","noteheadDoubleWhole",
|
|||
"flag8thUp","flag8thDown","augmentationDot",
|
||||
"timeSig0","timeSig1","timeSig2","timeSig3","timeSig4","timeSig5","timeSig6",
|
||||
"timeSig7","timeSig8","timeSig9","timeSigCommon",
|
||||
"barlineSingle","barlineFinal","dynamicForte","dynamicPiano"]
|
||||
"barlineSingle","barlineFinal","dynamicForte","dynamicPiano",
|
||||
"repeatLeft","repeatRight","repeatRightLeft","repeatDots"]
|
||||
|
||||
def verify(data, expected, what):
|
||||
actual = hashlib.sha256(data).hexdigest()
|
||||
|
|
|
|||
|
|
@ -12,15 +12,15 @@
|
|||
use epiphany_core::{
|
||||
AcousticPitch, AcousticRealization, AnchorOffset, Canvas, ChordSymbol, CmnNominal,
|
||||
CrossCuttingRegistry, Event, EventArena, EventDuration, EventPosition, IdentifiedPitch,
|
||||
IdentityContext, Marker, Measure, MetricTimeModel, MusicalDuration, MusicalPosition, Pitch,
|
||||
PitchSpaceId, PitchSpacePosition, RationalTime, RegionContent, RegionEdge, RegionTimeModel,
|
||||
ScalePosition, Score, Spanner, Staff, StaffBasedContent, StaffExtent, StaffInstance,
|
||||
StaffLineConfiguration, StemConfiguration, Tie, TieClass, TimeAnchor, TimeExtent,
|
||||
TuningReference, Voice, WallClockTime,
|
||||
IdentityContext, Marker, Measure, MeasurePosition, MetricTimeModel, MusicalDuration,
|
||||
MusicalPosition, Pitch, PitchSpaceId, PitchSpacePosition, RationalTime, RegionContent,
|
||||
RegionEdge, RegionTimeModel, RepeatKind, RepeatStructure, ScalePosition, Score, Spanner, Staff,
|
||||
StaffBasedContent, StaffExtent, StaffInstance, StaffLineConfiguration, StemConfiguration, Tie,
|
||||
TieClass, TimeAnchor, TimeExtent, TuningReference, Voice, Volta, WallClockTime,
|
||||
};
|
||||
use epiphany_core::{
|
||||
ChordSymbolId, EventId, InstrumentId, MarkerId, MeasureId, PitchId, RegionId, ReplicaId,
|
||||
SlurId, SpannerId, StaffId, StaffInstanceId, TieId, VoiceId,
|
||||
ChordSymbolId, EventId, InstrumentId, MarkerId, MeasureId, PitchId, RegionId,
|
||||
RepeatStructureId, ReplicaId, SlurId, SpannerId, StaffId, StaffInstanceId, TieId, VoiceId,
|
||||
};
|
||||
|
||||
use epiphany_determinism::fuzz::SplitMix64;
|
||||
|
|
@ -196,6 +196,67 @@ pub fn ten_measure_single_staff(seed: u64) -> Score {
|
|||
score
|
||||
}
|
||||
|
||||
/// [`ten_measure_single_staff`] plus three repeat structures — the
|
||||
/// repeat-rendering acceptance fixture (schema-major-2 E1). Measure-anchored so
|
||||
/// every boundary coincides with a barline column, it exercises each visual
|
||||
/// form at once: a simple repeat over measures 2–4 whose end meets the volta
|
||||
/// repeat's start (the combined `repeatRightLeft` sign), a `Volta`-kind repeat
|
||||
/// over measures 5–7 with a first ending over measure 7 and a "2 3" ending
|
||||
/// over measure 8 (brackets + ending numerals), and a simple repeat of the
|
||||
/// last measure closing on the region end (the dot pair beside the final
|
||||
/// barline). Invariant-clean.
|
||||
pub fn ten_measure_with_repeats(seed: u64) -> Score {
|
||||
let mut score = ten_measure_single_staff(seed);
|
||||
let measures: Vec<MeasureId> = score.canvas.regions[0].staff_instances()[0]
|
||||
.measures
|
||||
.iter()
|
||||
.map(|measure| measure.id)
|
||||
.collect();
|
||||
let at_start = |index: usize| TimeAnchor::Measure {
|
||||
id: measures[index],
|
||||
position: MeasurePosition::Start,
|
||||
offset: AnchorOffset::Zero,
|
||||
};
|
||||
|
||||
score.cross_cutting.repeats.push(RepeatStructure {
|
||||
id: score.identity.mint::<RepeatStructureId>(),
|
||||
start: at_start(1),
|
||||
end: at_start(4),
|
||||
kind: RepeatKind::SimpleRepeat { count: 2 },
|
||||
voltas: Vec::new(),
|
||||
});
|
||||
score.cross_cutting.repeats.push(RepeatStructure {
|
||||
id: score.identity.mint::<RepeatStructureId>(),
|
||||
start: at_start(4),
|
||||
end: at_start(7),
|
||||
kind: RepeatKind::Volta,
|
||||
voltas: vec![
|
||||
Volta {
|
||||
endings: vec![1],
|
||||
start: at_start(6),
|
||||
end: at_start(7),
|
||||
},
|
||||
Volta {
|
||||
endings: vec![2, 3],
|
||||
start: at_start(7),
|
||||
end: at_start(8),
|
||||
},
|
||||
],
|
||||
});
|
||||
score.cross_cutting.repeats.push(RepeatStructure {
|
||||
id: score.identity.mint::<RepeatStructureId>(),
|
||||
start: at_start(9),
|
||||
end: TimeAnchor::Measure {
|
||||
id: measures[9],
|
||||
position: MeasurePosition::End,
|
||||
offset: AnchorOffset::Zero,
|
||||
},
|
||||
kind: RepeatKind::SimpleRepeat { count: 2 },
|
||||
voltas: Vec::new(),
|
||||
});
|
||||
score
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
@ -215,6 +276,23 @@ mod tests {
|
|||
assert_eq!(s.cross_cutting.chord_symbols.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repeat_fixture_is_invariant_clean_and_carries_three_repeats() {
|
||||
let s = ten_measure_with_repeats(1);
|
||||
let v = check_invariants(&s);
|
||||
assert!(v.is_empty(), "repeat fixture has violations: {v:?}");
|
||||
assert_eq!(s.cross_cutting.repeats.len(), 3);
|
||||
// One volta structure with two brackets; the rest simple repeats.
|
||||
assert_eq!(
|
||||
s.cross_cutting
|
||||
.repeats
|
||||
.iter()
|
||||
.map(|rp| rp.voltas.len())
|
||||
.sum::<usize>(),
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
/// 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
|
||||
|
|
|
|||
|
|
@ -208,7 +208,14 @@ pub fn gen_layout_content(rng: &mut Rng) -> LayoutContent {
|
|||
_ => BarlineKind::Final,
|
||||
}
|
||||
}
|
||||
match rng.below(5) {
|
||||
fn placement(rng: &mut Rng) -> RepeatPlacement {
|
||||
match rng.below(3) {
|
||||
0 => RepeatPlacement::At(time(rng)),
|
||||
1 => RepeatPlacement::RegionEnd,
|
||||
_ => RepeatPlacement::Unresolved,
|
||||
}
|
||||
}
|
||||
match rng.below(6) {
|
||||
0 => LayoutContent::Structural,
|
||||
1 => LayoutContent::Staff(StaffContent {
|
||||
clefs: clefs(rng),
|
||||
|
|
@ -229,7 +236,7 @@ pub fn gen_layout_content(rng: &mut Rng) -> LayoutContent {
|
|||
.boolean()
|
||||
.then(|| epiphany_core::StaffPosition(rng.range(0, 9) as i16)),
|
||||
}),
|
||||
_ => LayoutContent::Measure(MeasureContent {
|
||||
4 => LayoutContent::Measure(MeasureContent {
|
||||
start: time(rng),
|
||||
barline: barline(rng),
|
||||
time_signature: rng.boolean().then(|| TimeSignatureContent {
|
||||
|
|
@ -237,6 +244,18 @@ pub fn gen_layout_content(rng: &mut Rng) -> LayoutContent {
|
|||
denominator: *rng.choose(&[2, 4, 8, 16]),
|
||||
}),
|
||||
}),
|
||||
_ => LayoutContent::Repeat(RepeatContent {
|
||||
barlines: rng.boolean(),
|
||||
start: placement(rng),
|
||||
end: placement(rng),
|
||||
voltas: (0..rng.below(3))
|
||||
.map(|index| VoltaContent {
|
||||
endings: vec![index as u32 + 1],
|
||||
start: placement(rng),
|
||||
end: placement(rng),
|
||||
})
|
||||
.collect(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue