Skip to content

Commit 8d8d0d0

Browse files
committed
fix(core): GP tuplets last times/enters of their written value
A tuplet plays `tuplet_enters` notes in the time of `tuplet_times`, so each note lasts times/enters of its written value; the importer applied enters/times, following the `guitarpro` crate's inverted `Duration::time()`. A triplet eighth lasted 720 ticks instead of 320, every bar with a tuplet overfilled and its last beats started after the next bar's first. Double dots now add three quarters of the base. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 138721f commit 8d8d0d0

1 file changed

Lines changed: 14 additions & 9 deletions

File tree

‎core/src/gp.rs‎

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -894,11 +894,15 @@ fn map_gp_note_marks(
894894

895895
// ── duration helpers ──────────────────────────────────────────────────────────
896896

897-
/// Computes the beat duration in GP ticks from the public `Duration` fields.
897+
/// Computes the beat duration in GP ticks from the public `Duration` fields:
898+
/// `base = PPQN * 4 / value`, plus half the base for a dot (three quarters for
899+
/// a double dot), then the tuplet ratio.
898900
///
899-
/// Matches the internal `Duration::time()` logic in the `guitarpro` crate
900-
/// (which is `pub(crate)` and cannot be called externally):
901-
/// `base = PPQN * 4 / value`, with dotted and tuplet adjustments.
901+
/// A tuplet plays `tuplet_enters` notes in the time of `tuplet_times` — the GP
902+
/// readers store a triplet as 3 : 2 — so each note lasts `times / enters` of
903+
/// its written value. The crate's own `Duration::time()` (0.4.2) applies the
904+
/// inverse ratio and ignores double dots; following it overfilled every bar
905+
/// with a tuplet, starting its last beats after the next bar's first.
902906
fn gp_duration_ticks(dur: &GpDuration) -> u32 {
903907
if dur.value == 0 {
904908
return u32::from(GP_PPQN);
@@ -907,18 +911,19 @@ fn gp_duration_ticks(dur: &GpDuration) -> u32 {
907911
.saturating_mul(4)
908912
.checked_div(u32::from(dur.value))
909913
.unwrap_or_else(|| u32::from(GP_PPQN));
910-
let dotted_extra = if dur.dotted {
914+
let dots = if dur.double_dotted {
915+
base.saturating_mul(3).checked_div(4).unwrap_or(0)
916+
} else if dur.dotted {
911917
base.checked_div(2).unwrap_or(0)
912918
} else {
913919
0
914920
};
915-
let time = base.saturating_add(dotted_extra);
916-
// Apply tuplet factor (same convention as guitarpro's convert_time).
921+
let time = base.saturating_add(dots);
917922
if dur.tuplet_enters == 0 || dur.tuplet_times == 0 || dur.tuplet_enters == dur.tuplet_times {
918923
return time;
919924
}
920-
time.saturating_mul(u32::from(dur.tuplet_enters))
921-
.checked_div(u32::from(dur.tuplet_times))
925+
time.saturating_mul(u32::from(dur.tuplet_times))
926+
.checked_div(u32::from(dur.tuplet_enters))
922927
.unwrap_or(time)
923928
}
924929

0 commit comments

Comments
 (0)