Phase 3 tranche 1: casting-off, K1 schema-fill, value-restoring undo

The chosen Phase-3 attack, run as two parallel waves. 829 workspace
tests pass; clippy -D warnings, fmt, and rustdoc clean; all three
spec documents build with zero undefined references.

Casting-off (epiphany-engrave/src/casting.rs, wired into the
Engraver):
- Greedy first-fit system breaking per region at measure-start
  barline columns; a measure that would overflow the content width
  starts a new system. Hard SystemBreakAt/PageBreakAt always
  honoured; soft breaks honoured unless pathological (skipped with
  the spec's warning + IrOverride-recorded decision).
- Vertical stacking from real content extents with the inter-system
  gap read from the vertical band model; page overflow starts the
  next page. World frame: pages stacked vertically, coordinates
  baked into glyphs/strokes, so the SVG renderer, hit-testing, and
  the GUI viewport are unchanged.
- Real ResolvedPage/ResolvedSystem trees (1-based page numbers,
  content bounding boxes, staves from staff-line segments, measures
  from barline columns); every chosen break appends an
  EngravingDecision with MUSCLOID EngravedBreak provenance,
  UserOverride-attributed via the new ConstrainedLayoutIR
  break_origins sidecar; staff lines split per system with
  synthesized continuation provenance.
- Break-constraint evaluation flips: satisfied iff the layout breaks
  at the slot. The two single-system tests invert deliberately
  (a hard break is now honoured; a user break is honoured and
  attributed instead of warned). Geometric constraints evaluate in
  the pre-casting spaced frame (documented).
- Page geometry is engraver-side PageGeometry (A4 portrait at an
  8 mm staff: page 105 x 148.5 staff spaces, margins 7.5, content
  90 x 133.5; arithmetic documented) — Canvas.layout_defaults has no
  graph home and is a schema-major addition (P12-I7).
  ENGRAVER_VERSION = 2. Goldens regenerated: ten_measure_single_staff
  engraves as 2 systems (viewBox 84x20.6, was 103x11);
  valid_score_rich as 3 systems; stub goldens byte-identical.

K1 schema-fill (Operation Catalog 0.4.0 -> 0.5.0, ratified first;
wire discriminants strictly appended):
- CreateStaff (24 / tag InsertStaff 24): set-union mint of a global
  Staff; CreateStaffInstance now preconditions that its referenced
  staff is live.
- SetTimeSignature (25): value-carrying meter-change LWW keyed by
  (region, resolved position); the carried TimeSignature mints
  set-union; StructuralFieldCollision on meter_sequence.
- SetTempoSegment (26): LWW keyed by (scope, resolved start) over
  the score or region tempo map; a write that would malform the map
  refuses with the appended PreconditionFailureReason 11
  (TempoMapMalformed).
- SetStaffLayout (27): LWW advisory over the staff instance's three
  inline layout fields.
- Create score/canvas remain deliberately unavailable slots: the
  root and canvas are inline singletons with no addressable object
  model (P12-K8), not force-designed.

Value-restoring undo (P11-C8 narrowed; catalog §UndoTransaction
rewritten and per-primitive undo notes updated):
- Canonical-order write chains (base-seeded) across all eleven LWW
  families. StrictInverse restores each written key to its
  chain-predecessor value iff the transaction's write is still the
  key's last writer, else refuses the whole undo with a
  TransactionConflict naming the superseder; BestEffort restores the
  still-last keys. Clean compensations are Applied; only minted-
  object tombstone repairs ride AppliedWithRepair (no new repair
  vocabulary). Mixed mint+overwrite transactions compose; strand
  guards refuse tombstoning mints still referenced by live
  non-members.
- Undo-of-undo pinned and tested: restorations are chain writes, so
  undoing the undo's transaction restores the undone value, and a
  second undo of the same transaction conflicts (absence-restores
  repeat idempotently — documented asymmetry, P12-K11).
- Permutation invariance pinned across five delivery orders; the
  convergence generators gain the new ops and a tx-then-undo flow.
- Still deferred in normative text: delete resurrection (needs a
  system-derived tag outside the ratified closed set), Transpose
  inversion (P12-K2), Cascade dependent closure.

Trackers: Binary Format companion 0.1.0 -> 0.2.0 (appended wire/tag
tables, PreconditionFailureReason 11, payload layouts, history row —
a schema-minor evolution under its own rules); nine new Pass-12 rows
(C5, K8-K11, I7-I10); core-spec OperationKind listing gains the four
kinds; revision-history rows in core spec and companion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEs4aYiu8MXjdYdMxw8PTd
This commit is contained in:
Levi Neuwirth 2026-07-02 21:55:26 -04:00
parent 3e91a8302a
commit 0316160395
44 changed files with 6700 additions and 642 deletions

View File

@ -2124,6 +2124,15 @@ canonical_value! {
// Score settings (M2d) — value-typed set-* payloads embed these.
ScoreMetadata,
MetricGrid,
// Phase-3 ops tranche — value-typed create/set payloads embed these.
// `TimeSignature::dec` re-validates the beat-group sum through
// `TimeSignature::new` (the "reject at construction *and* at decode"
// discipline the operation catalog's §"Meter and Tempo Overwrites"
// requires), so a malformed value never round-trips.
Staff,
TimeSignature,
TempoSegment,
StaffLineConfiguration,
}
#[cfg(test)]

View File

@ -378,6 +378,35 @@ pub(crate) fn subjects_of(kind: &OperationKind, score: &Score) -> BarrierSubject
ctx(location.map(|(r, _)| r), location.map(|(_, si)| si)),
)
}
// Phase-3 first tranche. A staff is a global (score-root) object with
// no regional containment; a meter overwrite edits its region's grid
// slot and names the carried signature it mints; a tempo overwrite
// names its region scope (the score-level map is score-wide state); a
// layout overwrite names its staff instance.
OperationKind::CreateStaff(op) => {
one(TypedObjectId::Staff(op.staff_id()), EditContext::default())
}
OperationKind::SetTimeSignature(op) => {
let mut objects = vec![(TypedObjectId::Region(op.region), ctx(Some(op.region), None))];
if let Some(signature) = &op.time_signature {
objects.push((
TypedObjectId::TimeSignature(signature.id),
ctx(Some(op.region), None),
));
}
BarrierSubjects::Objects(objects)
}
OperationKind::SetTempoSegment(op) => match op.region {
Some(region) => one(TypedObjectId::Region(region), ctx(Some(region), None)),
None => BarrierSubjects::ScoreWide,
},
OperationKind::SetStaffLayout(op) => one(
TypedObjectId::StaffInstance(op.staff_instance),
ctx(
region_of_staff_instance(score, op.staff_instance),
Some(op.staff_instance),
),
),
OperationKind::SetMetadata(_) | OperationKind::DeclareTransaction(_) => {
BarrierSubjects::ScoreWide
}

View File

