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.
97 lines
5.2 KiB
Markdown
97 lines
5.2 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
## What this is
|
|
|
|
A REAPER MIDI-editor grid/step-entry mode written in Lua, built for
|
|
keyboard-and-speech use with OSARA. Pure ReaScript — no ReaPack, no
|
|
js_ReaScriptAPI, no ReaImGui. `README.md` is the user manual and is unusually
|
|
detailed; read the relevant section before changing behaviour, and update it
|
|
when behaviour changes.
|
|
|
|
## Commands
|
|
|
|
```
|
|
lua test_lib.lua # run the tests
|
|
powershell -ExecutionPolicy Bypass -File install.ps1 # install on Windows (REAPER must be CLOSED)
|
|
powershell -ExecutionPolicy Bypass -File install.ps1 -Uninstall
|
|
./install.sh # install on macOS/Linux
|
|
./install.sh --uninstall
|
|
```
|
|
|
|
There is no build, no lint, no package manager, no test framework. `test_lib.lua`
|
|
is a single script of `eq(got, want, label)` assertions printing `ALL PASS` or a
|
|
failure count; to run one case, comment out the others. It stubs a global
|
|
`reaper` table, so it only covers the pure logic (scale stepping, note naming,
|
|
grid labels, chord construction). Anything touching MIDI data or the REAPER API
|
|
can only be exercised inside REAPER, though the note-jump tests show how far
|
|
a stubbed `MIDI_GetNote` will get you. It resolves `midigrid_lib.lua`
|
|
relative to its own path, so it runs from any checkout.
|
|
|
|
## Architecture
|
|
|
|
**`midigrid_lib.lua`** holds essentially all the logic as module `M`. Every
|
|
action script is a thin adapter: resolve its own directory from
|
|
`reaper.get_action_context()`, `dofile` the lib, check `G.isActive()`, and either
|
|
`G.passThrough(...)` or call a couple of lib functions. New behaviour belongs in
|
|
the lib; new action scripts should stay ~10 lines.
|
|
|
|
**No private cursor state.** Time is REAPER's edit cursor; pitch is the MIDI
|
|
editor's `active_note_row`; cell size is `MIDI_GetGrid()`. This is what lets grid
|
|
mode and plain OSARA editing interleave without drifting, and why the user's
|
|
existing grid-size keybindings just work. Do not introduce a cursor of your own.
|
|
|
|
**Action scripts must never call `reaper.defer`.** A still-running script makes
|
|
REAPER show its "already running — terminate or launch new instance?" prompt on
|
|
the next keypress, which destroys key repeat. All timing lives in
|
|
`MidiGrid_Daemon.lua`, one background loop that auto-starts (`G.ensureDaemon()`),
|
|
heartbeats via ExtState, refuses to start a second copy, and releases notes on
|
|
`atexit`. Actions post a request to ExtState and exit immediately. If you need
|
|
something to happen later — note release, stop-at-bar-end, restoring borrowed
|
|
loop/repeat state — add it to the daemon loop, not to an action.
|
|
|
|
**ExtState section `midigrid`** is the only state. Persisted (`M.set`/`M.get`):
|
|
`active`, `hold`, `root`, `scale`, `vel`, `velstep`, `chan`, `previewdur`,
|
|
`stoplead`. Transient (`M.setTemp`/`M.getTemp`, persist flag false): `seq`,
|
|
`pitches`, `dur`, `hb`, `stopat`, `stoparm`, `savedloop`, `savedrep`,
|
|
`savedarm`, `routed`. Preview traffic must stay transient — persisting it
|
|
would write `reaper.ini` on every keypress.
|
|
|
|
**Preview is routed to the edited track.** `StuffMIDIMessage` feeds the virtual
|
|
MIDI keyboard, which only reaches armed, monitoring tracks, so `M.routePreview`
|
|
borrows the record arm/monitor/input of the take's track (standing down other
|
|
armed tracks) and `M.restoreRouting` hands it all back. `M.preview` re-points
|
|
lazily on every call, so switching items just works. The daemon restores when
|
|
grid mode goes off, the editor closes, or REAPER exits.
|
|
|
|
**Every grid key falls back.** When grid mode is off, each bound key forwards to
|
|
what it did before via `G.passThrough` (e.g. `_OSARA_PREVCHORD`, or a numeric
|
|
command id), so nothing is permanently taken away.
|
|
|
|
**All feedback is speech**, via `G.say` → `osara_outputMessage`, falling back to
|
|
the console when OSARA is absent. There is no window and no `gfx`.
|
|
|
|
Musical positions are converted through quarter-notes (`MIDI_GetPPQPosFromProjQN`
|
|
and friends), not raw ticks, so edits stay correct across tempo changes.
|
|
|
|
## Installation model
|
|
|
|
`install.ps1` (Windows) and `install.sh` (macOS/Linux) edit `reaper-kb.ini`
|
|
directly and are idempotent (they strip all `RSmidigrid` lines first, and back
|
|
up to `reaper-kb.ini.midigrid-backup`). REAPER rewrites that file on exit, so
|
|
it must be closed or changes are lost. The two scripts are deliberate mirrors:
|
|
same action list, same key table, same output. **Any change to one must be made
|
|
to the other.** They differ only in resource-path discovery
|
|
(`%APPDATA%\REAPER` vs `~/Library/Application Support/REAPER`) and in
|
|
`install.sh --real-control`, which exists because modifier bit 8 means Ctrl on
|
|
Windows but Command on macOS (physical Control is bit 32).
|
|
|
|
Adding a new action means adding a row to **both** tables in each installer: the
|
|
`$actions` table (`SCR 4 <section> <id> "<description>" <path>`) and, if it needs
|
|
a key, `$keys` (flags: 1 base, +4 Shift, +8 Ctrl, +16 Alt; section 0 = Main,
|
|
32060 = MIDI Editor). `KEY` lines must reference the named command — the SCR id
|
|
with a **leading underscore** (`_RSmidigrid_open`). Without the underscore REAPER
|
|
silently converts the binding to a no-op; this is exactly why importing
|
|
`MidiGrid.ReaperKeyMap` does not work and the file is kept for reference only.
|