--[[ 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 reaper.defer(loop) end -- Never leave a note hanging if the daemon is terminated or Reaper quits. reaper.atexit(allOff) loop()