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.
This commit is contained in:
+106
@@ -0,0 +1,106 @@
|
||||
<#
|
||||
MIDI Grid installer.
|
||||
|
||||
Registers the scripts as Reaper actions and binds them, by editing
|
||||
reaper-kb.ini directly. This is done rather than via "Import key map"
|
||||
because Reaper's importer silently converts unresolved script bindings
|
||||
into "No-op (no action)" entries -- see README, Installation notes.
|
||||
|
||||
REAPER MUST BE CLOSED. It rewrites reaper-kb.ini on exit and will
|
||||
discard anything written while it is running.
|
||||
|
||||
Usage: powershell -ExecutionPolicy Bypass -File install.ps1
|
||||
powershell -ExecutionPolicy Bypass -File install.ps1 -Uninstall
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string] $ScriptDir = $PSScriptRoot,
|
||||
[string] $ResourcePath = "$env:APPDATA\REAPER",
|
||||
[switch] $Uninstall
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$kb = Join-Path $ResourcePath 'reaper-kb.ini'
|
||||
if (-not (Test-Path $kb)) { throw "reaper-kb.ini not found at $kb" }
|
||||
|
||||
if (Get-Process reaper* -ErrorAction SilentlyContinue) {
|
||||
throw "REAPER is running. Close it completely and run this again."
|
||||
}
|
||||
|
||||
# id, section, description, script filename
|
||||
$actions = @(
|
||||
@('RSmidigrid_open', 0, 'MIDI Grid: Open selected item in grid mode', 'MidiGrid_OpenInGrid.lua'),
|
||||
@('RSmidigrid_daemon', 0, 'MIDI Grid: Preview daemon (auto-started)', 'MidiGrid_Daemon.lua'),
|
||||
@('RSmidigrid_mode', 32060, 'MIDI Grid: Toggle grid mode', 'MidiGrid_ToggleMode.lua'),
|
||||
@('RSmidigrid_left', 32060, 'MIDI Grid: Cursor left one cell', 'MidiGrid_Left.lua'),
|
||||
@('RSmidigrid_right', 32060, 'MIDI Grid: Cursor right one cell', 'MidiGrid_Right.lua'),
|
||||
@('RSmidigrid_up', 32060, 'MIDI Grid: Pitch up one scale degree', 'MidiGrid_Up.lua'),
|
||||
@('RSmidigrid_down', 32060, 'MIDI Grid: Pitch down one scale degree', 'MidiGrid_Down.lua'),
|
||||
@('RSmidigrid_cell', 32060, 'MIDI Grid: Toggle note at cursor cell', 'MidiGrid_ToggleCell.lua'),
|
||||
@('RSmidigrid_hold', 32060, 'MIDI Grid: Toggle hold mode', 'MidiGrid_ToggleHold.lua'),
|
||||
@('RSmidigrid_audit', 32060, 'MIDI Grid: Repeat current cell', 'MidiGrid_AuditionCell.lua'),
|
||||
@('RSmidigrid_scale', 32060, 'MIDI Grid: Set root and scale', 'MidiGrid_SetScale.lua')
|
||||
)
|
||||
|
||||
# flags, keycode, section, id
|
||||
# flags: 1 base, +4 Shift, +8 Ctrl, +16 Alt
|
||||
# section: 0 = Main, 32060 = MIDI Editor
|
||||
$keys = @(
|
||||
@(21, 71, 0, 'RSmidigrid_open'),
|
||||
@(17, 71, 32060, 'RSmidigrid_mode'),
|
||||
@(1, 32805, 32060, 'RSmidigrid_left'),
|
||||
@(1, 32807, 32060, 'RSmidigrid_right'),
|
||||
@(1, 32806, 32060, 'RSmidigrid_up'),
|
||||
@(1, 32808, 32060, 'RSmidigrid_down'),
|
||||
@(1, 13, 32060, 'RSmidigrid_cell'),
|
||||
@(17, 72, 32060, 'RSmidigrid_hold'),
|
||||
@(17, 65, 32060, 'RSmidigrid_audit'),
|
||||
@(17, 83, 32060, 'RSmidigrid_scale')
|
||||
)
|
||||
|
||||
$backup = "$kb.midigrid-backup"
|
||||
if (-not (Test-Path $backup)) {
|
||||
Copy-Item $kb $backup
|
||||
Write-Host "Backed up original to $backup"
|
||||
}
|
||||
|
||||
$lines = [System.Collections.Generic.List[string]]::new()
|
||||
$lines.AddRange([System.IO.File]::ReadAllLines($kb))
|
||||
|
||||
# Drop any previous MIDI Grid lines so re-running is idempotent.
|
||||
for ($i = $lines.Count - 1; $i -ge 0; $i--) {
|
||||
if ($lines[$i] -match '(?i)RSmidigrid') { $lines.RemoveAt($i) }
|
||||
}
|
||||
|
||||
if ($Uninstall) {
|
||||
[System.IO.File]::WriteAllLines($kb, $lines, (New-Object System.Text.UTF8Encoding($false)))
|
||||
Write-Host "Removed MIDI Grid actions and bindings."
|
||||
Write-Host "Note: keys it had taken over (arrows, Enter) are now unbound in the"
|
||||
Write-Host "MIDI editor. Rebind them, or restore $backup."
|
||||
return
|
||||
}
|
||||
|
||||
foreach ($a in $actions) {
|
||||
$path = Join-Path $ScriptDir $a[3]
|
||||
if (-not (Test-Path $path)) { throw "Missing script: $path" }
|
||||
$lines.Insert(0, ('SCR 4 {0} {1} "{2}" {3}' -f $a[1], $a[0], $a[2], $path))
|
||||
}
|
||||
|
||||
# KEY lines reference the *named command*, which is the SCR id with a
|
||||
# leading underscore. Without it Reaper cannot resolve the binding and the
|
||||
# key silently does nothing.
|
||||
foreach ($k in $keys) {
|
||||
$new = 'KEY {0} {1} _{3} {2}' -f $k[0], $k[1], $k[2], $k[3]
|
||||
$pat = '^KEY {0} {1} \S+ {2}(\s|$)' -f $k[0], $k[1], $k[2]
|
||||
$found = $false
|
||||
for ($i = 0; $i -lt $lines.Count; $i++) {
|
||||
if ($lines[$i] -match $pat) { $lines[$i] = $new; $found = $true; break }
|
||||
}
|
||||
if (-not $found) { $lines.Add($new) }
|
||||
}
|
||||
|
||||
[System.IO.File]::WriteAllLines($kb, $lines, (New-Object System.Text.UTF8Encoding($false)))
|
||||
|
||||
Write-Host "Installed $($actions.Count) actions and $($keys.Count) key bindings."
|
||||
Write-Host "Start REAPER, select a MIDI item, and press Alt+Shift+G."
|
||||
Reference in New Issue
Block a user