Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions cli/src/generation_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@ mod tests {
use griff_core::event::{NoteMarks, Pitch, Tempo, Ticks, TimeSignature, Tuning, Velocity};
use griff_core::midi;
use griff_core::score::{
AtomEvent, AtomNote, EventGroup, EventGroupKind, LossReport, MasterBar, RepeatMarker,
Track, Voice,
index_from_ordinal, AtomEvent, AtomNote, EventGroup, EventGroupKind, LossReport, MasterBar,
RepeatMarker, Track, Voice,
};
use griff_core::slice::TickRange;
use std::cell::Cell;
Expand All @@ -137,7 +137,7 @@ mod tests {
.map(|i| {
let start = u32::try_from(i).unwrap().saturating_mul(1920);
MasterBar {
index: i,
index: index_from_ordinal(i),
tick_range: TickRange::new(Ticks(start), Ticks(start.saturating_add(1920)))
.unwrap(),
time_signature: TimeSignature {
Expand Down
24 changes: 16 additions & 8 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use griff_core::{
ingest,
midi::{self, MidiError},
novelty, rerank,
score::{AtomEvent, LossReport, Score, Track, Voice},
score::{index_from_ordinal, AtomEvent, LossReport, Score, Track, Voice},
scoring,
slice::{self, TickRange},
split, structure, syncopation, technique, unfold,
Expand Down Expand Up @@ -860,12 +860,20 @@ fn cmd_phrases(path: &Path) -> Result<(), CliError> {

/// The index of the master bar containing `tick`, or the last bar when the tick
/// falls at or past the end of the timeline.
fn bar_at_tick(score: &Score, tick: u32) -> usize {
///
/// Display only. The two branches are not quite the same kind of number — the
/// hit returns a stored canonical index, the fallback the last *ordinal* — and
/// that pre-existing conflation is left alone here; SWG-CORE-01 only makes
/// both sides the same width so neither has to be narrowed to meet the other.
fn bar_at_tick(score: &Score, tick: u32) -> u64 {
score
.master_bars
.iter()
.find(|mb| tick >= mb.tick_range.start.0 && tick < mb.tick_range.end.0)
.map_or_else(|| score.master_bars.len().saturating_sub(1), |mb| mb.index)
.map_or_else(
|| index_from_ordinal(score.master_bars.len().saturating_sub(1)),
|mb| mb.index,
)
}

/// Renders the heuristic signals that fired at a phrase boundary as a comma
Expand Down Expand Up @@ -2328,8 +2336,8 @@ mod tests {
use griff_core::generate::RhythmTemplate;
use griff_core::gesture;
use griff_core::score::{
AtomEvent, AtomNote, EventGroup, EventGroupKind, LossReport, MasterBar, RepeatMarker,
Score, SourceMeta, Voice,
index_from_ordinal, AtomEvent, AtomNote, EventGroup, EventGroupKind, LossReport, MasterBar,
RepeatMarker, Score, SourceMeta, Voice,
};
use griff_core::slice::TickRange;

Expand Down Expand Up @@ -2567,7 +2575,7 @@ mod tests {
}

/// One 4/4 bar with explicit bounds (no arithmetic in the fixture).
fn mbar(index: usize, start: u32, end: u32) -> MasterBar {
fn mbar(index: u64, start: u32, end: u32) -> MasterBar {
MasterBar {
index,
tick_range: TickRange::new(Ticks(start), Ticks(end)).expect("ordered"),
Expand Down Expand Up @@ -2732,7 +2740,7 @@ mod tests {
.map(|i| {
let start = u32::try_from(i).unwrap_or(0).saturating_mul(1920);
MasterBar {
index: i,
index: index_from_ordinal(i),
tick_range: TickRange::new(Ticks(start), Ticks(start.saturating_add(1920)))
.expect("ordered"),
time_signature: TimeSignature {
Expand Down Expand Up @@ -3039,7 +3047,7 @@ mod tests {
.map(|i| {
let start = u32::try_from(i).expect("small index").saturating_mul(1920);
MasterBar {
index: i,
index: index_from_ordinal(i),
tick_range: TickRange::new(Ticks(start), Ticks(start.saturating_add(1920)))
.expect("ordered"),
time_signature: TimeSignature {
Expand Down
2 changes: 1 addition & 1 deletion cockpit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4044,7 +4044,7 @@ mod tests {
const BAR: u32 = 3840;
let master_bars = (0..bars)
.map(|i| MasterBar {
index: i as usize,
index: u64::from(i),
tick_range: TickRange::new(Ticks(i * BAR), Ticks((i + 1) * BAR)).expect("range"),
time_signature: TimeSignature::new(4, 4).expect("4/4"),
tempo: Tempo::from_bpm_integer(120).expect("120"),
Expand Down
6 changes: 3 additions & 3 deletions cockpit/src/swang.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,8 +307,8 @@ fn line_col(source: &str, offset: u32) -> (u32, u32) {
mod tests {
use griff_core::event::{NoteMarks, Pitch, Tempo, Ticks, TimeSignature, Tuning, Velocity};
use griff_core::score::{
AtomEvent, AtomNote, EventGroup, EventGroupKind, LossReport, MasterBar, RepeatMarker,
Score, Track, Voice,
index_from_ordinal, AtomEvent, AtomNote, EventGroup, EventGroupKind, LossReport, MasterBar,
RepeatMarker, Score, Track, Voice,
};
use griff_core::slice::TickRange;

Expand All @@ -321,7 +321,7 @@ mod tests {
.map(|i| {
let start = u32::try_from(i).unwrap() * BAR;
MasterBar {
index: i,
index: index_from_ordinal(i),
tick_range: TickRange::new(Ticks(start), Ticks(start + BAR)).expect("ordered"),
time_signature: TimeSignature {
numerator: 4,
Expand Down
9 changes: 5 additions & 4 deletions core/src/candidate_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1028,8 +1028,9 @@ mod tests {
use crate::layered_path::{PathError, StateId};
use crate::rerank::SetCandidate;
use crate::score::{
AtomEvent, AtomNote, AtomRest, EventGroup, EventGroupKind, ImportWarning, LossReport,
MasterBar, RepeatMarker, Score, SourceMeta, TechniqueSpan, Track, Voice,
index_from_ordinal, AtomEvent, AtomNote, AtomRest, EventGroup, EventGroupKind,
ImportWarning, LossReport, MasterBar, RepeatMarker, Score, SourceMeta, TechniqueSpan,
Track, Voice,
};
use crate::scoring::{Axes, Axis, Scored, WeightPolicy};
use crate::slice::TickRange;
Expand All @@ -1049,7 +1050,7 @@ mod tests {
for (index, notes) in bars.iter().enumerate() {
let start = u32::try_from(index).unwrap() * BAR;
master_bars.push(MasterBar {
index,
index: index_from_ordinal(index),
tick_range: TickRange::new(Ticks(start), Ticks(start + BAR)).unwrap(),
time_signature: TimeSignature::new(4, 4).unwrap(),
tempo: Tempo::from_bpm_integer(120).unwrap(),
Expand Down Expand Up @@ -1512,7 +1513,7 @@ mod tests {

/// Every fact of a master bar, in a comparable form (`MasterBar` is not
/// `PartialEq`, and `Tempo` holds an `f64`).
fn bar_facts(bar: &MasterBar) -> (usize, TickRange, TimeSignature, Tempo, RepeatMarker) {
fn bar_facts(bar: &MasterBar) -> (u64, TickRange, TimeSignature, Tempo, RepeatMarker) {
(
bar.index,
bar.tick_range,
Expand Down
8 changes: 6 additions & 2 deletions core/src/dump.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,12 @@ pub struct NormTrack {
/// A normalized bar: shared transport plus the track's voices in it.
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct NormBar {
/// Zero-based bar index.
pub index: usize,
/// Zero-based bar index, copied from the stored `MasterBar::index`.
///
/// Carries a canonical value, so it carries the canonical width: reducing
/// it to `usize` here would be a truncating conversion on a 32-bit target,
/// inside a type that is serialized (spec §1.2, SWG-CORE-01).
pub index: u64,
/// Meter as `[numerator, denominator]`.
pub time_sig: [u8; 2],
/// Tempo in BPM at the bar start.
Expand Down
6 changes: 3 additions & 3 deletions core/src/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use crate::event::{
};
use crate::pitch::{PitchClassSet, PitchRange, PitchSelectionError, ScaleLadder};
use crate::score::{
AtomEvent, AtomNote, EventGroup, EventGroupKind, LossReport, MasterBar, RepeatMarker, Score,
Track, Voice,
index_from_ordinal, AtomEvent, AtomNote, EventGroup, EventGroupKind, LossReport, MasterBar,
RepeatMarker, Score, Track, Voice,
};
use crate::slice::TickRange;

Expand Down Expand Up @@ -447,7 +447,7 @@ fn bars_to_score(
let tick_range =
TickRange::new(bar_start, bar_end).map_err(|_| GenerationError::InvalidConstraints)?;
master_bars.push(MasterBar {
index,
index: index_from_ordinal(index),
tick_range,
time_signature: c.time_signature,
tempo: c.tempo,
Expand Down
6 changes: 3 additions & 3 deletions core/src/generation_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -521,8 +521,8 @@ mod tests {
use crate::corpus::{ChunkId, ChunkMeta, SourceFormat, SourceRef};
use crate::event::{NoteMarks, Pitch, Tempo, Ticks, TimeSignature, Tuning, Velocity};
use crate::score::{
AtomEvent, AtomNote, EventGroup, EventGroupKind, LossReport, MasterBar, RepeatMarker,
Score, Track, Voice,
index_from_ordinal, AtomEvent, AtomNote, EventGroup, EventGroupKind, LossReport, MasterBar,
RepeatMarker, Score, Track, Voice,
};
use crate::slice::TickRange;

Expand Down Expand Up @@ -562,7 +562,7 @@ mod tests {
.map(|i| {
let start = u32::try_from(i).expect("small").saturating_mul(1920);
MasterBar {
index: i,
index: index_from_ordinal(i),
tick_range: TickRange::new(Ticks(start), Ticks(start.saturating_add(1920)))
.expect("ordered"),
time_signature: TimeSignature {
Expand Down
7 changes: 4 additions & 3 deletions core/src/gp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ use crate::{
TechniqueEvidence, Tempo, Ticks, TimeSignature, Tuning, Velocity,
},
score::{
AtomEvent, AtomNote, AtomRest, EventGroup, EventGroupKind, ImportWarning, LossReport,
MasterBar, RepeatMarker, Score, SourceMeta, TechniqueSpan, Track, Voice,
index_from_ordinal, AtomEvent, AtomNote, AtomRest, EventGroup, EventGroupKind,
ImportWarning, LossReport, MasterBar, RepeatMarker, Score, SourceMeta, TechniqueSpan,
Track, Voice,
},
slice::TickRange,
};
Expand Down Expand Up @@ -279,7 +280,7 @@ fn build_gp_master_bars(
.unwrap_or(Tempo::FALLBACK_120);

MasterBar {
index: idx,
index: index_from_ordinal(idx),
tick_range: TickRange {
start: Ticks(start),
end: Ticks(end),
Expand Down
15 changes: 8 additions & 7 deletions core/src/midi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ use crate::{
event::{NoteMarks, Pitch, Tempo, Ticks, TimeSignature, Tuning, ValidationError, Velocity},
fretboard::{self, assign_inferred_positions, FingeringWeights},
score::{
AtomEvent, AtomNote, EventGroup, EventGroupKind, ImportWarning, LossReport, MasterBar,
RepeatMarker, Score, SourceMeta, Track as ScoreTrack, Voice,
index_from_ordinal, AtomEvent, AtomNote, EventGroup, EventGroupKind, ImportWarning,
LossReport, MasterBar, RepeatMarker, Score, SourceMeta, Track as ScoreTrack, Voice,
},
slice::TickRange,
};
Expand Down Expand Up @@ -345,7 +345,7 @@ fn abs_to_delta(
/// other tempo converts to its nearest in-range value and records a
/// [`ImportWarning::TempoApproximated`] on `loss` (S16 Phase 4-pre A: an
/// approximation is a reported fact, never a silent rounding).
fn tempo_to_micros(tempo: Tempo, bar_index: usize, loss: &mut LossReport) -> u32 {
fn tempo_to_micros(tempo: Tempo, bar_index: u64, loss: &mut LossReport) -> u32 {
let max_u24 = u32::from(u24::max_value());
if let Some(exact) = tempo.to_micros_per_quarter_exact() {
if (1..=max_u24).contains(&exact) {
Expand Down Expand Up @@ -513,7 +513,7 @@ fn build_master_bars(
.map_err(|_| MidiError::TickOverflow)?;

master_bars.push(MasterBar {
index,
index: index_from_ordinal(index),
tick_range,
time_signature: sig,
tempo,
Expand Down Expand Up @@ -545,7 +545,7 @@ fn build_score_track(
let mut loss = LossReport::new();
let name = name_result.unwrap_or_else(|()| {
loss.add(ImportWarning::TrackNameInvalidUtf8 {
track_index: raw_idx,
track_index: index_from_ordinal(raw_idx),
});
None
});
Expand Down Expand Up @@ -829,6 +829,7 @@ mod tests {
bar_ticks, build_master_bars, build_score_meta_track, export_score, import_score,
tempo_to_micros, MidiError, Ppqn,
};
use crate::score::index_from_ordinal;
use crate::{
event::{NoteMarks, Pitch, Tempo, Ticks, TimeSignature, Tuning, Velocity},
score::{
Expand Down Expand Up @@ -893,7 +894,7 @@ mod tests {
// different exact tempo — projected again (clean), no second event.
let tempo_same_mpq = Tempo::from_micros_per_quarter(495_868).expect("495 868 µs is valid");
let bar = |index: usize, tempo: Tempo| MasterBar {
index,
index: index_from_ordinal(index),
tick_range: TickRange::new(
Ticks(u32::try_from(index).expect("small") * 1920),
Ticks((u32::try_from(index).expect("small") + 1) * 1920),
Expand Down Expand Up @@ -953,7 +954,7 @@ mod tests {
);

let bar = |index: usize, tempo: Tempo| MasterBar {
index,
index: index_from_ordinal(index),
tick_range: TickRange::new(
Ticks(u32::try_from(index).expect("small") * 1920),
Ticks((u32::try_from(index).expect("small") + 1) * 1920),
Expand Down
35 changes: 30 additions & 5 deletions core/src/score.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,34 @@ use crate::{
slice::TickRange,
};

// ── ordinal ↔ canonical index ──────────────────────────────────────────────

/// Widens an operational vector position into the canonical index width.
///
/// The two are different things and the census says so (H4): an ordinal is
/// where an element sits in a `Vec`, a canonical index is a stored exact fact
/// that may disagree with it. Importers legitimately derive the second from
/// the first, and this is the only sanctioned way to do it.
///
/// Total and lossless: `usize` is at most 64 bits on every target Rust
/// supports. It is a named function rather than a bare cast so that each
/// ordinal → canonical crossing is greppable, and so that the reverse
/// direction — which is *not* total — cannot be written by accident.
#[must_use]
pub const fn index_from_ordinal(ordinal: usize) -> u64 {
ordinal as u64
}

// ── loss reporting ─────────────────────────────────────────────────────────────

/// A loss or approximation incurred during format import or export.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ImportWarning {
/// A track name was present but could not be decoded as UTF-8.
TrackNameInvalidUtf8 {
/// Zero-based index of the affected track.
track_index: usize,
/// Zero-based index of the affected track. Fixed-width so the value
/// means the same thing on every target (SWG-CORE-01).
track_index: u64,
},
/// The MIDI file used SMPTE/timecode timing; `griff` does not yet support
/// it.
Expand All @@ -43,7 +62,8 @@ pub enum ImportWarning {
/// approximation is a reported fact, never a silent rounding).
TempoApproximated {
/// Zero-based master-bar index whose tempo was approximated.
bar_index: usize,
/// Fixed-width (SWG-CORE-01).
bar_index: u64,
/// The microseconds-per-quarter value actually written.
nearest_micros: u32,
},
Expand Down Expand Up @@ -258,8 +278,13 @@ impl RepeatMarker {
/// `MasterBar` is the single source of truth for transport (ADR-0003).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct MasterBar {
/// Zero-based bar index.
pub index: usize,
/// Zero-based bar index, as stored.
///
/// Not the vector position: an importer may disagree with the ordinal and
/// the disagreement is itself an exact fact. Fixed-width rather than
/// platform-sized, so a score written on a 64-bit host means the same
/// thing on a 32-bit one (spec §1.2, SWG-CORE-01).
pub index: u64,
/// Absolute half-open tick range `[start, end)` of this bar.
pub tick_range: TickRange,
/// Meter of this bar.
Expand Down
9 changes: 7 additions & 2 deletions core/src/semantic_diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,10 @@ pub enum SemanticPathSegment {
ordinal: usize,
/// The agreed stored bar index; `None` when the index itself
/// differs, so both diff directions render the same path.
index: Option<usize>,
///
/// A canonical value, so the canonical width (SWG-CORE-01) — the
/// `ordinal` beside it is an operational position and stays `usize`.
index: Option<u64>,
},
/// A track, by position.
Track {
Expand Down Expand Up @@ -218,7 +221,9 @@ pub enum SemanticPathSegment {
/// Zero-based position in the projection's `bars`.
ordinal: usize,
/// The agreed stored bar index; `None` when it differs.
index: Option<usize>,
///
/// Canonical width, for the same reason as the exact segment above.
index: Option<u64>,
},
/// A normalized-projection note, by position.
Note {
Expand Down
6 changes: 3 additions & 3 deletions core/src/slice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use std::ops::Range;

use crate::event::{Ticks, ValidationError};
use crate::score::{
AtomEvent, AtomNote, AtomRest, EventGroup, LossReport, MasterBar, Score, TechniqueSpan, Track,
Voice,
index_from_ordinal, AtomEvent, AtomNote, AtomRest, EventGroup, LossReport, MasterBar, Score,
TechniqueSpan, Track, Voice,
};

/// Half-open tick range: `start <= tick < end`.
Expand Down Expand Up @@ -71,7 +71,7 @@ pub fn extract_bars(score: &Score, bars: Range<usize>) -> Score {
.iter()
.enumerate()
.map(|(i, b)| MasterBar {
index: i,
index: index_from_ordinal(i),
tick_range: rebased_range(b.tick_range, offset),
time_signature: b.time_signature,
tempo: b.tempo,
Expand Down
Loading
Loading