10 Commits
Author SHA1 Message Date
TalonandClaude Opus 5 0f06554a18 Keep the edit cursor when opening an item in grid mode
Opening an item in the MIDI editor parks the edit cursor at the item
start, and the open action then made that stick. Listening to a phrase
and hitting Alt+Shift+G to write on top of it meant arrowing back to
where you already were, one cell at a time, on every entry.

The cursor now stays where it was and is only tidied onto the nearest
grid line, so the first left/right press moves a whole cell rather than
a remainder. The action reads the cursor before the open command runs,
since the open command is what moves it.

snapCursorToCell rounds in quarter notes, like everything else here, so
the landing spot survives a tempo change, and clamps the cell index into
the item: the floor is the cell containing the item start, which is
where the action used to land, and the ceiling is the last cell that
starts inside the item. Clamping the index rather than the time means a
cursor parked before an off-grid item start rounds forward into the
first whole cell instead of into the partial one, where a note would
begin outside the item.

The action also re-reads the take from the editor handle now, so the
grid size and the spoken position come from the take actually being
edited rather than from the selected item's active take.

Toggling grid mode on with Alt+G is left alone: it does not move the
cursor at all, and cell() floors, so an off-grid cursor still works.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 01:21:21 +02:00
Talon c55ab24ae4 Add octave jumps and jumping to notes that exist
Arrowing cell by cell is right inside a dense bar and tedious across an
empty one. Three pairs of keys now jump straight to a note that is really
there, one pair for each axis:

  Alt+Up/Down            the column -- next pitch sounding in this cell
  Alt+Left/Right         the row    -- next note at the cursor's pitch
  Ctrl+Alt+Left/Right    anywhere   -- next note in the take, any pitch

plus Ctrl+Alt+Up/Down for whole-octave movement, which needs no scale
awareness because an octave preserves the pitch class.

All the jumps ignore muted notes, which are not audible content, and the
row and any-note jumps anchor on note starts, so a note held across eight
cells is one stop rather than eight. Landing in a chord takes whichever
note is nearest the pitch you came from rather than always the bottom one.

They sit on Ctrl+Alt rather than plain Ctrl because REAPER already uses
Ctrl+arrows to move between items.

The logic lives in the lib as usual, with four run* drivers following the
runShift pattern, so the eight new action scripts are seven lines each.

test_lib.lua previously dofile'd an absolute D:\ path and so could not run
outside one machine; it now resolves the lib relative to itself. Stubbing
MIDI_GetNote and the PPQ conversions reaches further than the old "pure
logic only" boundary, so the new tests cover the search functions and the
drivers' speech and cursor movement too.
2026-08-24 01:15:57 +02:00
TalonandClaude Opus 5 0acb7d58f6 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>
2026-08-24 00:55:25 +02:00
Talon bf69961412 Halve the bar-stop lead, and add exact bar looping
60ms was too blunt: at 180 BPM a 32nd note is 42ms, so the lead swallowed the
final 32nd of the bar, and it swallowed a whole 32nd from 125 BPM upward.

Testing against GetPlayPosition2 rather than GetPlayPosition removes the
render-ahead from the equation -- it reports the block about to be rendered
rather than what is currently audible -- so the lead only has to cover the
daemon's polling interval, not both delays. 35ms suffices, which is safe for
32nds to about 214 BPM.

Polling cannot do better than its own interval, so for genuine precision
Ctrl+Alt+Shift+Space loops the bar via the loop points instead, letting the
audio engine own the boundary. That is sample accurate at any tempo or grid
size. It borrows loop range and repeat state; the daemon restores both once
transport stops, including when stopped with Space.
2026-08-14 20:43:24 +02:00
Talon fc378e733b 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.
2026-08-14 20:04:20 +02:00
Talon 8fd1b4755f Add play-from-start-of-bar
Alt+Space plays from the downbeat of the bar the cursor is in. Auditioning a
cell says what a note is; playing from the bar line says whether it is in the
right place, which is the question that matters at 16ths and 32nds and cannot
be answered from a note in isolation.

REAPER can almost do this by chaining a go-to-measure action with play, but
that leaves the edit cursor at the bar line -- and the edit cursor is the grid
cursor, so you would lose your editing position every time you listened. Here
the cursor is moved just long enough to start playback and then restored with
seekplay=false so the restore cannot drag playback with it.

Works regardless of grid mode, being a listening aid rather than an edit.
2026-08-14 19:57:55 +02:00
Talon 51dff21e85 Add whole-take time shifting
Ctrl+Shift+Left/Right move every note in the take by one grid cell, for
clips that landed off the beat.

Since the whole take moves together, notes cannot collide with each other;
the hazard is the item boundary. Notes pushed past either end stop sounding
but are not deleted, so the operation is reversible -- shifting back restores
them. The plain keys still refuse when that would happen and report the count,
because a clip that has silently lost its first bar is a bad thing to discover
later. Ctrl+Alt+Shift forces it and names the consequence.

Positions are converted through QN rather than offset in raw ticks, so shifts
stay musically correct across tempo changes.
2026-08-14 18:46:33 +02:00
Talon 542f59e0db Add note length editing for sustaining already-entered notes
Hold mode only governs entry, so sustaining existing notes previously meant
toggling hold on and re-entering them. That works but deletes and recreates
each note, silently resetting velocity to the default.

L and Shift+L resize the note under the cursor by whole grid cells, editing it
in place so velocity, channel and note identity survive. They are independent
of hold mode, so there is no state to set up or unset. Alt+J fuses a whole run
of repeated notes at one pitch in a single keystroke.

Lengthening absorbs same-pitch notes it runs into, taking their tail if longer,
so growing across a repeated run fuses it instead of creating overlaps.

Also makes noteEndingAt/noteStartingAt public and fixes their two call sites in
toggleCell, which would otherwise have been nil-call errors in hold mode.
2026-08-14 18:27:14 +02:00
Talon c9e927ca3d Add diatonic chord entry and velocity control
Chords are built by stacking scale thirds rather than from a quality table,
so the chord follows the degree automatically -- in C major, Alt+3 on C gives
C major, on D gives D minor, on B gives B diminished. Toggling delegates to
toggleCell, so chord tones inherit hold-mode joining, splitting and trimming
unchanged.

Velocity keys act on the note under the cursor if there is one, and on the
default for new notes otherwise, announcing which. That avoids a mode switch
between setting a level to draw at and shaping dynamics after the fact.

Also fixes install.ps1 failing to resolve its own directory: $PSScriptRoot is
not reliably populated during parameter binding, so it is resolved in the body.

Tests cover chord quality per scale degree and range clamping.
2026-08-14 18:18:12 +02:00
Talon 8fd6f947ed Accessible grid-based MIDI entry mode for REAPER
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.
2026-08-14 18:13:45 +02:00