Play preview through the edited track's instrument
StuffMIDIMessage feeds the virtual MIDI keyboard, which only reaches tracks that are armed and monitoring -- normally not the track whose item you are editing. Opening an item in grid mode therefore sounded every preview through whatever instrument happened to be armed, which is confusing when the notes you are writing come back in the wrong voice. Grid mode now borrows the record routing: the edited take's track is armed and monitoring with all MIDI inputs (anything narrower can exclude the virtual keyboard), and every other armed track is stood down so it cannot answer as well. The prior arm, monitor and input of each track touched is saved by GUID in transient ExtState. routePreview re-points lazily, so moving the editor to an item on another track costs nothing until the target actually changes. The daemon hands the routing back when grid mode goes off, when the editor closes, or on exit -- the same way it already restores the loop and repeat state the loop-bar action borrows -- so the project is put back even if grid mode is never toggled off. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -681,6 +681,90 @@ function M.nudgeVelocity(take, pitch, delta)
|
||||
return "default", v
|
||||
end
|
||||
|
||||
------------------------------------------------------------ preview routing
|
||||
|
||||
--[[
|
||||
Preview notes leave through StuffMIDIMessage, i.e. the virtual MIDI
|
||||
keyboard, and Reaper feeds that to whatever tracks happen to be armed and
|
||||
monitoring. That is usually NOT the track whose item you are editing, so
|
||||
the grid would sound your notes through some unrelated instrument.
|
||||
|
||||
Grid mode therefore borrows the record routing: the edited take's track is
|
||||
armed and monitoring with MIDI input, every other armed track is stood
|
||||
down so it cannot answer as well, and the previous state of every track we
|
||||
touched is remembered so it can be handed straight back.
|
||||
|
||||
The borrow is undone when grid mode is switched off, when the MIDI editor
|
||||
closes, and on Reaper exit -- all three by the daemon, in the same way it
|
||||
restores the loop/repeat state the loop-bar action borrows.
|
||||
]]
|
||||
|
||||
-- "All MIDI inputs, all channels": 4096 | (device << 5) | channel, with
|
||||
-- device 63 meaning all. Anything narrower may exclude the virtual keyboard.
|
||||
local ALL_MIDI_IN = 4096 + 63 * 32
|
||||
|
||||
local function trackByGUID(guid)
|
||||
for i = 0, reaper.CountTracks(0) - 1 do
|
||||
local tr = reaper.GetTrack(0, i)
|
||||
if reaper.GetTrackGUID(tr) == guid then return tr end
|
||||
end
|
||||
end
|
||||
|
||||
local function armState(tr)
|
||||
return ("%s,%d,%d,%d"):format(
|
||||
reaper.GetTrackGUID(tr),
|
||||
reaper.GetMediaTrackInfo_Value(tr, "I_RECARM"),
|
||||
reaper.GetMediaTrackInfo_Value(tr, "I_RECMON"),
|
||||
reaper.GetMediaTrackInfo_Value(tr, "I_RECINPUT"))
|
||||
end
|
||||
|
||||
-- Hand back every track's arm/monitor/input exactly as we found it.
|
||||
function M.restoreRouting()
|
||||
local saved = M.getTemp("savedarm", "")
|
||||
M.setTemp("savedarm", "")
|
||||
M.setTemp("routed", "")
|
||||
if saved == "" then return false end
|
||||
for guid, arm, mon, inp in saved:gmatch("({[^}]*}),(%-?%d+),(%-?%d+),(%-?%d+)") do
|
||||
local tr = trackByGUID(guid)
|
||||
if tr then
|
||||
reaper.SetMediaTrackInfo_Value(tr, "I_RECARM", tonumber(arm))
|
||||
reaper.SetMediaTrackInfo_Value(tr, "I_RECMON", tonumber(mon))
|
||||
reaper.SetMediaTrackInfo_Value(tr, "I_RECINPUT", tonumber(inp))
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Point preview at the track this take lives on. Cheap to call on every
|
||||
-- keypress: it only touches the project when the target has changed.
|
||||
function M.routePreview(take)
|
||||
if not take then return end
|
||||
local tr = reaper.GetMediaItemTake_Track(take)
|
||||
if not tr then return end
|
||||
local guid = reaper.GetTrackGUID(tr)
|
||||
if M.getTemp("routed", "") == guid then return end
|
||||
|
||||
-- Give the previously borrowed track back before borrowing another.
|
||||
M.restoreRouting()
|
||||
|
||||
local saved = {}
|
||||
for i = 0, reaper.CountTracks(0) - 1 do
|
||||
local t = reaper.GetTrack(0, i)
|
||||
local armed = reaper.GetMediaTrackInfo_Value(t, "I_RECARM") == 1
|
||||
if t == tr or armed then
|
||||
saved[#saved + 1] = armState(t)
|
||||
if t ~= tr then reaper.SetMediaTrackInfo_Value(t, "I_RECARM", 0) end
|
||||
end
|
||||
end
|
||||
|
||||
reaper.SetMediaTrackInfo_Value(tr, "I_RECINPUT", ALL_MIDI_IN)
|
||||
reaper.SetMediaTrackInfo_Value(tr, "I_RECARM", 1)
|
||||
reaper.SetMediaTrackInfo_Value(tr, "I_RECMON", 1)
|
||||
|
||||
M.setTemp("savedarm", table.concat(saved, ";"))
|
||||
M.setTemp("routed", guid)
|
||||
end
|
||||
|
||||
------------------------------------------------------------------ audition
|
||||
|
||||
--[[
|
||||
@@ -694,6 +778,9 @@ end
|
||||
]]
|
||||
function M.preview(pitches, dur)
|
||||
if not pitches or #pitches == 0 then return end
|
||||
-- Sound through the instrument of the track being edited, not through
|
||||
-- whatever else happened to be armed.
|
||||
M.routePreview(select(2, M.editor()))
|
||||
M.setTemp("pitches", table.concat(pitches, ","))
|
||||
M.setTemp("dur", dur or M.getNum("previewdur", 0.4))
|
||||
M.setTemp("seq", (tonumber(M.getTemp("seq", "0")) or 0) + 1)
|
||||
|
||||
Reference in New Issue
Block a user