@ -5233,6 +5233,90 @@ mod tests {
.expect("an Event target does not match a Pitch-kind barrier");
}
#[test]
fn phase3_operation_kinds_derive_subjects_and_gate_on_barriers() {
// The Phase-3 tranche (CreateStaff / SetTimeSignature / SetTempoSegment
// / SetStaffLayout) participates in the barrier gate: a whole-score
// barrier naming each tag refuses the matching edit before minting.
let mut session = open_plain(7);
let region = session.score().canvas.regions[0].id;
let instance = session.score().canvas.regions[0].staff_instances()[0].id;
let staff = session.score().staves[0].id;
let instrument = session.score().staves[0].instrument;
let anchor = epiphany_core::TimeAnchor::Region {
id: region,
edge: epiphany_core::RegionEdge::Start,
offset: epiphany_core::AnchorOffset::Zero,
};
let edits: Vec<(OperationKindTag, OperationKind)> = vec![
(
OperationKindTag::InsertStaff,
OperationKind::CreateStaff(epiphany_ops::CreateStaffOp {
staff: epiphany_ops::valuegen::staff(staff, instrument),
}),
),
(
OperationKindTag::SetTimeSignature,
OperationKind::SetTimeSignature(epiphany_ops::SetTimeSignatureOp {
region,
anchor: anchor.clone(),
time_signature: None,
}),
),
(
OperationKindTag::SetTempoSegment,
OperationKind::SetTempoSegment(epiphany_ops::SetTempoSegmentOp {
region: Some(region),
start: anchor,
segment: None,
}),
),
(
OperationKindTag::SetStaffLayout,
OperationKind::SetStaffLayout(epiphany_ops::SetStaffLayoutOp {
staff_instance: instance,
instrument_override: None,
staff_lines_override: None,
visible: false,
}),
),
];
for (tag, kind) in &edits {
session.set_active_extensions(vec![extension_prohibiting(0xE7, *tag)]);
assert_eq!(
session.apply(kind.clone()),
Err(EditorError::BarrierProhibited {
extension: ExtensionRef(0xE7),
operation: *tag,
}),
"a whole-score barrier naming {tag:?} must refuse the edit"
);
}
assert!(session.applied_operations().is_empty());
// With no barriers active, the layout advisory applies end to end (the
// spec declares no advisory preconditions for the tranche).
session.set_active_extensions(vec![]);
session
.apply(OperationKind::SetStaffLayout(
epiphany_ops::SetStaffLayoutOp {
staff_instance: instance,
instrument_override: None,
staff_lines_override: None,
visible: false,
},
))
.expect("an unbarred layout write applies");
let materialized = session
.score()
.staff_instances()
.find(|(_, si)| si.id == instance)
.expect("instance survives")
.1
.clone();
assert!(!materialized.visible);
}
#[test]
fn an_unsafe_edit_crosses_the_barrier_and_records_the_tombstone_obligation() {
let mut session = open_plain(7);

View File

@ -123,6 +123,10 @@ fn payload_label(payload: &OperationPayload) -> &'static str {
OperationKind::InsertEvent(_) => "InsertEvent",
OperationKind::RespellPitch(_) => "RespellPitch",
OperationKind::DeclareTransaction(_) => "DeclareTransaction",
OperationKind::CreateStaff(_) => "CreateStaff",
OperationKind::SetTimeSignature(_) => "SetTimeSignature",
OperationKind::SetTempoSegment(_) => "SetTempoSegment",
OperationKind::SetStaffLayout(_) => "SetStaffLayout",
_ => "primitive",
},
OperationPayload::ResolveConflict(_) => "ResolveConflict",
@ -176,7 +180,7 @@ struct EditorApp {
impl EditorApp {
fn new() -> Self {
let score = epiphany_testkit::fixtures::ten_measure_single_staff(0);
let session = EditorSession::open(score, Box::new(Engraver))
let session = EditorSession::open(score, Box::new(Engraver::default()))
.expect("the ten-measure fixture renders under the real engraver");
EditorApp {
session,

View File

@ -5,18 +5,18 @@ edition.workspace = true
rust-version.workspace = true
authors.workspace = true
repository.workspace = true
description = "Agent I's Epiphany engraving solver (spec Chapter 9): turns a ConstrainedLayoutIR into a ResolvedLayoutIR with real geometry. A deterministic horizontal-spacing pass (the first axis of the planned two-pass spring layout) that evaluates the declared hard constraints and reports SolverTier::Minimal. The vertical spring pass and casting-off are deferred to a later tier."
description = "Agent I's Epiphany engraving solver (spec Chapter 9): turns a ConstrainedLayoutIR into a ResolvedLayoutIR with real geometry. A deterministic horizontal-spacing pass plus Minimal-tier casting-off (greedy system breaking at measure boundaries, vertical system stacking, page assignment) that evaluates the declared hard constraints — break constraints included — and reports SolverTier::Minimal."
[dependencies]
# The solver consumes/produces the Chapter 7 IR stages and implements the
# Chapter 9 `ConstraintSolver` interface — all defined in epiphany-layout-ir,
# which re-exports the epiphany-core ids/types the solver references.
# which re-exports most of the epiphany-core ids/types the solver references.
epiphany-layout-ir.workspace = true
# The casting-off pass matches provenance sources by their typed id (staves for
# per-system staff records, measures for per-system measure records).
epiphany-core.workspace = true
[dev-dependencies]
# Tests drive the solver from real score fixtures via epiphany-core's generators
# (and a couple of leaf id/time types).
epiphany-core.workspace = true
# The criterion-6 round-trip test mirrors the hand-off gate's fixtures, including
# epiphany-testkit's `ten_measure_single_staff` (testkit does not depend on this
# crate, so this dev-dep introduces no cycle).

View File

@ -15,26 +15,30 @@ on the product side of the spec's core/product boundary, so replacing the
`StubSolver` *inside* `layout-ir` would blur it (`spec/PHASE2_QUICKSTART.md`,
crate topology).
**This phase ships the renderer-against-stub slice** (QUICKSTART, Agent I,
**Phase 2 shipped the renderer-against-stub slice** (QUICKSTART, Agent I,
"Development pattern": build the renderer against the stub solver first, then
grow the real solver). So this crate is an **honest scaffold**: `Engraver` runs a
genuine deterministic *horizontal spacing pass* — the first axis of the planned
two-pass spring layout — placing each spring slot left-to-right by its preferred
width, rather than echoing the stub's input columns. It does **not yet** run the
vertical pass, the soft-spring stretch/compress solve, or evaluate the IR's
declared hard constraints.
grow the real solver): a genuine deterministic *horizontal spacing pass* — the
first axis of the planned two-pass spring layout — later joined by real
hard-constraint evaluation (which earned the `Minimal` tier).
**Phase 3's layout track adds CASTING-OFF** (see "Casting-off (2026-07)" below):
greedy system breaking at measure boundaries, vertical system stacking, page
assignment, a populated `ResolvedPage`/`ResolvedSystem` tree, and full break-
constraint evaluation. Still deferred: the vertical soft-spring solve within a
system, per-system justification/stretch, and optimal break search.
### Honest tier
By the same rule `layout-ir`'s `StubSolver` follows, a solver that does not
evaluate the declared hard constraints and computes no quality metrics MUST
report `SolverTier::Stub`, never `Minimal` (Chapter 9 §"Conformance Tiers").
`Engraver::tier()` therefore reports `Stub` today; it is promoted to `Minimal` in
the same change that lands real hard-constraint satisfaction. A regression test
(`reports_the_honest_stub_tier_until_it_earns_minimal`) guards this so the tier
cannot be silently inflated. The quality-metric vector stays the conservative
`Engraver::tier()` reported `Stub` until real hard-constraint satisfaction
landed and now reports `Minimal` — which it fully earns after casting-off: the
break constraint family is genuinely supported (spec §"Conformance Tiers",
Minimal row), and `Minimal` makes no optimality claim, so greedy first-fit
casting-off is legitimate. The quality-metric vector stays the conservative
all-worst placeholder (`QualityMetricVector::unmeasured`) until the Quality Metric
Catalog lands (Phase 3, explicitly out of Agent I's scope).
Catalog lands (`Standard` tier work).
## Implementation decisions (QUICKSTART "Decisions you'll need to make")
@ -69,6 +73,106 @@ Catalog lands (Phase 3, explicitly out of Agent I's scope).
layer; it changes only `position`.** It assigns each glyph the `x` of its
spring slot and keeps its baseline `y` (the vertical pass is future work).
## Casting-off (2026-07) — decisions
1. **Greedy first-fit system breaking, at measure boundaries.** The casting-off
walk visits each region's spaced spring-slot columns in x order and breaks
before a **barline column** (this projection draws each measure's barline at
its *start* column, so breaking before the barline keeps every measure
intact; the region-final barline closes the region and is never a
candidate) whenever the measure beginning there would overflow the page
content width. Rationale: `SolverTier::Minimal` requires the break family
supported and hard constraints satisfied, with **no optimality claim**
(Chapter 9 §"Conformance Tiers"), so an optimal (KnuthPlass-style) search
is deliberately rejected at this tier — greedy first-fit is deterministic,
linear, and easy to validate. Consequences accepted and documented:
a region with no measures never wraps automatically; a single measure wider
than the page yields an overfull system (no mid-measure emergency break).
2. **Break-constraint semantics: "breaks at slot S" ⇔ S starts a system.** A
`SystemBreakAt`/`PageBreakAt` is satisfied iff the final layout starts a
system/page at that slot (a region's first slot is trivially at a
boundary). Hard breaks are always honoured (mid-measure if necessary — a
`Required` constraint binds absolutely); soft breaks are honoured unless
the closing system would carry **no musical content** (no notehead/rest
column) — the pathological path: the break is skipped, the soft violation
warned, and the unhonoured preference recorded as an
`EngravingDecision` with `DecisionSource::IrOverride` (spec's
override-resolution rule: record, never silently drop).
3. **Frame of constraint evaluation.** Geometric constraints (no-collision,
alignment, position-within) are evaluated against the **pre-casting spaced
geometry** — the frame they are expressed in. Casting-off then relocates
whole systems by per-system rigid motions, which cannot un-satisfy an
intra-system geometric obligation; evaluating post-casting would instead
make *every* `PositionWithin` (whose rect pins the region's source-frame
vertical envelope) unsatisfiable for any casting-off solver, which cannot
be the spec's intent. Break constraints are evaluated against the **final
break structure**.
4. **Page geometry is an engraver parameter (`PageGeometry`), defaulted to A4
at an 8 mm staff.** The spec names `Canvas.layout_defaults` ("paper size,
margins") but never defines the type, and core does not implement it;
adding a graph field now would violate the companion's frozen-layout rule,
so the graph home (`CanvasLayoutDefaults`) is **staged to the data-model
schema major** and the engraver takes the geometry as a constructor
parameter. Default arithmetic (1 staff space = staff height / 4 = 2.0 mm at
an 8 mm staff): A4 210 × 297 mm → **105 × 148.5** staff spaces; 15 mm
margins → **7.5** staff spaces; content area 180 × 267 mm → **90 × 133.5**
staff spaces. 90 staff spaces wraps the ten-measure hand-off fixture
(≈ 99 staff spaces spaced) into two systems — an honest multi-system
default golden.
5. **World-frame convention: pages stacked vertically in one world.** Page 1's
top-left corner sits at the origin; page *n*'s frame begins a full page
height plus `INTER_PAGE_GAP` (8 staff spaces, a presentation constant)
below page *n 1*'s. Every glyph/stroke position is **baked** into this
single y-up world frame (per-system rigid translation: x back to the left
margin, y to the stacked position), so the SVG renderer and the hit-test
map work unchanged on the flat lists — no per-page transform exists
anywhere downstream. The inter-*system* gap is read from
`VerticalBand::inter_system_gap` (preferred 4.0 staff spaces), so the
casting-off gap and the band model cannot drift.
6. **System-spanning strokes are split; the split is provenance-honest.** The
five staff lines span the whole region; a break cuts them at the systems'
content edges. The first segment keeps the original stroke's exact
provenance (round-trip preservation); each later segment is synthesized
(`SynthesisKind::Registered(SYSTEM_CONTINUATION_SYNTHESIS)`, the codebase's
convention for kinds the normative vocabulary does not name) with a
`continuation_instance_key(original stable id, ordinal)` instance key. The
round-trip contract in `layout-ir` was relaxed accordingly (containment +
declared-synthesis additions; the stub still must add nothing).
7. **Engraved-break decisions.** Every chosen system/page break appends an
`EngravingDecision` whose **target** is the `MUSCLOID` id synthesized from
the owning region's source under `SynthesisKind::EngravedBreak`, keyed by
the breaking slot's (content-derived) identity. Source attribution:
`UserOverride(id)` when the break constraint was projected from a user
break override (the id flows through the new
`ConstrainedLayoutIR.break_origins`), else `Automatic`; a skipped soft
break records `IrOverride`. A boundary that actually opens a page records
`PageBreak`; a later page opening at a region's own first system records
`PageBreak` too; other boundaries record `SystemBreak`.
8. **Inverted tests.** Two tests that pinned the single-system semantics were
deliberately inverted and renamed:
`a_hard_break_cannot_be_honoured_by_single_system_minimal`
`a_hard_break_is_honoured_by_casting_off` (Unsatisfiable → Solved with the
system count increasing), and
`a_users_break_flows_to_a_soft_violation_not_a_failure`
`a_users_break_is_honoured_and_recorded_with_its_override` (soft-violation
warning → clean Solved, break at the anchor's column, decision recorded
with the user's override id). The pathological-soft path keeps the old
warning semantics under a new, honest name
(`a_pathological_soft_break_is_skipped_and_recorded_as_ir_override`).
9. **Deferred refinements** (named, not implied): per-system justification
(stretching the soft springs so every full system ends at the right
margin); the vertical spring solve (band heights are carried, not yet
renegotiated; systems stack by real content extents); widow/orphan control
and optimal/lookahead casting-off quality (a `Standard`-tier concern, with
`casting_off_quality` in the metric vector); casting-off caching /
incremental re-cast (the spec's incremental-layout section names the
casting-off cache; `solve_incremental` currently re-solves from scratch,
which remains observationally equivalent); per-system clef/key restatement
(cautionary signatures at system starts, `SynthesisKind::Cautionary`);
multi-system-aware x→time inversion for editor click-to-insert
(`epiphany-editor-core`'s `position_at` interpolates one global x axis and
is correct only within the first system of a wrapped region).
## Pass 12 candidates
See `spec/PASS12_BATCH.md` (rows P12-I1, P12-I2, P12-I3) — all three are now
@ -85,3 +189,35 @@ resolved:
- **P12-I3 (resolved by I-4a)**`BRAVURA_METRICS` is re-extracted from the same
SHA-pinned `bravura-1.392` font the outlines come from, with bboxes rounded
outward to contain the drawn ink (a `render-svg` test proves containment).
### New candidates from the casting-off slice (proposed rows; spec not edited)
- **P12 (proposed) — `Canvas.layout_defaults` is named but never defined.** The
spec references layout defaults ("paper size, margins") on the canvas, but no
chapter defines the type, its units, or its defaulting rules, and the core
graph does not carry it. Proposal: define `CanvasLayoutDefaults { page_size:
Size2D, margins: Margins }` in staff spaces in the data-model chapter,
staged to the **data-model schema major** (adding the field changes the
canonical graph encoding); until then, page geometry is a solver parameter
(this crate's `PageGeometry`) and the spec should say a solver MAY default
it.
- **P12 (proposed) — break-constraint satisfaction semantics.** Chapter 7
defines `SystemBreakAt { slot }` but not what geometric fact makes it
*satisfied*. This crate pins: satisfied iff the final layout **starts a
system at that slot** (page analog for `PageBreakAt`); a region's first slot
is trivially at a boundary. The spec should ratify (or correct) this
predicate, since `Unsatisfiable`-vs-`Solved` conformance hangs on it.
- **P12 (proposed) — user-override attribution across IR stages.** The decision
record for an honoured break must cite `DecisionSource::UserOverride(id)`,
but the normative `LayoutConstraint` carries no origin, so the override id
has no channel from the logical stage's `EngravingOverride` to the solver.
This implementation carries a non-canonical `break_origins` sidecar on
`ConstrainedLayoutIR`; the spec should bless that channel (or widen the
normalized constraint record).
- **P12 (proposed) — synthesis kind for split continuations.** Casting-off
splits region-spanning strokes (staff lines) at system boundaries; the
segments in later systems are engraver-synthesized objects whose kind the
normative `SynthesisKind` set does not name (`EngravedBreak` is the break
itself, not its artefacts). Carried as
`Registered(SYSTEM_CONTINUATION_SYNTHESIS)`; the spec should either add a
continuation kind or bless the registered id.

View File

@ -6,29 +6,38 @@ production-side replacement for `epiphany-layout-ir`'s interface-only
`StubSolver`; the two live in separate crates so the spec's core/product boundary
stays sharp.
## Status: honest scaffold (renderer-against-stub phase)
Per the QUICKSTART development pattern, Agent I builds the SVG renderer
([`epiphany-render-svg`](../epiphany-render-svg)) against the **stub solver**
first, then grows this crate into the real two-pass spring solver. This commit is
the first increment:
## Status: `Minimal` tier, with casting-off
- `Engraver` runs a deterministic **horizontal spacing pass** (the first axis of
the planned two-pass spring layout): each spring slot is placed left-to-right by
its preferred width instead of being echoed verbatim.
- It honestly reports `SolverTier::Stub` — it does not yet evaluate the IR's
declared hard constraints or compute quality metrics, so it has not earned
`Minimal`. It is promoted to `Minimal` in the change that lands real constraint
satisfaction.
the two-pass spring layout): each spring slot is placed left-to-right by a
collision-aware advance derived from real glyph bearings.
- A **casting-off pass** (Phase 3's layout track) then breaks the spaced line
into **systems** at measure boundaries (greedy first-fit against a
`PageGeometry` — default A4 portrait at an 8 mm staff), stacks systems
vertically at the vertical-band model's inter-system gap, assigns **pages**
by content height, and populates the real `ResolvedPage`/`ResolvedSystem`
tree. Every position is baked into a single y-up world frame (pages stacked
vertically), so the SVG renderer and hit-testing consume the flat
glyph/stroke lists unchanged.
- The IR's declared constraints are **evaluated** — geometric families against
the pre-casting spaced frame, break constraints against the final break
structure (hard breaks are always honoured; a pathological soft break is
skipped and recorded as an `IrOverride` decision). Chosen breaks are recorded
as `EngravingDecision`s with `SynthesisKind::EngravedBreak` targets,
attributed to the user override that requested them when one did.
- It reports `SolverTier::Minimal`: hard constraints (break family included)
satisfied, **no optimality claim** — the quality-metric vector stays the
honest all-worst placeholder until the Quality Metric Catalog lands.
The vertical spring pass, soft-constraint solve, hard-constraint evaluation, and
the quality-metric vector are the next-phase / Phase-3 work. See `DECISIONS.md`.
Deferred: the vertical soft-spring solve, per-system justification/stretch,
optimal break search, widow/orphan control, and casting-off caching. See
`DECISIONS.md`.
```rust
use epiphany_engrave::Engraver;
use epiphany_layout_ir::{ConstraintSolver, SolverConfig};
let report = Engraver.solve(&constrained_ir, &SolverConfig::default());
assert!(report.satisfied_hard_constraints); // for constraint-free stub-pipeline input
let resolved = report.layout; // hand to epiphany-render-svg
let report = Engraver::default().solve(&constrained_ir, &SolverConfig::default());
assert!(report.satisfied_hard_constraints);
let resolved = report.layout; // real pages/systems; hand to epiphany-render-svg
```

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -15,8 +15,10 @@
//! accidental zone). Reserving the next slot's left overhang against *this* slot's
//! advance is what protects a note's accidental from overlapping the previous
//! note — a single per-slot `preferred_width` could only reserve space to the
//! right of a slot's source. The vertical pass, the soft-spring stretch/compress
//! solve, and constraint evaluation remain `Minimal`-tier work (next phase).
//! right of a slot's source. The casting-off pass (`crate::casting`) then breaks
//! this spaced line into systems and pages; the vertical soft-spring
//! stretch/compress solve and per-system justification remain deferred (see
//! `DECISIONS.md`).
use std::collections::BTreeMap;

View File

@ -349,6 +349,35 @@ object is covered); the provenance-preservation contract itself is unchanged.
> `req:binfmt:condition-depth`), and adopts the open-value `ObjectKind`
> decode stance (P12-E3, `req:binfmt:object-kind-open`).
- **Casting-off support surface (2026-07, the engrave casting-off slice).**
Three small, non-canonical additions made for `epiphany-engrave`'s
casting-off pass, kept here because they are IR-shape/contract concerns:
1. **`ConstrainedLayoutIR.break_origins` (`BreakOrigin`)** — the spec's
`LayoutConstraint` enum is normative and carries no origin field, but a
casting-off solver that honours a user break must record the decision with
`DecisionSource::UserOverride(id)` (Chapter 7 §"Note Layout" /
§"Engraving Overrides"), and the override id would otherwise be lost at the
logical→constrained boundary. The projection therefore records the
attribution *alongside* the constraint list (slot, break class, override
id) rather than widening the normative enum. Non-canonical, like every
constrained-stage value.
2. **`continuation_instance_key`** — the stable
`SynthesisInstanceKey` derivation for engraver-synthesized *continuations*
of an existing object (the per-system segments a casting-off break cuts a
region-spanning staff line into): keyed on the original object's stable id
plus the 1-based continuation ordinal, hashed under `MUSCLOID` domain
separation so segments of different lines cannot collide for one
`(source, kind)` pair.
3. **Round-trip contract: solver-synthesized additions.** `round_trip_with`
previously asserted the constrained→resolved provenance maps *equal*; a
casting-off solver legitimately synthesizes new objects (staff-line
continuation segments), which Chapter 7 §"Provenance" explicitly allows
for engraver-synthesized objects. The contract is now: every constrained
object survives with its exact provenance (containment, not equality);
every solver addition must declare a `SynthesisKind` and derive from an
already-laid-out source (so the recovered source set is unchanged); the
`Stub` tier must add nothing.
## Pass 12 candidates (ambiguities for the spec, not resolved in code)
1. **Strength attachment to constraint instances.** Chapter 9 §"Strength Levels"

View File

@ -1100,16 +1100,17 @@ mod tests {
tag: 7
})
);
// Operation-kind tag 24 is one past the v1 vocabulary.
// Operation-kind tag 28 is one past the v1 vocabulary (the Phase-3
// ops tranche appended 24..=27; encodings are append-only).
let mut bytes = vec![0u8];
bytes.extend(set_blob(&[]));
bytes.extend(set_blob(&[vec![24u8]]));
bytes.extend(set_blob(&[vec![28u8]]));
bytes.push(0);
assert_eq!(
EditBarrier::decode_canonical_bytes(&bytes),
Err(BarrierDecodeError::InvalidTag {
kind: "OperationKindTag",
tag: 24
tag: 28
})
);
}

View File

@ -106,6 +106,12 @@ pub struct ConstrainedLayoutIR {
pub strokes: Vec<Stroke>,
pub vertical_bands: Vec<VerticalBand>,
pub constraints: Vec<LayoutConstraint>,
/// The user-override attributions behind the projected break constraints in
/// `constraints` (one entry per break constraint that originated in a user
/// break override), so a casting-off solver can cite the override id in the
/// decision it records. Engraver-independent constraints (tests, tools) have
/// no entry here and are attributed `DecisionSource::Automatic`.
pub break_origins: Vec<BreakOrigin>,
pub engraving_decisions: Vec<EngravingDecision>,
/// Engraving-coverage gaps surfaced rather than hidden: a pitch with no
/// resolved spelling, a glyph the bundled metrics do not carry. Not a hard
@ -176,6 +182,29 @@ pub enum BreakKind {
Soft,
}
/// Which break-constraint family a [`BreakOrigin`] attributes: a
/// [`LayoutConstraint::SystemBreakAt`] or a [`LayoutConstraint::PageBreakAt`].
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum BreakClass {
System,
Page,
}
/// The user-override origin of a projected break constraint (Chapter 7
/// §"Engraving Overrides"): the spring slot the override's anchor realized to,
/// which break family it projected into, and the override id. The
/// [`LayoutConstraint`] enum is the spec's normative shape and carries no
/// origin, so the projection records the attribution alongside the constraint
/// list; a casting-off solver that honours the break cites this id in its
/// engraving-decision record (`DecisionSource::UserOverride`, Chapter 7
/// §"Note Layout"). Non-canonical, like every constrained-stage value.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct BreakOrigin {
pub slot: SpringSlotId,
pub class: BreakClass,
pub override_id: crate::engraving::EngravingOverrideId,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct ConstraintRegistryId(pub u128);
@ -497,14 +526,18 @@ const TIME_SIG_X: f32 = 0.5; // a time signature sits this far right of its barl
const TIME_DIGIT_X: f32 = 0.8; // x advance per time-signature digit
/// The horizontal half-reach of an emitted `PositionWithin` region, in staff
/// spaces. v0 performs no casting-off, so a region imposes no *horizontal*
/// bound on its glyphs — a conformant solver may re-space columns freely along
/// the open canvas. The containment obligation v0 can honestly state is the
/// **vertical** envelope (which the spacing pass computes from the very glyph
/// geometry the solvers preserve), so the emitted rect pins that envelope and
/// leaves the horizontal span at canvas scale: wide enough for any plausible
/// re-spacing, finite because the validator rejects non-finite constraint
/// regions.
/// spaces. The constrained stage performs no casting-off, so a region imposes
/// no *horizontal* bound on its glyphs — a conformant solver may re-space
/// columns freely along the open canvas. The containment obligation this stage
/// can honestly state is the **vertical** envelope (which the spacing pass
/// computes from the very glyph geometry it emits), so the emitted rect pins
/// that envelope and leaves the horizontal span at canvas scale: wide enough
/// for any plausible re-spacing, finite because the validator rejects
/// non-finite constraint regions. Geometric constraints are expressed — and
/// evaluated — in *this stage's frame*: a casting-off solver that relocates
/// whole systems (a per-system rigid motion) evaluates them against its
/// pre-casting spaced geometry, where the obligation is meaningful (see
/// `epiphany-engrave`).
const POSITION_WITHIN_X_REACH: f32 = 1.0e6;
/// The registry id for the engraver's **structural-line synthesis** (staff
@ -583,6 +616,7 @@ pub fn try_to_constrained(
let mut vertical_bands = Vec::new();
let mut horizontal_slots = Vec::new();
let mut constraints = Vec::new();
let mut break_origins = Vec::new();
let mut constrained_regions = Vec::new();
// Regions tile left-to-right; this advances by each region's width so all
// coordinates stay globally monotonic (the solver's coordinate remap relies
@ -1348,6 +1382,17 @@ pub fn try_to_constrained(
} else {
LayoutConstraint::PageBreakAt { slot, kind }
});
// Record the attribution so the casting-off solver's decision can
// cite the user override that asked for this break.
break_origins.push(BreakOrigin {
slot,
class: if system {
BreakClass::System
} else {
BreakClass::Page
},
override_id: override_record.id,
});
}
// A staff band per manifested staff that carries glyphs, in first-glyph
@ -1398,6 +1443,7 @@ pub fn try_to_constrained(
strokes,
vertical_bands,
constraints,
break_origins,
engraving_decisions: logical.engraving_decisions.clone(),
diagnostics,
catalog,

View File

@ -92,10 +92,10 @@ pub use cache::{
ResolvedSystemCache, SystemId,
};
pub use constrained::{
active_clef, is_rigid_width_stroke, to_constrained, try_to_constrained, Axis, BreakKind,
ConstrainedLayoutIR, ConstrainedLayoutRegion, ConstrainedValidationError, ConstraintParameters,
ConstraintRegistryId, GlyphObject, GlyphObjectId, GlyphStyle, LayoutConstraint,
LayoutTransformError, SpringSlot, Stroke,
active_clef, is_rigid_width_stroke, to_constrained, try_to_constrained, Axis, BreakClass,
BreakKind, BreakOrigin, ConstrainedLayoutIR, ConstrainedLayoutRegion,
ConstrainedValidationError, ConstraintParameters, ConstraintRegistryId, GlyphObject,
GlyphObjectId, GlyphStyle, LayoutConstraint, LayoutTransformError, SpringSlot, Stroke,
};
pub use engrave_theory::{
accidental_glyph, clef_glyph, flag_glyph, has_stem, key_signature, notehead_glyph, rest_glyph,
@ -123,8 +123,8 @@ pub use logical::{
VerticalExtent,
};
pub use provenance::{
manifestation_layout_id, stable_layout_id, synthesized_layout_id, LayoutObjectId, Provenance,
SynthesisInstanceKey, SynthesisKind, SynthesisRegistryId,
continuation_instance_key, manifestation_layout_id, stable_layout_id, synthesized_layout_id,
LayoutObjectId, Provenance, SynthesisInstanceKey, SynthesisKind, SynthesisRegistryId,
};
pub use render::{
to_render, ColorConfiguration, ColorSpace, PassthroughRenderProducer,

View File

@ -194,11 +194,49 @@ pub fn synthesized_layout_id(
LayoutObjectId(p.finish_trunc128())
}
/// Derives the [`SynthesisInstanceKey`] for an engraver-synthesized
/// **continuation** of an existing layout object — e.g. the segment of a
/// region-spanning staff line that a casting-off system break places in a later
/// system. Keyed on the original object's stable id (which already encodes its
/// full semantic identity — source, manifestation, line index) and the 1-based
/// continuation ordinal, hashed under the layout domain tag so keys from
/// different originals can never collide for one `(source, kind)` pair. The
/// ordinal names *which continuation of that object* the key identifies (its
/// second system's segment, its third's, …), which is the segment's semantic
/// identity — a casting-off artefact exists only relative to the break
/// structure that created it.
pub fn continuation_instance_key(original: LayoutObjectId, ordinal: u32) -> SynthesisInstanceKey {
let mut p = Preimage::new(DomainTag::LAYOUT_OBJECT_ID);
p.push_bytes(b"synthesis-instance/continuation");
p.push_u64_le((original.0 >> 64) as u64);
p.push_u64_le(original.0 as u64);
p.push_u64_le(ordinal as u64);
SynthesisInstanceKey(p.finish_trunc128())
}
#[cfg(test)]
mod tests {
use super::*;
use epiphany_core::{EventId, RegionId};
#[test]
fn continuation_keys_are_stable_and_distinct() {
let a = LayoutObjectId(0x1111);
let b = LayoutObjectId(0x2222);
assert_eq!(
continuation_instance_key(a, 1),
continuation_instance_key(a, 1)
);
assert_ne!(
continuation_instance_key(a, 1),
continuation_instance_key(a, 2)
);
assert_ne!(
continuation_instance_key(a, 1),
continuation_instance_key(b, 1)
);
}
#[test]
fn stable_id_is_a_pure_function_of_source() {
let src = TypedObjectId::Event(EventId::from_raw(0x1234));

View File

@ -128,6 +128,12 @@ pub fn round_trip(score: &Score) -> RoundTripReport {
/// and `stable_id` — must survive that re-spacing unchanged, and the recovered
/// source set must still be exactly the set laid out. This is the strictly stronger
/// statement: a solver may move geometry, never lose a provenance trace.
///
/// A conformant solver may also **synthesize** additional objects of its own — a
/// casting-off pass splits a region-spanning staff line into per-system segments —
/// provided each addition declares a [`SynthesisKind`](crate::SynthesisKind) and
/// derives from a source that is already laid out (so the recovered source set is
/// unchanged); the stub passthrough must add nothing.
pub fn round_trip_with<S: ConstraintSolver>(score: &Score, solver: &S) -> RoundTripReport {
let logical = to_logical(score);
let constrained = to_constrained(&logical);
@ -231,10 +237,39 @@ pub fn round_trip_with<S: ConstraintSolver>(score: &Score, solver: &S) -> RoundT
.map(|g| &g.provenance)
.chain(report.layout.strokes.iter().map(|s| &s.provenance)),
);
assert_eq!(
constrained_map, resolved_map,
"provenance not preserved constrained -> resolved"
);
// Every constrained object survives into the resolved layout with its exact
// provenance. A conformant solver may additionally *synthesize* objects of
// its own — a casting-off pass splits a region-spanning staff line into
// per-system segments (Chapter 7 §"Provenance": engraver-synthesized
// objects declare a `SynthesisKind`) — so the resolved map may be a strict
// superset; the stub tier, a verbatim passthrough, must add nothing.
for (id, provenance) in &constrained_map {
assert_eq!(
resolved_map.get(id),
Some(provenance),
"constrained object {id:?} is not preserved (with its exact provenance) in resolved"
);
}
let constrained_sources: BTreeSet<TypedObjectId> =
constrained_map.values().map(|p| p.source).collect();
for (id, provenance) in &resolved_map {
if constrained_map.contains_key(id) {
continue;
}
assert_ne!(
solver.tier(),
SolverTier::Stub,
"the stub passthrough must not add objects (added {id:?})"
);
assert!(
provenance.synthesis.is_some(),
"solver-added object {id:?} must declare a synthesis kind"
);
assert!(
constrained_sources.contains(&provenance.source),
"solver-added object {id:?} must derive from a laid-out source, not invent one"
);
}
let render = to_render(&report.layout);
for (resolved_glyph, primitive) in report.layout.glyphs.iter().zip(&render.primitives) {

View File

@ -607,6 +607,7 @@ mod tests {
strokes: vec![],
vertical_bands: vec![band],
constraints: vec![],
break_origins: vec![],
engraving_decisions: vec![],
diagnostics: vec![],
catalog,
@ -693,6 +694,7 @@ mod tests {
strokes: vec![],
vertical_bands: vec![band],
constraints: vec![],
break_origins: vec![],
engraving_decisions: vec![],
diagnostics: vec![],
catalog: GlyphCatalogIdentity::default(),

View File

@ -783,3 +783,136 @@ equivocation fuzz plus the unchanged `run_equivocation_fuzz` gate this).
~87K env/s at 1K / 10K / 50K — the 50K row cleared its budget by ~8.7x,
the gate printed its XPASS promotion notice, and the row was flipped to
`Pass` in the same change (see `epiphany-testkit/DECISIONS.md` F1).
## Phase-3 first tranche: staff/meter/tempo/layout ops + value-restoring undo (2026-07)
The ratified catalog text (operation_catalog §CreateStaff, §"Meter and Tempo
Overwrites", §SetStaffLayout, and the **rewritten** §UndoTransaction) is the
contract for this tranche. Wire facts are strictly additive:
`OperationKind` discriminants `CreateStaff = 24`, `SetTimeSignature = 25`,
`SetTempoSegment = 26`, `SetStaffLayout = 27`; `OperationKindTag`
discriminants `InsertStaff = 24` (Create→Insert tag naming, cf.
`InsertRegion`), `SetTimeSignature = 25`, `SetTempoSegment = 26`,
`SetStaffLayout = 27`; `PreconditionFailureReason::TempoMapMalformed = 11`.
No other discriminant table changed. Decisions the catalog left to the
implementation:
- **The differing-value re-create / re-carry precondition reason is
`TargetMissing`.** §CreateStaff makes a byte-identical re-create idempotent
(`AlreadyApplied`) and a differing value under a live id "a precondition
no-op", without naming a reason; the reducer reuses `TargetMissing` (the
generic dangling/unusable-target reason `ModifyCrossCutting`'s malformed
branch already uses) rather than minting a new discriminant. The same
discipline (and reason) applies to the `TimeSignature` value a
`SetTimeSignature` carries. A tombstoned id refuses as `TargetTombstoned`.
- **Tempo-map well-formedness is checked against the chain state, under the
coarse resolved-position key.** The resulting-map precondition
(`TempoMapMalformed`) evaluates the scope's prospective segment list built
from the tempo write chains (base-seeded under `reduce_onto`), ordered by
the same `resolved_anchor_position` key the LWW slot uses: carried-start /
key agreement, per-shape end data (a non-constant shape needs `end` +
`end_tempo`; a constant `end_tempo` must equal `start_tempo` — the graph
invariant checker's rules), resolved ends ≥ their start and ≤ the next
key. Removals cannot malform a map (the gap rule holds the prior tempo) and
always apply.
- **Graph normalization on removal.** A meter removal that empties a grid the
meter writes themselves created normalizes the region's
`default_metric_grid` back to `None` (unless a whole-grid write or the base
holds a grid independently); a tempo-segment removal that leaves a region's
local map empty and `initial`-less normalizes `local_tempo_map` to `None`
(an empty local map would *shadow* the score map rather than fall back to
it). Both keep "create on set" and "remove last" inverses of each other.
- **`CreateStaffInstance` now preconditions a live staff — graph-aware only.**
§CreateStaff makes the check normative now that staves are mintable; like
the insert preconditions, it is enforced when a graph is present (base-free
reduction has no staff universe), which keeps every existing seeded-base
scenario vacuously green. `CreateStaff`'s own instrument/group resolution
preconditions are graph-aware for the same reason.
### Value-restoring undo: the write-chain design
Every LWW overwrite family now maintains, per key, the **canonical-order write
chain** `WriteChain<V> { base: Option<V>, writes: Vec<(op, tx, value)> }`
(replacing the former `last_*` last-writer maps; each chain's last write is
still the LWW concurrent-differing comparison point). Families: event modify,
identified-pitch modify, respell, cross-cutting modify, metadata, metric grid,
meter change (new), tempo segment (new), staff layout (new), and the user
system/page breaks. The reducer applies operations in canonical order, so
appends are inherently canonical and every verdict below is
permutation-invariant by construction (pinned by
`undo_restoration_is_permutation_invariant` and the convergence gates).
- **Seeding.** `seed_from_graph` seeds each chain's `base` with the base-graph
value (events, pitch values, cross-cutting values, metadata, grids, meter
changes, tempo segments, staff-instance layout fields, break anchors, and
explicit user-chosen per-pitch spelling attachments). Mints seed the chains
they create state for (`InsertEvent` → event + pitch chains,
`InsertIdentifiedPitch` → pitch chain, `CreateCrossCutting` → structure
chain, `CreateRegion` → grid/meter/break/tempo chains from the carried
content, `CreateStaffInstance` → layout chain), so a chain-predecessor is
defined identically under `reduce()` and `reduce_onto()` for op-minted
objects. Only base objects in base-free reduction lack seeds — the
established API-divergence caveat (see M2d) applies unchanged.
- **Undo verdict per written key** (`WriteChain::undo_verdict`): if the target
transaction's write is still the key's last, restore the chain-predecessor —
the latest non-member write, else the base value, else *absence*; if a later
writer superseded it, `StrictInverse` refuses the **whole** undo with a
`TransactionConflict` (`caused_by` = undo + the canonically-first
superseding writer) while `BestEffort` restores the still-last keys and
skips the superseded. Keys whose owning object is tombstoned — or is itself
one of the transaction's mints, about to be tombstoned by the same undo —
are skipped entirely (no live slot to restore; not a conflict).
- **Absence semantics per key.** Optional slots restore literal absence: the
grid clears, the meter change / tempo segment / break is removed (with the
normalizations above). The canonical bookkeeping maps (`spellings`,
`breaks`, `page_breaks`) return to **key-absence** whenever the predecessor
is the *base* value — base state lives in the graph, not the operational
ledger — while the graph restores the base value (spelling attachment,
break anchor). Always-valued families (event, pitch, cross-cutting,
metadata, staff layout) with no known predecessor (reachable only base-free,
for pre-horizon objects) restore nothing — a bookkeeping-only outcome.
- **Effects.** A fully clean compensation is `Applied`; `AppliedWithRepair`
carries **only** the minted-object tombstone repairs (`CascadeDeleted`), per
the catalog — restorations add no repair vocabulary. A transaction that
minted nothing and wrote nothing this reduction knows of stays the
`TargetMissing` no-op (the pre-tranche behavior); a transaction that *did*
write chains is now genuinely undoable — the sanctioned semantic change to
previously-TargetMissing overwrite-transaction undos.
- **Mixed transactions** compose both passes; `StrictInverse` refuses the
whole undo if *either* part fails (a tombstoned mint keeps the pre-existing
`TombstonedTarget` conflict; a superseded key raises the
`TransactionConflict` above), and the refusal applies nothing. Two new
strand guards protect the mint pass: a minted `Staff` still manifested by a
live non-member instance, and a minted `TimeSignature` still referenced by
a meter change that survives the restoration pass, refuse under
`StrictInverse` (a `TransactionConflict` naming the blocked object and its
referencer — reusing ratified conflict vocabulary rather than minting a new
kind; a dedicated "stranded reference" kind is a Pass-12 question) and are
skipped (left live) under `BestEffort`.
- **Undo-of-undo (pinned).** A restoration that restores a *value* enters the
key's chain as a new write by the undo operation. Consequences, both
pinned by tests: (i) undoing the first undo's own enclosing transaction
restores the value the first undo removed
(`undo_of_undo_restores_the_pre_undo_value`); (ii) a *second* undo of the
same target transaction finds the key superseded by the first undo —
`Conflicted` under `StrictInverse`, skipped under `BestEffort`
(`a_second_undo_of_the_same_transaction_sees_the_first_as_superseding`).
An *absence* restoration is not representable as a chain write and leaves
the chain unchanged, so repeating it is idempotent — the one asymmetry,
accepted until a chain-native absence entry is worth its weight.
- **Deferred residue** (unchanged from the catalog's "Still deferred"):
delete resurrection (P11-C8), `Transpose` inversion (P12-K2) — so a
transpose between a write and its undo is *not* re-applied on top of the
restored value (the chain predecessor wins; transpose composition is not a
chain write) — and `Cascade`'s dependent closure (`Cascade` remains
`StrictInverse` over the same set).
Proposed Pass-12 rows from this tranche: (1) the `TargetMissing` reuse for
differing-value re-creates/re-carries (vs. a dedicated reason discriminant);
(2) a conflict-kind vocabulary for undo strand-blocks (live-reference
tombstone refusal) instead of `TransactionConflict` reuse; (3) whether an
undo's chain write should carry a distinguished provenance so a second undo
of the same transaction could be defined as idempotent rather than
conflicting; (4) P12-C5 stands as filed (the decomposition pre-pass still
honors only the first governing meter — the reduction semantics are pinned
here and tested under `a_mid_region_meter_change_reduces_cleanly_p12_c5`).

View File

@ -220,6 +220,7 @@ fn precondition_reason(reader: &mut Reader<'_>) -> Result<PreconditionFailureRea
PreconditionFailureRegistryId,
)?)),
10 => Ok(PreconditionFailureReason::ContainerNotEmpty),
11 => Ok(PreconditionFailureReason::TempoMapMalformed),
tag => Err(MaterializedDecodeError::InvalidTag {
kind: "PreconditionFailureReason",
tag,

View File

@ -152,6 +152,11 @@ pub enum PreconditionFailureReason {
/// has live children (an empty-only delete; the caller deletes contents
/// first).
ContainerNotEmpty,
/// A `SetTempoSegment` write whose *resulting* tempo map would be malformed
/// — segments out of order or overlapping, a non-constant shape missing its
/// end data, or a carried segment whose own start disagrees with the
/// operation's start key (operation_catalog §"Meter and Tempo Overwrites").
TempoMapMalformed,
/// An extension-declared precondition failed.
ExtensionPrecondition(ExtensionPreconditionId),
/// A registered precondition code from a versioned registry.
@ -173,6 +178,8 @@ impl PreconditionFailureReason {
PreconditionFailureReason::Registered(_) => 9,
// Additive (Group 3); keeps the ratified 0..=9 discriminants stable.
PreconditionFailureReason::ContainerNotEmpty => 10,
// Additive (Phase-3 tranche, SetTempoSegment); appended past 10.
PreconditionFailureReason::TempoMapMalformed => 11,
}
}
}

View File

@ -30,12 +30,12 @@ use crate::causal::CausalContext;
use crate::envelope::OperationEnvelope;
use crate::opset::OperationSet;
use crate::payload::{
CreateCrossCuttingOp, CreateRegionOp, CreateStaffInstanceOp, CreateVoiceOp, CrossCuttingValue,
DeleteCrossCuttingOp, DeleteEventOp, DeleteIdentifiedPitchOp, DeleteRegionOp,
DeleteStaffInstanceOp, DeleteVoiceOp, InsertEventOp, InsertIdentifiedPitchOp,
CreateCrossCuttingOp, CreateRegionOp, CreateStaffInstanceOp, CreateStaffOp, CreateVoiceOp,
CrossCuttingValue, DeleteCrossCuttingOp, DeleteEventOp, DeleteIdentifiedPitchOp,
DeleteRegionOp, DeleteStaffInstanceOp, DeleteVoiceOp, InsertEventOp, InsertIdentifiedPitchOp,
ModifyCrossCuttingOp, ModifyEventOp, ModifyIdentifiedPitchOp, OperationKind, OperationPayload,
RespellPitchOp, SetMetadataOp, SetMetricGridOp, SetUserPageBreakOp, SetUserSystemBreakOp,
TransposeOp, TupletCompensation,
RespellPitchOp, SetMetadataOp, SetMetricGridOp, SetStaffLayoutOp, SetTempoSegmentOp,
SetTimeSignatureOp, SetUserPageBreakOp, SetUserSystemBreakOp, TransposeOp, TupletCompensation,
};
use crate::stamp::{HybridLogicalClock, OperationStamp};
use crate::support::AuthorId;
@ -85,7 +85,7 @@ fn pitch(n: u64) -> PitchId {
/// Generates a random payload over the shared id space.
fn gen_payload(rng: &mut SplitMix64) -> OperationPayload {
let kind = match rng.below(21) {
let kind = match rng.below(25) {
0 => {
let voice = VoiceId::new(ReplicaId(7), rng.below(3));
let position = MusicalPosition(RationalTime::from_int(rng.below(4) as i32));
@ -196,7 +196,7 @@ fn gen_payload(rng: &mut SplitMix64) -> OperationPayload {
region: RegionId::new(ReplicaId(7), rng.below(3)),
grid: rng.chance(2).then(valuegen::metric_grid),
}),
_ => OperationKind::SetUserPageBreak(SetUserPageBreakOp {
20 => OperationKind::SetUserPageBreak(SetUserPageBreakOp {
region: RegionId::new(ReplicaId(7), 0),
anchor: valuegen::region_start_anchor(
RegionId::new(ReplicaId(7), 0),
@ -204,6 +204,57 @@ fn gen_payload(rng: &mut SplitMix64) -> OperationPayload {
),
present: rng.chance(2),
}),
// Phase-3 first tranche: staff mint, meter/tempo overwrites, layout
// advisory — over the same shared id space so mints, re-carries,
// overwrites, and removals genuinely interact.
21 => OperationKind::CreateStaff(CreateStaffOp {
staff: valuegen::staff(
StaffId::new(ReplicaId(7), rng.below(3)),
epiphany_core::InstrumentId::new(ReplicaId(7), rng.below(2)),
),
}),
22 => {
let region = RegionId::new(ReplicaId(7), rng.below(3));
OperationKind::SetTimeSignature(SetTimeSignatureOp {
region,
anchor: valuegen::region_start_anchor(
region,
MusicalPosition(RationalTime::from_int(rng.below(3) as i32 * 4)),
),
time_signature: (!rng.chance(3)).then(|| {
valuegen::time_signature(
epiphany_core::TimeSignatureId::new(ReplicaId(7), rng.below(3)),
rng.below(3) as u16 + 2,
)
}),
})
}
23 => {
let region = RegionId::new(ReplicaId(7), rng.below(3));
let at = rng.below(3) as i32 * 4;
OperationKind::SetTempoSegment(SetTempoSegmentOp {
region: rng.chance(2).then_some(region),
start: valuegen::region_start_anchor(
region,
MusicalPosition(RationalTime::from_int(at)),
),
segment: (!rng.chance(3)).then(|| {
valuegen::tempo_segment(
region,
MusicalPosition(RationalTime::from_int(at)),
60.0 + rng.below(4) as f64 * 30.0,
)
}),
})
}
_ => OperationKind::SetStaffLayout(SetStaffLayoutOp {
staff_instance: StaffInstanceId::new(ReplicaId(7), rng.below(3)),
instrument_override: None,
staff_lines_override: rng
.chance(2)
.then(epiphany_core::StaffLineConfiguration::default),
visible: rng.chance(2),
}),
};
OperationPayload::Primitive(kind)
}

View File

@ -117,11 +117,12 @@ pub use migrate::{migrate_v0_envelope, project_v1_to_v0, MigrationError};
pub use opset::{AcceptOutcome, OperationSet};
pub use payload::{
ChangeRegionTimeModelOp, CreateCrossCuttingOp, CreateRegionOp, CreateStaffInstanceOp,
CreateVoiceOp, CrossCuttingValue, DeleteCrossCuttingOp, DeleteEventOp, DeleteIdentifiedPitchOp,
DeleteRegionOp, DeleteStaffInstanceOp, DeleteVoiceOp, InsertEventOp, InsertIdentifiedPitchOp,
ModifyCrossCuttingOp, ModifyEventOp, ModifyIdentifiedPitchOp, OperationKind, OperationKindTag,
OperationPayload, PositionRemapping, ResolveConflictPayload, ResolveEquivocationPayload,
RespellPitchOp, SetMetadataOp, SetMetricGridOp, SetUserPageBreakOp, SetUserSystemBreakOp,
CreateStaffOp, CreateVoiceOp, CrossCuttingValue, DeleteCrossCuttingOp, DeleteEventOp,
DeleteIdentifiedPitchOp, DeleteRegionOp, DeleteStaffInstanceOp, DeleteVoiceOp, InsertEventOp,
InsertIdentifiedPitchOp, ModifyCrossCuttingOp, ModifyEventOp, ModifyIdentifiedPitchOp,
OperationKind, OperationKindTag, OperationPayload, PositionRemapping, ResolveConflictPayload,
ResolveEquivocationPayload, RespellPitchOp, SetMetadataOp, SetMetricGridOp, SetStaffLayoutOp,
SetTempoSegmentOp, SetTimeSignatureOp, SetUserPageBreakOp, SetUserSystemBreakOp,
TransactionCategory, TransactionDescriptor, TransposeOp, TupletCompensation,
};
pub use reduce::{

View File

@ -167,6 +167,11 @@ fn project_kind(kind: &OperationKind) -> V0OperationKind {
OperationKind::SetMetadata(op) => V0OperationKind::SetMetadata(op.clone()),
OperationKind::SetMetricGrid(op) => V0OperationKind::SetMetricGrid(op.clone()),
OperationKind::SetUserPageBreak(op) => V0OperationKind::SetUserPageBreak(op.clone()),
// v1-native (Phase-3 first tranche): projected verbatim.
OperationKind::CreateStaff(op) => V0OperationKind::CreateStaff(op.clone()),
OperationKind::SetTimeSignature(op) => V0OperationKind::SetTimeSignature(op.clone()),
OperationKind::SetTempoSegment(op) => V0OperationKind::SetTempoSegment(op.clone()),
OperationKind::SetStaffLayout(op) => V0OperationKind::SetStaffLayout(op.clone()),
}
}
@ -309,6 +314,11 @@ fn migrate_kind(kind: &V0OperationKind, context: &Score) -> Result<OperationKind
V0OperationKind::SetMetadata(op) => OperationKind::SetMetadata(op.clone()),
V0OperationKind::SetMetricGrid(op) => OperationKind::SetMetricGrid(op.clone()),
V0OperationKind::SetUserPageBreak(op) => OperationKind::SetUserPageBreak(op.clone()),
// v1-native (Phase-3 first tranche): identity round-trip.
V0OperationKind::CreateStaff(op) => OperationKind::CreateStaff(op.clone()),
V0OperationKind::SetTimeSignature(op) => OperationKind::SetTimeSignature(op.clone()),
V0OperationKind::SetTempoSegment(op) => OperationKind::SetTempoSegment(op.clone()),
V0OperationKind::SetStaffLayout(op) => OperationKind::SetStaffLayout(op.clone()),
})
}

View File

@ -33,9 +33,10 @@
use epiphany_core::{
Beam, CanonicalValue, Event, EventDuration, EventId, EventPosition, IdentifiedPitch,
MetricGrid, MusicalDuration, MusicalPosition, OperationId, Pitch, PitchId, PitchSpelling,
Region, RegionId, RegionTimeModel, Rest, ScoreMetadata, Slur, Spanner, StaffInstance,
StaffInstanceId, Tie, TimeAnchor, TransactionId, TupletId, TypedObjectId, Voice, VoiceId,
InstrumentId, MetricGrid, MusicalDuration, MusicalPosition, OperationId, Pitch, PitchId,
PitchSpelling, Region, RegionId, RegionTimeModel, Rest, ScoreMetadata, Slur, Spanner, Staff,
StaffId, StaffInstance, StaffInstanceId, StaffLineConfiguration, TempoSegment, Tie, TimeAnchor,
TimeSignature, TransactionId, TupletId, TypedObjectId, Voice, VoiceId,
};
use epiphany_determinism::{sorted_canonical, CanonicalDecode, CanonicalEncode, DecodeError};
@ -154,6 +155,21 @@ pub enum OperationKind {
/// Set a user page-break preference (LWW advisory; the page-break sibling of
/// `SetUserSystemBreak`).
SetUserPageBreak(SetUserPageBreakOp),
// --- Phase-3 first tranche: staff mint, meter/tempo overwrites, layout
// advisory (operation_catalog §CreateStaff, §"Meter and Tempo Overwrites",
// §SetStaffLayout). Discriminants extend additively past 23. ---
/// Mint a global staff on the score root (set-union creation).
CreateStaff(CreateStaffOp),
/// Set, replace, or remove the single meter change at a resolved position
/// in a region's default metric grid (LWW structural overwrite).
SetTimeSignature(SetTimeSignatureOp),
/// Set, replace, or remove the single tempo segment starting at a resolved
/// position in the score-level or region-local tempo map (LWW structural
/// overwrite).
SetTempoSegment(SetTempoSegmentOp),
/// Overwrite a staff instance's inline layout advisories as a unit (LWW
/// advisory).
SetStaffLayout(SetStaffLayoutOp),
}
impl OperationKind {
@ -183,6 +199,11 @@ impl OperationKind {
OperationKind::SetMetadata(_) => 21,
OperationKind::SetMetricGrid(_) => 22,
OperationKind::SetUserPageBreak(_) => 23,
// Phase-3 first tranche; appended past the golden-locked 0..=23.
OperationKind::CreateStaff(_) => 24,
OperationKind::SetTimeSignature(_) => 25,
OperationKind::SetTempoSegment(_) => 26,
OperationKind::SetStaffLayout(_) => 27,
}
}
@ -215,6 +236,11 @@ impl OperationKind {
OperationKind::SetMetadata(_) => OperationKindTag::SetMetadata,
OperationKind::SetMetricGrid(_) => OperationKindTag::SetMetricGrid,
OperationKind::SetUserPageBreak(_) => OperationKindTag::SetUserPageBreak,
// Create→Insert tag-naming convention, cf. `InsertRegion`.
OperationKind::CreateStaff(_) => OperationKindTag::InsertStaff,
OperationKind::SetTimeSignature(_) => OperationKindTag::SetTimeSignature,
OperationKind::SetTempoSegment(_) => OperationKindTag::SetTempoSegment,
OperationKind::SetStaffLayout(_) => OperationKindTag::SetStaffLayout,
}
}
}
@ -250,6 +276,10 @@ impl CanonicalEncode for OperationKind {
OperationKind::SetMetadata(op) => op.encode_canonical(out),
OperationKind::SetMetricGrid(op) => op.encode_canonical(out),
OperationKind::SetUserPageBreak(op) => op.encode_canonical(out),
OperationKind::CreateStaff(op) => op.encode_canonical(out),
OperationKind::SetTimeSignature(op) => op.encode_canonical(out),
OperationKind::SetTempoSegment(op) => op.encode_canonical(out),
OperationKind::SetStaffLayout(op) => op.encode_canonical(out),
}
}
}
@ -284,6 +314,12 @@ pub enum OperationKindTag {
DeleteVoice,
SetMetadata,
SetMetricGrid,
// Phase-3 first tranche. `InsertStaff` follows the tag layer's
// Create→Insert naming convention (cf. `InsertRegion` for `CreateRegion`).
InsertStaff,
SetTimeSignature,
SetTempoSegment,
SetStaffLayout,
}
impl OperationKindTag {
@ -313,6 +349,11 @@ impl OperationKindTag {
OperationKindTag::DeleteVoice => 21,
OperationKindTag::SetMetadata => 22,
OperationKindTag::SetMetricGrid => 23,
// Phase-3 first tranche; appended past the golden-locked 0..=23.
OperationKindTag::InsertStaff => 24,
OperationKindTag::SetTimeSignature => 25,
OperationKindTag::SetTempoSegment => 26,
OperationKindTag::SetStaffLayout => 27,
}
}
}
@ -378,6 +419,10 @@ impl CanonicalDecode for OperationKindTag {
21 => OperationKindTag::DeleteVoice,
22 => OperationKindTag::SetMetadata,
23 => OperationKindTag::SetMetricGrid,
24 => OperationKindTag::InsertStaff,
25 => OperationKindTag::SetTimeSignature,
26 => OperationKindTag::SetTempoSegment,
27 => OperationKindTag::SetStaffLayout,
_ => return Err(DecodeError::MalformedDomainTag),
})
}
@ -1132,6 +1177,150 @@ impl CanonicalEncode for SetUserPageBreakOp {
}
}
// --- Phase-3 first tranche (operation_catalog §CreateStaff, §"Meter and Tempo
// Overwrites", §SetStaffLayout). ----------------------------------------------
/// Mint a global [`Staff`] on the score root (operation_catalog §CreateStaff).
/// Carries the full global-staff value (v1): identity, name, abbreviation,
/// instrument reference, default staff-line configuration, and optional group
/// membership. Set-union creation, completing the structural-container family
/// upward: staff *instances* reference global staves. A repeat create carrying
/// a byte-identical value is idempotent; a differing value under a live id is a
/// precondition no-op.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct CreateStaffOp {
pub staff: Staff,
}
impl CreateStaffOp {
/// The minted staff's identifier.
pub fn staff_id(&self) -> StaffId {
self.staff.id
}
}
impl CanonicalEncode for CreateStaffOp {
fn encode_canonical(&self, out: &mut Vec<u8>) {
push_lp_bytes(out, &self.staff.canonical_bytes());
}
}
/// Set, replace, or (`None`) remove the single meter change at the anchor's
/// resolved musical position in a region's default metric grid
/// (operation_catalog §"Meter and Tempo Overwrites"). Carries the full
/// [`TimeSignature`] value (v1), minted set-union under the same discipline as
/// `CreateStaff`. LWW structural overwrite keyed by `(region, resolved
/// position)`; concurrent differing writes collide on the field
/// `meter_sequence`.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct SetTimeSignatureOp {
pub region: RegionId,
pub anchor: TimeAnchor,
pub time_signature: Option<TimeSignature>,
}
impl SetTimeSignatureOp {
/// The anchor's resolved musical position — the canonical LWW key (the
/// same coarse resolution the user-break advisories use).
pub fn resolved_position(&self) -> MusicalPosition {
resolved_anchor_position(&self.anchor)
}
}
impl CanonicalEncode for SetTimeSignatureOp {
fn encode_canonical(&self, out: &mut Vec<u8>) {
push_canon(out, &self.region);
push_lp_bytes(out, &self.anchor.canonical_bytes());
match &self.time_signature {
None => push_tag(out, 0),
Some(signature) => {
push_tag(out, 1);
push_lp_bytes(out, &signature.canonical_bytes());
}
}
}
}
/// Set, replace, or (`None`) remove the single tempo segment starting at the
/// resolved position, in the score-level tempo map (`region: None`) or the
/// region's local map (`Some`; a set on a region with no local map creates
/// one) (operation_catalog §"Meter and Tempo Overwrites"). LWW structural
/// overwrite keyed by `(scope, resolved start)`; a write that would malform
/// the resulting map is refused
/// ([`PreconditionFailureReason::TempoMapMalformed`](crate::PreconditionFailureReason)).
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct SetTempoSegmentOp {
pub region: Option<RegionId>,
pub start: TimeAnchor,
pub segment: Option<TempoSegment>,
}
impl SetTempoSegmentOp {
/// The start anchor's resolved musical position — the canonical LWW key's
/// position half (the scope is the other half).
pub fn resolved_start(&self) -> MusicalPosition {
resolved_anchor_position(&self.start)
}
}
impl CanonicalEncode for SetTempoSegmentOp {
fn encode_canonical(&self, out: &mut Vec<u8>) {
// Catalog: an Option discriminant and (when present) `region`, then the
// length-framed `start`, then an Option discriminant and (when present)
// the length-framed `segment`.
match &self.region {
None => push_tag(out, 0),
Some(region) => {
push_tag(out, 1);
push_canon(out, region);
}
}
push_lp_bytes(out, &self.start.canonical_bytes());
match &self.segment {
None => push_tag(out, 0),
Some(segment) => {
push_tag(out, 1);
push_lp_bytes(out, &segment.canonical_bytes());
}
}
}
}
/// Overwrite a staff instance's inline layout advisories as a unit
/// (operation_catalog §SetStaffLayout): the three non-break layout advisories
/// with a graph home (`instrument_override`, `staff_lines_override`,
/// `visible`). LWW *advisory* keyed by `staff_instance` — no conflicts. The
/// richer engraving-override vocabulary has no durable graph home yet and
/// remains projected layout state.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct SetStaffLayoutOp {
pub staff_instance: StaffInstanceId,
pub instrument_override: Option<InstrumentId>,
pub staff_lines_override: Option<StaffLineConfiguration>,
pub visible: bool,
}
impl CanonicalEncode for SetStaffLayoutOp {
fn encode_canonical(&self, out: &mut Vec<u8>) {
push_canon(out, &self.staff_instance);
match &self.instrument_override {
None => push_tag(out, 0),
Some(instrument) => {
push_tag(out, 1);
push_canon(out, instrument);
}
}
match &self.staff_lines_override {
None => push_tag(out, 0),
Some(lines) => {
push_tag(out, 1);
push_lp_bytes(out, &lines.canonical_bytes());
}
}
push_u8_bool(out, self.visible);
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -1142,9 +1331,9 @@ mod tests {
// GOLDEN LOCK: the discriminant byte leads every canonically-encoded
// primitive payload (operation_catalog §"Value-Typed Payloads"), so the
// literal values are normative wire facts. Encodings are append-only:
// new kinds append past 23; the values below never change.
// new kinds append past 27; the values below never change.
use crate::valuegen;
use epiphany_core::{MusicalDuration, MusicalPosition, StaffId};
use epiphany_core::{MusicalDuration, MusicalPosition, TimeSignatureId};
let r = ReplicaId(1);
let event_a = EventId::new(r, 1);
@ -1155,6 +1344,7 @@ mod tests {
let instance = StaffInstanceId::new(r, 6);
let voice = VoiceId::new(r, 7);
let slur_id = SlurId::new(r, 8);
let instrument = InstrumentId::new(r, 10);
let event_value = || {
valuegen::insert_event_value(
event_a,
@ -1167,7 +1357,7 @@ mod tests {
let slur_value = || CrossCuttingValue::Slur(valuegen::slur(slur_id, event_a, event_b));
let anchor = || valuegen::region_start_anchor(region, MusicalPosition::origin());
let table: [(OperationKind, u8); 24] = [
let table: [(OperationKind, u8); 28] = [
(
OperationKind::InsertEvent(InsertEventOp {
staff_instance: instance,
@ -1313,6 +1503,41 @@ mod tests {
}),
23,
),
(
OperationKind::CreateStaff(CreateStaffOp {
staff: valuegen::staff(staff, instrument),
}),
24,
),
(
OperationKind::SetTimeSignature(SetTimeSignatureOp {
region,
anchor: anchor(),
time_signature: Some(valuegen::time_signature(TimeSignatureId::new(r, 11), 4)),
}),
25,
),
(
OperationKind::SetTempoSegment(SetTempoSegmentOp {
region: Some(region),
start: anchor(),
segment: Some(valuegen::tempo_segment(
region,
MusicalPosition::origin(),
120.0,
)),
}),
26,
),
(
OperationKind::SetStaffLayout(SetStaffLayoutOp {
staff_instance: instance,
instrument_override: Some(instrument),
staff_lines_override: None,
visible: true,
}),
27,
),
];
for (kind, expected) in &table {
assert_eq!(
@ -1430,6 +1655,10 @@ mod tests {
OperationKindTag::DeleteVoice,
OperationKindTag::SetMetadata,
OperationKindTag::SetMetricGrid,
OperationKindTag::InsertStaff,
OperationKindTag::SetTimeSignature,
OperationKindTag::SetTempoSegment,
OperationKindTag::SetStaffLayout,
];
let encoded: std::collections::BTreeSet<_> = tags
.iter()
@ -1442,14 +1671,14 @@ mod tests {
fn operation_kind_tag_decode_mirrors_encode_exactly() {
// Every non-registered variant round-trips through its 1-byte form, and
// the registered variant through its 17-byte (tag + registry id) form.
let mut tags: Vec<OperationKindTag> = (0u8..24)
let mut tags: Vec<OperationKindTag> = (0u8..28)
.filter(|d| *d != 16)
.map(|d| OperationKindTag::decode_canonical(&[d]).expect("known discriminant"))
.collect();
tags.push(OperationKindTag::Registered(OperationKindRegistryId(
0x0102_0304_0506_0708_090A_0B0C_0D0E_0F10,
)));
assert_eq!(tags.len(), 24, "the full v1 tag vocabulary");
assert_eq!(tags.len(), 28, "the full v1 tag vocabulary");
for tag in tags {
let bytes = tag.to_canonical_bytes();
let decoded = OperationKindTag::decode_canonical(&bytes).expect("round-trips");
@ -1465,10 +1694,10 @@ mod tests {
#[test]
fn operation_kind_tag_decode_rejects_malformed_bytes() {
use epiphany_determinism::DecodeError;
// Unknown discriminant (24 is one past the v1 vocabulary): rejected,
// Unknown discriminant (28 is one past the v1 vocabulary): rejected,
// never normalized.
assert_eq!(
OperationKindTag::decode_canonical(&[24]),
OperationKindTag::decode_canonical(&[28]),
Err(DecodeError::MalformedDomainTag)
);
// Empty input.
@ -1480,6 +1709,21 @@ mod tests {
assert!(OperationKindTag::decode_canonical(&[16; 19]).is_err());
}
#[test]
fn phase3_tag_discriminants_are_golden() {
// GOLDEN LOCK (Phase-3 first tranche): appended past the ratified
// 0..=23; the values below never change.
for (tag, expected) in [
(OperationKindTag::InsertStaff, 24u8),
(OperationKindTag::SetTimeSignature, 25),
(OperationKindTag::SetTempoSegment, 26),
(OperationKindTag::SetStaffLayout, 27),
] {
assert_eq!(tag.discriminant(), expected);
assert_eq!(tag.to_canonical_bytes(), vec![expected]);
}
}
#[test]
fn reassign_remapping_is_order_independent() {
let e1 = EventId::new(ReplicaId(1), 1);

File diff suppressed because it is too large Load Diff

View File

@ -92,6 +92,11 @@ pub enum V0OperationKind {
SetMetadata(crate::payload::SetMetadataOp),
SetMetricGrid(crate::payload::SetMetricGridOp),
SetUserPageBreak(crate::payload::SetUserPageBreakOp),
// Phase-3 first tranche — also v1-native; round-trip by identity.
CreateStaff(crate::payload::CreateStaffOp),
SetTimeSignature(crate::payload::SetTimeSignatureOp),
SetTempoSegment(crate::payload::SetTempoSegmentOp),
SetStaffLayout(crate::payload::SetStaffLayoutOp),
}
/// v0 `InsertEvent`: the event was a bare [`EventId`] plus the reduction-relevant

View File

@ -40,6 +40,14 @@
//! applies to it identically (the replacement's span must not straddle the
//! region boundary any more than an inserted one may).
//!
//! The Phase-3 first tranche (`CreateStaff`, `SetTimeSignature`,
//! `SetTempoSegment`, `SetStaffLayout` — operation_catalog §CreateStaff,
//! §"Meter and Tempo Overwrites", §SetStaffLayout) declares **no advisory
//! preconditions**: every precondition those entries name (reference
//! resolution, mint freshness, resulting-map well-formedness, target
//! liveness) is invariant and enforced by the reducer in all modes, so this
//! module gains no new checks for them.
//!
//! ### Implemented here
//!
//! * InsertEvent / ModifyEvent duration-not-crossing-region-boundary

View File

@ -288,6 +288,74 @@ pub fn metric_grid() -> epiphany_core::MetricGrid {
}
}
/// A minimal global [`Staff`](epiphany_core::Staff) (Phase-3 tranche) — the
/// value a `CreateStaff` mints: a five-line staff named for its counter,
/// referencing `instrument`, with no abbreviation or group.
pub fn staff(id: StaffId, instrument: epiphany_core::InstrumentId) -> epiphany_core::Staff {
epiphany_core::Staff {
id,
name: format!("staff-{}", id.counter()),
abbreviation: None,
instrument,
default_staff_lines: epiphany_core::StaffLineConfiguration::default(),
group: None,
}
}
/// A well-formed `numerator`/4 [`TimeSignature`](epiphany_core::TimeSignature)
/// (Phase-3 tranche): `numerator` quarter-note beat groups summing exactly to
/// the measure duration, so [`epiphany_core::TimeSignature::new`]'s beat-group
/// invariant holds by construction. Distinct numerators give distinct values,
/// letting a harness drive the set-union mint's identical/differing re-carry
/// branches deterministically.
pub fn time_signature(
id: epiphany_core::TimeSignatureId,
numerator: u16,
) -> epiphany_core::TimeSignature {
let numerator = numerator.max(1);
let quarter = MusicalDuration(epiphany_core::RationalTime::new(1, 4).expect("1/4 is valid"));
let measure = MusicalDuration(
epiphany_core::RationalTime::new(numerator as i64, 4).expect("n/4 is valid"),
);
let beat_groups = (0..numerator)
.map(|i| epiphany_core::BeatGroup {
duration: quarter.clone(),
subdivision: None,
accent: u8::from(i == 0),
})
.collect();
epiphany_core::TimeSignature::new(
id,
epiphany_core::TimeSignatureDisplay::Standard {
numerator,
denominator: epiphany_core::PowerOfTwo::new(4).expect("4 is a power of two"),
},
measure,
beat_groups,
)
.expect("beat groups sum to the measure duration by construction")
}
/// A constant [`TempoSegment`](epiphany_core::TempoSegment) (Phase-3 tranche)
/// starting at the given region-relative musical position, open-ended, at
/// `bpm` quarter-note beats per minute. Its start anchor resolves (under the
/// operation layer's coarse anchor resolution) to exactly `start`, so it
/// satisfies `SetTempoSegment`'s start-key agreement precondition when keyed
/// by the same anchor.
pub fn tempo_segment(
region: RegionId,
start: MusicalPosition,
bpm: f64,
) -> epiphany_core::TempoSegment {
epiphany_core::TempoSegment {
start: region_start_anchor(region, start),
end: None,
start_tempo: epiphany_core::Tempo::quarter(bpm).expect("positive finite bpm"),
end_tempo: None,
shape: epiphany_core::TempoShape::Constant,
}
}
/// An explicit, user-chosen per-pitch [`SpellingAttachment`] — the engraved-layer
/// spelling a materialized score carries after a `RespellPitch`. The v0 → v1
/// migration recovers a respell's spelling from exactly these attachments

View File

@ -6,16 +6,18 @@ use epiphany_core::{
AnchorOffset, AnnotationAnchor, Comment, CommentId, CueEvent, CueRendering, Event,
EventDuration, EventId, EventPosition, GestureAnchoring, GraphicGesture, GraphicGestureId,
Marker, MarkerId, MusicalDuration, MusicalPosition, OperationId, PitchId, RationalTime,
RegionEdge, RegionTimeModel, ReplicaId, Score, SlurId, StaffInstanceId, TimeAnchor,
TransactionId, TypedObjectId, VoiceId, VoiceOrigin, WallClockTime,
RegionEdge, RegionId, RegionTimeModel, ReplicaId, Score, SlurId, StaffId, StaffInstanceId,
TimeAnchor, TimeSignatureId, TransactionId, TypedObjectId, VoiceId, VoiceOrigin, WallClockTime,
};
use epiphany_ops::{
valuegen, AuthorId, CausalContext, ChangeRegionTimeModelOp, ConflictKind, CreateCrossCuttingOp,
CrossCuttingValue, DeleteEventOp, HybridLogicalClock, InsertEventOp, NoOpReason,
OperationEffect, OperationEnvelope, OperationKind, OperationPayload, OperationSet,
OperationStamp, PositionRemapping, PreconditionFailureReason, ReanchorReason, RepairKind,
RepairRecord, SetUserSystemBreakOp, TransactionCategory, TransactionDescriptor,
TupletCompensation, UndoPolicy, UndoTransactionPayload,
CreateStaffInstanceOp, CreateStaffOp, CrossCuttingValue, DeleteEventOp, DeleteStaffInstanceOp,
HybridLogicalClock, InsertEventOp, ModifyEventOp, NoOpReason, OperationEffect,
OperationEnvelope, OperationKind, OperationPayload, OperationSet, OperationStamp,
PositionRemapping, PreconditionFailureReason, ReanchorReason, RepairKind, RepairRecord,
RespellPitchOp, SetMetricGridOp, SetStaffLayoutOp, SetTempoSegmentOp, SetTimeSignatureOp,
SetUserSystemBreakOp, TransactionCategory, TransactionDescriptor, TupletCompensation,
UndoPolicy, UndoTransactionPayload,
};
fn envelope(
@ -2751,3 +2753,901 @@ fn referent_reanchoring_is_permutation_invariant() {
);
}
}
// ===========================================================================
// Phase-3 first tranche: CreateStaff, SetTimeSignature, SetTempoSegment,
// SetStaffLayout (operation_catalog §CreateStaff, §"Meter and Tempo
// Overwrites", §SetStaffLayout), and value-restoring undo (§UndoTransaction).
// ===========================================================================
fn seen(replica: u64, counter: u64) -> CausalContext {
CausalContext::new().with_seen(ReplicaId(replica), counter)
}
fn prim(kind: OperationKind) -> OperationPayload {
OperationPayload::Primitive(kind)
}
/// A single-replica causal chain of primitives (each member sees its
/// predecessor), optionally under one transaction.
fn chain(
replica: u64,
start_physical: i64,
tx: Option<TransactionId>,
kinds: Vec<OperationKind>,
) -> Vec<OperationEnvelope> {
kinds
.into_iter()
.enumerate()
.map(|(index, kind)| {
let counter = index as u64;
let ctx = if counter == 0 {
CausalContext::new()
} else {
seen(replica, counter - 1)
};
envelope(
replica,
counter,
start_physical + counter as i64,
ctx,
tx,
prim(kind),
)
})
.collect()
}
fn set_meter(
region: RegionId,
at: i32,
signature: Option<epiphany_core::TimeSignature>,
) -> OperationKind {
OperationKind::SetTimeSignature(SetTimeSignatureOp {
region,
anchor: valuegen::region_start_anchor(region, MusicalPosition(RationalTime::from_int(at))),
time_signature: signature,
})
}
fn set_tempo(
region_scope: Option<RegionId>,
anchor_region: RegionId,
at: i32,
segment: Option<epiphany_core::TempoSegment>,
) -> OperationKind {
OperationKind::SetTempoSegment(SetTempoSegmentOp {
region: region_scope,
start: valuegen::region_start_anchor(
anchor_region,
MusicalPosition(RationalTime::from_int(at)),
),
segment,
})
}
fn effect_for(result: &epiphany_ops::GraphMaterialization, id: OperationId) -> &OperationEffect {
result
.state
.effects
.iter()
.find(|(e, _)| *e == id)
.map(|(_, effect)| effect)
.expect("every accepted operation produces an effect")
}
fn precondition_noop(reason: PreconditionFailureReason) -> OperationEffect {
OperationEffect::NoOp {
reason: NoOpReason::PreconditionFailedUnderReduction { reason },
}
}
#[test]
fn create_staff_mints_into_the_graph_and_checks_references() {
let base = epiphany_core::generators::valid_score(300);
let instrument = base.instruments[0].id;
let staff_id = StaffId::new(ReplicaId(60), 1);
let envelopes = chain(
60,
10,
None,
vec![
OperationKind::CreateStaff(CreateStaffOp {
staff: valuegen::staff(staff_id, instrument),
}),
// A staff naming an undeclared instrument is refused (graph-aware
// reference-resolution precondition).
OperationKind::CreateStaff(CreateStaffOp {
staff: valuegen::staff(
StaffId::new(ReplicaId(60), 2),
epiphany_core::InstrumentId::new(ReplicaId(60), 99),
),
}),
],
);
let mut set = OperationSet::new();
set.accept_all(envelopes.clone());
let result = set.reduce_onto(&base);
assert_eq!(
effect_for(&result, envelopes[0].id),
&OperationEffect::Applied
);
assert_eq!(
effect_for(&result, envelopes[1].id),
&precondition_noop(PreconditionFailureReason::TargetMissing),
);
assert!(result.score.staves.iter().any(|s| s.id == staff_id));
assert!(matches!(
result.state.objects.get(&TypedObjectId::Staff(staff_id)),
Some(epiphany_ops::ObjectState::Live)
));
assert!(check_invariants(&result.score).is_empty());
}
#[test]
fn create_staff_instance_now_requires_a_live_staff() {
let base = epiphany_core::generators::valid_score(301);
let region = base.canvas.regions[0].id;
let instrument = base.instruments[0].id;
let minted_staff = StaffId::new(ReplicaId(61), 1);
let envelopes = chain(
61,
10,
None,
vec![
// Referencing a staff that was never minted: refused.
OperationKind::CreateStaffInstance(CreateStaffInstanceOp {
region,
instance: valuegen::staff_instance(
StaffInstanceId::new(ReplicaId(61), 5),
StaffId::new(ReplicaId(61), 9),
),
}),
// Mint the staff, then an instance referencing it applies.
OperationKind::CreateStaff(CreateStaffOp {
staff: valuegen::staff(minted_staff, instrument),
}),
OperationKind::CreateStaffInstance(CreateStaffInstanceOp {
region,
instance: valuegen::staff_instance(
StaffInstanceId::new(ReplicaId(61), 6),
minted_staff,
),
}),
],
);
let mut set = OperationSet::new();
set.accept_all(envelopes.clone());
let result = set.reduce_onto(&base);
assert_eq!(
effect_for(&result, envelopes[0].id),
&precondition_noop(PreconditionFailureReason::TargetMissing),
);
assert_eq!(
effect_for(&result, envelopes[2].id),
&OperationEffect::Applied
);
assert!(result
.score
.staff_instances()
.any(|(_, si)| si.id == StaffInstanceId::new(ReplicaId(61), 6)));
assert!(check_invariants(&result.score).is_empty());
}
#[test]
fn set_time_signature_sets_replaces_and_removes_in_the_grid() {
let base = epiphany_core::generators::valid_score(302);
let region = base.canvas.regions[0].id;
let ts_a = valuegen::time_signature(TimeSignatureId::new(ReplicaId(62), 1), 4);
let ts_b = valuegen::time_signature(TimeSignatureId::new(ReplicaId(62), 2), 3);
let envelopes = chain(
62,
10,
None,
vec![
set_meter(region, 0, Some(ts_a.clone())),
set_meter(region, 0, Some(ts_b.clone())),
set_meter(region, 0, None),
],
);
// Set: the grid is created around the meter change; the signature mints.
let mut set = OperationSet::new();
set.accept_all(envelopes[..1].to_vec());
let result = set.reduce_onto(&base);
let grid = |result: &epiphany_ops::GraphMaterialization| {
result.score.canvas.regions[0]
.content
.staff_based()
.expect("fixture is staff based")
.default_metric_grid
.clone()
};
let after_set = grid(&result).expect("a set creates the grid");
assert_eq!(after_set.meter_sequence.len(), 1);
assert_eq!(after_set.meter_sequence[0].time_signature, ts_a.id);
assert!(result.score.time_signatures.iter().any(|t| t.id == ts_a.id));
assert!(check_invariants(&result.score).is_empty());
// Replace: the causally-later write overwrites the single slot.
let mut set = OperationSet::new();
set.accept_all(envelopes[..2].to_vec());
let result = set.reduce_onto(&base);
let after_replace = grid(&result).expect("still present after replace");
assert_eq!(after_replace.meter_sequence.len(), 1);
assert_eq!(after_replace.meter_sequence[0].time_signature, ts_b.id);
assert!(check_invariants(&result.score).is_empty());
// Remove: the slot empties; the grid the set created normalizes away.
let mut set = OperationSet::new();
set.accept_all(envelopes.to_vec());
let result = set.reduce_onto(&base);
assert_eq!(grid(&result), None);
assert!(check_invariants(&result.score).is_empty());
}
#[test]
fn a_mid_region_meter_change_reduces_cleanly_p12_c5() {
// P12-C5: a mid-region SetTimeSignature reduces cleanly and materializes
// into the grid; the decomposition pre-pass still honors only the first
// governing meter (P12-H4) but must not crash on the multi-meter grid.
let base = epiphany_core::generators::valid_score(303);
let region = base.canvas.regions[0].id;
let ts_a = valuegen::time_signature(TimeSignatureId::new(ReplicaId(63), 1), 4);
let ts_b = valuegen::time_signature(TimeSignatureId::new(ReplicaId(63), 2), 3);
let envelopes = chain(
63,
10,
None,
vec![
set_meter(region, 0, Some(ts_a.clone())),
set_meter(region, 8, Some(ts_b.clone())),
],
);
let mut set = OperationSet::new();
set.accept_all(envelopes.clone());
let result = set.reduce_onto(&base);
for env in &envelopes {
assert_eq!(effect_for(&result, env.id), &OperationEffect::Applied);
}
let grid = result.score.canvas.regions[0]
.content
.staff_based()
.expect("fixture is staff based")
.default_metric_grid
.as_ref()
.expect("the sets created the grid");
assert_eq!(
grid.meter_sequence
.iter()
.map(|m| m.time_signature)
.collect::<Vec<_>>(),
vec![ts_a.id, ts_b.id],
"meter changes stay ordered by resolved position"
);
assert!(check_invariants(&result.score).is_empty());
// The pre-pass tolerates the multi-meter grid (P12-H4 owns honoring it).
let _ =
epiphany_core::derive_annotations(&result.score, &epiphany_core::PrePassProfile::default());
}
#[test]
fn set_tempo_segment_materializes_in_score_and_region_scope() {
let base = epiphany_core::generators::valid_score(304);
let region = base.canvas.regions[0].id;
let score_seg = valuegen::tempo_segment(region, MusicalPosition::origin(), 120.0);
let local_seg = valuegen::tempo_segment(region, MusicalPosition::origin(), 90.0);
let mut ramp =
valuegen::tempo_segment(region, MusicalPosition(RationalTime::from_int(4)), 60.0);
ramp.shape = epiphany_core::TempoShape::Linear; // no end data: malformed
let envelopes = chain(
64,
10,
None,
vec![
set_tempo(None, region, 0, Some(score_seg.clone())),
set_tempo(Some(region), region, 0, Some(local_seg.clone())),
set_tempo(None, region, 4, Some(ramp)),
set_tempo(Some(region), region, 0, None),
],
);
let mut set = OperationSet::new();
set.accept_all(envelopes.clone());
let result = set.reduce_onto(&base);
assert_eq!(
effect_for(&result, envelopes[0].id),
&OperationEffect::Applied
);
assert_eq!(
effect_for(&result, envelopes[1].id),
&OperationEffect::Applied
);
assert_eq!(
effect_for(&result, envelopes[2].id),
&precondition_noop(PreconditionFailureReason::TempoMapMalformed),
);
assert_eq!(
effect_for(&result, envelopes[3].id),
&OperationEffect::Applied
);
assert_eq!(result.score.tempo_map.segments, vec![score_seg]);
// The local map was created by the set and normalized away by the remove.
assert_eq!(result.score.canvas.regions[0].local_tempo_map, None);
assert!(check_invariants(&result.score).is_empty());
// The region-scoped set alone creates (and keeps) the local map.
let mut set = OperationSet::new();
set.accept_all(envelopes[..2].to_vec());
let result = set.reduce_onto(&base);
let local = result.score.canvas.regions[0]
.local_tempo_map
.as_ref()
.expect("a set on a region with no local map creates one");
assert_eq!(local.segments, vec![local_seg]);
assert!(check_invariants(&result.score).is_empty());
}
#[test]
fn set_staff_layout_is_an_advisory_lww_with_tombstone_noop() {
let base = epiphany_core::generators::valid_score(305);
let region = base.canvas.regions[0].id;
let instance = base.canvas.regions[0].staff_instances()[0].id;
let staff = base.canvas.regions[0].staff_instances()[0].staff;
let instrument = base.instruments[0].id;
// Two concurrent differing writes: advisory LWW — no conflict; the later
// in canonical order (greater replica at an equal stamp) wins.
let earlier = envelope(
65,
0,
10,
CausalContext::new(),
None,
prim(OperationKind::SetStaffLayout(SetStaffLayoutOp {
staff_instance: instance,
instrument_override: Some(instrument),
staff_lines_override: None,
visible: true,
})),
);
let later = envelope(
66,
0,
10,
CausalContext::new(),
None,
prim(OperationKind::SetStaffLayout(SetStaffLayoutOp {
staff_instance: instance,
instrument_override: None,
staff_lines_override: Some(epiphany_core::StaffLineConfiguration { line_count: 1 }),
visible: false,
})),
);
let mut set = OperationSet::new();
set.accept_all(vec![earlier.clone(), later.clone()]);
let result = set.reduce_onto(&base);
assert!(
result.state.conflicts.is_empty(),
"advisory LWW never conflicts"
);
assert_eq!(effect_for(&result, earlier.id), &OperationEffect::Applied);
assert_eq!(effect_for(&result, later.id), &OperationEffect::Applied);
let materialized = result
.score
.staff_instances()
.find(|(_, si)| si.id == instance)
.expect("instance survives")
.1
.clone();
assert_eq!(materialized.instrument_override, None);
assert_eq!(
materialized.staff_lines_override,
Some(epiphany_core::StaffLineConfiguration { line_count: 1 })
);
assert!(!materialized.visible);
assert!(check_invariants(&result.score).is_empty());
// A tombstoned target is a no-op: mint an empty instance, delete it, then
// aim a layout write at it.
let fresh = StaffInstanceId::new(ReplicaId(67), 1);
let envelopes = chain(
67,
10,
None,
vec![
OperationKind::CreateStaffInstance(CreateStaffInstanceOp {
region,
instance: valuegen::staff_instance(fresh, staff),
}),
OperationKind::DeleteStaffInstance(DeleteStaffInstanceOp {
staff_instance: fresh,
}),
OperationKind::SetStaffLayout(SetStaffLayoutOp {
staff_instance: fresh,
instrument_override: None,
staff_lines_override: None,
visible: false,
}),
],
);
let mut set = OperationSet::new();
set.accept_all(envelopes.clone());
let result = set.reduce_onto(&base);
assert_eq!(
effect_for(&result, envelopes[2].id),
&OperationEffect::NoOp {
reason: NoOpReason::TargetTombstoned,
},
);
}
/// The full value-restoring undo sweep: one transaction overwrites every LWW
/// family (and mints an event), and its undo restores each family to the base
/// state (operation_catalog §UndoTransaction "Value restoration").
#[test]
fn undo_restores_overwritten_values_across_every_family() {
let base = epiphany_core::generators::valid_score(306);
let region = base.canvas.regions[0].id;
let (staff_instance, target_voice) = target(&base);
let base_instance = base
.staff_instances()
.find(|(_, si)| si.id == staff_instance)
.expect("fixture instance")
.1
.clone();
let base_event_id = base_instance.voices[0].events[0];
let base_event = base.events.get(base_event_id).expect("base event").clone();
let mut pitches = Vec::new();
base_event.collect_identified_pitches(&mut pitches);
let base_pitch = pitches[0].id;
// A same-placement replacement value with different pitch content.
let mut replacement = base_event.clone();
if let Event::Pitched(pe) = &mut replacement {
pe.pitches[0].pitch = valuegen::pitch_value_nth(5);
}
let ts = valuegen::time_signature(TimeSignatureId::new(ReplicaId(70), 1), 4);
let segment = valuegen::tempo_segment(region, MusicalPosition::origin(), 120.0);
let inserted_event = EventId::new(ReplicaId(70), 100);
let inserted_pitch = PitchId::new(ReplicaId(70), 101);
let tx = TransactionId::from_raw(70);
let mut kinds = vec![
OperationKind::DeclareTransaction(TransactionDescriptor {
id: tx,
label: String::from("overwrite everything"),
category: Some(TransactionCategory::Structural),
}),
OperationKind::ModifyEvent(ModifyEventOp {
event: replacement.clone(),
}),
OperationKind::RespellPitch(RespellPitchOp {
pitch: base_pitch,
spelling: valuegen::spelling(3),
}),
OperationKind::SetMetricGrid(SetMetricGridOp {
region,
grid: Some(valuegen::metric_grid()),
}),
OperationKind::SetUserSystemBreak(SetUserSystemBreakOp {
region,
anchor: valuegen::region_start_anchor(
region,
MusicalPosition(RationalTime::from_int(8)),
),
present: true,
}),
set_meter(region, 0, Some(ts.clone())),
set_tempo(None, region, 0, Some(segment)),
OperationKind::SetStaffLayout(SetStaffLayoutOp {
staff_instance,
instrument_override: None,
staff_lines_override: None,
visible: false,
}),
OperationKind::SetMetadata(epiphany_ops::SetMetadataOp {
metadata: valuegen::score_metadata(7),
}),
];
kinds.push(OperationKind::InsertEvent(InsertEventOp {
staff_instance,
event: valuegen::insert_event_value(
inserted_event,
target_voice,
MusicalPosition(RationalTime::from_int(100)),
MusicalDuration::whole(),
&[inserted_pitch],
),
}));
let n = kinds.len() as u64;
let mut envelopes = chain(70, 10, Some(tx), kinds);
let undo = envelope(
70,
n,
10 + n as i64,
seen(70, n - 1),
None,
OperationPayload::UndoTransaction(UndoTransactionPayload {
target: tx,
policy: UndoPolicy::StrictInverse,
}),
);
envelopes.push(undo.clone());
let mut set = OperationSet::new();
set.accept_all(envelopes);
let result = set.reduce_onto(&base);
// Effect: the tombstone repairs only (the inserted event + pitch, and the
// transaction-minted time signature); restorations are not repairs.
match effect_for(&result, undo.id) {
OperationEffect::AppliedWithRepair { repairs } => {
assert!(repairs
.iter()
.all(|r| matches!(r.kind, RepairKind::CascadeDeleted)));
let targets: Vec<TypedObjectId> = repairs.iter().map(|r| r.target).collect();
assert!(targets.contains(&TypedObjectId::Event(inserted_event)));
assert!(targets.contains(&TypedObjectId::TimeSignature(ts.id)));
}
other => panic!("expected AppliedWithRepair, got {other:?}"),
}
assert!(result.state.conflicts.is_empty());
// Every overwritten family is back at its base value.
assert_eq!(
result.score.events.get(base_event_id),
Some(&base_event),
"the modified event's base value is restored"
);
assert_eq!(result.state.spellings.get(&base_pitch), None);
assert!(result.score.spelling_attachments.is_empty());
let content = result.score.canvas.regions[0]
.content
.staff_based()
.expect("fixture is staff based");
assert_eq!(content.default_metric_grid, None);
assert!(content.user_system_breaks.is_empty());
assert!(result.state.breaks.is_empty());
assert!(result.score.tempo_map.segments.is_empty());
assert!(!result.score.time_signatures.iter().any(|t| t.id == ts.id));
let restored_instance = result
.score
.staff_instances()
.find(|(_, si)| si.id == staff_instance)
.expect("instance survives")
.1
.clone();
assert_eq!(
(
restored_instance.instrument_override,
restored_instance.staff_lines_override.clone(),
restored_instance.visible
),
(
base_instance.instrument_override,
base_instance.staff_lines_override.clone(),
base_instance.visible
)
);
assert_eq!(result.score.metadata, base.metadata);
assert!(!result.score.events.contains(inserted_event));
assert!(check_invariants(&result.score).is_empty());
}
/// Replaced (rather than first-written) keys restore the *pre-transaction*
/// writes, not absence.
#[test]
fn undo_restores_the_pre_transaction_writes_for_replaced_keys() {
let base = epiphany_core::generators::valid_score(307);
let region = base.canvas.regions[0].id;
let (staff_instance, _) = target(&base);
let ts_pre = valuegen::time_signature(TimeSignatureId::new(ReplicaId(71), 1), 4);
let ts_tx = valuegen::time_signature(TimeSignatureId::new(ReplicaId(71), 2), 3);
let seg_pre = valuegen::tempo_segment(region, MusicalPosition::origin(), 120.0);
let seg_tx = valuegen::tempo_segment(region, MusicalPosition::origin(), 90.0);
let tx = TransactionId::from_raw(71);
let kinds = vec![
// Pre-transaction writers.
set_meter(region, 0, Some(ts_pre.clone())),
set_tempo(None, region, 0, Some(seg_pre.clone())),
OperationKind::SetStaffLayout(SetStaffLayoutOp {
staff_instance,
instrument_override: None,
staff_lines_override: None,
visible: false,
}),
// The transaction replaces all three keys.
OperationKind::DeclareTransaction(TransactionDescriptor {
id: tx,
label: String::from("replace"),
category: None,
}),
set_meter(region, 0, Some(ts_tx.clone())),
set_tempo(None, region, 0, Some(seg_tx)),
OperationKind::SetStaffLayout(SetStaffLayoutOp {
staff_instance,
instrument_override: None,
staff_lines_override: None,
visible: true,
}),
];
let mut envelopes: Vec<OperationEnvelope> = kinds
.into_iter()
.enumerate()
.map(|(index, kind)| {
let counter = index as u64;
let ctx = if counter == 0 {
CausalContext::new()
} else {
seen(71, counter - 1)
};
let tx_of = (counter >= 3).then_some(tx);
envelope(71, counter, 10 + counter as i64, ctx, tx_of, prim(kind))
})
.collect();
let undo = envelope(
71,
7,
20,
seen(71, 6),
None,
OperationPayload::UndoTransaction(UndoTransactionPayload {
target: tx,
policy: UndoPolicy::StrictInverse,
}),
);
envelopes.push(undo.clone());
let mut set = OperationSet::new();
set.accept_all(envelopes);
let result = set.reduce_onto(&base);
assert!(result.state.conflicts.is_empty());
let grid = result.score.canvas.regions[0]
.content
.staff_based()
.expect("fixture is staff based")
.default_metric_grid
.as_ref()
.expect("the pre-transaction meter survives");
assert_eq!(grid.meter_sequence.len(), 1);
assert_eq!(grid.meter_sequence[0].time_signature, ts_pre.id);
// The transaction-minted signature is tombstoned and gone; the
// pre-transaction one survives.
assert!(!result
.score
.time_signatures
.iter()
.any(|t| t.id == ts_tx.id));
assert!(result
.score
.time_signatures
.iter()
.any(|t| t.id == ts_pre.id));
assert_eq!(result.score.tempo_map.segments, vec![seg_pre]);
let instance = result
.score
.staff_instances()
.find(|(_, si)| si.id == staff_instance)
.expect("instance survives")
.1
.clone();
assert!(
!instance.visible,
"the pre-transaction layout write returns"
);
assert!(check_invariants(&result.score).is_empty());
}
/// A mixed mint + overwrite transaction: both compensation parts compose, and
/// `StrictInverse` refuses the whole undo when *either* part fails while
/// `BestEffort` compensates what it cleanly can.
#[test]
fn mixed_transaction_undo_composes_and_conflicts_by_policy() {
let base = epiphany_core::generators::valid_score(308);
let (staff_instance, target_voice) = target(&base);
let base_instance = base
.staff_instances()
.find(|(_, si)| si.id == staff_instance)
.expect("fixture instance")
.1
.clone();
let base_pitch = {
let event = base.events.get(base_instance.voices[0].events[0]).unwrap();
let mut pitches = Vec::new();
event.collect_identified_pitches(&mut pitches);
pitches[0].id
};
let inserted_event = EventId::new(ReplicaId(72), 100);
let tx = TransactionId::from_raw(72);
let tx_ops = |replica: u64| {
chain(
replica,
10,
Some(tx),
vec![
OperationKind::DeclareTransaction(TransactionDescriptor {
id: tx,
label: String::from("mixed"),
category: None,
}),
OperationKind::InsertEvent(InsertEventOp {
staff_instance,
event: valuegen::insert_event_value(
EventId::new(ReplicaId(replica), 100),
target_voice,
MusicalPosition(RationalTime::from_int(100)),
MusicalDuration::whole(),
&[PitchId::new(ReplicaId(replica), 101)],
),
}),
OperationKind::RespellPitch(RespellPitchOp {
pitch: base_pitch,
spelling: valuegen::spelling(2),
}),
],
)
};
// (A) A superseding respell after the transaction: StrictInverse refuses
// the whole undo — the minted event is NOT tombstoned either.
let mut envelopes = tx_ops(72);
envelopes.push(envelope(
72,
3,
20,
seen(72, 2),
None,
prim(OperationKind::RespellPitch(RespellPitchOp {
pitch: base_pitch,
spelling: valuegen::spelling(4),
})),
));
let strict = envelope(
72,
4,
30,
seen(72, 3),
None,
OperationPayload::UndoTransaction(UndoTransactionPayload {
target: tx,
policy: UndoPolicy::StrictInverse,
}),
);
envelopes.push(strict.clone());
let mut set = OperationSet::new();
set.accept_all(envelopes.clone());
let result = set.reduce_onto(&base);
assert!(matches!(
effect_for(&result, strict.id),
OperationEffect::Conflicted { .. }
));
assert!(
result.score.events.contains(inserted_event),
"a refused strict undo tombstones nothing"
);
assert_eq!(
result.state.spellings.get(&base_pitch),
Some(&valuegen::spelling(4))
);
assert!(check_invariants(&result.score).is_empty());
// (B) The same set under BestEffort: the mint is tombstoned, the
// superseded spelling is skipped.
let best = envelope(
72,
5,
40,
seen(72, 4),
None,
OperationPayload::UndoTransaction(UndoTransactionPayload {
target: tx,
policy: UndoPolicy::BestEffort,
}),
);
envelopes.push(best.clone());
let mut set = OperationSet::new();
set.accept_all(envelopes);
let result = set.reduce_onto(&base);
assert!(matches!(
effect_for(&result, best.id),
OperationEffect::AppliedWithRepair { .. }
));
assert!(!result.score.events.contains(inserted_event));
assert_eq!(
result.state.spellings.get(&base_pitch),
Some(&valuegen::spelling(4)),
"the superseding write stands under BestEffort"
);
assert!(check_invariants(&result.score).is_empty());
}
/// Undoing a staff mint refuses while a live (non-member) instance still
/// references the staff (operation_catalog §CreateStaff undo semantics), and
/// `BestEffort` keeps the staff alive rather than stranding the instance.
#[test]
fn undo_of_a_staff_mint_refuses_while_an_instance_references_it() {
let base = epiphany_core::generators::valid_score(309);
let region = base.canvas.regions[0].id;
let instrument = base.instruments[0].id;
let staff_id = StaffId::new(ReplicaId(73), 1);
let instance_id = StaffInstanceId::new(ReplicaId(73), 2);
let tx = TransactionId::from_raw(73);
let mut envelopes = chain(
73,
10,
Some(tx),
vec![
OperationKind::DeclareTransaction(TransactionDescriptor {
id: tx,
label: String::from("staff mint"),
category: None,
}),
OperationKind::CreateStaff(CreateStaffOp {
staff: valuegen::staff(staff_id, instrument),
}),
],
);
// A non-member instance referencing the minted staff.
let mut instance_env = envelope(
73,
2,
20,
seen(73, 1),
None,
prim(OperationKind::CreateStaffInstance(CreateStaffInstanceOp {
region,
instance: valuegen::staff_instance(instance_id, staff_id),
})),
);
instance_env.transaction = None;
envelopes.push(instance_env);
let strict = envelope(
73,
3,
30,
seen(73, 2),
None,
OperationPayload::UndoTransaction(UndoTransactionPayload {
target: tx,
policy: UndoPolicy::StrictInverse,
}),
);
let best = envelope(
73,
4,
40,
seen(73, 3),
None,
OperationPayload::UndoTransaction(UndoTransactionPayload {
target: tx,
policy: UndoPolicy::BestEffort,
}),
);
envelopes.push(strict.clone());
envelopes.push(best.clone());
let mut set = OperationSet::new();
set.accept_all(envelopes);
let result = set.reduce_onto(&base);
assert!(matches!(
effect_for(&result, strict.id),
OperationEffect::Conflicted { .. }
));
// BestEffort skips the stranding tombstone: nothing left to compensate,
// but the undo itself applies cleanly with no repairs.
assert_eq!(effect_for(&result, best.id), &OperationEffect::Applied);
assert!(result.score.staves.iter().any(|s| s.id == staff_id));
assert!(matches!(
result.state.objects.get(&TypedObjectId::Staff(staff_id)),
Some(epiphany_ops::ObjectState::Live)
));
assert!(check_invariants(&result.score).is_empty());
}

View File

@ -85,7 +85,10 @@ fn main() -> ExitCode {
let config = SolverConfig::default();
let (report, tier) = match solver.as_str() {
"stub" => (StubSolver.solve(&constrained, &config), StubSolver.tier()),
"real" => (Engraver.solve(&constrained, &config), Engraver.tier()),
"real" => (
Engraver::default().solve(&constrained, &config),
Engraver::default().tier(),
),
other => return fail(&format!("unknown solver {other:?}; known: {SOLVERS}")),
};

View File

@ -263,7 +263,7 @@ fn engrave_pipeline(score: &Score) -> (ConstrainedLayoutIR, RenderOutput) {
use epiphany_engrave::Engraver;
let constrained = to_constrained(&to_logical(score));
let layout = Engraver
let layout = Engraver::default()
.solve(&constrained, &SolverConfig::default())
.layout;
let out = render(&layout, &RenderOptions::default());
@ -285,7 +285,7 @@ fn engraver_output_is_golden_locked_well_formed_with_every_glyph_drawn() {
for (fixture, score) in fixtures() {
let constrained = to_constrained(&to_logical(&score));
let report = Engraver.solve(&constrained, &SolverConfig::default());
let report = Engraver::default().solve(&constrained, &SolverConfig::default());
assert_eq!(
report.status,
SolveStatus::Solved,

View File

@ -2,12 +2,12 @@ fixture=ten_measure_single_staff solver=engrave
glyph_count=51
path_count=51
fallback_rect_count=0
stroke_count=91
provenance_count=142
stroke_count=96
provenance_count=147
layer_count=1
hard_constraint_count=90
xml_well_formed=true
view_box=[-3.059857 -3.632 102.98304 11.024]
view_box=[5.4999995 -26.090391 83.99065 20.589813]
class_counts:
barline=10
clef=1

View File

@ -1,150 +1,155 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="1029.8304" height="110.24" viewBox="0 0 102.983 11.024">
<svg xmlns="http://www.w3.org/2000/svg" width="839.9065" height="205.8981" viewBox="0 0 83.9906 20.5898">
<!-- 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.0599 7.392) scale(1 -1)">
<g transform="translate(-5.5 -5.5006) 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="-0.9949" y1="0" x2="97.2984" y2="0" stroke="#000000" stroke-width="0.13" data-prov="5235156954d3cdda987a3da1af222a55" data-source-kind="3" data-kind="stroke"/>
<line x1="-0.9949" y1="1" x2="97.2984" y2="1" stroke="#000000" stroke-width="0.13" data-prov="10cbe3e182e937b8e3d90a03397b2e97" data-source-kind="3" data-kind="stroke"/>
<line x1="-0.9949" y1="2" x2="97.2984" y2="2" stroke="#000000" stroke-width="0.13" data-prov="94825cd467fe6fb512d271e66b59674a" data-source-kind="3" data-kind="stroke"/>
<line x1="-0.9949" y1="3" x2="97.2984" y2="3" stroke="#000000" stroke-width="0.13" data-prov="bc2cd26cec8e4650eedcf04fbc91320f" data-source-kind="3" data-kind="stroke"/>
<line x1="-0.9949" y1="4" x2="97.2984" 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.98" y1="-1" x2="5.98" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="dbd573fbc0c21f9b07a8537f658b048c" data-source-kind="0" data-kind="stroke"/>
<line x1="4.1846" y1="-1" x2="5.9652" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="07ffb45f25e59b0955bd4e248522b2ef" data-source-kind="1" data-kind="stroke"/>
<line x1="8.0607" y1="-1" x2="8.0607" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="276b6f72f2bcb818f84229b6833ce022" data-source-kind="0" data-kind="stroke"/>
<line x1="6.2652" y1="-1" x2="8.0459" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="3539af5cd23dc6ca259a35fef4aa45c2" data-source-kind="1" data-kind="stroke"/>
<line x1="10.1414" y1="-1" x2="10.1414" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="d1b0067ebb2b2b9115a77aaadc53bffe" data-source-kind="0" data-kind="stroke"/>
<line x1="8.3459" y1="-1" x2="10.1266" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="9b43d5d2a83ce1086364f6f3ac4b5ba2" data-source-kind="1" data-kind="stroke"/>
<line x1="12.0064" y1="-1" x2="12.0064" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="13303d184ca25b99835d71e21f3a96df" data-source-kind="0" data-kind="stroke"/>
<line x1="10.4266" y1="-1" x2="12.2072" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="d6c7a9458a06532e1c7507e71363c0dd" data-source-kind="1" data-kind="stroke"/>
<line x1="15.5027" y1="-1" x2="15.5027" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="aa33134cf72cebda65d1b2b427230541" data-source-kind="0" data-kind="stroke"/>
<line x1="13.7072" y1="-1" x2="15.4879" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="20fbe0162b0bc4dd93e2c3e3da73bb83" data-source-kind="1" data-kind="stroke"/>
<line x1="17.5834" y1="-1" x2="17.5834" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="01d06ae931cf05983e4b17744108297b" data-source-kind="0" data-kind="stroke"/>
<line x1="15.7879" y1="-1" x2="17.5686" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="cd9abf9ed66ab876f68b9d88ce1e5199" data-source-kind="1" data-kind="stroke"/>
<line x1="19.664" y1="-1" x2="19.664" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="47c9ce76466974ecfabe4a9cafb75c3c" data-source-kind="0" data-kind="stroke"/>
<line x1="17.8686" y1="-1" x2="19.6492" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="7ade41165c11102f29e87cb09eb3f88f" data-source-kind="1" data-kind="stroke"/>
<line x1="21.5291" y1="-1" x2="21.5291" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="e6d1c0a44d13be2700ad4381e1d518fa" data-source-kind="0" data-kind="stroke"/>
<line x1="19.9492" y1="-1" x2="21.7299" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="32c2aa89808756c46e8b7afcfb2eb057" data-source-kind="1" data-kind="stroke"/>
<line x1="25.0254" y1="-1" x2="25.0254" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="370a65aab516f6792e49a1ed28bd5cd9" data-source-kind="0" data-kind="stroke"/>
<line x1="23.2299" y1="-1" x2="25.0105" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="0a3ae3c155d29c21e266e663a8b1b214" data-source-kind="1" data-kind="stroke"/>
<line x1="27.106" y1="-1" x2="27.106" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="75b4bca159d5c28b10b2078bb457bb53" data-source-kind="0" data-kind="stroke"/>
<line x1="25.3105" y1="-1" x2="27.0912" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="c27e3e835224dc54cef9b36818099cd0" data-source-kind="1" data-kind="stroke"/>
<line x1="29.1867" y1="-1" x2="29.1867" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="c4531390972e55a43af2bd9f85e3c250" data-source-kind="0" data-kind="stroke"/>
<line x1="27.3912" y1="-1" x2="29.1719" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="4b43990bc25bf4afffda3e2a7d4dcab7" data-source-kind="1" data-kind="stroke"/>
<line x1="31.0517" y1="-1" x2="31.0517" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="888c6ef019fc4b0fc714672a4444c60c" data-source-kind="0" data-kind="stroke"/>
<line x1="29.4719" y1="-1" x2="31.2525" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="8e120598410629bb86883f318071a6b5" data-source-kind="1" data-kind="stroke"/>
<line x1="34.548" y1="-1" x2="34.548" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="6c70ad9006ed8fbb104d77eba3f2dbcb" data-source-kind="0" data-kind="stroke"/>
<line x1="32.7525" y1="-1" x2="34.5332" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="6d2724bb96160f831b4e987ce6b4349a" data-source-kind="1" data-kind="stroke"/>
<line x1="36.6287" y1="-1" x2="36.6287" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="debe82da2c34817a871d8a4cc5cc3fdd" data-source-kind="0" data-kind="stroke"/>
<line x1="34.8332" y1="-1" x2="36.6139" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="7a9f84c8590c7e8156fd929e35153cf8" data-source-kind="1" data-kind="stroke"/>
<line x1="38.7093" y1="-1" x2="38.7093" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="7b524c2e9e5657b68fd1a81337887c2e" data-source-kind="0" data-kind="stroke"/>
<line x1="36.9139" y1="-1" x2="38.6945" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="c4a9602f838e0ea74a9fe68da12baff2" data-source-kind="1" data-kind="stroke"/>
<line x1="40.5744" y1="-1" x2="40.5744" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="cb31fbbd4a3e3eb17ed06e65f40589e5" data-source-kind="0" data-kind="stroke"/>
<line x1="38.9945" y1="-1" x2="40.7752" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="76860486e62be8520184bb3e8c284d94" data-source-kind="1" data-kind="stroke"/>
<line x1="44.0707" y1="-1" x2="44.0707" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="bc1cc9bede7fc3c72827cfbf184cdb7e" data-source-kind="0" data-kind="stroke"/>
<line x1="42.2752" y1="-1" x2="44.0558" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="a3fd2ae47f432e61881cb1e44a7108e6" data-source-kind="1" data-kind="stroke"/>
<line x1="46.1513" y1="-1" x2="46.1513" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="95891bc2b58231a1618b5d422e6131b0" data-source-kind="0" data-kind="stroke"/>
<line x1="44.3558" y1="-1" x2="46.1365" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="a9e2ee2be83c0a4978db517bf182efe9" data-source-kind="1" data-kind="stroke"/>
<line x1="48.232" y1="-1" x2="48.232" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="ed21002861831422bdbb36e8f981d2a9" data-source-kind="0" data-kind="stroke"/>
<line x1="46.4365" y1="-1" x2="48.2172" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="2bac60a88ddddcf403dd6edfb67b5bb0" data-source-kind="1" data-kind="stroke"/>
<line x1="50.097" y1="-1" x2="50.097" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="e49748251746a6dbdf15aa8dd983e6e7" data-source-kind="0" data-kind="stroke"/>
<line x1="48.5172" y1="-1" x2="50.2978" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="a592bc951f4afa64726aa30ccdd6ddfa" data-source-kind="1" data-kind="stroke"/>
<line x1="53.5933" y1="-1" x2="53.5933" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="8d2938a01abfe451991829fbde6d2b94" data-source-kind="0" data-kind="stroke"/>
<line x1="51.7978" y1="-1" x2="53.5785" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="c73f1aad686621adde07e7fe7b160ad7" data-source-kind="1" data-kind="stroke"/>
<line x1="55.674" y1="-1" x2="55.674" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="88366a6edcce03db41d39d08b2616742" data-source-kind="0" data-kind="stroke"/>
<line x1="53.8785" y1="-1" x2="55.6591" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="4e052448582a8fdc672f38b02dc205e1" data-source-kind="1" data-kind="stroke"/>
<line x1="57.7546" y1="-1" x2="57.7546" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="77a10c7862f0ffce98d309bb71ce675f" data-source-kind="0" data-kind="stroke"/>
<line x1="55.9591" y1="-1" x2="57.7398" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="68c691f2336e5a322116641b9f2f7054" data-source-kind="1" data-kind="stroke"/>
<line x1="59.6197" y1="-1" x2="59.6197" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="cfa457e70519a1826f321a56cb3fd03b" data-source-kind="0" data-kind="stroke"/>
<line x1="58.0398" y1="-1" x2="59.8205" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="3c3f0d6b5ab00374d7628cd6f9876bc9" data-source-kind="1" data-kind="stroke"/>
<line x1="63.116" y1="-1" x2="63.116" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="273d04eeece605789035a29d80023c2c" data-source-kind="0" data-kind="stroke"/>
<line x1="61.3205" y1="-1" x2="63.1011" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="9ec226573f8ea2320fc444c562163ddb" data-source-kind="1" data-kind="stroke"/>
<line x1="65.1966" y1="-1" x2="65.1966" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="a23b09fda2784b566bf1de48e89a2baa" data-source-kind="0" data-kind="stroke"/>
<line x1="63.4011" y1="-1" x2="65.1818" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="9e3345d5dddffbb9cf263a1708fc2589" data-source-kind="1" data-kind="stroke"/>
<line x1="67.2773" y1="-1" x2="67.2773" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="edf9ca4611cfc17faace6241cae3b73d" data-source-kind="0" data-kind="stroke"/>
<line x1="65.4818" y1="-1" x2="67.2625" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="ea06a5b83026202fbe3eed55ca3680c9" data-source-kind="1" data-kind="stroke"/>
<line x1="69.1423" y1="-1" x2="69.1423" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="f1e3c0367633266d5f188e5bd6d98ad4" data-source-kind="0" data-kind="stroke"/>
<line x1="67.5625" y1="-1" x2="69.3431" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="b8d324f0620ebb59f66346b61a4d214e" data-source-kind="1" data-kind="stroke"/>
<line x1="72.6386" y1="-1" x2="72.6386" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="2bf824905032462ee8c9988b780feabf" data-source-kind="0" data-kind="stroke"/>
<line x1="70.8431" y1="-1" x2="72.6238" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="579f0f80918268f1ba03890d14832b56" data-source-kind="1" data-kind="stroke"/>
<line x1="74.7193" y1="-1" x2="74.7193" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="08a65aebf8baaded74feb3ec7cc36d18" data-source-kind="0" data-kind="stroke"/>
<line x1="72.9238" y1="-1" x2="74.7045" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="f04f04c436808c53ab1c2b169d7f5a33" data-source-kind="1" data-kind="stroke"/>
<line x1="76.7999" y1="-1" x2="76.7999" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="d155779804ad660f4466745153872349" data-source-kind="0" data-kind="stroke"/>
<line x1="75.0045" y1="-1" x2="76.7851" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="3b9174fe7361dbfc454fbe17b9ec81b1" data-source-kind="1" data-kind="stroke"/>
<line x1="78.665" y1="-1" x2="78.665" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="af73177552d5fa95b746caf62d000623" data-source-kind="0" data-kind="stroke"/>
<line x1="77.0851" y1="-1" x2="78.8658" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="46e6eac9f03c1e09a15cb0bb07628570" data-source-kind="1" data-kind="stroke"/>
<line x1="82.1613" y1="-1" x2="82.1613" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="7c839e4fb5f2522c8fde3b48577eafae" data-source-kind="0" data-kind="stroke"/>
<line x1="80.3658" y1="-1" x2="82.1465" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="0e4ec9f37979b06c54f0e2d499df5bf1" data-source-kind="1" data-kind="stroke"/>
<line x1="84.242" y1="-1" x2="84.242" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="0c8e0b8170c827494e606d4541ce2c89" data-source-kind="0" data-kind="stroke"/>
<line x1="82.4465" y1="-1" x2="84.2271" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="61f1ed037baf82bf6594769a9205df07" data-source-kind="1" data-kind="stroke"/>
<line x1="86.3226" y1="-1" x2="86.3226" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="42480ffc14ad129d8dec456e7bad317c" data-source-kind="0" data-kind="stroke"/>
<line x1="84.5271" y1="-1" x2="86.3078" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="adc82c9de5d3ed333e335c32f7690a6b" data-source-kind="1" data-kind="stroke"/>
<line x1="88.4033" y1="-1" x2="88.4033" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="0f408610c63793003f04e3af5a03493f" data-source-kind="0" data-kind="stroke"/>
<line x1="86.6078" y1="-1" x2="88.3885" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="1dee0e17b4b9167cda8e51b45e2ab47c" data-source-kind="1" data-kind="stroke"/>
<line x1="90.484" y1="-1" x2="90.484" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="99d50b32a7d1eac1019c14132a834144" data-source-kind="0" data-kind="stroke"/>
<line x1="88.6885" y1="-1" x2="90.4692" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="c33f0e929fa3ea4230c07aca80f60c04" data-source-kind="1" data-kind="stroke"/>
<line x1="92.5647" y1="-1" x2="92.5647" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="572e96450f21b18771e510c6c41444dc" data-source-kind="0" data-kind="stroke"/>
<line x1="90.7692" y1="-1" x2="92.5498" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="67463b29bfb160641501f497f6134aa6" data-source-kind="1" data-kind="stroke"/>
<line x1="94.6453" y1="-1" x2="94.6453" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="68935c410dbe357c3f80b01c6e6d3cb1" data-source-kind="0" data-kind="stroke"/>
<line x1="92.8498" y1="-1" x2="94.6305" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="75f41055f9e23911ca51158dab6488df" data-source-kind="1" data-kind="stroke"/>
<line x1="95.8911" y1="-1" x2="95.8911" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="9e9ba65603d31963bdc6d55dfacc5eba" data-source-kind="0" data-kind="stroke"/>
<line x1="94.9305" y1="-1" x2="96.7112" 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="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.4846 -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.5652 -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(8.6459 -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(10.7266 -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(14.0072 -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(16.0879 -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(18.1686 -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(20.2492 -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(23.5299 -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(25.6105 -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(27.6912 -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(29.7719 -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(33.0525 -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(35.1332 -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(37.2139 -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(39.2945 -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(42.5752 -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(44.6558 -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(46.7365 -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(48.8172 -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(52.0978 -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(54.1785 -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(56.2591 -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(58.3398 -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(61.6205 -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(63.7011 -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(65.7818 -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(67.8625 -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(71.1431 -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(73.2238 -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(75.3045 -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(77.3851 -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(80.6658 -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(82.7465 -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(84.8271 -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(86.9078 -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(88.9885 -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(91.0692 -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(93.1498 -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(95.2305 -1)" fill="#000000" data-prov="df598a181d9acccffc346705fce5da9b" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
<path d="M0.144 0V4H0V0Z" transform="translate(2.9846 0)" fill="#000000" data-prov="867b15e7cfaeccccfe301517166fe106" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
<path d="M0.144 0V4H0V0Z" transform="translate(12.5072 0)" fill="#000000" data-prov="2d8913235b36cc0b1c41dc5238cba014" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
<path d="M0.144 0V4H0V0Z" transform="translate(22.0299 0)" fill="#000000" data-prov="b263edd8a4514fa237d4e7942c04be8c" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
<path d="M0.144 0V4H0V0Z" transform="translate(31.5525 0)" fill="#000000" data-prov="abc1bd57e0116cfc47c4f84f0c22b2e6" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
<path d="M0.144 0V4H0V0Z" transform="translate(41.0752 0)" fill="#000000" data-prov="9f811a0578b546d35393e29bb06afe9b" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
<path d="M0.144 0V4H0V0Z" transform="translate(50.5978 0)" fill="#000000" data-prov="7c62f742d0a60d8a6177a714ef9241af" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
<path d="M0.144 0V4H0V0Z" transform="translate(60.1205 0)" fill="#000000" data-prov="0b1e63cbd07616159ee70000e3c76056" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
<path d="M0.144 0V4H0V0Z" transform="translate(69.6431 0)" fill="#000000" data-prov="f21f4e337050d50f36f7d905196db794" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
<path d="M0.144 0V4H0V0Z" transform="translate(79.1658 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(97.0112 0)" fill="#000000" data-prov="d99180b4838ec1269b3f4caee0201968" data-source-kind="9" data-glyph="barlineFinal" data-class="barline"/>
<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="87.4256" 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="87.4256" 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="87.4256" 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="87.4256" 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="87.4256" 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.0626" y1="-13.8926" x2="24.0626" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="aa33134cf72cebda65d1b2b427230541" data-source-kind="0" data-kind="stroke"/>
<line x1="22.2671" y1="-13.8926" x2="24.0477" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="20fbe0162b0bc4dd93e2c3e3da73bb83" data-source-kind="1" data-kind="stroke"/>
<line x1="26.1432" y1="-13.8926" x2="26.1432" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="01d06ae931cf05983e4b17744108297b" data-source-kind="0" data-kind="stroke"/>
<line x1="24.3477" y1="-13.8926" x2="26.1284" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="cd9abf9ed66ab876f68b9d88ce1e5199" data-source-kind="1" data-kind="stroke"/>
<line x1="28.2239" y1="-13.8926" x2="28.2239" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="47c9ce76466974ecfabe4a9cafb75c3c" data-source-kind="0" data-kind="stroke"/>
<line x1="26.4284" y1="-13.8926" x2="28.2091" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="7ade41165c11102f29e87cb09eb3f88f" data-source-kind="1" data-kind="stroke"/>
<line x1="30.0889" y1="-13.8926" x2="30.0889" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="e6d1c0a44d13be2700ad4381e1d518fa" data-source-kind="0" data-kind="stroke"/>
<line x1="28.5091" y1="-13.8926" x2="30.2897" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="32c2aa89808756c46e8b7afcfb2eb057" data-source-kind="1" data-kind="stroke"/>
<line x1="33.5852" y1="-13.8926" x2="33.5852" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="370a65aab516f6792e49a1ed28bd5cd9" data-source-kind="0" data-kind="stroke"/>
<line x1="31.7897" y1="-13.8926" x2="33.5704" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="0a3ae3c155d29c21e266e663a8b1b214" data-source-kind="1" data-kind="stroke"/>
<line x1="35.6659" y1="-13.8926" x2="35.6659" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="75b4bca159d5c28b10b2078bb457bb53" data-source-kind="0" data-kind="stroke"/>
<line x1="33.8704" y1="-13.8926" x2="35.6511" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="c27e3e835224dc54cef9b36818099cd0" data-source-kind="1" data-kind="stroke"/>
<line x1="37.7465" y1="-13.8926" x2="37.7465" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="c4531390972e55a43af2bd9f85e3c250" data-source-kind="0" data-kind="stroke"/>
<line x1="35.9511" y1="-13.8926" x2="37.7317" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="4b43990bc25bf4afffda3e2a7d4dcab7" data-source-kind="1" data-kind="stroke"/>
<line x1="39.6116" y1="-13.8926" x2="39.6116" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="888c6ef019fc4b0fc714672a4444c60c" data-source-kind="0" data-kind="stroke"/>
<line x1="38.0317" y1="-13.8926" x2="39.8124" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="8e120598410629bb86883f318071a6b5" data-source-kind="1" data-kind="stroke"/>
<line x1="43.1079" y1="-13.8926" x2="43.1079" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="6c70ad9006ed8fbb104d77eba3f2dbcb" data-source-kind="0" data-kind="stroke"/>
<line x1="41.3124" y1="-13.8926" x2="43.093" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="6d2724bb96160f831b4e987ce6b4349a" data-source-kind="1" data-kind="stroke"/>
<line x1="45.1885" y1="-13.8926" x2="45.1885" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="debe82da2c34817a871d8a4cc5cc3fdd" data-source-kind="0" data-kind="stroke"/>
<line x1="43.393" y1="-13.8926" x2="45.1737" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="7a9f84c8590c7e8156fd929e35153cf8" data-source-kind="1" data-kind="stroke"/>
<line x1="47.2692" y1="-13.8926" x2="47.2692" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="7b524c2e9e5657b68fd1a81337887c2e" data-source-kind="0" data-kind="stroke"/>
<line x1="45.4737" y1="-13.8926" x2="47.2544" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="c4a9602f838e0ea74a9fe68da12baff2" data-source-kind="1" data-kind="stroke"/>
<line x1="49.1342" y1="-13.8926" x2="49.1342" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="cb31fbbd4a3e3eb17ed06e65f40589e5" data-source-kind="0" data-kind="stroke"/>
<line x1="47.5544" y1="-13.8926" x2="49.335" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="76860486e62be8520184bb3e8c284d94" data-source-kind="1" data-kind="stroke"/>
<line x1="52.6305" y1="-13.8926" x2="52.6305" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="bc1cc9bede7fc3c72827cfbf184cdb7e" data-source-kind="0" data-kind="stroke"/>
<line x1="50.835" y1="-13.8926" x2="52.6157" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="a3fd2ae47f432e61881cb1e44a7108e6" data-source-kind="1" data-kind="stroke"/>
<line x1="54.7112" y1="-13.8926" x2="54.7112" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="95891bc2b58231a1618b5d422e6131b0" data-source-kind="0" data-kind="stroke"/>
<line x1="52.9157" y1="-13.8926" x2="54.6964" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="a9e2ee2be83c0a4978db517bf182efe9" data-source-kind="1" data-kind="stroke"/>
<line x1="56.7918" y1="-13.8926" x2="56.7918" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="ed21002861831422bdbb36e8f981d2a9" data-source-kind="0" data-kind="stroke"/>
<line x1="54.9964" y1="-13.8926" x2="56.777" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="2bac60a88ddddcf403dd6edfb67b5bb0" data-source-kind="1" data-kind="stroke"/>
<line x1="58.6569" y1="-13.8926" x2="58.6569" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="e49748251746a6dbdf15aa8dd983e6e7" data-source-kind="0" data-kind="stroke"/>
<line x1="57.077" y1="-13.8926" x2="58.8577" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="a592bc951f4afa64726aa30ccdd6ddfa" data-source-kind="1" data-kind="stroke"/>
<line x1="62.1532" y1="-13.8926" x2="62.1532" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="8d2938a01abfe451991829fbde6d2b94" data-source-kind="0" data-kind="stroke"/>
<line x1="60.3577" y1="-13.8926" x2="62.1383" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="c73f1aad686621adde07e7fe7b160ad7" data-source-kind="1" data-kind="stroke"/>
<line x1="64.2338" y1="-13.8926" x2="64.2338" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="88366a6edcce03db41d39d08b2616742" data-source-kind="0" data-kind="stroke"/>
<line x1="62.4383" y1="-13.8926" x2="64.219" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="4e052448582a8fdc672f38b02dc205e1" data-source-kind="1" data-kind="stroke"/>
<line x1="66.3145" y1="-13.8926" x2="66.3145" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="77a10c7862f0ffce98d309bb71ce675f" data-source-kind="0" data-kind="stroke"/>
<line x1="64.519" y1="-13.8926" x2="66.2997" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="68c691f2336e5a322116641b9f2f7054" data-source-kind="1" data-kind="stroke"/>
<line x1="68.1795" y1="-13.8926" x2="68.1795" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="cfa457e70519a1826f321a56cb3fd03b" data-source-kind="0" data-kind="stroke"/>
<line x1="66.5997" y1="-13.8926" x2="68.3803" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="3c3f0d6b5ab00374d7628cd6f9876bc9" data-source-kind="1" data-kind="stroke"/>
<line x1="71.6758" y1="-13.8926" x2="71.6758" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="273d04eeece605789035a29d80023c2c" data-source-kind="0" data-kind="stroke"/>
<line x1="69.8803" y1="-13.8926" x2="71.661" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="9ec226573f8ea2320fc444c562163ddb" data-source-kind="1" data-kind="stroke"/>
<line x1="73.7565" y1="-13.8926" x2="73.7565" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="a23b09fda2784b566bf1de48e89a2baa" data-source-kind="0" data-kind="stroke"/>
<line x1="71.961" y1="-13.8926" x2="73.7417" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="9e3345d5dddffbb9cf263a1708fc2589" data-source-kind="1" data-kind="stroke"/>
<line x1="75.8371" y1="-13.8926" x2="75.8371" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="edf9ca4611cfc17faace6241cae3b73d" data-source-kind="0" data-kind="stroke"/>
<line x1="74.0417" y1="-13.8926" x2="75.8223" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="ea06a5b83026202fbe3eed55ca3680c9" data-source-kind="1" data-kind="stroke"/>
<line x1="77.7022" y1="-13.8926" x2="77.7022" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="f1e3c0367633266d5f188e5bd6d98ad4" data-source-kind="0" data-kind="stroke"/>
<line x1="76.1223" y1="-13.8926" x2="77.903" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="b8d324f0620ebb59f66346b61a4d214e" data-source-kind="1" data-kind="stroke"/>
<line x1="81.1985" y1="-13.8926" x2="81.1985" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="2bf824905032462ee8c9988b780feabf" data-source-kind="0" data-kind="stroke"/>
<line x1="79.403" y1="-13.8926" x2="81.1837" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="579f0f80918268f1ba03890d14832b56" data-source-kind="1" data-kind="stroke"/>
<line x1="83.2791" y1="-13.8926" x2="83.2791" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="08a65aebf8baaded74feb3ec7cc36d18" data-source-kind="0" data-kind="stroke"/>
<line x1="81.4837" y1="-13.8926" x2="83.2643" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="f04f04c436808c53ab1c2b169d7f5a33" data-source-kind="1" data-kind="stroke"/>
<line x1="85.3598" y1="-13.8926" x2="85.3598" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="d155779804ad660f4466745153872349" data-source-kind="0" data-kind="stroke"/>
<line x1="83.5643" y1="-13.8926" x2="85.345" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="3b9174fe7361dbfc454fbe17b9ec81b1" data-source-kind="1" data-kind="stroke"/>
<line x1="87.2248" y1="-13.8926" x2="87.2248" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="af73177552d5fa95b746caf62d000623" data-source-kind="0" data-kind="stroke"/>
<line x1="85.645" y1="-13.8926" x2="87.4256" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="46e6eac9f03c1e09a15cb0bb07628570" data-source-kind="1" data-kind="stroke"/>
<line x1="10.5605" y1="-23.5904" x2="10.5605" y2="-20.0904" stroke="#000000" stroke-width="0.12" data-prov="7c839e4fb5f2522c8fde3b48577eafae" data-source-kind="0" data-kind="stroke"/>
<line x1="8.765" y1="-23.5904" x2="10.5457" y2="-23.5904" stroke="#000000" stroke-width="0.13" data-prov="0e4ec9f37979b06c54f0e2d499df5bf1" data-source-kind="1" data-kind="stroke"/>
<line x1="12.6412" y1="-23.5904" x2="12.6412" y2="-20.0904" stroke="#000000" stroke-width="0.12" data-prov="0c8e0b8170c827494e606d4541ce2c89" data-source-kind="0" data-kind="stroke"/>
<line x1="10.8457" y1="-23.5904" x2="12.6263" y2="-23.5904" stroke="#000000" stroke-width="0.13" data-prov="61f1ed037baf82bf6594769a9205df07" data-source-kind="1" data-kind="stroke"/>
<line x1="14.7218" y1="-23.5904" x2="14.7218" y2="-20.0904" stroke="#000000" stroke-width="0.12" data-prov="42480ffc14ad129d8dec456e7bad317c" data-source-kind="0" data-kind="stroke"/>
<line x1="12.9263" y1="-23.5904" x2="14.707" y2="-23.5904" stroke="#000000" stroke-width="0.13" data-prov="adc82c9de5d3ed333e335c32f7690a6b" data-source-kind="1" data-kind="stroke"/>
<line x1="16.8025" y1="-23.5904" x2="16.8025" y2="-20.0904" stroke="#000000" stroke-width="0.12" data-prov="0f408610c63793003f04e3af5a03493f" data-source-kind="0" data-kind="stroke"/>
<line x1="15.007" y1="-23.5904" x2="16.7877" y2="-23.5904" stroke="#000000" stroke-width="0.13" data-prov="1dee0e17b4b9167cda8e51b45e2ab47c" data-source-kind="1" data-kind="stroke"/>
<line x1="18.8832" y1="-23.5904" x2="18.8832" y2="-20.0904" stroke="#000000" stroke-width="0.12" data-prov="99d50b32a7d1eac1019c14132a834144" data-source-kind="0" data-kind="stroke"/>
<line x1="17.0877" y1="-23.5904" x2="18.8684" y2="-23.5904" stroke="#000000" stroke-width="0.13" data-prov="c33f0e929fa3ea4230c07aca80f60c04" data-source-kind="1" data-kind="stroke"/>
<line x1="20.9639" y1="-23.5904" x2="20.9639" y2="-20.0904" stroke="#000000" stroke-width="0.12" data-prov="572e96450f21b18771e510c6c41444dc" data-source-kind="0" data-kind="stroke"/>
<line x1="19.1684" y1="-23.5904" x2="20.949" y2="-23.5904" stroke="#000000" stroke-width="0.13" data-prov="67463b29bfb160641501f497f6134aa6" data-source-kind="1" data-kind="stroke"/>
<line x1="23.0445" y1="-23.5904" x2="23.0445" y2="-20.0904" stroke="#000000" stroke-width="0.12" data-prov="68935c410dbe357c3f80b01c6e6d3cb1" data-source-kind="0" data-kind="stroke"/>
<line x1="21.249" y1="-23.5904" x2="23.0297" y2="-23.5904" stroke="#000000" stroke-width="0.13" data-prov="75f41055f9e23911ca51158dab6488df" data-source-kind="1" data-kind="stroke"/>
<line x1="24.2903" y1="-23.5904" x2="24.2903" y2="-20.0904" stroke="#000000" stroke-width="0.12" data-prov="9e9ba65603d31963bdc6d55dfacc5eba" data-source-kind="0" data-kind="stroke"/>
<line x1="23.3297" y1="-23.5904" x2="25.1104" y2="-23.5904" 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="c183669ac91f35eb7a0040b48fde85ab" data-source-kind="25" data-kind="stroke"/>
<line x1="7.565" y1="-22.5904" x2="25.6976" y2="-22.5904" stroke="#000000" stroke-width="0.13" data-prov="e5c54b1ebf58ed5292d1b98f73ad084e" data-source-kind="3" data-kind="stroke"/>
<line x1="7.565" y1="-21.5904" x2="25.6976" y2="-21.5904" stroke="#000000" stroke-width="0.13" data-prov="9ee2d26461a7bc2f28eaf20e07b55ee0" data-source-kind="3" data-kind="stroke"/>
<line x1="7.565" y1="-20.5904" x2="25.6976" y2="-20.5904" stroke="#000000" stroke-width="0.13" data-prov="c5feea54a17beefce1088337f84e39fc" data-source-kind="3" data-kind="stroke"/>
<line x1="7.565" y1="-19.5904" x2="25.6976" y2="-19.5904" stroke="#000000" stroke-width="0.13" data-prov="688349b3e25cac395b2dcca6e297dcfe" data-source-kind="3" data-kind="stroke"/>
<line x1="7.565" y1="-18.5904" x2="25.6976" y2="-18.5904" 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(22.5671 -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(24.6477 -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(26.7284 -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(28.8091 -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.0897 -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.1704 -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.2511 -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.3317 -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(41.6124 -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(43.693 -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(45.7737 -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(47.8544 -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(51.135 -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(53.2157 -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(55.2964 -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(57.377 -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(60.6577 -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(62.7383 -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(64.819 -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(66.8997 -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(70.1803 -13.8926)" 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(72.261 -13.8926)" 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(74.3417 -13.8926)" 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(76.4223 -13.8926)" 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(79.703 -13.8926)" 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(81.7836 -13.8926)" 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(83.8643 -13.8926)" 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(85.945 -13.8926)" 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(9.065 -23.5904)" 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(11.1457 -23.5904)" 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(13.2263 -23.5904)" 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(15.307 -23.5904)" 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(17.3877 -23.5904)" 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(19.4684 -23.5904)" 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(21.549 -23.5904)" 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(23.6297 -23.5904)" 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.144 0V4H0V0Z" transform="translate(21.0671 -12.8926)" fill="#000000" data-prov="2d8913235b36cc0b1c41dc5238cba014" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
<path d="M0.144 0V4H0V0Z" transform="translate(30.5897 -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.1124 -12.8926)" fill="#000000" data-prov="abc1bd57e0116cfc47c4f84f0c22b2e6" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
<path d="M0.144 0V4H0V0Z" transform="translate(49.635 -12.8926)" fill="#000000" data-prov="9f811a0578b546d35393e29bb06afe9b" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
<path d="M0.144 0V4H0V0Z" transform="translate(59.1577 -12.8926)" fill="#000000" data-prov="7c62f742d0a60d8a6177a714ef9241af" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
<path d="M0.144 0V4H0V0Z" transform="translate(68.6803 -12.8926)" fill="#000000" data-prov="0b1e63cbd07616159ee70000e3c76056" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
<path d="M0.144 0V4H0V0Z" transform="translate(78.203 -12.8926)" fill="#000000" data-prov="f21f4e337050d50f36f7d905196db794" data-source-kind="9" data-glyph="barlineSingle" data-class="barline"/>
<path d="M0.144 0V4H0V0Z" transform="translate(7.565 -22.5904)" 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(25.4104 -22.5904)" fill="#000000" data-prov="d99180b4838ec1269b3f4caee0201968" data-source-kind="9" data-glyph="barlineFinal" data-class="barline"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -7,7 +7,7 @@ provenance_count=49
layer_count=1
hard_constraint_count=15
xml_well_formed=true
view_box=[-3.1598568 -3.632 31.588375 11.024]
view_box=[5.4999995 -38.57536 15.298422 33.074783]
class_counts:
barline=1
clef=3

View File

@ -1,57 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="315.8838" height="110.24" viewBox="0 0 31.5884 11.024">
<svg xmlns="http://www.w3.org/2000/svg" width="152.9842" height="330.7478" viewBox="0 0 15.2984 33.0748">
<!-- 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.1599 7.392) scale(1 -1)">
<g transform="translate(-5.5 -5.5006) scale(1 -1)">
<g data-layer="0">
<line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="5fc3f9fc71005c8f91cef3c82cf457d7" data-source-kind="6" data-kind="stroke"/>
<line x1="-1.0949" y1="0" x2="9.3932" y2="0" stroke="#000000" stroke-width="0.13" data-prov="540fd4c2706753fcc11d7abf84677260" data-source-kind="3" data-kind="stroke"/>
<line x1="-1.0949" y1="1" x2="9.3932" y2="1" stroke="#000000" stroke-width="0.13" data-prov="fea4bf7d60f88df52ba88e1026e63342" data-source-kind="3" data-kind="stroke"/>
<line x1="-1.0949" y1="2" x2="9.3932" y2="2" stroke="#000000" stroke-width="0.13" data-prov="2ce0db586e615b2e13faf76b21a0d74e" data-source-kind="3" data-kind="stroke"/>
<line x1="-1.0949" y1="3" x2="9.3932" y2="3" stroke="#000000" stroke-width="0.13" data-prov="d6f4c9a4f9b381f86948af62dd614587" data-source-kind="3" data-kind="stroke"/>
<line x1="-1.0949" y1="4" x2="9.3932" y2="4" stroke="#000000" stroke-width="0.13" data-prov="d26797f8b55b1986957fe0b86a4d8ad3" data-source-kind="3" data-kind="stroke"/>
<line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="fbab9b9c1117f1a77321eef2528313c7" data-source-kind="2" data-kind="stroke"/>
<line x1="4.78" y1="-1" x2="4.78" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="685bb2f9eba2acf767017fe46340d32d" data-source-kind="0" data-kind="stroke"/>
<line x1="2.9846" y1="-1" x2="4.7652" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="badbeee9b287e9d086850c2be907fce8" data-source-kind="1" data-kind="stroke"/>
<line x1="6.8607" y1="-1" x2="6.8607" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="bfca9c8f8054f3d500e342f0b4750c5e" data-source-kind="0" data-kind="stroke"/>
<line x1="5.0652" y1="-1" x2="6.8459" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="9a0cb14210151fc9b638f964beb65c6c" data-source-kind="1" data-kind="stroke"/>
<line x1="8.1065" y1="-1" x2="8.1065" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="150cdc069f098c84e4293a2bb4ae6693" data-source-kind="0" data-kind="stroke"/>
<line x1="7.1459" y1="-1" x2="8.9266" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="01fbbb3559a0372164da3f005943fc45" data-source-kind="1" data-kind="stroke"/>
<line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="ccedf36ea2c89203f216fe2bdcf32577" data-source-kind="12" data-kind="stroke"/>
<line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="0bc5e2a029d8599bd4d15a916eb90318" data-source-kind="22" data-kind="stroke"/>
<line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="5078100681364e0eef0becb94f607df4" data-source-kind="14" data-kind="stroke"/>
<line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="850009fc86dd7744c4dea1ea6a28be42" data-source-kind="15" data-kind="stroke"/>
<line x1="0" y1="0" x2="0" y2="0" stroke="#000000" stroke-width="0" data-prov="0b3280aa906b67171cb829398b7bf1f0" data-source-kind="25" data-kind="stroke"/>
<line x1="10.7266" y1="0" x2="10.7266" y2="0" stroke="#000000" stroke-width="0" data-prov="d9a97871d4088c0f7e7c1ab7e4fd49b7" data-source-kind="6" data-kind="stroke"/>
<line x1="10.3932" y1="0" x2="16.5023" y2="0" stroke="#000000" stroke-width="0.13" data-prov="74a1941933b935bf49129ddec9ceb7cb" data-source-kind="3" data-kind="stroke"/>
<line x1="10.3932" y1="1" x2="16.5023" y2="1" stroke="#000000" stroke-width="0.13" data-prov="9127c3ddf63996fbde1522f2af47e640" data-source-kind="3" data-kind="stroke"/>
<line x1="10.3932" y1="2" x2="16.5023" y2="2" stroke="#000000" stroke-width="0.13" data-prov="6244e1812ecbd0f97ace48fed547d2a0" data-source-kind="3" data-kind="stroke"/>
<line x1="10.3932" y1="3" x2="16.5023" y2="3" stroke="#000000" stroke-width="0.13" data-prov="daaa82fcc1b3fceb63ecca023f71c3a6" data-source-kind="3" data-kind="stroke"/>
<line x1="10.3932" y1="4" x2="16.5023" y2="4" stroke="#000000" stroke-width="0.13" data-prov="00ce5a2fe2e45b498b1164e3ce0239f0" data-source-kind="3" data-kind="stroke"/>
<line x1="10.7266" y1="0" x2="10.7266" y2="0" stroke="#000000" stroke-width="0" data-prov="374743e32dbbfe82916c87b68c2fcbd3" data-source-kind="2" data-kind="stroke"/>
<line x1="15.291" y1="-1" x2="15.291" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="27a2f99d23428f969f8f22d7e42f89a4" data-source-kind="0" data-kind="stroke"/>
<line x1="13.7111" y1="-1" x2="15.4918" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="370371bf62378295492e01246d4ee16f" data-source-kind="1" data-kind="stroke"/>
<line x1="16.0188" y1="-0.5" x2="16.0188" y2="3" stroke="#000000" stroke-width="0.12" data-prov="4ba26f525e6c664f9f3727b3f001c4f7" data-source-kind="0" data-kind="stroke"/>
<line x1="17.2918" y1="0" x2="17.2918" y2="0" stroke="#000000" stroke-width="0" data-prov="cc34303d8801d20b413432a3a1b66aa0" data-source-kind="6" data-kind="stroke"/>
<line x1="17.0944" y1="0" x2="26.3635" y2="0" stroke="#000000" stroke-width="0.13" data-prov="ff986fa2737b7cc76925b8b003dfb446" data-source-kind="3" data-kind="stroke"/>
<line x1="17.0944" y1="1" x2="26.3635" y2="1" stroke="#000000" stroke-width="0.13" data-prov="8c632b729231878fa18c479fd24d7436" data-source-kind="3" data-kind="stroke"/>
<line x1="17.0944" y1="2" x2="26.3635" y2="2" stroke="#000000" stroke-width="0.13" data-prov="0c8b92c6e5cb8d4afcffe28972595e85" data-source-kind="3" data-kind="stroke"/>
<line x1="17.0944" y1="3" x2="26.3635" y2="3" stroke="#000000" stroke-width="0.13" data-prov="d01bcd62385bf77312ae65017a9b4a50" data-source-kind="3" data-kind="stroke"/>
<line x1="17.0944" y1="4" x2="26.3635" y2="4" stroke="#000000" stroke-width="0.13" data-prov="c882fdc5188e96b8a1950c11dfd401e9" data-source-kind="3" data-kind="stroke"/>
<line x1="17.2918" y1="0" x2="17.2918" y2="0" stroke="#000000" stroke-width="0" data-prov="2fe40e9238d3fc28c88d7082dbbec593" data-source-kind="2" data-kind="stroke"/>
<line x1="21.8562" y1="-1" x2="21.8562" y2="2.5" stroke="#000000" stroke-width="0.12" data-prov="8b99315d75c981620504d9274be1fa45" data-source-kind="0" data-kind="stroke"/>
<line x1="20.2764" y1="-1" x2="22.057" y2="-1" stroke="#000000" stroke-width="0.13" data-prov="95dfefec6481cc7be34a31c283b7fd8c" data-source-kind="1" data-kind="stroke"/>
<line x1="23.6369" y1="-0.5" x2="23.6369" y2="3" stroke="#000000" stroke-width="0.12" data-prov="b98aa6fd428e2b9f68fefec4b05a2b78" data-source-kind="0" data-kind="stroke"/>
<path d="M1.504 1.66C1.496 1.708 1.504 1.712 1.528 1.736C1.96 2.14 2.288 2.648 2.288 3.26C2.288 3.608 2.192 3.952 2.028 4.192C1.968 4.28 1.864 4.392 1.82 4.392C1.764 4.392 1.64 4.288 1.56 4.2C1.264 3.872 1.168 3.372 1.168 2.956C1.168 2.724 1.196 2.464 1.224 2.3C1.232 2.252 1.236 2.244 1.188 2.204C0.612 1.728 0 1.156 0 0.348C0 -0.348 0.476 -1.008 1.456 -1.008C1.548 -1.008 1.652 -1 1.732 -0.984C1.776 -0.976 1.784 -0.972 1.792 -1.02C1.84 -1.288 1.9 -1.636 1.9 -1.824C1.9 -2.416 1.5 -2.488 1.264 -2.488C1.048 -2.488 0.944 -2.424 0.944 -2.372C0.944 -2.344 0.98 -2.332 1.072 -2.304C1.196 -2.268 1.34 -2.16 1.34 -1.928C1.34 -1.708 1.2 -1.52 0.956 -1.52C0.688 -1.52 0.528 -1.732 0.528 -1.98C0.528 -2.24 0.684 -2.632 1.288 -2.632C1.556 -2.632 2.076 -2.512 2.076 -1.832C2.076 -1.604 2.004 -1.224 1.96 -0.976C1.952 -0.928 1.956 -0.932 2.012 -0.908C2.416 -0.748 2.684 -0.408 2.684 0.044C2.684 0.556 2.308 1.008 1.72 1.008C1.616 1.008 1.616 1.008 1.604 1.08ZM1.88 3.772C2.012 3.772 2.12 3.664 2.12 3.444C2.12 3 1.74 2.64 1.424 2.364C1.396 2.34 1.38 2.344 1.372 2.396C1.356 2.5 1.348 2.636 1.348 2.764C1.348 3.388 1.636 3.772 1.88 3.772ZM1.444 1.048C1.456 0.972 1.456 0.976 1.384 0.952C1.032 0.832 0.804 0.516 0.804 0.176C0.804 -0.184 0.992 -0.44 1.264 -0.532C1.296 -0.544 1.344 -0.556 1.372 -0.556C1.404 -0.556 1.42 -0.536 1.42 -0.512C1.42 -0.484 1.388 -0.472 1.36 -0.46C1.192 -0.388 1.072 -0.216 1.072 -0.032C1.072 0.196 1.228 0.368 1.472 0.436C1.536 0.452 1.544 0.448 1.552 0.404L1.752 -0.788C1.76 -0.832 1.756 -0.832 1.696 -0.844C1.632 -0.856 1.552 -0.864 1.472 -0.864C0.772 -0.864 0.32 -0.476 0.32 0.08C0.32 0.316 0.36 0.632 0.692 1.008C0.932 1.276 1.116 1.424 1.304 1.576C1.344 1.608 1.352 1.604 1.36 1.56ZM1.72 0.412C1.712 0.46 1.716 0.472 1.764 0.468C2.088 0.44 2.356 0.168 2.356 -0.184C2.356 -0.436 2.204 -0.64 1.98 -0.752C1.932 -0.776 1.924 -0.776 1.916 -0.728Z" transform="translate(0 1)" fill="#000000" data-prov="11b3eeb335691c9e35abc39fd2b199b8" data-source-kind="4" data-glyph="gClef" data-class="clef"/>
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(3.2846 -1)" fill="#000000" data-prov="a9ea8a975336c483b806550f06c6ac3a" 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(5.3652 -1)" fill="#000000" data-prov="f75057d1424ec76a43140fd09f5430f6" 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.4459 -1)" fill="#000000" data-prov="ae10a4d1e0e114150bb2f37428592071" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
<path d="M0.144 0V4H0V0ZM0.912 4H0.412V0H0.912Z" transform="translate(9.2266 0)" fill="#000000" data-prov="688cee6a171fdd158a2ecee088684062" data-source-kind="9" data-glyph="barlineFinal" data-class="barline"/>
<path d="M1.504 1.66C1.496 1.708 1.504 1.712 1.528 1.736C1.96 2.14 2.288 2.648 2.288 3.26C2.288 3.608 2.192 3.952 2.028 4.192C1.968 4.28 1.864 4.392 1.82 4.392C1.764 4.392 1.64 4.288 1.56 4.2C1.264 3.872 1.168 3.372 1.168 2.956C1.168 2.724 1.196 2.464 1.224 2.3C1.232 2.252 1.236 2.244 1.188 2.204C0.612 1.728 0 1.156 0 0.348C0 -0.348 0.476 -1.008 1.456 -1.008C1.548 -1.008 1.652 -1 1.732 -0.984C1.776 -0.976 1.784 -0.972 1.792 -1.02C1.84 -1.288 1.9 -1.636 1.9 -1.824C1.9 -2.416 1.5 -2.488 1.264 -2.488C1.048 -2.488 0.944 -2.424 0.944 -2.372C0.944 -2.344 0.98 -2.332 1.072 -2.304C1.196 -2.268 1.34 -2.16 1.34 -1.928C1.34 -1.708 1.2 -1.52 0.956 -1.52C0.688 -1.52 0.528 -1.732 0.528 -1.98C0.528 -2.24 0.684 -2.632 1.288 -2.632C1.556 -2.632 2.076 -2.512 2.076 -1.832C2.076 -1.604 2.004 -1.224 1.96 -0.976C1.952 -0.928 1.956 -0.932 2.012 -0.908C2.416 -0.748 2.684 -0.408 2.684 0.044C2.684 0.556 2.308 1.008 1.72 1.008C1.616 1.008 1.616 1.008 1.604 1.08ZM1.88 3.772C2.012 3.772 2.12 3.664 2.12 3.444C2.12 3 1.74 2.64 1.424 2.364C1.396 2.34 1.38 2.344 1.372 2.396C1.356 2.5 1.348 2.636 1.348 2.764C1.348 3.388 1.636 3.772 1.88 3.772ZM1.444 1.048C1.456 0.972 1.456 0.976 1.384 0.952C1.032 0.832 0.804 0.516 0.804 0.176C0.804 -0.184 0.992 -0.44 1.264 -0.532C1.296 -0.544 1.344 -0.556 1.372 -0.556C1.404 -0.556 1.42 -0.536 1.42 -0.512C1.42 -0.484 1.388 -0.472 1.36 -0.46C1.192 -0.388 1.072 -0.216 1.072 -0.032C1.072 0.196 1.228 0.368 1.472 0.436C1.536 0.452 1.544 0.448 1.552 0.404L1.752 -0.788C1.76 -0.832 1.756 -0.832 1.696 -0.844C1.632 -0.856 1.552 -0.864 1.472 -0.864C0.772 -0.864 0.32 -0.476 0.32 0.08C0.32 0.316 0.36 0.632 0.692 1.008C0.932 1.276 1.116 1.424 1.304 1.576C1.344 1.608 1.352 1.604 1.36 1.56ZM1.72 0.412C1.712 0.46 1.716 0.472 1.764 0.468C2.088 0.44 2.356 0.168 2.356 -0.184C2.356 -0.436 2.204 -0.64 1.98 -0.752C1.932 -0.776 1.924 -0.776 1.916 -0.728Z" transform="translate(10.7266 1)" fill="#000000" data-prov="22ee2bcfa4f95b655d9005ee6936f95c" 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(14.0111 -1)" fill="#000000" data-prov="570646848d323a130dfd8621abc0523a" 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.7918 -0.5)" fill="#000000" data-prov="8203471bd393291712730bfe067a49f4" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
<path d="M1.504 1.66C1.496 1.708 1.504 1.712 1.528 1.736C1.96 2.14 2.288 2.648 2.288 3.26C2.288 3.608 2.192 3.952 2.028 4.192C1.968 4.28 1.864 4.392 1.82 4.392C1.764 4.392 1.64 4.288 1.56 4.2C1.264 3.872 1.168 3.372 1.168 2.956C1.168 2.724 1.196 2.464 1.224 2.3C1.232 2.252 1.236 2.244 1.188 2.204C0.612 1.728 0 1.156 0 0.348C0 -0.348 0.476 -1.008 1.456 -1.008C1.548 -1.008 1.652 -1 1.732 -0.984C1.776 -0.976 1.784 -0.972 1.792 -1.02C1.84 -1.288 1.9 -1.636 1.9 -1.824C1.9 -2.416 1.5 -2.488 1.264 -2.488C1.048 -2.488 0.944 -2.424 0.944 -2.372C0.944 -2.344 0.98 -2.332 1.072 -2.304C1.196 -2.268 1.34 -2.16 1.34 -1.928C1.34 -1.708 1.2 -1.52 0.956 -1.52C0.688 -1.52 0.528 -1.732 0.528 -1.98C0.528 -2.24 0.684 -2.632 1.288 -2.632C1.556 -2.632 2.076 -2.512 2.076 -1.832C2.076 -1.604 2.004 -1.224 1.96 -0.976C1.952 -0.928 1.956 -0.932 2.012 -0.908C2.416 -0.748 2.684 -0.408 2.684 0.044C2.684 0.556 2.308 1.008 1.72 1.008C1.616 1.008 1.616 1.008 1.604 1.08ZM1.88 3.772C2.012 3.772 2.12 3.664 2.12 3.444C2.12 3 1.74 2.64 1.424 2.364C1.396 2.34 1.38 2.344 1.372 2.396C1.356 2.5 1.348 2.636 1.348 2.764C1.348 3.388 1.636 3.772 1.88 3.772ZM1.444 1.048C1.456 0.972 1.456 0.976 1.384 0.952C1.032 0.832 0.804 0.516 0.804 0.176C0.804 -0.184 0.992 -0.44 1.264 -0.532C1.296 -0.544 1.344 -0.556 1.372 -0.556C1.404 -0.556 1.42 -0.536 1.42 -0.512C1.42 -0.484 1.388 -0.472 1.36 -0.46C1.192 -0.388 1.072 -0.216 1.072 -0.032C1.072 0.196 1.228 0.368 1.472 0.436C1.536 0.452 1.544 0.448 1.552 0.404L1.752 -0.788C1.76 -0.832 1.756 -0.832 1.696 -0.844C1.632 -0.856 1.552 -0.864 1.472 -0.864C0.772 -0.864 0.32 -0.476 0.32 0.08C0.32 0.316 0.36 0.632 0.692 1.008C0.932 1.276 1.116 1.424 1.304 1.576C1.344 1.608 1.352 1.604 1.36 1.56ZM1.72 0.412C1.712 0.46 1.716 0.472 1.764 0.468C2.088 0.44 2.356 0.168 2.356 -0.184C2.356 -0.436 2.204 -0.64 1.98 -0.752C1.932 -0.776 1.924 -0.776 1.916 -0.728Z" transform="translate(17.2918 1)" fill="#000000" data-prov="592b318a5a382abc0a975f47f08f0ad3" 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(20.5764 -1)" fill="#000000" data-prov="cfe927096cef847eb22b0f0da758ae25" 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.357 -0.5)" fill="#000000" data-prov="156fb4f4836e433fab4e81ed7492b103" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
<line x1="8.6599" y1="-12.8926" x2="8.6599" y2="-12.8926" stroke="#000000" stroke-width="0" data-prov="5fc3f9fc71005c8f91cef3c82cf457d7" data-source-kind="6" data-kind="stroke"/>
<line x1="7.565" y1="-12.8926" x2="18.0531" y2="-12.8926" stroke="#000000" stroke-width="0.13" data-prov="540fd4c2706753fcc11d7abf84677260" data-source-kind="3" data-kind="stroke"/>
<line x1="7.565" y1="-11.8926" x2="18.0531" y2="-11.8926" stroke="#000000" stroke-width="0.13" data-prov="fea4bf7d60f88df52ba88e1026e63342" data-source-kind="3" data-kind="stroke"/>
<line x1="7.565" y1="-10.8926" x2="18.0531" y2="-10.8926" stroke="#000000" stroke-width="0.13" data-prov="2ce0db586e615b2e13faf76b21a0d74e" data-source-kind="3" data-kind="stroke"/>
<line x1="7.565" y1="-9.8926" x2="18.0531" y2="-9.8926" stroke="#000000" stroke-width="0.13" data-prov="d6f4c9a4f9b381f86948af62dd614587" data-source-kind="3" data-kind="stroke"/>
<line x1="7.565" y1="-8.8926" x2="18.0531" y2="-8.8926" stroke="#000000" stroke-width="0.13" data-prov="d26797f8b55b1986957fe0b86a4d8ad3" data-source-kind="3" data-kind="stroke"/>
<line x1="8.6599" y1="-12.8926" x2="8.6599" y2="-12.8926" stroke="#000000" stroke-width="0" data-prov="fbab9b9c1117f1a77321eef2528313c7" data-source-kind="2" data-kind="stroke"/>
<line x1="13.4399" y1="-13.8926" x2="13.4399" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="685bb2f9eba2acf767017fe46340d32d" data-source-kind="0" data-kind="stroke"/>
<line x1="11.6444" y1="-13.8926" x2="13.4251" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="badbeee9b287e9d086850c2be907fce8" data-source-kind="1" data-kind="stroke"/>
<line x1="15.5206" y1="-13.8926" x2="15.5206" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="bfca9c8f8054f3d500e342f0b4750c5e" data-source-kind="0" data-kind="stroke"/>
<line x1="13.7251" y1="-13.8926" x2="15.5058" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="9a0cb14210151fc9b638f964beb65c6c" data-source-kind="1" data-kind="stroke"/>
<line x1="16.7663" y1="-13.8926" x2="16.7663" y2="-10.3926" stroke="#000000" stroke-width="0.12" data-prov="150cdc069f098c84e4293a2bb4ae6693" data-source-kind="0" data-kind="stroke"/>
<line x1="15.8058" y1="-13.8926" x2="17.5864" y2="-13.8926" stroke="#000000" stroke-width="0.13" data-prov="01fbbb3559a0372164da3f005943fc45" data-source-kind="1" data-kind="stroke"/>
<line x1="8.6599" y1="-12.8926" x2="8.6599" y2="-12.8926" stroke="#000000" stroke-width="0" data-prov="ccedf36ea2c89203f216fe2bdcf32577" data-source-kind="12" data-kind="stroke"/>
<line x1="8.6599" y1="-12.8926" x2="8.6599" y2="-12.8926" stroke="#000000" stroke-width="0" data-prov="0bc5e2a029d8599bd4d15a916eb90318" data-source-kind="22" data-kind="stroke"/>
<line x1="8.6599" y1="-12.8926" x2="8.6599" y2="-12.8926" stroke="#000000" stroke-width="0" data-prov="5078100681364e0eef0becb94f607df4" data-source-kind="14" data-kind="stroke"/>
<line x1="8.6599" y1="-12.8926" x2="8.6599" y2="-12.8926" stroke="#000000" stroke-width="0" data-prov="850009fc86dd7744c4dea1ea6a28be42" data-source-kind="15" data-kind="stroke"/>
<line x1="8.6599" y1="-12.8926" x2="8.6599" y2="-12.8926" stroke="#000000" stroke-width="0" data-prov="0b3280aa906b67171cb829398b7bf1f0" data-source-kind="25" data-kind="stroke"/>
<line x1="7.8983" y1="-23.918" x2="7.8983" y2="-23.918" stroke="#000000" stroke-width="0" data-prov="d9a97871d4088c0f7e7c1ab7e4fd49b7" data-source-kind="6" data-kind="stroke"/>
<line x1="7.565" y1="-23.918" x2="13.6741" y2="-23.918" stroke="#000000" stroke-width="0.13" data-prov="74a1941933b935bf49129ddec9ceb7cb" data-source-kind="3" data-kind="stroke"/>
<line x1="7.565" y1="-22.918" x2="13.6741" y2="-22.918" stroke="#000000" stroke-width="0.13" data-prov="9127c3ddf63996fbde1522f2af47e640" data-source-kind="3" data-kind="stroke"/>
<line x1="7.565" y1="-21.918" x2="13.6741" y2="-21.918" stroke="#000000" stroke-width="0.13" data-prov="6244e1812ecbd0f97ace48fed547d2a0" data-source-kind="3" data-kind="stroke"/>
<line x1="7.565" y1="-20.918" x2="13.6741" y2="-20.918" stroke="#000000" stroke-width="0.13" data-prov="daaa82fcc1b3fceb63ecca023f71c3a6" data-source-kind="3" data-kind="stroke"/>
<line x1="7.565" y1="-19.918" x2="13.6741" y2="-19.918" stroke="#000000" stroke-width="0.13" data-prov="00ce5a2fe2e45b498b1164e3ce0239f0" data-source-kind="3" data-kind="stroke"/>
<line x1="7.8983" y1="-23.918" x2="7.8983" y2="-23.918" stroke="#000000" stroke-width="0" data-prov="374743e32dbbfe82916c87b68c2fcbd3" data-source-kind="2" data-kind="stroke"/>
<line x1="12.4628" y1="-24.918" x2="12.4628" y2="-21.418" stroke="#000000" stroke-width="0.12" data-prov="27a2f99d23428f969f8f22d7e42f89a4" data-source-kind="0" data-kind="stroke"/>
<line x1="10.8829" y1="-24.918" x2="12.6636" y2="-24.918" stroke="#000000" stroke-width="0.13" data-prov="370371bf62378295492e01246d4ee16f" data-source-kind="1" data-kind="stroke"/>
<line x1="13.1905" y1="-24.418" x2="13.1905" y2="-20.918" stroke="#000000" stroke-width="0.12" data-prov="4ba26f525e6c664f9f3727b3f001c4f7" data-source-kind="0" data-kind="stroke"/>
<line x1="7.7624" y1="-34.9434" x2="7.7624" y2="-34.9434" stroke="#000000" stroke-width="0" data-prov="cc34303d8801d20b413432a3a1b66aa0" data-source-kind="6" data-kind="stroke"/>
<line x1="7.565" y1="-34.9434" x2="16.8341" y2="-34.9434" stroke="#000000" stroke-width="0.13" data-prov="ff986fa2737b7cc76925b8b003dfb446" data-source-kind="3" data-kind="stroke"/>
<line x1="7.565" y1="-33.9434" x2="16.8341" y2="-33.9434" stroke="#000000" stroke-width="0.13" data-prov="8c632b729231878fa18c479fd24d7436" data-source-kind="3" data-kind="stroke"/>
<line x1="7.565" y1="-32.9434" x2="16.8341" y2="-32.9434" stroke="#000000" stroke-width="0.13" data-prov="0c8b92c6e5cb8d4afcffe28972595e85" data-source-kind="3" data-kind="stroke"/>
<line x1="7.565" y1="-31.9434" x2="16.8341" y2="-31.9434" stroke="#000000" stroke-width="0.13" data-prov="d01bcd62385bf77312ae65017a9b4a50" data-source-kind="3" data-kind="stroke"/>
<line x1="7.565" y1="-30.9434" x2="16.8341" y2="-30.9434" stroke="#000000" stroke-width="0.13" data-prov="c882fdc5188e96b8a1950c11dfd401e9" data-source-kind="3" data-kind="stroke"/>
<line x1="7.7624" y1="-34.9434" x2="7.7624" y2="-34.9434" stroke="#000000" stroke-width="0" data-prov="2fe40e9238d3fc28c88d7082dbbec593" data-source-kind="2" data-kind="stroke"/>
<line x1="12.3268" y1="-35.9434" x2="12.3268" y2="-32.4434" stroke="#000000" stroke-width="0.12" data-prov="8b99315d75c981620504d9274be1fa45" data-source-kind="0" data-kind="stroke"/>
<line x1="10.7469" y1="-35.9434" x2="12.5276" y2="-35.9434" stroke="#000000" stroke-width="0.13" data-prov="95dfefec6481cc7be34a31c283b7fd8c" data-source-kind="1" data-kind="stroke"/>
<line x1="14.1075" y1="-35.4434" x2="14.1075" y2="-31.9434" stroke="#000000" stroke-width="0.12" data-prov="b98aa6fd428e2b9f68fefec4b05a2b78" data-source-kind="0" data-kind="stroke"/>
<path d="M1.504 1.66C1.496 1.708 1.504 1.712 1.528 1.736C1.96 2.14 2.288 2.648 2.288 3.26C2.288 3.608 2.192 3.952 2.028 4.192C1.968 4.28 1.864 4.392 1.82 4.392C1.764 4.392 1.64 4.288 1.56 4.2C1.264 3.872 1.168 3.372 1.168 2.956C1.168 2.724 1.196 2.464 1.224 2.3C1.232 2.252 1.236 2.244 1.188 2.204C0.612 1.728 0 1.156 0 0.348C0 -0.348 0.476 -1.008 1.456 -1.008C1.548 -1.008 1.652 -1 1.732 -0.984C1.776 -0.976 1.784 -0.972 1.792 -1.02C1.84 -1.288 1.9 -1.636 1.9 -1.824C1.9 -2.416 1.5 -2.488 1.264 -2.488C1.048 -2.488 0.944 -2.424 0.944 -2.372C0.944 -2.344 0.98 -2.332 1.072 -2.304C1.196 -2.268 1.34 -2.16 1.34 -1.928C1.34 -1.708 1.2 -1.52 0.956 -1.52C0.688 -1.52 0.528 -1.732 0.528 -1.98C0.528 -2.24 0.684 -2.632 1.288 -2.632C1.556 -2.632 2.076 -2.512 2.076 -1.832C2.076 -1.604 2.004 -1.224 1.96 -0.976C1.952 -0.928 1.956 -0.932 2.012 -0.908C2.416 -0.748 2.684 -0.408 2.684 0.044C2.684 0.556 2.308 1.008 1.72 1.008C1.616 1.008 1.616 1.008 1.604 1.08ZM1.88 3.772C2.012 3.772 2.12 3.664 2.12 3.444C2.12 3 1.74 2.64 1.424 2.364C1.396 2.34 1.38 2.344 1.372 2.396C1.356 2.5 1.348 2.636 1.348 2.764C1.348 3.388 1.636 3.772 1.88 3.772ZM1.444 1.048C1.456 0.972 1.456 0.976 1.384 0.952C1.032 0.832 0.804 0.516 0.804 0.176C0.804 -0.184 0.992 -0.44 1.264 -0.532C1.296 -0.544 1.344 -0.556 1.372 -0.556C1.404 -0.556 1.42 -0.536 1.42 -0.512C1.42 -0.484 1.388 -0.472 1.36 -0.46C1.192 -0.388 1.072 -0.216 1.072 -0.032C1.072 0.196 1.228 0.368 1.472 0.436C1.536 0.452 1.544 0.448 1.552 0.404L1.752 -0.788C1.76 -0.832 1.756 -0.832 1.696 -0.844C1.632 -0.856 1.552 -0.864 1.472 -0.864C0.772 -0.864 0.32 -0.476 0.32 0.08C0.32 0.316 0.36 0.632 0.692 1.008C0.932 1.276 1.116 1.424 1.304 1.576C1.344 1.608 1.352 1.604 1.36 1.56ZM1.72 0.412C1.712 0.46 1.716 0.472 1.764 0.468C2.088 0.44 2.356 0.168 2.356 -0.184C2.356 -0.436 2.204 -0.64 1.98 -0.752C1.932 -0.776 1.924 -0.776 1.916 -0.728Z" transform="translate(8.6599 -11.8926)" fill="#000000" data-prov="11b3eeb335691c9e35abc39fd2b199b8" data-source-kind="4" data-glyph="gClef" data-class="clef"/>
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(11.9444 -13.8926)" fill="#000000" data-prov="a9ea8a975336c483b806550f06c6ac3a" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(14.0251 -13.8926)" fill="#000000" data-prov="f75057d1424ec76a43140fd09f5430f6" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(16.1058 -13.8926)" fill="#000000" data-prov="ae10a4d1e0e114150bb2f37428592071" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
<path d="M0.144 0V4H0V0ZM0.912 4H0.412V0H0.912Z" transform="translate(17.8864 -12.8926)" fill="#000000" data-prov="688cee6a171fdd158a2ecee088684062" data-source-kind="9" data-glyph="barlineFinal" data-class="barline"/>
<path d="M1.504 1.66C1.496 1.708 1.504 1.712 1.528 1.736C1.96 2.14 2.288 2.648 2.288 3.26C2.288 3.608 2.192 3.952 2.028 4.192C1.968 4.28 1.864 4.392 1.82 4.392C1.764 4.392 1.64 4.288 1.56 4.2C1.264 3.872 1.168 3.372 1.168 2.956C1.168 2.724 1.196 2.464 1.224 2.3C1.232 2.252 1.236 2.244 1.188 2.204C0.612 1.728 0 1.156 0 0.348C0 -0.348 0.476 -1.008 1.456 -1.008C1.548 -1.008 1.652 -1 1.732 -0.984C1.776 -0.976 1.784 -0.972 1.792 -1.02C1.84 -1.288 1.9 -1.636 1.9 -1.824C1.9 -2.416 1.5 -2.488 1.264 -2.488C1.048 -2.488 0.944 -2.424 0.944 -2.372C0.944 -2.344 0.98 -2.332 1.072 -2.304C1.196 -2.268 1.34 -2.16 1.34 -1.928C1.34 -1.708 1.2 -1.52 0.956 -1.52C0.688 -1.52 0.528 -1.732 0.528 -1.98C0.528 -2.24 0.684 -2.632 1.288 -2.632C1.556 -2.632 2.076 -2.512 2.076 -1.832C2.076 -1.604 2.004 -1.224 1.96 -0.976C1.952 -0.928 1.956 -0.932 2.012 -0.908C2.416 -0.748 2.684 -0.408 2.684 0.044C2.684 0.556 2.308 1.008 1.72 1.008C1.616 1.008 1.616 1.008 1.604 1.08ZM1.88 3.772C2.012 3.772 2.12 3.664 2.12 3.444C2.12 3 1.74 2.64 1.424 2.364C1.396 2.34 1.38 2.344 1.372 2.396C1.356 2.5 1.348 2.636 1.348 2.764C1.348 3.388 1.636 3.772 1.88 3.772ZM1.444 1.048C1.456 0.972 1.456 0.976 1.384 0.952C1.032 0.832 0.804 0.516 0.804 0.176C0.804 -0.184 0.992 -0.44 1.264 -0.532C1.296 -0.544 1.344 -0.556 1.372 -0.556C1.404 -0.556 1.42 -0.536 1.42 -0.512C1.42 -0.484 1.388 -0.472 1.36 -0.46C1.192 -0.388 1.072 -0.216 1.072 -0.032C1.072 0.196 1.228 0.368 1.472 0.436C1.536 0.452 1.544 0.448 1.552 0.404L1.752 -0.788C1.76 -0.832 1.756 -0.832 1.696 -0.844C1.632 -0.856 1.552 -0.864 1.472 -0.864C0.772 -0.864 0.32 -0.476 0.32 0.08C0.32 0.316 0.36 0.632 0.692 1.008C0.932 1.276 1.116 1.424 1.304 1.576C1.344 1.608 1.352 1.604 1.36 1.56ZM1.72 0.412C1.712 0.46 1.716 0.472 1.764 0.468C2.088 0.44 2.356 0.168 2.356 -0.184C2.356 -0.436 2.204 -0.64 1.98 -0.752C1.932 -0.776 1.924 -0.776 1.916 -0.728Z" transform="translate(7.8983 -22.918)" fill="#000000" data-prov="22ee2bcfa4f95b655d9005ee6936f95c" data-source-kind="4" data-glyph="gClef" data-class="clef"/>
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(11.1829 -24.918)" fill="#000000" data-prov="570646848d323a130dfd8621abc0523a" 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.9636 -24.418)" fill="#000000" data-prov="8203471bd393291712730bfe067a49f4" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
<path d="M1.504 1.66C1.496 1.708 1.504 1.712 1.528 1.736C1.96 2.14 2.288 2.648 2.288 3.26C2.288 3.608 2.192 3.952 2.028 4.192C1.968 4.28 1.864 4.392 1.82 4.392C1.764 4.392 1.64 4.288 1.56 4.2C1.264 3.872 1.168 3.372 1.168 2.956C1.168 2.724 1.196 2.464 1.224 2.3C1.232 2.252 1.236 2.244 1.188 2.204C0.612 1.728 0 1.156 0 0.348C0 -0.348 0.476 -1.008 1.456 -1.008C1.548 -1.008 1.652 -1 1.732 -0.984C1.776 -0.976 1.784 -0.972 1.792 -1.02C1.84 -1.288 1.9 -1.636 1.9 -1.824C1.9 -2.416 1.5 -2.488 1.264 -2.488C1.048 -2.488 0.944 -2.424 0.944 -2.372C0.944 -2.344 0.98 -2.332 1.072 -2.304C1.196 -2.268 1.34 -2.16 1.34 -1.928C1.34 -1.708 1.2 -1.52 0.956 -1.52C0.688 -1.52 0.528 -1.732 0.528 -1.98C0.528 -2.24 0.684 -2.632 1.288 -2.632C1.556 -2.632 2.076 -2.512 2.076 -1.832C2.076 -1.604 2.004 -1.224 1.96 -0.976C1.952 -0.928 1.956 -0.932 2.012 -0.908C2.416 -0.748 2.684 -0.408 2.684 0.044C2.684 0.556 2.308 1.008 1.72 1.008C1.616 1.008 1.616 1.008 1.604 1.08ZM1.88 3.772C2.012 3.772 2.12 3.664 2.12 3.444C2.12 3 1.74 2.64 1.424 2.364C1.396 2.34 1.38 2.344 1.372 2.396C1.356 2.5 1.348 2.636 1.348 2.764C1.348 3.388 1.636 3.772 1.88 3.772ZM1.444 1.048C1.456 0.972 1.456 0.976 1.384 0.952C1.032 0.832 0.804 0.516 0.804 0.176C0.804 -0.184 0.992 -0.44 1.264 -0.532C1.296 -0.544 1.344 -0.556 1.372 -0.556C1.404 -0.556 1.42 -0.536 1.42 -0.512C1.42 -0.484 1.388 -0.472 1.36 -0.46C1.192 -0.388 1.072 -0.216 1.072 -0.032C1.072 0.196 1.228 0.368 1.472 0.436C1.536 0.452 1.544 0.448 1.552 0.404L1.752 -0.788C1.76 -0.832 1.756 -0.832 1.696 -0.844C1.632 -0.856 1.552 -0.864 1.472 -0.864C0.772 -0.864 0.32 -0.476 0.32 0.08C0.32 0.316 0.36 0.632 0.692 1.008C0.932 1.276 1.116 1.424 1.304 1.576C1.344 1.608 1.352 1.604 1.36 1.56ZM1.72 0.412C1.712 0.46 1.716 0.472 1.764 0.468C2.088 0.44 2.356 0.168 2.356 -0.184C2.356 -0.436 2.204 -0.64 1.98 -0.752C1.932 -0.776 1.924 -0.776 1.916 -0.728Z" transform="translate(7.7624 -33.9434)" fill="#000000" data-prov="592b318a5a382abc0a975f47f08f0ad3" data-source-kind="4" data-glyph="gClef" data-class="clef"/>
<path d="M0.388 -0.5C0.744 -0.5 1.18 -0.172 1.18 0.168C1.18 0.372 1.02 0.5 0.792 0.5C0.352 0.5 0 0.176 0 -0.168C0 -0.376 0.172 -0.5 0.388 -0.5Z" transform="translate(11.0469 -35.9434)" fill="#000000" data-prov="cfe927096cef847eb22b0f0da758ae25" 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.8276 -35.4434)" fill="#000000" data-prov="156fb4f4836e433fab4e81ed7492b103" data-source-kind="1" data-glyph="noteheadBlack" data-class="notehead"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -33,17 +33,18 @@ use epiphany_ops::{
AnomalousReplicaSegment, AuthorId, CausalContext, ChangeRegionTimeModelOp, ConflictId,
ConflictKind, ConflictKindRegistryId, ConflictRecord, ConflictRegistry,
ConflictResolutionState, CreateCrossCuttingOp, CreateRegionOp, CreateStaffInstanceOp,
CreateVoiceOp, CrossCuttingValue, DeleteCrossCuttingOp, DeleteEventOp, DeleteIdentifiedPitchOp,
DeleteRegionOp, DeleteStaffInstanceOp, DeleteVoiceOp, ExtensionPreconditionId, FieldPath,
HybridLogicalClock, InsertEventOp, InsertIdentifiedPitchOp, IntegrityAnomaly,
IntegrityAnomalyKind, IntegrityAnomalyRegistryId, MaterializedState, ModifyCrossCuttingOp,
ModifyEventOp, ModifyIdentifiedPitchOp, NoOpReason, ObjectKind, ObjectState, OperationEffect,
OperationEnvelope, OperationKind, OperationKindRegistryId, OperationPayload, OperationSet,
OperationStamp, PendingReason, PositionRemapping, PreconditionFailureReason,
PreconditionFailureRegistryId, ReanchorReason, ReanchorReasonRegistryId, ReanchorResult,
RepairKind, RepairKindRegistryId, RepairRecord, ReplicaAnomalyReason, ReplicaAnomalyRegistryId,
ResolutionAction, ResolutionRegistryId, ResolveConflictPayload, RespellPitchOp,
SerializedCanonicalInputs, SetMetadataOp, SetMetricGridOp, SetUserPageBreakOp,
CreateStaffOp, CreateVoiceOp, CrossCuttingValue, DeleteCrossCuttingOp, DeleteEventOp,
DeleteIdentifiedPitchOp, DeleteRegionOp, DeleteStaffInstanceOp, DeleteVoiceOp,
ExtensionPreconditionId, FieldPath, HybridLogicalClock, InsertEventOp, InsertIdentifiedPitchOp,
IntegrityAnomaly, IntegrityAnomalyKind, IntegrityAnomalyRegistryId, MaterializedState,
ModifyCrossCuttingOp, ModifyEventOp, ModifyIdentifiedPitchOp, NoOpReason, ObjectKind,
ObjectState, OperationEffect, OperationEnvelope, OperationKind, OperationKindRegistryId,
OperationPayload, OperationSet, OperationStamp, PendingReason, PositionRemapping,
PreconditionFailureReason, PreconditionFailureRegistryId, ReanchorReason,
ReanchorReasonRegistryId, ReanchorResult, RepairKind, RepairKindRegistryId, RepairRecord,
ReplicaAnomalyReason, ReplicaAnomalyRegistryId, ResolutionAction, ResolutionRegistryId,
ResolveConflictPayload, RespellPitchOp, SerializedCanonicalInputs, SetMetadataOp,
SetMetricGridOp, SetStaffLayoutOp, SetTempoSegmentOp, SetTimeSignatureOp, SetUserPageBreakOp,
SetUserSystemBreakOp, TransactionCategory, TransactionDescriptor, TransposeOp,
TupletCompensation, TupletCompensationKind, UndoPolicy, UndoTransactionPayload,
};
@ -409,7 +410,7 @@ pub fn conflict_registry(rng: &mut Rng) -> ConflictRegistry {
/// A typed precondition failure (every core and registered variant).
pub fn precondition_failure_reason(rng: &mut Rng) -> PreconditionFailureReason {
match rng.below(10) {
match rng.below(12) {
0 => PreconditionFailureReason::TargetMissing,
1 => PreconditionFailureReason::TargetTombstoned,
2 => PreconditionFailureReason::WrongRegionTimeModel,
@ -418,7 +419,9 @@ pub fn precondition_failure_reason(rng: &mut Rng) -> PreconditionFailureReason {
5 => PreconditionFailureReason::PositionOutsideRegion,
6 => PreconditionFailureReason::PitchSpaceMismatch,
7 => PreconditionFailureReason::VoiceMissing,
8 => PreconditionFailureReason::ExtensionPrecondition(ExtensionPreconditionId(
8 => PreconditionFailureReason::ContainerNotEmpty,
9 => PreconditionFailureReason::TempoMapMalformed,
10 => PreconditionFailureReason::ExtensionPrecondition(ExtensionPreconditionId(
rng.next_u64() as u128,
)),
_ => PreconditionFailureReason::Registered(PreconditionFailureRegistryId(
@ -637,7 +640,7 @@ pub fn operation_payload(rng: &mut Rng, events: u64, pitches: u64) -> OperationP
}
_ => {}
}
let kind = match rng.below(24) {
let kind = match rng.below(28) {
0 => {
let pitches = if rng.boolean() {
vec![obj_pitch(rng.below(pitches))]
@ -773,6 +776,52 @@ pub fn operation_payload(rng: &mut Rng, events: u64, pitches: u64) -> OperationP
),
present: rng.boolean(),
}),
// Phase-3 tranche: staff mint, meter/tempo overwrites, layout advisory
// over the shared id space. The signature's value derives from its id
// so an id re-carry is byte-identical (the idempotent mint branch);
// distinct ids give distinct values (the differing-value branch).
23 => OperationKind::CreateStaff(CreateStaffOp {
staff: valuegen::staff(
StaffId::new(OBJ_REPLICA, rng.below(2)),
InstrumentId::new(OBJ_REPLICA, 0),
),
}),
24 => {
let region = RegionId::new(OBJ_REPLICA, rng.below(2));
let signature = rng.below(2);
OperationKind::SetTimeSignature(SetTimeSignatureOp {
region,
anchor: valuegen::region_start_anchor(
region,
MusicalPosition(RationalTime::from_int(rng.below(3) as i32 * 4)),
),
time_signature: rng.boolean().then(|| {
valuegen::time_signature(
TimeSignatureId::new(OBJ_REPLICA, signature),
signature as u16 + 3,
)
}),
})
}
25 => {
let region = RegionId::new(OBJ_REPLICA, rng.below(2));
let at = MusicalPosition(RationalTime::from_int(rng.below(3) as i32 * 4));
OperationKind::SetTempoSegment(SetTempoSegmentOp {
region: rng.boolean().then_some(region),
start: valuegen::region_start_anchor(region, at.clone()),
segment: rng.boolean().then(|| {
valuegen::tempo_segment(region, at, 60.0 + rng.below(4) as f64 * 30.0)
}),
})
}
26 => OperationKind::SetStaffLayout(SetStaffLayoutOp {
staff_instance: StaffInstanceId::new(OBJ_REPLICA, rng.below(2)),
instrument_override: None,
staff_lines_override: rng
.boolean()
.then(epiphany_core::StaffLineConfiguration::default),
visible: rng.boolean(),
}),
_ => OperationKind::Registered(
OperationKindRegistryId(rng.next_u64() as u128),
rng.byte_vec(0, 16),
@ -831,6 +880,19 @@ impl Session {
/// predecessor) with a prior-history DVV (own history always seen; others
/// sampled).
fn author(&mut self, rng: &mut Rng, r: usize, payload: OperationPayload) {
self.author_tx(rng, r, None, payload);
}
/// As [`Session::author`], additionally stamping the envelope as a member
/// of `tx`. Same-replica sequential authorship makes each member causally
/// cover its transaction descriptor (the descriptor-precedence rule).
fn author_tx(
&mut self,
rng: &mut Rng,
r: usize,
tx: Option<TransactionId>,
payload: OperationPayload,
) {
let n_replicas = self.counters.len();
let replica = ReplicaId(r as u64 + 1);
let c = self.counters[r];
@ -875,7 +937,7 @@ impl Session {
author: AuthorId(replica.0 as u128),
stamp: OperationStamp::new(HybridLogicalClock::new(WallClockTime(l), logical), id),
causal_context: ctx,
transaction: None,
transaction: tx,
payload,
};
debug_assert!(epiphany_ops::well_formed(&env).is_ok());
@ -1122,13 +1184,83 @@ pub fn graph_edit_session(
);
}
// Phase-3 tranche: the targeted tx-then-undo flow. One declared transaction
// overwrites LWW keys across the families (spelling, meter, tempo, staff
// layout) and is then undone, so the convergence gates genuinely exercise
// value-restoring undo (a randomly-generated undo almost always hits
// TargetMissing). The overwrite keys (position 400) are disjoint from the
// random-edit keys below, so the undo's verdict — restore vs. superseded —
// stays a pure function of this deterministic seed.
let region = base.canvas.regions[0].id;
let instrument = base.instruments[0].id;
let far = MusicalPosition(RationalTime::from_int(400));
let undo_tx = TransactionId::new(OBJ_REPLICA, 7001);
session.author(
rng,
0,
OperationPayload::Primitive(OperationKind::CreateStaff(CreateStaffOp {
staff: valuegen::staff(StaffId::new(OBJ_REPLICA, 7000), instrument),
})),
);
session.author_tx(
rng,
0,
Some(undo_tx),
OperationPayload::Primitive(OperationKind::DeclareTransaction(TransactionDescriptor {
id: undo_tx,
label: String::from("tx-then-undo flow"),
category: Some(TransactionCategory::Layout),
})),
);
for kind in [
OperationKind::RespellPitch(RespellPitchOp {
pitch: obj_pitch(0),
spelling: valuegen::spelling(6),
}),
OperationKind::SetTimeSignature(SetTimeSignatureOp {
region,
anchor: valuegen::region_start_anchor(region, far.clone()),
time_signature: Some(valuegen::time_signature(
TimeSignatureId::new(OBJ_REPLICA, 7002),
5,
)),
}),
OperationKind::SetTempoSegment(SetTempoSegmentOp {
region: None,
start: valuegen::region_start_anchor(region, far.clone()),
segment: Some(valuegen::tempo_segment(region, far.clone(), 132.0)),
}),
OperationKind::SetStaffLayout(SetStaffLayoutOp {
staff_instance: targets[0].0,
instrument_override: None,
staff_lines_override: None,
visible: false,
}),
] {
session.author_tx(rng, 0, Some(undo_tx), OperationPayload::Primitive(kind));
}
let policy = if rng.boolean() {
UndoPolicy::StrictInverse
} else {
UndoPolicy::BestEffort
};
session.author(
rng,
0,
OperationPayload::UndoTransaction(UndoTransactionPayload {
target: undo_tx,
policy,
}),
);
for _ in 0..80 {
let r = rng.below(2) as usize;
// Mix the original edit kinds with the Group-1/2 (M2) ops so the real-Score
// gate exercises their *graph* materialization (reduce_onto +
// Mix the original edit kinds with the Group-1/2 (M2) ops — and the
// Phase-3 meter/tempo/layout overwrites — so the real-Score gate
// exercises their *graph* materialization (reduce_onto +
// check_invariants), not just the bookkeeping projection. Each targets a
// live object minted by the phases above.
let kind = match rng.below(9) {
// live object minted by the phases above (or the base region).
let kind = match rng.below(12) {
0 => OperationKind::DeleteEvent(DeleteEventOp {
event: obj_event(rng.below(total)),
tuplet_compensation: TupletCompensation::NotInTuplet,
@ -1181,7 +1313,7 @@ pub fn graph_edit_session(
7 => OperationKind::DeleteCrossCutting(DeleteCrossCuttingOp {
structure: TypedObjectId::Slur(SlurId::new(OBJ_REPLICA, rng.below(n_slurs))),
}),
_ => {
8 => {
let k = rng.below(n_slurs);
OperationKind::ModifyCrossCutting(ModifyCrossCuttingOp {
// Re-point the slur's end to another even (replica-0) event.
@ -1192,6 +1324,44 @@ pub fn graph_edit_session(
)),
})
}
// Phase-3 tranche: meter / tempo / layout overwrites on the base
// region, keyed away from the tx-then-undo flow's key (400). The
// signature value derives from its id so an id re-carry is
// byte-identical (mint idempotence) while distinct ids differ.
9 => {
let signature = rng.below(2);
OperationKind::SetTimeSignature(SetTimeSignatureOp {
region,
anchor: valuegen::region_start_anchor(
region,
MusicalPosition(RationalTime::from_int(rng.below(3) as i32 * 8 + 200)),
),
time_signature: rng.boolean().then(|| {
valuegen::time_signature(
TimeSignatureId::new(OBJ_REPLICA, 7100 + signature),
signature as u16 + 3,
)
}),
})
}
10 => {
let at = MusicalPosition(RationalTime::from_int(rng.below(3) as i32 * 8 + 200));
OperationKind::SetTempoSegment(SetTempoSegmentOp {
region: rng.boolean().then_some(region),
start: valuegen::region_start_anchor(region, at.clone()),
segment: rng.boolean().then(|| {
valuegen::tempo_segment(region, at, 60.0 + rng.below(4) as f64 * 20.0)
}),
})
}
_ => OperationKind::SetStaffLayout(SetStaffLayoutOp {
staff_instance: targets[rng.below(targets.len() as u64) as usize].0,
instrument_override: None,
staff_lines_override: rng
.boolean()
.then(epiphany_core::StaffLineConfiguration::default),
visible: rng.boolean(),
}),
};
session.author(rng, r, OperationPayload::Primitive(kind));
}

View File

@ -372,6 +372,7 @@ pub fn gen_constrained_layout_ir(rng: &mut Rng) -> ConstrainedLayoutIR {
.collect(),
vertical_bands: vec![band],
constraints: vec![],
break_origins: vec![],
engraving_decisions: (0..decisions)
.map(|_| gen_engraving_decision(rng))
.collect(),

View File

@ -57,7 +57,16 @@ code instead is the failure mode this batch exists to prevent.
| ~~P12-E2~~ **RESOLVED (Binary Format 0.1.0 §8.2, `req:binfmt:condition-depth`)** | `epiphany-layout-ir` E | The spec places no bound on `BarrierCondition` recursion; the decoder needs one against adversarial bytes. `MAX_CONDITION_DEPTH = 64` implemented — the companion pins 64 as the normative bound (decoders MUST reject deeper; writers MUST NOT emit deeper). | ✅ done |
| ~~P12-E3~~ **RESOLVED (Binary Format 0.1.0 §8.1, `req:binfmt:object-kind-open`)** | `epiphany-layout-ir` E | Barrier `ObjectKind` byte form = the `TypedObjectId` 16-bit discriminant (2 LE bytes) with open-value decode (unknown kinds never match, preserving append-only forward compat). Representation and open-value stance ratified. | ✅ done |
| P12-E4 | `epiphany-editor-core` E | Barrier matching for operations with no graph target (`SetMetadata`, `DeclareTransaction` — implemented: score-wide barriers only) and for opaque `Registered` operations (implemented: fully conservative match) is unspecified. | G (Ch. 8) |
| P12-E5 | `epiphany-editor-core` E | The unsafe-edit tombstone MUST has no defined mechanism: the manifest-side form (drop declaration + preserved roots? an explicit tombstone record?), interaction with `required = true`, and whether crossing immediately deactivates the extension's remaining barriers (implemented: yes, recorded via `extensions_requiring_tombstone()` for the next bundle write). | G (Ch. 8) |
| P12-E5 | `epiphany-editor-core` E | The unsafe-edit tombstone MUST has no defined mechanism: the manifest-side form (drop declaration + preserved roots? an explicit tombstone record?), interaction with `required = true`, and whether crossing immediately deactivates the extension's remaining barriers (implemented: yes, recorded via `extensions_requiring_tombstone()` for the next bundle write). | G (Ch. 8) || P12-C5 | `epiphany-ops`/`epiphany-core` C/H | Mid-region meter changes: `SetTimeSignature` (catalog §Meter and Tempo Overwrites) reduces a second `MeterChange` into a region's grid cleanly, but the decomposition pre-pass honours only the first governing meter (P12-H4's single-meter simplification), so derived notation ignores the change until multi-meter decomposition lands. Reduction semantics pinned; the derived-annotation gap is H4's. | G / Pass 12 (decomposition) |
| P12-K8 | `epiphany-ops` K | Create score / canvas remain unavailable K1 slots: the document root and canvas are inline singletons (no `TypedObjectId::Canvas`; the root is never op-minted — genesis is `Score::empty` + bundle creation). Turning them into operations needs an addressable root/canvas object model. Decide: define one, or ratify genesis as deliberately outside the operation set. | G (graph model) |
| P12-K9 | `epiphany-ops` K | Differing-value re-creates (a live id re-carried with different content: `CreateStaff`, the carried `TimeSignature`, container creates) refuse with `TargetMissing`, which misnames the situation. Decide whether a dedicated `PreconditionFailureReason` (appended) is warranted. | G / Pass 12 (vocabulary) |
| P12-K10 | `epiphany-ops` K | Undo strand-blocks (StrictInverse refusing to tombstone a minted object still referenced by a live non-member, e.g. a staff with a surviving instance) reuse `ConflictKind::TransactionConflict`. Decide whether undo refusals deserve their own conflict kind. | G / Pass 12 (undo) |
| P12-K11 | `epiphany-ops` K | An undo's value restorations enter the write chains as ordinary writes by the undo op, so a second undo of the same transaction sees the first as a superseding writer (Conflicted/skip) while absence-restorations repeat idempotently — a documented asymmetry. Decide whether chain writes need distinguished undo provenance so repeated undo is uniformly idempotent. | G / Pass 12 (undo) |
| P12-I7 | `epiphany-engrave` I | Page geometry has no graph home: the spec names `Canvas.layout_defaults` ("paper size, margins") but defines no type, and adding a `Canvas` field is a schema-major change under the companion's frozen-layout rule. Casting-off therefore uses an engraver-side `PageGeometry` default (A4 at an 8mm staff: 105x148.5 ss, 7.5 ss margins, documented arithmetic). Define `CanvasLayoutDefaults` with the data-model schema-major; until then solvers MAY default. | G (graph model, schema-major) |
| P12-I8 | `epiphany-engrave` I | Break-constraint satisfaction predicate: implemented as "a `SystemBreakAt`/`PageBreakAt` is satisfied iff the final layout starts a system/page at that slot" (a region-first slot is trivially satisfied). Ch7/Ch9 never define satisfaction for break constraints; ratify the predicate. | G / Pass 12 (solver) |
| P12-I9 | `epiphany-layout-ir` I | Honouring a user break must attribute the decision to its override (`DecisionSource::UserOverride(id)`), but constraints carry no override identity; implemented via a `ConstrainedLayoutIR.break_origins` sidecar populated by `to_constrained`. Bless the sidecar or widen the normalized constraint record. | G / Pass 12 (solver) |
| P12-I10 | `epiphany-layout-ir` I | System-spanning strokes split at system boundaries need synthesized provenance for continuation segments; implemented as `SynthesisKind::Registered(SYSTEM_CONTINUATION_SYNTHESIS)` with a deterministic `(original, ordinal)` instance key. Add a first-class continuation synthesis kind or bless the registered id. | G / Pass 12 (provenance) |
## Not yet open elsewhere
@ -65,7 +74,9 @@ Agent I (Track A) has contributed P12-I1..I6. Track B's Agent K has
contributed P12-K1..K7; H has contributed P12-H1..H7 (H6/H7 from the 2026-07
spec-compliance audit follow-up, alongside K3/K4). The 2026-07 Push-3 wiring
work added C1..C4 (re-anchoring), D1 (bundle operation index), and E1..E5
(edit barriers). Agent J's Binary Format companion now exists
(edit barriers). The Phase-3 first tranche (casting-off + K1 schema-fill +
value-restoring undo, 2026-07-02) added C5, K8..K11, and I7..I10.
Agent J's Binary Format companion now exists
(`spec/binary_format.tex`, v0.1.0): it ratified the P12-D1/E1/E2/E3 inputs
(struck through above) and discharged the crates' provisional-codec notes
(core P11-4, ops "provisional canonical encoding", bundle P11-D2/D4/D5). Its

Binary file not shown.

View File

@ -235,7 +235,7 @@
{\Large\scshape\color{epiphanyslate}Binary Format}\\[6pt]
{\large\itshape\color{epiphanyslate}A companion to the Core Specification}\\[14pt]
{\color{epiphanygold}\rule{3in}{0.8pt}}\\[24pt]
{\normalsize\color{epiphanyink}Version 0.1.0 --- Phase 2 (canonical wire format: primitives through bundle physical layout + K0 payload framing)}\\[4pt]
{\normalsize\color{epiphanyink}Version 0.2.0 --- Phase 2/3 (canonical wire format: primitives through bundle physical layout + K0 and Phase-3-tranche payload framing)}\\[4pt]
{\small\color{epiphanyslate}Normative for the byte layouts it defines}
\vfill
\end{titlepage}
@ -1287,7 +1287,7 @@ AcceptLoser, \tablenums{1} KeepWinner, \tablenums{2} Override
\label{req:binfmt:kind-discriminants}
The \texttt{OperationKind} wire discriminant is one byte, golden-locked to
the table below. The vocabulary is \textbf{append-only}: new primitive kinds
take discriminants past \tablenums{23}; the assignments below never change.
take discriminants past \tablenums{27}; the assignments below never change.
\end{requirement}
Each row also pins the payload's byte layout; the field \emph{set and order}
@ -1385,6 +1385,25 @@ sequence sorted ascending by canonical bytes.
\texttt{region} (16) \cat{} $\mathrm{lp}$(\texttt{TimeAnchor}) \cat{}
\texttt{present} (bool, 1) &
\sectionsc{Score Settings} \\
\tablenums{24} & \texttt{CreateStaff} &
$\mathrm{lp}$(\texttt{Staff}) &
\sectionsc{CreateStaff} \\
\tablenums{25} & \texttt{SetTimeSignature} &
\texttt{region} (16) \cat{} $\mathrm{lp}$(\texttt{TimeAnchor}) \cat{}
option tag (\tablenums{0}/\tablenums{1}) \cat{}
$\mathrm{lp}$(\texttt{TimeSignature}) if present &
\sectionsc{Meter and Tempo Overwrites} \\
\tablenums{26} & \texttt{SetTempoSegment} &
option tag \cat{} \texttt{region} (16) if present \cat{}
$\mathrm{lp}$(\texttt{TimeAnchor}) \cat{} option tag \cat{}
$\mathrm{lp}$(\texttt{TempoSegment}) if present &
\sectionsc{Meter and Tempo Overwrites} \\
\tablenums{27} & \texttt{SetStaffLayout} &
\texttt{staff\_instance} (16) \cat{} option tag \cat{}
\texttt{instrument\_override} (16) if present \cat{} option tag \cat{}
$\mathrm{lp}$(\texttt{StaffLineConfiguration}) if present \cat{}
\texttt{visible} (bool, 1) &
\sectionsc{SetStaffLayout} \\
\bottomrule
\end{longtable}
\endgroup
@ -1429,7 +1448,7 @@ assigned independently of Section~\ref{sec:ops:kinds}, with both encode and
validating decode. One byte; only \texttt{Registered} carries a payload (its
\texttt{OperationKindRegistryId}, 16 big-endian bytes, total 17). Unknown
discriminants and wrong lengths are decode errors. Append-only past
\tablenums{23}.
\tablenums{27}.
\end{requirement}
\begingroup\small
@ -1450,15 +1469,19 @@ discriminants and wrong lengths are decode errors. Append-only past
\tablenums{9} & \texttt{InsertRegion} & \tablenums{21} & \texttt{DeleteVoice} \\
\tablenums{10} & \texttt{DeleteRegion} & \tablenums{22} & \texttt{SetMetadata} \\
\tablenums{11} & \texttt{InsertStaffInstance} & \tablenums{23} & \texttt{SetMetricGrid} \\
\tablenums{24} & \texttt{InsertStaff} & \tablenums{25} & \texttt{SetTimeSignature} \\
\tablenums{26} & \texttt{SetTempoSegment} & \tablenums{27} & \texttt{SetStaffLayout} \\
\bottomrule
\end{longtable}
\endgroup
\textbf{Naming mapping.} The kind-to-tag projection renames two pairs:
\textbf{Naming mapping.} The kind-to-tag projection renames three pairs:
\texttt{OperationKind::\allowbreak CreateRegion} projects to
\texttt{OperationKindTag::\allowbreak InsertRegion}, and
\texttt{OperationKind::\allowbreak CreateStaffInstance} projects to
\texttt{OperationKindTag::\allowbreak InsertStaffInstance}. The mismatch is
\texttt{OperationKindTag::\allowbreak InsertStaffInstance}, and
\texttt{OperationKind::\allowbreak CreateStaff} projects to
\texttt{OperationKindTag::\allowbreak InsertStaff}. The mismatch is
intentional
(the tag space predates the M2c naming) and is pinned here so barrier
authors target the right tag.
@ -1529,7 +1552,8 @@ trailing bytes are decode errors.
\tablenums{6} PitchSpaceMismatch; \tablenums{7} VoiceMissing;
\tablenums{8} ExtensionPrecondition \cat{} id (16 BE);
\tablenums{9} Registered \cat{} id (16 BE);
\tablenums{10} ContainerNotEmpty. \\
\tablenums{10} ContainerNotEmpty;
\tablenums{11} TempoMapMalformed. \\
\texttt{RepairRecord} &
(struct) \texttt{kind} (\texttt{RepairKind}) \cat{} \texttt{target}
(\texttt{TypedObjectId}). \\
@ -2519,7 +2543,20 @@ layouts of Section~\ref{sec:values:representative}.
forms (P12-E1/E2/E3); pins the no-varint rule, the frozen-positional
schema-evolution keystone, and the id-leads envelope property;
\texttt{SnapshotId} derivation deferred (open question). \\
\bottomrule
\today & Operation wire forms & 0.2.0 --- Phase-3 first tranche: appended
\texttt{OperationKind} wire discriminants \tablenums{24}--\tablenums{27}
(\texttt{CreateStaff}, \texttt{SetTimeSignature},
\texttt{SetTempoSegment}, \texttt{SetStaffLayout}) with their payload
layouts, the matching \texttt{OperationKindTag} discriminants
\tablenums{24}--\tablenums{27} (kind-to-tag naming gains
\texttt{CreateStaff}~$\rightarrow$~\texttt{InsertStaff}), and
\texttt{PreconditionFailureReason} \tablenums{11}
(\texttt{TempoMapMalformed}) --- a schema-\emph{minor} evolution under this
document's own append-only rules (Chapter~\ref{ch:evolution}); no existing
assignment changed. Semantics: Operation Catalog 0.5.0 (\sectionsc{CreateStaff},
\sectionsc{Meter and Tempo Overwrites}, \sectionsc{SetStaffLayout}, and the
value-restoring \sectionsc{UndoTransaction} revision). \\
\bottomrule
\end{longtable}
\end{document}

Binary file not shown.

View File

@ -5713,10 +5713,16 @@ pub enum OperationKind {
DeleteRegion(DeleteRegionOp),
InsertStaffInstance(InsertStaffInstanceOp),
DeleteStaffInstance(DeleteStaffInstanceOp),
InsertStaff(InsertStaffOp),
// Metric-model operations
SetTimeSignature(SetTimeSignatureOp),
SetTempoSegment(SetTempoSegmentOp),
// Layout-semantic operations
SetUserSystemBreak(SetUserSystemBreakOp),
SetUserPageBreak(SetUserPageBreakOp),
SetStaffLayout(SetStaffLayoutOp),
// Transaction declaration. Carries metadata; member primitives
// reference the transaction by id in their envelopes.
@ -14553,6 +14559,25 @@ layouts they own versus inherit:
closing the representational gap that blocked the logical-stage
override projection §Engraving Overrides requires.
\\
\today & Phase-3 first tranche (casting-off + catalog) &
Companion movements, no core-spec byte or architecture change. The
Operation Catalog (0.4.0 $\rightarrow$ 0.5.0) gains the Phase-3
schema-fill tranche --- \texttt{CreateStaff} (set-union global-staff
mint, plus a staff-liveness precondition on
\texttt{CreateStaffInstance}), \texttt{SetTimeSignature} and
\texttt{SetTempoSegment} (LWW structural overwrites beneath the
whole-grid \texttt{SetMetricGrid}), \texttt{SetStaffLayout} (LWW
advisory) --- and a rewritten \sectionsc{UndoTransaction}:
\texttt{StrictInverse}/\texttt{BestEffort} now perform
\emph{value restoration} for overwrite primitives via
canonical-order write chains (delete resurrection, Transpose
inversion, and Cascade closure stay deferred, P11-C8 narrowed).
The Binary Format companion (0.1.0 $\rightarrow$ 0.2.0) appends
the corresponding wire discriminants. This document's
\texttt{OperationKind} listing gains the four kinds. The create
score/canvas slots remain deliberately unavailable pending an
addressable root model (P12-K8).
\\
\bottomrule
\end{longtable}

Binary file not shown.

View File

@ -226,7 +226,7 @@
{\Large\scshape\color{epiphanyslate}Operation Catalog}\\[6pt]
{\large\itshape\color{epiphanyslate}A companion to the Core Specification}\\[14pt]
{\color{epiphanygold}\rule{3in}{0.8pt}}\\[24pt]
{\normalsize\color{epiphanyink}Version 0.4.0 --- Phase 2 (K0 representative + broad-K0 M2 groups)}\\[4pt]
{\normalsize\color{epiphanyink}Version 0.5.0 --- Phase 2/3 (K0 representative + broad-K0 M2 groups + Phase-3 first tranche)}\\[4pt]
{\small\color{epiphanyslate}Normative for the operation kinds it defines}
\vfill
\end{titlepage}
@ -462,8 +462,9 @@ recording the winner (later in canonical order), the loser, and the field
\texttt{spelling}. The winner materialises and carries the \texttt{Conflicted}
effect tag.
\textbf{Undo semantics.} Undo restores the pre-operation spelling (or removes the
spelling if the operation introduced the first one), under the active policy.
\textbf{Undo semantics.} Value-restoring (Section~\ref{sec:k0:undo}): undo
restores the pre-operation spelling (or removes the spelling if the operation
introduced the first one), under the active policy.
\textbf{Re-anchoring.} If the target pitch is tombstoned, the respelling is a
no-op (\texttt{TargetTombstoned}).
@ -515,10 +516,10 @@ trimming of a tuplet member remains a later refinement.
\emph{differing} values produce a \texttt{StructuralFieldCollision} on the field
\texttt{event}, recording the winner (later in canonical order) and the loser.
\textbf{Undo semantics.} Under the prototype's minted-object undo
(Section~\ref{sec:k0:undo}) a field overwrite mints nothing, so undoing the
enclosing transaction does not restore the prior event value; a snapshot-and-
restore inverse is a Phase-3 refinement (P11-C8).
\textbf{Undo semantics.} Value-restoring (Section~\ref{sec:k0:undo}): undoing
the enclosing transaction restores the event's chain-predecessor value,
conflicting (\texttt{StrictInverse}) or skipping (\texttt{BestEffort}) when a
later modification has superseded it.
\textbf{Re-anchoring.} If the target event is tombstoned, the modification is a
no-op (\texttt{TargetTombstoned}/\texttt{TargetMissing}).
@ -553,11 +554,12 @@ bookkeeping, which tombstones or mints the pitch object either way.
is idempotent). Modify: two concurrent differing writes of one pitch produce a
\texttt{StructuralFieldCollision} on the field \texttt{pitch}.
\textbf{Undo semantics.} Insert is the only minting member: undoing the enclosing
transaction tombstones the minted pitch, re-resting the event if it was the only
one (Section~\ref{sec:k0:undo}). The prototype's minted-object undo has nothing
to tombstone for delete or modify, so neither is inverted under it (re-introducing
a tombstoned pitch or restoring a prior value is a Phase-3 refinement, P11-C8).
\textbf{Undo semantics.} Insert: undoing the enclosing transaction tombstones
the minted pitch, re-resting the event if it was the only one
(Section~\ref{sec:k0:undo}). Modify: value-restoring
(Section~\ref{sec:k0:undo}) --- the pitch's chain-predecessor value is
restored unless superseded. Delete: still not inverted (re-introducing a
tombstoned pitch is the deferred resurrection case, P11-C8).
\textbf{Re-anchoring.} An operation whose target event or pitch is tombstoned is
a no-op; tombstoning a pitch runs the cross-cutting re-anchoring table over any
@ -670,9 +672,8 @@ Graph-aware reduction overwrites the structure in place.
\textbf{Conflict cases.} Two concurrent differing modifications of one structure
produce a \texttt{StructuralFieldCollision} on the field \texttt{cross\_cutting}.
\textbf{Undo semantics.} A modification mints nothing, so the prototype's
minted-object undo (Section~\ref{sec:k0:undo}) does not restore the prior
structure value (a snapshot-and-restore inverse is Phase-3, P11-C8).
\textbf{Undo semantics.} Value-restoring (Section~\ref{sec:k0:undo}): undo
restores the structure's chain-predecessor value unless superseded.
\textbf{Re-anchoring.} If the target structure is tombstoned, the modification is
a no-op; re-deriving the endpoints lets a subsequent endpoint tombstone re-anchor
@ -754,6 +755,45 @@ re-introduce it (P11-C8).
\textbf{Re-anchoring.} Not applicable (the containers are minted/tombstoned by id;
the empty-only precondition means a delete never strands live children).
\section{CreateStaff}
\label{sec:k0:create-staff}
\textbf{Payload schema.} \texttt{CreateStaffOp \{ staff: Staff \}} --- the full
global-staff value (v1): identity, name, abbreviation, instrument reference,
default staff-line configuration, and optional group membership.
\textbf{Canonical encoding.} The length-framed canonical bytes of
\texttt{staff}.
\textbf{Reduction rule.} Set-union creation of a global \texttt{Staff} on the
score root, completing the structural-container family
(Section~\ref{sec:k0:structural-containers}) upward: staff \emph{instances}
reference global staves, and until this primitive existed a resolvable staff
could only be base-seeded. A create mints the staff live if its id is fresh; a
repeat create carrying a byte-identical value reduces idempotently, and a
create whose id is already live with a \emph{differing} value is a
precondition no-op. Graph-aware reduction additionally preconditions that the
referenced instrument is live and, when \texttt{group} is present, that the
staff group resolves --- the mint must leave the graph satisfying the
reference-resolution invariants.
With staves mintable, \texttt{CreateStaffInstance}
(Section~\ref{sec:k0:structural-containers}) preconditions that the
instance's referenced \texttt{Staff} is live (previously the reference was
satisfiable only from the seeded base, so the check was vacuous).
\textbf{Conflict cases.} None at reduction time (set-union; the differing-value
re-create is a precondition gate, not a conflict).
\textbf{Undo semantics.} Undo of a create tombstones the minted staff
(Section~\ref{sec:k0:undo}); \texttt{StrictInverse} conflicts if a live staff
instance references it (tombstoning it would strand the instance).
\textbf{Re-anchoring.} Not applicable (a staff mint references no tombstonable
anchor; there is no \texttt{DeleteStaff} in this catalogue revision --- an
empty-only staff delete mirroring the container discipline is a later
schema-fill).
\section{SetUserSystemBreak}
\label{sec:k0:set-user-system-break}
@ -810,15 +850,103 @@ single slot).
SetMetricGrid: two concurrent differing grids for one region produce a
\texttt{StructuralFieldCollision} on the field \texttt{metric\_grid}.
\textbf{Undo semantics.} All three are field overwrites that mint nothing, so the
prototype's minted-object undo (Section~\ref{sec:k0:undo}) does not restore the
prior metadata, grid, or break preference (a snapshot-and-restore inverse is a
Phase-3 refinement, P11-C8).
\textbf{Undo semantics.} All three are value-restoring field overwrites
(Section~\ref{sec:k0:undo}): undo restores the prior metadata, grid, or break
preference from the key's write chain unless superseded.
\textbf{Re-anchoring.} The advisory breaks degrade as for SetUserSystemBreak; the
metric grid and metadata are keyed by region / singleton and do not re-anchor (a
deleted region's settings are no-ops --- \texttt{TargetMissing}).
\section{Meter and Tempo Overwrites}
\label{sec:k0:meter-tempo}
\textbf{Payload schema.} The finer-grained metric-model overwrites beneath the
whole-grid \texttt{SetMetricGrid} (Section~\ref{sec:k0:score-settings}):
\texttt{SetTimeSignatureOp \{ region: RegionId, anchor: TimeAnchor,
time\_signature: Option<TimeSignature> \}} sets, replaces, or (\texttt{None})
removes the single \texttt{MeterChange} at the anchor's resolved musical
position in the region's default metric grid, carrying the full
\texttt{TimeSignature} value (v1); \texttt{SetTempoSegmentOp \{ region:
Option<RegionId>, start: TimeAnchor, segment: Option<TempoSegment> \}} sets,
replaces, or removes the single tempo segment starting at the resolved
position, in the score-level tempo map (\texttt{region: None}) or the region's
local map (\texttt{Some}; a set on a region with no local map creates one).
\textbf{Canonical encoding.} Time signature: \texttt{region}, the
length-framed \texttt{anchor}, then an \texttt{Option} discriminant and (when
present) the length-framed \texttt{time\_signature} value. Tempo segment: an
\texttt{Option} discriminant and (when present) \texttt{region}, then the
length-framed \texttt{start}, then an \texttt{Option} discriminant and (when
present) the length-framed \texttt{segment}.
\textbf{Reduction rule.} Last-writer-wins structural overwrites keyed by
\texttt{(region, resolved position)} (time signature) and \texttt{(scope,
resolved start)} (tempo segment). A carried \texttt{TimeSignature} is minted
set-union under the same discipline as \texttt{CreateStaff}: fresh id mints;
byte-identical re-carry is idempotent; a differing value under a live id is a
precondition no-op. The time-signature value's beat-group sum is validated at
construction and again at decode, so a malformed value never reaches
reduction. A tempo-segment write preconditions that the \emph{resulting} map
is well-formed (segments ordered and non-overlapping; a non-constant shape
carries its end data; the carried segment's own start equals the operation's
\texttt{start} key) --- a write that would malform the map is refused as a
precondition no-op (\texttt{TempoMapMalformed}). Graph-aware reduction applies
the meter change to \texttt{default\_metric\_grid.meter\_sequence} and the
segment to the scoped tempo map.
\textbf{Conflict cases.} Two concurrent differing writes of one key produce a
\texttt{StructuralFieldCollision} on the field \texttt{meter\_sequence} or
\texttt{tempo\_segments} respectively, with the standard winner/loser
recording; identical concurrent writes reduce idempotently.
\textbf{Undo semantics.} Value-restoring per Section~\ref{sec:k0:undo}: undo
restores the key's chain-predecessor value (or its absence).
\textbf{Re-anchoring.} An event-anchored \texttt{anchor}/\texttt{start} whose
event is later tombstoned degrades by the framework's anchor rules; the
overwrite keys on the \emph{resolved} position, so the recorded change
survives its anchor.
\begin{openquestion}
\textbf{P12-C5.} A mid-region meter change authored by
\texttt{SetTimeSignature} reduces cleanly and materialises into the grid, but
the notational-decomposition pre-pass currently honours only a region's
\emph{first} governing meter (P12-H4's single-meter simplification), so the
derived notation ignores the change until multi-meter decomposition lands.
The reduction-level semantics are pinned here; the derived-annotation gap is
P12-H4's.
\end{openquestion}
\section{SetStaffLayout}
\label{sec:k0:set-staff-layout}
\textbf{Payload schema.} \texttt{SetStaffLayoutOp \{ staff\_instance:
StaffInstanceId, instrument\_override: Option<InstrumentId>,
staff\_lines\_override: Option<StaffLineConfiguration>, visible: bool \}} ---
the non-break layout advisories with a graph home: the staff instance's three
inline advisory fields, overwritten as a unit.
\textbf{Canonical encoding.} \texttt{staff\_instance}, an \texttt{Option}
discriminant and (when present) \texttt{instrument\_override}, an
\texttt{Option} discriminant and (when present) the length-framed
\texttt{staff\_lines\_override}, then the boolean.
\textbf{Reduction rule.} A last-writer-wins \emph{advisory} overwrite keyed by
\texttt{staff\_instance}. Preconditions: the staff instance is live; a present
\texttt{instrument\_override} resolves to a live instrument under graph-aware
reduction. The richer engraving-override vocabulary (stem direction, notehead
shape, custom positions) has no durable graph home yet and remains projected
layout state --- extending this primitive to cover it is staged with the
data-model expansion.
\textbf{Conflict cases.} None (LWW advisory).
\textbf{Undo semantics.} Value-restoring per Section~\ref{sec:k0:undo}.
\textbf{Re-anchoring.} If the staff instance is tombstoned, the overwrite is a
no-op (\texttt{TargetTombstoned}).
\section{DeclareTransaction}
\label{sec:k0:declare-transaction}
@ -911,12 +1039,39 @@ policy: UndoPolicy \}}, with \texttt{UndoPolicy} one of \texttt{StrictInverse},
\texttt{BestEffort}, \texttt{Cascade}. Value-complete.
\textbf{Reduction rule.} A forward compensating edit computed against the
materialised state at the undo's canonical position (never literal time travel).
The prototype models the compensation as tombstoning the objects the target
transaction minted: \texttt{StrictInverse} conflicts (\texttt{TombstonedTarget})
if any minted object was already tombstoned; \texttt{BestEffort} tombstones the
survivors; \texttt{Cascade} is \texttt{StrictInverse} over the same set
(dependent-closure undo is a Phase-3 refinement, P11-C8).
materialised state at the undo's canonical position (never literal time
travel). The compensation has two parts.
\emph{Minted-object tombstoning} (as before): every object the target
transaction minted is tombstoned. \texttt{StrictInverse} conflicts
(\texttt{TombstonedTarget}) if any minted object was already tombstoned;
\texttt{BestEffort} tombstones the survivors.
\emph{Value restoration} (this revision): for every last-writer-wins overwrite
the target transaction performed --- event and identified-pitch modification,
respelling, cross-cutting modification, metadata, metric grid, meter change,
tempo segment, staff layout, and the user break advisories --- the reducer
maintains, per overwritten key, the \emph{canonical-order write chain} of
(writer, value) pairs. Undoing the transaction restores each written key to
its chain-predecessor value (or its absence, where the transaction introduced
the first value), \emph{provided the transaction's write is still the key's
last writer}. Because the chain is keyed by canonical order, the restored
value is a pure function of the operation set: permutation-invariant by
construction. When a causally-later or canonically-later write has
superseded the key, \texttt{StrictInverse} refuses the whole undo with a
\texttt{TransactionConflict} conflict naming the undo and the superseding
writer; \texttt{BestEffort} restores the still-last-written keys and skips
the superseded ones. Restorations are expressed in the effect status (a fully
clean compensation is \texttt{Applied}; a mixed one is
\texttt{AppliedWithRepair} carrying only the tombstone repairs) --- no new
repair vocabulary.
\emph{Still deferred} (P11-C8, narrowed): re-introducing content tombstoned by
\emph{delete} primitives (a deterministic resurrection needs a system-derived
identifier derivation the ratified closed tag set does not yet include);
\texttt{Transpose} inversion (interval algebra, P12-K2); and
\texttt{Cascade}'s dependent-closure computation --- \texttt{Cascade} remains
\texttt{StrictInverse} over the same set.
% ===========================================================================
\chapter{v0 \texorpdfstring{$\rightarrow$}{->} v1 Payload Migration}
@ -987,21 +1142,26 @@ a fresh design.
structural metric grid, advisory page break).
\end{description}
\section*{Remaining framework slots (Phase 3 --- unavailable, MUST reject)}
\section*{Implemented in the Phase-3 first tranche (now in Chapter~\ref{ch:k0})}
\begin{description}
\item[Create score / canvas / staff]
The remaining structural mints (the document root, the canvas, and global
staves) the Phase-2 slice does not exercise. Discipline: set-union creation.
\item[Set time signature / tempo segment]
The finer-grained metric-model overwrites beneath the whole-grid
\texttt{SetMetricGrid} (Section~\ref{sec:k0:score-settings}): a single meter
change or tempo segment rather than the region's entire grid. Discipline:
last-writer-wins structural overwrite.
\item[Set layout]
The non-break layout advisories (the page/system-break advisories themselves
are implemented --- Sections~\ref{sec:k0:set-user-system-break} and
\ref{sec:k0:score-settings}). Discipline: LWW advisory.
\item[Create staff; set time signature / tempo segment; set layout]
Sections~\ref{sec:k0:create-staff}, \ref{sec:k0:meter-tempo}, and
\ref{sec:k0:set-staff-layout}. The disciplines are as drafted: set-union
creation, LWW structural overwrite, and LWW advisory respectively.
\end{description}
\section*{Remaining framework slots (unavailable, MUST reject)}
\begin{description}
\item[Create score / canvas]
The document root and the canvas are \emph{inline singletons}, not
id-addressed objects: \texttt{TypedObjectId} has no Canvas kind, and the
root is never op-minted --- genesis today is the empty-document
constructor plus bundle creation, outside the operation set. Turning these
into operations requires an addressable root/canvas object model (a
graph-model decision for G, filed as a Pass-12 row), so the slots remain
deliberately unavailable rather than force-designed.
\end{description}
\begin{nongoal}