2026-08-14 18:13:45 +02:00
|
|
|
|
# MIDI Grid — accessible quick MIDI entry for REAPER
|
|
|
|
|
|
|
|
|
|
|
|
A grid / step-sequencer entry mode for REAPER's MIDI editor, built for
|
|
|
|
|
|
keyboard-and-speech use with [OSARA](https://osara.reaperaccessibility.com/).
|
|
|
|
|
|
|
|
|
|
|
|
Left and right walk the timeline one grid cell at a time. Up and down walk the
|
|
|
|
|
|
pitch axis **by scale degree**. Enter toggles a note in the current cell.
|
2026-08-24 01:15:57 +02:00
|
|
|
|
With a modifier the same arrows jump an octave, or straight to the next note
|
|
|
|
|
|
that actually exists — up the cell, along the pitch, or anywhere in the take.
|
2026-08-14 18:13:45 +02:00
|
|
|
|
Everything you land on is spoken and sounded, so you can hear what is already
|
|
|
|
|
|
there as you move.
|
|
|
|
|
|
|
|
|
|
|
|
It is **not** a replacement for the MIDI editor. It is a fast way to block out
|
|
|
|
|
|
a clip; switch grid mode off and OSARA's normal editing is right there,
|
|
|
|
|
|
unchanged, on the same cursor.
|
|
|
|
|
|
|
|
|
|
|
|
## Requirements
|
|
|
|
|
|
|
|
|
|
|
|
- REAPER
|
|
|
|
|
|
- **OSARA** — for speech output
|
|
|
|
|
|
- **SWS** — for the note-naming octave preference
|
|
|
|
|
|
|
|
|
|
|
|
No other extensions. No ReaPack, no js_ReaScriptAPI, no ReaImGui.
|
|
|
|
|
|
|
|
|
|
|
|
## Install
|
|
|
|
|
|
|
|
|
|
|
|
Clone or download this repository anywhere you like, then:
|
|
|
|
|
|
|
|
|
|
|
|
1. **Close REAPER completely.** It rewrites `reaper-kb.ini` when it exits and
|
|
|
|
|
|
will throw away anything written while it is running.
|
2026-08-14 22:13:59 +02:00
|
|
|
|
2. Run the installer for your platform:
|
|
|
|
|
|
|
|
|
|
|
|
**Windows**
|
2026-08-14 18:13:45 +02:00
|
|
|
|
|
|
|
|
|
|
```powershell
|
|
|
|
|
|
powershell -ExecutionPolicy Bypass -File install.ps1
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-08-14 22:13:59 +02:00
|
|
|
|
**macOS / Linux**
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
chmod +x install.sh # first time only
|
|
|
|
|
|
./install.sh
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
3. Start REAPER, select a MIDI item, press **`Alt+Shift+G`** (`Option+Shift+G`
|
|
|
|
|
|
on a Mac).
|
2026-08-14 18:13:45 +02:00
|
|
|
|
|
|
|
|
|
|
You should hear something like *"Grid mode on, C major, grid 1/16, hold off,
|
|
|
|
|
|
C5, 1.1.00"*.
|
|
|
|
|
|
|
2026-08-14 22:13:59 +02:00
|
|
|
|
To remove it again:
|
2026-08-14 18:13:45 +02:00
|
|
|
|
|
2026-08-14 22:13:59 +02:00
|
|
|
|
```powershell
|
|
|
|
|
|
powershell -ExecutionPolicy Bypass -File install.ps1 -Uninstall # Windows
|
|
|
|
|
|
```
|
|
|
|
|
|
```bash
|
|
|
|
|
|
./install.sh --uninstall # macOS / Linux
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Both installers back up `reaper-kb.ini` to `reaper-kb.ini.midigrid-backup`
|
|
|
|
|
|
before their first change, and are safe to re-run.
|
|
|
|
|
|
|
|
|
|
|
|
### macOS notes
|
|
|
|
|
|
|
|
|
|
|
|
`install.sh` does exactly what `install.ps1` does — same actions, same
|
|
|
|
|
|
bindings — but finds the resource folder at
|
|
|
|
|
|
`~/Library/Application Support/REAPER` (falling back to `~/.config/REAPER` on
|
|
|
|
|
|
Linux). If yours is elsewhere, find it via **Options → Show REAPER resource
|
|
|
|
|
|
path** and pass it:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
./install.sh --resource-path "/path/to/REAPER"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**Modifiers.** In `reaper-kb.ini` the modifier bit that means Ctrl on Windows
|
|
|
|
|
|
means **Command** on macOS; the physical Control key is a different bit. So by
|
|
|
|
|
|
default every `Ctrl+…` binding in the [key table](#keys) is `Command+…` on a
|
|
|
|
|
|
Mac, which is what Mac users expect. To bind the physical Control key instead:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
./install.sh --real-control
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
`Alt` in the key table is the `Option` key on a Mac either way.
|
|
|
|
|
|
|
|
|
|
|
|
### Manual installation
|
|
|
|
|
|
|
|
|
|
|
|
If you would rather not run a script — or you want a different key layout —
|
|
|
|
|
|
you can do the whole thing from REAPER's UI. Nothing about MIDI Grid depends
|
|
|
|
|
|
on the installer; it only writes action registrations and key bindings.
|
|
|
|
|
|
|
|
|
|
|
|
1. **Register the scripts.** In REAPER, open **Actions → Show action list**,
|
|
|
|
|
|
set the section selector to **Main**, and choose **New action → Load
|
|
|
|
|
|
ReaScript…**. Select these two files from wherever you put the repository:
|
|
|
|
|
|
|
|
|
|
|
|
- `MidiGrid_OpenInGrid.lua`
|
|
|
|
|
|
- `MidiGrid_Daemon.lua`
|
|
|
|
|
|
|
|
|
|
|
|
Then switch the section selector to **MIDI Editor** and load the remaining
|
|
|
|
|
|
`MidiGrid_*.lua` files the same way (everything except `midigrid_lib.lua`
|
|
|
|
|
|
and `test_lib.lua`, which are not actions). The section matters: an action
|
|
|
|
|
|
loaded into the wrong section will not be offered for the keys you want.
|
|
|
|
|
|
|
|
|
|
|
|
2. **Bind the keys.** Still in the action list, select an action, click
|
|
|
|
|
|
**Add…** under Shortcuts, and press the key combination. Use the
|
|
|
|
|
|
[key table](#keys) below for the intended layout; you can of course choose
|
|
|
|
|
|
your own. Only `Alt+Shift+G` needs to be in the Main section — everything
|
|
|
|
|
|
else belongs in MIDI Editor.
|
|
|
|
|
|
|
|
|
|
|
|
3. **Say yes when REAPER warns about a key that is already bound.** Grid mode
|
|
|
|
|
|
deliberately takes over `Left`, `Right`, `Up`, `Down` and `Enter` in the
|
|
|
|
|
|
MIDI editor; each of those forwards to its previous behaviour when grid
|
|
|
|
|
|
mode is off, so nothing is lost.
|
|
|
|
|
|
|
|
|
|
|
|
You do not need to bind everything. The minimum useful set is
|
|
|
|
|
|
`Alt+Shift+G` (open), `Alt+G` (toggle), the four arrows, and `Enter`.
|
|
|
|
|
|
`MidiGrid_Daemon.lua` needs to be **registered** as an action but never needs
|
|
|
|
|
|
a key — it auto-starts.
|
2026-08-14 18:13:45 +02:00
|
|
|
|
|
|
|
|
|
|
### Why not "Import key map"?
|
|
|
|
|
|
|
|
|
|
|
|
`MidiGrid.ReaperKeyMap` is included for reference, but **importing it does not
|
|
|
|
|
|
reliably work**, and the way it fails is silent and confusing: REAPER accepts
|
|
|
|
|
|
the file, registers the script actions, and then converts every key binding it
|
|
|
|
|
|
cannot resolve into a `No-op (no action)` entry. The keys appear bound in the
|
|
|
|
|
|
action list but do nothing.
|
|
|
|
|
|
|
|
|
|
|
|
The cause is that a `KEY` line must reference the *named command* — the `SCR`
|
|
|
|
|
|
id with a **leading underscore** (`_RSmidigrid_open`), not the bare id
|
2026-08-14 22:13:59 +02:00
|
|
|
|
(`RSmidigrid_open`). Both installers write the underscored form directly.
|
2026-08-14 18:13:45 +02:00
|
|
|
|
|
2026-08-14 22:13:59 +02:00
|
|
|
|
If you would rather not run a script, see [Manual installation](#manual-installation)
|
|
|
|
|
|
above. Binding by hand in the action list always works.
|
2026-08-14 18:13:45 +02:00
|
|
|
|
|
|
|
|
|
|
## Keys
|
|
|
|
|
|
|
|
|
|
|
|
**Main section**
|
|
|
|
|
|
|
|
|
|
|
|
| Key | Action |
|
|
|
|
|
|
|---|---|
|
|
|
|
|
|
| `Alt+Shift+G` | Open selected item in grid mode |
|
|
|
|
|
|
|
|
|
|
|
|
**MIDI editor section**
|
|
|
|
|
|
|
|
|
|
|
|
| Key | Grid mode ON | Grid mode OFF (passes through to) |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| `Left` / `Right` | Cursor back / forward one grid cell | OSARA previous / next chord |
|
|
|
|
|
|
| `Up` / `Down` | Pitch up / down one scale degree | OSARA higher / lower note in chord |
|
2026-08-24 01:15:57 +02:00
|
|
|
|
| `Ctrl+Alt+Up` / `Ctrl+Alt+Down` | Pitch up / down one **octave** | OSARA higher / lower note in chord |
|
|
|
|
|
|
| `Alt+Up` / `Alt+Down` | Jump to the next note above / below **in this cell** | OSARA higher / lower note in chord |
|
|
|
|
|
|
| `Alt+Left` / `Alt+Right` | Jump to the previous / next note **at this pitch** | OSARA previous / next chord |
|
|
|
|
|
|
| `Ctrl+Alt+Left` / `Ctrl+Alt+Right` | Jump to the previous / next note **anywhere** | OSARA previous / next chord |
|
2026-08-14 18:13:45 +02:00
|
|
|
|
| `Enter` | Toggle note at cursor cell | Edit: Event properties |
|
2026-08-14 18:46:33 +02:00
|
|
|
|
| `Ctrl+Shift+Left` / `Ctrl+Shift+Right` | Shift **all** notes one cell, refusing if any would leave the item | — |
|
|
|
|
|
|
| `Ctrl+Alt+Shift+Left` / `Ctrl+Alt+Shift+Right` | Same, but shift anyway | — |
|
2026-08-14 18:27:14 +02:00
|
|
|
|
| `L` / `Shift+L` | Lengthen / shorten note by one cell | Edit: Lengthen / shorten notes one grid unit |
|
|
|
|
|
|
| `Alt+J` | Join a run of repeated notes into one | — |
|
2026-08-14 18:18:12 +02:00
|
|
|
|
| `Alt+3` | Toggle diatonic triad on cursor pitch | — |
|
|
|
|
|
|
| `Alt+7` | Toggle diatonic seventh chord | — |
|
|
|
|
|
|
| `Alt+Shift+Up` / `Alt+Shift+Down` | Velocity up / down | — |
|
2026-08-14 18:13:45 +02:00
|
|
|
|
| `Alt+G` | **Toggle grid mode on/off** | — |
|
|
|
|
|
|
| `Alt+H` | Toggle hold mode | — |
|
2026-08-14 20:04:20 +02:00
|
|
|
|
| `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 |
|
2026-08-14 20:43:24 +02:00
|
|
|
|
| `Ctrl+Alt+Shift+Space` | Loop the current bar (exact) | works in both modes |
|
2026-08-14 18:13:45 +02:00
|
|
|
|
| `Alt+A` | Repeat current cell (speak + sound) | — |
|
|
|
|
|
|
| `Alt+S` | Set root and scale | — |
|
|
|
|
|
|
| `1`–`9` | Grid size — REAPER's own grid actions | same |
|
|
|
|
|
|
|
|
|
|
|
|
`Alt+Shift+G` **opens** grid mode; it does not close it. Use `Alt+G` inside
|
|
|
|
|
|
the editor to toggle back off.
|
|
|
|
|
|
|
|
|
|
|
|
## How it integrates
|
|
|
|
|
|
|
|
|
|
|
|
- **Grid size is REAPER's grid.** Read via `MIDI_GetGrid()`, so whatever you
|
|
|
|
|
|
already have bound to the MIDI editor's grid actions (by default `1` = 1/1,
|
|
|
|
|
|
`2` = 1/2, `4` = 1/4, `6` = 1/16, `8` = 1/8, `3` = 1/32) drives this too.
|
|
|
|
|
|
Nothing new to learn.
|
|
|
|
|
|
- **The cursor is REAPER's cursor.** Time is the native edit cursor; pitch is
|
|
|
|
|
|
the MIDI editor's `active_note_row`. No private cursor state exists, so grid
|
|
|
|
|
|
mode and OSARA cannot drift out of sync — you can switch between them
|
|
|
|
|
|
mid-phrase.
|
2026-08-24 01:21:21 +02:00
|
|
|
|
- **Opening keeps your place.** `Alt+Shift+G` leaves the edit cursor where it
|
|
|
|
|
|
already was, rounded to the nearest grid line, instead of dropping it back to
|
|
|
|
|
|
the start of the item — so you can listen to a bar, hit it, and start writing
|
|
|
|
|
|
right there. A cursor outside the item lands on the item's first or last cell.
|
2026-08-14 18:13:45 +02:00
|
|
|
|
- **Turning grid mode off restores everything.** Each bound key forwards to
|
|
|
|
|
|
what it did before, so nothing is permanently taken away.
|
|
|
|
|
|
|
|
|
|
|
|
## What gets spoken
|
|
|
|
|
|
|
|
|
|
|
|
- **Left / right** — cell position and everything sounding in it:
|
|
|
|
|
|
`"3.2.00, C5, E5"`, and those notes are sounded together.
|
|
|
|
|
|
- **Up / down** — the note name, plus `"on"` if this cell already holds that
|
|
|
|
|
|
pitch: `"E5, on"`.
|
|
|
|
|
|
- **Enter** — `"E5 on, added"` or `"E5 off, removed"`.
|
|
|
|
|
|
|
2026-08-24 01:15:57 +02:00
|
|
|
|
## Octaves
|
|
|
|
|
|
|
|
|
|
|
|
`Ctrl+Alt+Up` and `Ctrl+Alt+Down` move a whole octave, keeping the pitch class.
|
|
|
|
|
|
|
|
|
|
|
|
This needs no scale awareness, because an octave is transpositionally neutral:
|
|
|
|
|
|
a scale tone stays a scale tone, and a note you deliberately put outside the
|
|
|
|
|
|
key stays outside it. It is also the quick way to cover distance — seven
|
|
|
|
|
|
presses of `Up` to cross an octave in a major scale, one press of
|
|
|
|
|
|
`Ctrl+Alt+Up`.
|
|
|
|
|
|
|
|
|
|
|
|
At either end of the MIDI range it says *"top"* or *"bottom"* and stays put,
|
|
|
|
|
|
rather than clamping to a partial jump.
|
|
|
|
|
|
|
|
|
|
|
|
## 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 jump straight to a note that is really there, one
|
|
|
|
|
|
pair for each axis you might be looking along.
|
|
|
|
|
|
|
|
|
|
|
|
- **`Alt+Up` / `Alt+Down` — the column.** The next note above or below *in the
|
|
|
|
|
|
cell you are already on*; time does not move. This is how you walk a chord
|
|
|
|
|
|
you have stacked up — it stops only where notes are, so you never count
|
|
|
|
|
|
scale degrees through the gaps. Speaks the note, as `Up` and `Down` do:
|
|
|
|
|
|
*"E5, on"*.
|
|
|
|
|
|
- **`Alt+Left` / `Alt+Right` — the row.** The next note *at the pitch you are
|
|
|
|
|
|
on*, backwards or forwards in time; the pitch cursor stays put. Use it to
|
|
|
|
|
|
follow one line — a bass note, a hi-hat — through a bar with plenty else
|
|
|
|
|
|
going on. Speaks the cell it landed in, as `Left` and `Right` do:
|
|
|
|
|
|
*"2.1.00, C5"*.
|
|
|
|
|
|
- **`Ctrl+Alt+Left` / `Ctrl+Alt+Right` — anywhere.** The next note in the take
|
|
|
|
|
|
whatever its pitch; both cursors move. This is the one for crossing empty
|
|
|
|
|
|
space. When it lands in a chord it takes whichever note is nearest the pitch
|
|
|
|
|
|
you came from, rather than always the bottom one, and names it when the cell
|
|
|
|
|
|
holds more than one: *"1.1.00, C5, E5, G5, cursor G5"*.
|
|
|
|
|
|
|
|
|
|
|
|
Two rules they all share.
|
|
|
|
|
|
|
|
|
|
|
|
**Muted notes are invisible to them.** A muted note makes no sound, so stopping
|
|
|
|
|
|
on one and announcing it would be a lie.
|
|
|
|
|
|
|
|
|
|
|
|
**A sustained note is one stop, not one per cell it covers.** The row and
|
|
|
|
|
|
any-note jumps anchor on note *starts*, so a note held across eight cells is a
|
|
|
|
|
|
single destination. Jumping backwards from inside a held note therefore takes
|
|
|
|
|
|
you to its beginning, which is usually where you wanted to go.
|
|
|
|
|
|
|
|
|
|
|
|
When there is nothing that way they say so and stay where they are — *"No note
|
|
|
|
|
|
above in this cell"*, *"No later E5"*, *"No later notes"* — so a key that does
|
|
|
|
|
|
not move you still tells you why.
|
|
|
|
|
|
|
|
|
|
|
|
These four and the octave keys sit on `Ctrl+Alt` rather than plain `Ctrl`
|
|
|
|
|
|
because REAPER already uses `Ctrl`+arrows to move between items, which is
|
|
|
|
|
|
worth keeping.
|
|
|
|
|
|
|
2026-08-14 18:13:45 +02:00
|
|
|
|
## Hold mode
|
|
|
|
|
|
|
|
|
|
|
|
With hold **off**, toggling adjacent cells at the same pitch gives separate
|
|
|
|
|
|
repeated notes.
|
|
|
|
|
|
|
|
|
|
|
|
With hold **on**, a new note touching an existing note of the same pitch joins
|
|
|
|
|
|
it into one sustained note. Toggling a cell off in the middle of a sustained
|
|
|
|
|
|
run splits it in two; at either end, it shortens it. So you can draw a long
|
|
|
|
|
|
note by arrowing right and pressing Enter across several cells.
|
|
|
|
|
|
|
2026-08-14 18:27:14 +02:00
|
|
|
|
Hold mode governs **entry only** — it never retroactively merges notes that
|
|
|
|
|
|
are already there. To sustain what you have already written, use the length
|
|
|
|
|
|
keys below.
|
|
|
|
|
|
|
|
|
|
|
|
## Note length — sustaining notes you already entered
|
|
|
|
|
|
|
|
|
|
|
|
- **`L`** — lengthen the note under the cursor by one grid cell.
|
|
|
|
|
|
- **`Shift+L`** — shorten it by one cell. It never shrinks below a single
|
|
|
|
|
|
cell; it says *"minimum"* rather than deleting the note.
|
|
|
|
|
|
- **`Alt+J`** — fuse an entire run of repeated notes at this pitch into one
|
|
|
|
|
|
sustained note, in a single keystroke.
|
|
|
|
|
|
|
|
|
|
|
|
Lengthening **swallows** any same-pitch note it runs into, taking that note's
|
|
|
|
|
|
tail if it reached further. So pressing `L` across a row of repeated notes
|
|
|
|
|
|
fuses them rather than producing overlaps.
|
|
|
|
|
|
|
|
|
|
|
|
Each press reports the resulting length — *"E5, 3 cells"*, or *"E5, 2 cells,
|
|
|
|
|
|
joined 1"* when it absorbed something.
|
|
|
|
|
|
|
|
|
|
|
|
### Why not re-toggle with hold on instead?
|
|
|
|
|
|
|
|
|
|
|
|
Turning hold on and re-entering existing notes does work. But toggling a note
|
|
|
|
|
|
off and on **deletes and recreates it, resetting its velocity to the default**
|
|
|
|
|
|
— if you have shaped dynamics, that quietly discards them.
|
|
|
|
|
|
|
|
|
|
|
|
The length keys edit the note in place, so velocity, channel and note identity
|
|
|
|
|
|
survive. They also need no mode: `L` behaves identically whether hold is on or
|
|
|
|
|
|
off, so there is no state to set up beforehand and none to remember to turn
|
|
|
|
|
|
off afterwards.
|
|
|
|
|
|
|
2026-08-14 19:57:55 +02:00
|
|
|
|
## Hearing it in context
|
|
|
|
|
|
|
2026-08-14 20:04:20 +02:00
|
|
|
|
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`.
|
2026-08-14 20:43:24 +02:00
|
|
|
|
- **`Ctrl+Alt+Space`** — plays that bar **only**, stopping just before 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.
|
|
|
|
|
|
- **`Ctrl+Alt+Shift+Space`** — **loops** that bar, exactly. Repeats until you
|
|
|
|
|
|
press `Space`.
|
|
|
|
|
|
|
|
|
|
|
|
### Which of the two bar actions to use
|
|
|
|
|
|
|
|
|
|
|
|
`Ctrl+Alt+Space` stops by polling, and a polling loop can only be as precise as
|
|
|
|
|
|
its own interval — about 33 ms at frame rate. It therefore stops a little
|
|
|
|
|
|
early, and notes falling inside that window do not sound. In practice:
|
|
|
|
|
|
|
|
|
|
|
|
| Tempo | 16th note | 32nd note | 35 ms lead is |
|
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
| 120 BPM | 125 ms | 62 ms | 0.28 of a 16th, 0.56 of a 32nd |
|
|
|
|
|
|
| 180 BPM | 83 ms | 42 ms | 0.42 of a 16th, 0.84 of a 32nd |
|
|
|
|
|
|
| 240 BPM | 62 ms | 31 ms | 0.56 of a 16th, 1.12 of a 32nd |
|
|
|
|
|
|
|
|
|
|
|
|
So it is safe for 16ths at any sane tempo, and safe for 32nds up to about
|
|
|
|
|
|
214 BPM. Past that, the final 32nd of the bar is inside the lead and will not
|
|
|
|
|
|
be heard.
|
|
|
|
|
|
|
|
|
|
|
|
`Ctrl+Alt+Shift+Space` has no such limit. It hands the bar boundary to REAPER's
|
|
|
|
|
|
audio engine through the loop points, which is sample accurate, so the next
|
|
|
|
|
|
bar's downbeat can never leak in at any tempo or grid size. Use it when you are
|
|
|
|
|
|
working at 32nds or faster, or whenever you want certainty. Looping also suits
|
|
|
|
|
|
the job well — leave it running and edit while it plays.
|
|
|
|
|
|
|
|
|
|
|
|
It borrows the project's loop range and repeat state, and the daemon restores
|
|
|
|
|
|
both once transport stops, including when you stop with `Space`.
|
2026-08-14 19:57:55 +02:00
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
the whole question, and it cannot be judged from a note in isolation.
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
2026-08-14 20:04:20 +02:00
|
|
|
|
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.
|
2026-08-14 19:57:55 +02:00
|
|
|
|
|
2026-08-14 20:43:24 +02:00
|
|
|
|
It stops **35 ms before** the bar line rather than on it, or a note sitting
|
|
|
|
|
|
exactly on the next bar line would sound. The test uses `GetPlayPosition2`,
|
|
|
|
|
|
which reports the block REAPER is about to render rather than what you are
|
|
|
|
|
|
currently hearing — that removes render-ahead from the equation, so the lead
|
|
|
|
|
|
only has to cover the daemon's own polling interval. (Testing against
|
|
|
|
|
|
`GetPlayPosition` instead needed 60 ms to cover both, which was enough to
|
|
|
|
|
|
swallow a whole 32nd note from 125 BPM upward.)
|
2026-08-14 20:39:01 +02:00
|
|
|
|
|
2026-08-14 20:43:24 +02:00
|
|
|
|
`stoplead` in `ExtState` tunes it. Raising it is the fix if a next-bar note
|
|
|
|
|
|
still sneaks through; lowering it recovers a little more of the bar's tail at
|
|
|
|
|
|
the risk of leaks.
|
2026-08-14 20:39:01 +02:00
|
|
|
|
|
2026-08-14 18:46:33 +02:00
|
|
|
|
## Shifting notes in time
|
|
|
|
|
|
|
|
|
|
|
|
If a whole clip landed a couple of cells off the beat, `Ctrl+Shift+Left` and
|
|
|
|
|
|
`Ctrl+Shift+Right` move **every note in the take** by one grid cell.
|
|
|
|
|
|
|
|
|
|
|
|
Because the entire take moves together, notes can never collide with each
|
|
|
|
|
|
other. What is at stake is the **item boundary**: notes pushed past either end
|
|
|
|
|
|
stop sounding. They are *not* deleted — shifting back the other way brings
|
|
|
|
|
|
them straight back — but a clip that has quietly lost its first bar is not
|
|
|
|
|
|
something you want to discover two hours later.
|
|
|
|
|
|
|
|
|
|
|
|
So the plain keys **refuse** the shift if any note would end up outside, and
|
|
|
|
|
|
say how many: *"Not shifted left, 3 notes would fall outside the item. Add Alt
|
|
|
|
|
|
to force."*
|
|
|
|
|
|
|
|
|
|
|
|
Adding **Alt** (`Ctrl+Alt+Shift+Left` / `Right`) does it anyway, reporting the
|
|
|
|
|
|
consequence: *"Shifted left, 3 notes now outside the item and silent."*
|
|
|
|
|
|
|
|
|
|
|
|
Positions are converted through quarter-notes rather than offset in raw ticks,
|
|
|
|
|
|
so a shift stays musically correct across tempo changes.
|
|
|
|
|
|
|
2026-08-14 18:18:12 +02:00
|
|
|
|
## Chords
|
|
|
|
|
|
|
|
|
|
|
|
`Alt+3` stamps a **diatonic triad** on the cursor pitch; `Alt+7` a seventh
|
|
|
|
|
|
chord. Both are built by stacking scale thirds, so the quality follows the
|
|
|
|
|
|
degree automatically — in C major, `Alt+3` on C gives C major, on D gives D
|
|
|
|
|
|
minor, on B gives B diminished. There is no chord-quality table to configure
|
|
|
|
|
|
and no way to land on a chord outside the key.
|
|
|
|
|
|
|
|
|
|
|
|
If every tone of the chord is already in the cell, the keystroke removes them
|
|
|
|
|
|
all; otherwise it fills in whatever is missing. Chord tones obey hold mode,
|
|
|
|
|
|
splitting and trimming exactly like single notes.
|
|
|
|
|
|
|
|
|
|
|
|
You can also build chords by hand at any time — arrow to a pitch, press Enter,
|
|
|
|
|
|
arrow to another, press Enter. The cursor never moves horizontally when you
|
|
|
|
|
|
toggle, so stacking notes in one cell has always worked. `Alt+3` and `Alt+7`
|
|
|
|
|
|
are just the one-keystroke version.
|
|
|
|
|
|
|
|
|
|
|
|
In the chromatic scale, "stacking thirds" degenerates into stacked whole
|
|
|
|
|
|
tones. Predictable, but not musically useful — pick a real scale first.
|
|
|
|
|
|
|
|
|
|
|
|
## Velocity
|
|
|
|
|
|
|
|
|
|
|
|
`Alt+Shift+Up` and `Alt+Shift+Down` change velocity in steps of 8.
|
|
|
|
|
|
|
|
|
|
|
|
The key does one of two things depending on where you are, and says which:
|
|
|
|
|
|
|
|
|
|
|
|
- **On a note** — changes that note's velocity: *"E5 velocity 104"*, and
|
|
|
|
|
|
replays it at the new level so you can hear the change.
|
|
|
|
|
|
- **On an empty cell** — changes the default for newly inserted notes:
|
|
|
|
|
|
*"Default velocity 104"*.
|
|
|
|
|
|
|
|
|
|
|
|
So you can set a level and then draw at it, or draw first and shape the
|
|
|
|
|
|
dynamics afterwards, without a mode switch either way.
|
|
|
|
|
|
|
|
|
|
|
|
Set `velstep` in `ExtState` if steps of 8 are too coarse or too fine.
|
|
|
|
|
|
|
2026-08-14 18:13:45 +02:00
|
|
|
|
## Scales
|
|
|
|
|
|
|
|
|
|
|
|
`Alt+S` prompts for a root (`C`, `F#`, `Bb`…) and a scale number. Up and down
|
|
|
|
|
|
then move by scale degree, which is what makes entry fast — you cannot land on
|
|
|
|
|
|
a wrong note by accident. Pick `chromatic` for plain semitone movement.
|
|
|
|
|
|
|
|
|
|
|
|
If the cursor sits on a note outside the current scale, up/down move to the
|
|
|
|
|
|
nearest scale tone in that direction rather than getting stuck.
|
|
|
|
|
|
|
|
|
|
|
|
## Architecture notes
|
|
|
|
|
|
|
|
|
|
|
|
**The action scripts never call `reaper.defer`.** This matters. If a script is
|
|
|
|
|
|
still alive when you press its key again, REAPER interrupts you with a "script
|
|
|
|
|
|
is already running — terminate or launch new instance?" prompt, which makes
|
|
|
|
|
|
fast key repeat impossible and, if you answer "terminate" and tick remember,
|
|
|
|
|
|
permanently costs you every second keypress.
|
|
|
|
|
|
|
|
|
|
|
|
So preview note timing lives in `MidiGrid_Daemon.lua`, a single background loop
|
|
|
|
|
|
that auto-starts on first use and relaunches itself if it ever dies. Action
|
|
|
|
|
|
scripts post a request to transient `ExtState` and exit within a millisecond. A
|
|
|
|
|
|
new request cuts the previous note, so fast arrowing retriggers cleanly. The
|
|
|
|
|
|
daemon releases everything on `atexit`, so notes cannot hang.
|
|
|
|
|
|
|
|
|
|
|
|
Preview requests use **non-persisted** `ExtState` — persisting them would write
|
|
|
|
|
|
to `reaper.ini` on every keypress.
|
|
|
|
|
|
|
2026-08-24 00:55:25 +02:00
|
|
|
|
**Preview routing.** Preview notes go out through `StuffMIDIMessage`, i.e.
|
|
|
|
|
|
REAPER's virtual MIDI keyboard, which is only heard by tracks that are armed
|
|
|
|
|
|
and monitoring — normally not the track you are editing, so notes would sound
|
|
|
|
|
|
through whatever unrelated instrument happened to be armed. Grid mode therefore
|
|
|
|
|
|
borrows the record routing: on entering grid mode, and again whenever the
|
|
|
|
|
|
editor moves to an item on a different track, the edited take's track is armed
|
|
|
|
|
|
and monitoring with MIDI input, and every other armed track is stood down so it
|
|
|
|
|
|
cannot answer as well. The previous arm, monitor and input setting of every
|
|
|
|
|
|
track touched is remembered and handed straight back when grid mode is switched
|
|
|
|
|
|
off, when the MIDI editor closes, or when REAPER exits — the daemon does the
|
|
|
|
|
|
restoring, in the same way it restores the loop and repeat state the loop-bar
|
|
|
|
|
|
action borrows.
|
|
|
|
|
|
|
2026-08-14 18:13:45 +02:00
|
|
|
|
## Tunables
|
|
|
|
|
|
|
|
|
|
|
|
In `ExtState` section `midigrid`:
|
|
|
|
|
|
|
|
|
|
|
|
| Key | Default | Meaning |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| `vel` | 96 | Velocity of inserted notes |
|
2026-08-14 18:18:12 +02:00
|
|
|
|
| `velstep` | 8 | Velocity change per keypress |
|
2026-08-14 20:43:24 +02:00
|
|
|
|
| `stoplead` | 0.035 | How early to stop, in seconds, when playing one bar |
|
2026-08-14 18:13:45 +02:00
|
|
|
|
| `chan` | 0 | MIDI channel (0-based) |
|
|
|
|
|
|
| `previewdur` | 0.4 | Preview note length, seconds |
|
|
|
|
|
|
|
|
|
|
|
|
## Tests
|
|
|
|
|
|
|
|
|
|
|
|
`test_lib.lua` covers the pure logic — scale stepping, note naming, grid
|
|
|
|
|
|
labels — against a stubbed `reaper` table, so it runs outside REAPER:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
lua test_lib.lua
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Everything touching MIDI data or the REAPER API is only exercised in REAPER
|
|
|
|
|
|
itself.
|
|
|
|
|
|
|
|
|
|
|
|
## Limitations
|
|
|
|
|
|
|
2026-08-24 00:55:25 +02:00
|
|
|
|
- While grid mode is on, the edited track is held record-armed and monitoring
|
|
|
|
|
|
with MIDI input so that preview can reach its instrument (see *Preview
|
|
|
|
|
|
routing* above), and any other armed track is stood down. Both are handed
|
|
|
|
|
|
back when grid mode ends, but a REAPER crash mid-session would lose that
|
|
|
|
|
|
arm state.
|
2026-08-14 18:13:45 +02:00
|
|
|
|
- The grid aligns to the project timeline (bar lines), not the item start. For
|
|
|
|
|
|
an item starting off-grid, the first cell will be partial.
|
|
|
|
|
|
- Note octave naming follows the `midioctoffs` preference; with the default,
|
|
|
|
|
|
note 60 reads as C5.
|