diff --git a/MidiGrid_Daemon.lua b/MidiGrid_Daemon.lua index b3569d1..a55a72f 100644 --- a/MidiGrid_Daemon.lua +++ b/MidiGrid_Daemon.lua @@ -49,6 +49,24 @@ local function loop() 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 diff --git a/MidiGrid_PlayBarOnly.lua b/MidiGrid_PlayBarOnly.lua new file mode 100644 index 0000000..b6e6eb2 --- /dev/null +++ b/MidiGrid_PlayBarOnly.lua @@ -0,0 +1,12 @@ +--[[ + MIDI Grid: play only the bar the cursor is in, stopping at the bar line. + + The stop is carried out by the preview daemon rather than here, because an + action script must exit immediately -- one that stayed alive waiting for the + bar to end would trigger REAPER's "already running" prompt on the next press. +]] +local dir = ({ reaper.get_action_context() })[2]:match("@?(.*[\\/])") +local G = dofile(dir .. "midigrid_lib.lua") + +local bar = G.playFromBar(true) +G.say("Playing bar " .. bar .. " only") diff --git a/MidiGrid_PlayFromBar.lua b/MidiGrid_PlayFromBar.lua index c87f31b..e569c82 100644 --- a/MidiGrid_PlayFromBar.lua +++ b/MidiGrid_PlayFromBar.lua @@ -1,5 +1,5 @@ --[[ - MIDI Grid: play from the start of the bar the cursor is in. + MIDI Grid: play from the start of the bar the cursor is in, and keep going. Deliberately works whether or not grid mode is on -- it is a listening aid, not an editing action, and it is just as useful while navigating with OSARA @@ -8,5 +8,5 @@ local dir = ({ reaper.get_action_context() })[2]:match("@?(.*[\\/])") local G = dofile(dir .. "midigrid_lib.lua") -local bar = G.playFromBar() +local bar = G.playFromBar(false) G.say("Playing from bar " .. bar) diff --git a/README.md b/README.md index 0efd734..c27913b 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,8 @@ actions by hand in Actions → Show action list. That always works. | `Alt+Shift+Up` / `Alt+Shift+Down` | Velocity up / down | — | | `Alt+G` | **Toggle grid mode on/off** | — | | `Alt+H` | Toggle hold mode | — | -| `Alt+Space` | Play from start of the current bar | works in both modes | +| `Alt+Space` | Play from start of the current bar, onwards | works in both modes | +| `Ctrl+Alt+Space` | Play the current bar only, then stop | works in both modes | | `Alt+A` | Repeat current cell (speak + sound) | — | | `Alt+S` | Set root and scale | — | | `1`–`9` | Grid size — REAPER's own grid actions | same | @@ -152,8 +153,13 @@ off afterwards. ## Hearing it in context -`Alt+Space` plays from the **top of the bar the cursor is in**, and says which -bar. Stop with `Space` as usual. +Both of these play from the **top of the bar the cursor is in**, and say which +bar: + +- **`Alt+Space`** — plays from the bar line and keeps going. Stop with `Space`. +- **`Ctrl+Alt+Space`** — plays that bar **only**, stopping at the next bar + line. Nothing to stop by hand, so you can fire it repeatedly while nudging a + note and hear the same bar each time. Auditioning a cell tells you what a note *is*; playing from the downbeat tells you whether it is in the right *place*. At 16ths or 32nds that difference is @@ -163,8 +169,13 @@ Your position is preserved. The edit cursor doubles as the grid cursor, so it is moved to the bar line only long enough to start playback and then put straight back — you carry on editing exactly where you were. -This one works whether or not grid mode is on, since it is a listening aid -rather than an editing action. +Both work whether or not grid mode is on, since they are listening aids rather +than editing actions. + +The bar-only stop is carried out by the preview daemon, not by the action +script — a script that stayed alive waiting for the bar to end would trigger +REAPER's "already running" prompt on the next press. Stopping can move the +edit cursor depending on your preferences, so the daemon puts it back. ## Shifting notes in time diff --git a/install.ps1 b/install.ps1 index d74f718..0348cc4 100644 --- a/install.ps1 +++ b/install.ps1 @@ -60,7 +60,8 @@ $actions = @( @('RSmidigrid_shr', 32060, 'MIDI Grid: Shift all notes one cell right', 'MidiGrid_ShiftRight.lua'), @('RSmidigrid_shlf', 32060, 'MIDI Grid: Shift all notes left (force)', 'MidiGrid_ShiftLeftForce.lua'), @('RSmidigrid_shrf', 32060, 'MIDI Grid: Shift all notes right (force)', 'MidiGrid_ShiftRightForce.lua'), - @('RSmidigrid_playbar',32060, 'MIDI Grid: Play from start of current bar', 'MidiGrid_PlayFromBar.lua') + @('RSmidigrid_playbar',32060, 'MIDI Grid: Play from start of current bar', 'MidiGrid_PlayFromBar.lua'), + @('RSmidigrid_playone',32060, 'MIDI Grid: Play current bar only', 'MidiGrid_PlayBarOnly.lua') ) # flags, keycode, section, id @@ -88,7 +89,8 @@ $keys = @( @(13, 32807, 32060, 'RSmidigrid_shr'), @(29, 32805, 32060, 'RSmidigrid_shlf'), @(29, 32807, 32060, 'RSmidigrid_shrf'), - @(17, 32, 32060, 'RSmidigrid_playbar') + @(17, 32, 32060, 'RSmidigrid_playbar'), + @(25, 32, 32060, 'RSmidigrid_playone') ) $backup = "$kb.midigrid-backup" diff --git a/midigrid_lib.lua b/midigrid_lib.lua index 79463d2..d69badf 100644 --- a/midigrid_lib.lua +++ b/midigrid_lib.lua @@ -354,10 +354,12 @@ end ------------------------------------------------------------------ playback --- Start of the bar containing time t, plus the 1-based bar number. -function M.barStart(t) +-- Start and end of the bar containing time t, plus the 1-based bar number. +function M.barBounds(t) local _, measures = reaper.TimeMap2_timeToBeats(0, t) - return reaper.TimeMap2_beatsToTime(0, 0, measures), measures + 1 + return reaper.TimeMap2_beatsToTime(0, 0, measures), + reaper.TimeMap2_beatsToTime(0, 0, measures + 1), + measures + 1 end --[[ @@ -371,9 +373,9 @@ end subdivisions checkable: you hear the note in relation to the downbeat, which is the thing you are actually trying to judge. ]] -function M.playFromBar() +function M.playFromBar(stopAtBarEnd) local here = reaper.GetCursorPosition() - local barPos, barNum = M.barStart(here) + local barPos, barEnd, barNum = M.barBounds(here) reaper.PreventUIRefresh(1) reaper.SetEditCurPos(barPos, false, true) @@ -384,6 +386,18 @@ function M.playFromBar() reaper.SetEditCurPos(here, false, false) reaper.PreventUIRefresh(-1) + if stopAtBarEnd then + -- The daemon watches for this and stops transport, because an action + -- script must never stay alive to wait for it. + M.setTemp("stopat", barEnd) + -- Grace period: play state takes a moment to report as playing, and + -- without this the daemon would see "not playing" and cancel instantly. + M.setTemp("stoparm", reaper.time_precise() + 0.5) + M.ensureDaemon() + else + M.setTemp("stopat", "") + end + return barNum end