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 core/src/complement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -591,9 +591,9 @@ fn rotated_correlation(histogram: &[f64; 12], profile: &[f64; 12], tonic: u8) ->
for (x, y) in histogram.iter().zip(rotated.iter()) {
let dx = x - mean_x;
let dy = y - mean_y;
numerator += dx * dy;
var_x += dx * dx;
var_y += dy * dy;
numerator = dx.mul_add(dy, numerator);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve exact-tie key ordering

When a part's duration-weighted pitch-class histogram gives an exact tie between two rotated KK profiles, the documented tie-breaker is the first key scanned. The fused accumulation can split that exact tie by one ulp: for example histogram [194,498,821,804,875,237,243,964,12,952,316,475] makes D major and G major exact ties, but this line rounds D's numerator lower and estimate_harmony now picks G major, changing PartProfile::harmony and downstream complement pitch material for existing ambiguous parts. Either keep the previous accumulation or add an explicit epsilon/tie comparison so lint cleanup doesn't alter key ordering.

Useful? React with 👍 / 👎.

var_x = dx.mul_add(dx, var_x);
var_y = dy.mul_add(dy, var_y);
}
let denominator = (var_x * var_y).sqrt();
if denominator > 0.0 {
Expand Down
26 changes: 26 additions & 0 deletions core/src/gp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,11 @@ fn detect_gp_version(data: &[u8]) -> Option<u8> {

// ── Song → Score conversion ───────────────────────────────────────────────────

/// Converts a parsed Guitar Pro song into griff's canonical [`Score`] model.
///
/// The conversion builds the master timeline from measure headers, carries
/// tempos across bars that omit them, folds repeat markers into master bars, and
/// delegates per-track event extraction while collecting import losses.
fn gp_song_to_score(song: &guitarpro::Song) -> Score {
let mut loss = LossReport::new();

Expand Down Expand Up @@ -228,6 +233,11 @@ fn gp_song_to_score(song: &guitarpro::Song) -> Score {

// ── master bar construction ───────────────────────────────────────────────────

/// Builds canonical master bars from pre-normalised meter, start, tempo, and
/// repeat arrays.
///
/// Missing entries fall back to safe defaults so malformed or partially parsed
/// GP files still produce a structurally valid score where possible.
fn build_gp_master_bars(
meters: &[(u8, u8)],
starts: &[u32],
Expand Down Expand Up @@ -264,6 +274,11 @@ fn build_gp_master_bars(

// ── track construction ────────────────────────────────────────────────────────

/// Converts one Guitar Pro track into a canonical [`Track`].
///
/// Track-level metadata such as the MIDI channel and tuning is preserved, while
/// each populated GP voice is converted independently against the shared master
/// timeline starts.
fn build_gp_track(
gp_track: &guitarpro::Track,
song: &guitarpro::Song,
Expand Down Expand Up @@ -312,6 +327,11 @@ fn gp_tuning(strings: &[(i8, i8)]) -> Tuning {

// ── voice construction ────────────────────────────────────────────────────────

/// Converts a single Guitar Pro voice into a canonical [`Voice`].
///
/// Beats are positioned by accumulating their durations from each measure's
/// master-timeline start, and tied notes are resolved against per-string held
/// note state.
fn build_gp_voice(
gp_track: &guitarpro::Track,
voice_idx: usize,
Expand Down Expand Up @@ -379,6 +399,11 @@ struct StringCtx<'a> {
zero_indexed: bool,
}

/// Appends the canonical event representation for one Guitar Pro beat.
///
/// Normal and dead notes become note atoms, tied notes extend previously held
/// notes, unsupported note kinds are recorded as losses, and empty/rest beats
/// become rest groups.
fn append_beat(
beat: &guitarpro::Beat,
beat_start: u32,
Expand Down Expand Up @@ -507,6 +532,7 @@ fn extend_tie(
false
}

/// Creates a single-atom rest group spanning `duration` ticks from `start`.
fn rest_group(start: Ticks, duration: Ticks) -> EventGroup {
EventGroup {
kind: EventGroupKind::Single,
Expand Down