Keyboard- and speech-driven step entry for the MIDI editor, built for use with OSARA. Arrows walk grid cells and scale degrees, Enter toggles notes, and every position is spoken and auditioned. Uses REAPER's own edit cursor and active_note_row rather than private cursor state, so grid mode and OSARA editing stay in sync. Grid size comes from MIDI_GetGrid(), so existing grid keybindings drive it. When grid mode is off, every bound key forwards to its previous action. Preview timing lives in a background daemon so the action scripts can exit immediately -- a script still alive on the next keypress triggers REAPER's "already running" prompt and breaks key repeat.
58 lines
1.6 KiB
Lua
58 lines
1.6 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
|
|
|
|
reaper.defer(loop)
|
|
end
|
|
|
|
-- Never leave a note hanging if the daemon is terminated or Reaper quits.
|
|
reaper.atexit(allOff)
|
|
loop()
|