Add play-current-bar-only

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.
This commit is contained in:
2026-08-14 20:04:20 +02:00
parent 8fd1b4755f
commit fc378e733b
6 changed files with 71 additions and 14 deletions
+18
View File
@@ -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
+12
View File
@@ -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")
+2 -2
View File
@@ -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)
+16 -5
View File
@@ -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
+4 -2
View File
@@ -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"
+19 -5
View File
@@ -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