# acorde-core
Score data model and command engine for Rust and WebAssembly.
This crate has no I/O, filesystem, renderer, or async-runtime dependency. It provides Score, Part,
Staff, Measure, Note, notation types, ScoreEngine, serializable Command values, undo/redo,
validation, playback-event generation, score diff/patch, transposition, and music-theory helpers.
Playback-event generation coalesces contiguous authored ties into one continuous event while
keeping malformed or non-contiguous tie endings observable as separate events.
Pitches preserve fractional cents, and playback events expose exact MIDI-cent values in addition to
the rounded MIDI note, while notes and staves can carry tablature string/fret and
tuning metadata with serde defaults for older score JSON.
`Pitch::to_scientific_name()` preserves authored accidental runs beyond double-sharp/double-flat;
scientific-name parsing rejects an accidental run that would overflow the signed alteration range.
Validation rejects deserialized `microtone_cents` values outside the canonical -99..99 range.
`assign_tablature_positions` provides deterministic capo-aware placement, while
`optimize_tablature_positions` considers movement between notes and chord strings. `SetTabPositionCmd`
makes explicit positions editable through the command engine.
`SetTablatureConfigCmd` makes staff tuning, string count, and capo editable with undo/redo.
Ordered alternate fingering candidates are stored in `Note.fingerings`; `SetFingeringsCmd` keeps
the first candidate synchronized with the legacy `Note.fingering` field.
`Note::select_fingering` provides a non-mutating deterministic source-order, lowest-number, or
highest-number projection.
MusicXML guitar bend amounts are retained as optional cent values on `Note` and can be edited with
`SetGuitarBendAlterCmd`.
Unpitched notes can be resolved with `Part::percussion_instrument_for_note`, which prefers an
explicit note instrument ID, then a declared MIDI display-key mapping, and never guesses an
unmatched sound identity.
MEI staff-group boundaries are represented separately as `Part.staff_groups`; they are not
confused with `Score.part_groups`, which connect distinct parts.
Chord symbols preserve optional vertical placement and structured MusicXML degree
value/alter/type data through `ChordSymbol.degrees`. MEI/MusicXML/MSCX harmonic-function metadata
is preserved through the serde-defaulted `ChordSymbol.harmony_function` field.
`Pitch::try_with_microtone()` rejects cents outside the canonical `-99..=99` range without
clamping; the legacy `with_microtone()` constructor retains its clamping behavior for
compatibility. Measure-level styled text can be edited transactionally with
`Command::SetMeasureText`: provide an existing `text_index` to replace/remove an entry, or the
current length to append one. Score-level styled text uses the same contract through
`Command::SetScoreText`, keeping title-page annotations editable after import. Invalid indexes
return an error before mutation, and both operations participate in the normal undo/redo and
JSON command-history contracts.
`diff` and `ScorePatch` also preserve the complete score-level text collection, so collaboration
and snapshot synchronization do not drop title-page annotations.
Structured figured bass, measure presentation, measure text, tablature configuration, chord definitions, part names, and
common part MIDI routing use local score-patch operations. When a score contains fields without a dedicated positional
patch variant, patch generation uses an atomic full-score replacement instead of silently dropping
those fields. `ScoreChange` reports these editing changes explicitly as well.
Score-level annotation changes are reported with their complete old/new collections.
Fields without dedicated diff variants are reported with stable semantic paths rather than being
silently treated as equivalent.
`EngineHistory::base_matches` and `ScoreEngine::from_history_on_base` provide a deterministic
preflight and typed rejection for replaying a stale command log on an unrelated collaboration
snapshot.
`EngineHistory::compare` classifies equivalent, extending, divergent, and base-mismatched logs
without replaying commands; the WASM `ScoreEngine.compare_histories` method exposes the same
diagnostic to browser hosts.
`ScoreEngine::append_history_extension` replays and installs only a safe remote suffix
transactionally, preserving the incoming command-log shape and rejecting divergent logs without
mutating the local score.
`EngineHistory::conflict` reports the first divergent command keys and remaining lengths for
explainable host-side conflict UI; WASM exposes it as `history_conflict`.
~~~rust
use acorde_core::{Command, ScoreEngine, SetTempoCmd};
let mut engine = ScoreEngine::new();
engine.apply(Command::SetTempo(SetTempoCmd { bpm: 120 }))?;
engine.undo()?;
~~~
The current Score JSON schema version is 1. MIDI pitch-bend, Controller Change, Program Change,
and key/channel Aftertouch events are preserved per part with canonical 480-PPQ tick and channel
metadata. Tablature performance projections also preserve authored guitar techniques for
host-owned articulation, including authored bend alteration cents. Add acorde-io for format
conversion and acorde-layout
for renderer-independent layout.
Security invariants and resource-limit ownership are documented in the [security contract](../../docs/security/threat-model.md).
[API documentation](https://docs.rs/acorde-core) ยท [Repository](https://github.com/kent-tokyo/acorde)