Ctrl+Alt+Space plays the cursor's bar and stops at the next bar line, so the same bar can be fired repeatedly while nudging a note without reaching for stop. Alt+Space keeps its play-onwards behaviour. The stop is performed by the preview daemon rather than the action script: a script that stayed alive waiting for the bar to end would bring back REAPER's "already running" prompt on the next press, which is the problem the daemon exists to avoid. Stopping transport can move the edit cursor depending on preferences, and the edit cursor is the grid cursor, so the daemon restores it. A short arming window covers the gap between requesting playback and the transport reporting as playing, so the pending stop is not cancelled instantly.
76 lines
2.4 KiB
Lua
76 lines
2.4 KiB
Lua
--[[
|
|
MIDI Grid: preview daemon.
|
|
|
|
Runs quietly in the background and owns every preview note-on/note-off.
|
|
The action scripts never defer, so they always exit instantly and Reaper
|
|
never shows its "already running" prompt no matter how fast keys repeat.
|
|
|
|
Started automatically by the action scripts; you never need to run it.
|
|
]]
|
|
local dir = ({ reaper.get_action_context() })[2]:match("@?(.*[\\/])")
|
|
local G = dofile(dir .. "midigrid_lib.lua")
|
|
|
|
-- Refuse to start a second copy.
|
|
local hb = tonumber(G.getTemp("hb", "0")) or 0
|
|
if os.time() - hb <= 3 then return end
|
|
|
|
local sounding = {}
|
|
local releaseAt = 0
|
|
local lastSeq = tonumber(G.getTemp("seq", "0")) or 0
|
|
|
|
local function allOff()
|
|
for _, p in ipairs(sounding) do
|
|
reaper.StuffMIDIMessage(0, 0x80, p, 0)
|
|
end
|
|
sounding = {}
|
|
end
|
|
|
|
local function loop()
|
|
G.setTemp("hb", os.time())
|
|
|
|
local seq = tonumber(G.getTemp("seq", "0")) or 0
|
|
if seq ~= lastSeq then
|
|
lastSeq = seq
|
|
-- A new request cuts whatever is still sounding, so fast arrowing
|
|
-- retriggers cleanly instead of piling notes up.
|
|
allOff()
|
|
local vel = math.floor(G.getNum("vel", 96))
|
|
for s in tostring(G.getTemp("pitches", "")):gmatch("%d+") do
|
|
local p = tonumber(s)
|
|
if p and p >= 0 and p <= 127 then
|
|
sounding[#sounding + 1] = p
|
|
reaper.StuffMIDIMessage(0, 0x90, p, vel)
|
|
end
|
|
end
|
|
releaseAt = reaper.time_precise() + (tonumber(G.getTemp("dur", "0.4")) or 0.4)
|
|
end
|
|
|
|
if #sounding > 0 and reaper.time_precise() >= releaseAt then
|
|
allOff()
|
|
end
|
|
|
|
-- Pending "stop at end of bar" request, posted by the play-one-bar action.
|
|
local stopAt = tonumber(G.getTemp("stopat", ""))
|
|
if stopAt then
|
|
local playing = (reaper.GetPlayState() % 2) == 1
|
|
if playing and reaper.GetPlayPosition() >= stopAt then
|
|
-- Stopping can move the edit cursor depending on preferences, and the
|
|
-- edit cursor is the grid cursor, so put it back.
|
|
local cur = reaper.GetCursorPosition()
|
|
reaper.CSurf_OnStop()
|
|
reaper.SetEditCurPos(cur, false, false)
|
|
G.setTemp("stopat", "")
|
|
elseif not playing
|
|
and reaper.time_precise() > (tonumber(G.getTemp("stoparm", "0")) or 0) then
|
|
-- Transport stopped by other means; drop the pending request.
|
|
G.setTemp("stopat", "")
|
|
end
|
|
end
|
|
|
|
reaper.defer(loop)
|
|
end
|
|
|
|
-- Never leave a note hanging if the daemon is terminated or Reaper quits.
|
|
reaper.atexit(allOff)
|
|
loop()
|