Halve the bar-stop lead, and add exact bar looping

60ms was too blunt: at 180 BPM a 32nd note is 42ms, so the lead swallowed the
final 32nd of the bar, and it swallowed a whole 32nd from 125 BPM upward.

Testing against GetPlayPosition2 rather than GetPlayPosition removes the
render-ahead from the equation -- it reports the block about to be rendered
rather than what is currently audible -- so the lead only has to cover the
daemon's polling interval, not both delays. 35ms suffices, which is safe for
32nds to about 214 BPM.

Polling cannot do better than its own interval, so for genuine precision
Ctrl+Alt+Shift+Space loops the bar via the loop points instead, letting the
audio engine own the boundary. That is sample accurate at any tempo or grid
size. It borrows loop range and repeat state; the daemon restores both once
transport stops, including when stopped with Space.
This commit is contained in:
2026-08-14 20:43:24 +02:00
parent 39cdbeef1d
commit bf69961412
5 changed files with 128 additions and 22 deletions
+39
View File
@@ -401,6 +401,45 @@ function M.playFromBar(stopAtBarEnd)
return barNum
end
--[[
Loop the cursor's bar.
Where the play-once action stops by polling -- and so can only be as precise
as its polling interval -- this hands the boundary to REAPER's audio engine
via the loop points, which is sample accurate. The next bar's downbeat can
never leak in, at any tempo or grid size.
The trade is that it repeats until you stop it with Space, rather than
stopping itself. For checking placement that is arguably better: leave it
looping and edit while it plays.
Loop range and repeat state are saved and restored by the daemon once
transport stops, so this does not quietly rearrange the project.
]]
function M.loopBar()
local here = reaper.GetCursorPosition()
local barPos, barEnd, barNum = M.barBounds(here)
local s, e = reaper.GetSet_LoopTimeRange(false, true, 0, 0, false)
M.setTemp("savedloop", ("%.17g,%.17g"):format(s, e))
M.setTemp("savedrep", reaper.GetSetRepeat(-1))
M.setTemp("stoparm", reaper.time_precise() + 0.5)
M.setTemp("stopat", "") -- cancel any pending play-once stop
reaper.PreventUIRefresh(1)
reaper.GetSet_LoopTimeRange(true, true, barPos, barEnd, false)
reaper.GetSetRepeat(1)
reaper.SetEditCurPos(barPos, false, true)
if (reaper.GetPlayState() % 2) == 0 then
reaper.CSurf_OnPlay()
end
reaper.SetEditCurPos(here, false, false)
reaper.PreventUIRefresh(-1)
M.ensureDaemon()
return barNum
end
---------------------------------------------------------------- time shift
--[[