Timing engine: per-note tempo lookup for ms->PPQ conversion#3
Conversation
apply_timing used to sample Master_GetTempo() once at function entry, derive
a single ppq_per_ms ratio, then apply that same ratio to every note. Any
project with tempo automation produced wrong PPQ offsets for notes after
the first tempo change — by exactly the ratio of (new BPM / sampled BPM).
Fix: round-trip each note's ms offset through project time.
local function ms_offset_to_ppq(take, ppq_pos, offset_ms)
local t_sec = reaper.MIDI_GetProjTimeFromPPQPos(take, ppq_pos)
local target_ppq = reaper.MIDI_GetPPQPosFromProjTime(take, t_sec + offset_ms * 0.001)
return target_ppq - ppq_pos
end
Costs 2 API calls per note instead of 1 multiplication, but APPLY is user-
initiated, not per-frame — sub-millisecond total cost for typical drum
content (~500 notes per item).
Note duration is preserved in PPQ (n.new_endppq = new_start + (n.endppq -
n.ppq)), keeping musical-grid duration stable across a tempo change inside
the note. Only the note start gets the per-note ms->PPQ adjustment.
get_tempo_info() is unchanged; it powers the timing-slider tooltip preview,
which is a rough display estimate by design.
Mirrored to Scripts/dehumanizer-pro.lua. Syntax verified with luac.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Warning Review limit reached
More reviews will be available in 9 minutes and 45 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
- MEMORY.md: repo conventions (dual-path ReaPack mirroring) + 2026-06-16 session summary covering the three open fix PRs (#1 velocity parity, #2 lifecycle persistence, #3 per-note tempo). - HANDOFF.md: next-chat resume point with verification checklist for the three PRs and gotchas (script mirrored at /dehumanizer-pro.lua and /Scripts/dehumanizer-pro.lua per ReaPack packaging). No code changes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Summary
C4 from internal review.
apply_timingsampledMaster_GetTempo()once at function entry, derived a singleppq_per_msratio, and applied it to every note. Any project with tempo automation produced wrong PPQ offsets for notes after the first tempo change — by exactly the ratio of (new BPM / sampled BPM).Independent of #1 and #2; all three branch from
mainand can land in any order.The fix
Round-trip each note's ms offset through project time so Reaper applies the correct local tempo:
Cost
2 API calls per note instead of 1 multiplication. APPLY is user-triggered, not per-frame. For a 500-note item that's 1000 API roundtrips at APPLY-time — sub-millisecond total in practice. No effect on render-loop performance.
Preserved behavior
n.new_endppq = new_start + (n.endppq - n.ppq). Inside a tempo change that crosses a note, this keeps musical-grid duration stable; only the note start gets the per-note ms-to-PPQ adjustment. This matches the conservative choice — a humanizer should not silently retime note ends as a side effect of nudging the starts.get_tempo_info()is unchanged. It powers the timing-slider tooltip preview ("Lean center: X ms (Y PPQ)"), which is a rough display estimate — the user isn't pointing at a specific note when looking at the slider tooltip.Test plan
luac -palready verified on both file copies.🤖 Generated with Claude Code