Files
midigrid/CLAUDE.md
T
TalonandClaude Opus 5 2fe9fa75b2 Add a macOS/Linux installer, and manual install steps
install.ps1 could never have worked outside Windows: it reads
%APPDATA% for the resource path, guards on Get-Process, and needs
PowerShell installed. install.sh mirrors it exactly -- same actions,
same key table, same idempotent rewrite and backup -- differing only
in resource-path discovery and --real-control, which exists because
modifier bit 8 means Ctrl on Windows but Command on macOS.

Also document installing by hand from the action list, for anyone who
would rather not run either script.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 22:14:33 +02:00

4.7 KiB

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. Note it dofiles an absolute path to midigrid_lib.lua.

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. Preview traffic must stay transient — persisting it would write reaper.ini on every keypress.

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.sayosara_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.