Files
midigrid/install.ps1
T
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

159 lines
8.1 KiB
PowerShell

<#
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,
[string] $ResourcePath = "$env:APPDATA\REAPER",
[switch] $Uninstall
)
$ErrorActionPreference = 'Stop'
# $PSScriptRoot is not reliably populated during parameter binding, so
# resolve the script's own directory here instead of in the param block.
if (-not $ScriptDir) {
$ScriptDir = if ($PSScriptRoot) { $PSScriptRoot }
else { Split-Path -Parent $MyInvocation.MyCommand.Definition }
}
if (-not $ScriptDir) { throw "Could not determine script directory; pass -ScriptDir." }
$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_octup', 32060, 'MIDI Grid: Pitch up one octave', 'MidiGrid_OctaveUp.lua'),
@('RSmidigrid_octdn', 32060, 'MIDI Grid: Pitch down one octave', 'MidiGrid_OctaveDown.lua'),
@('RSmidigrid_colup', 32060, 'MIDI Grid: Next note above in this cell', 'MidiGrid_ColumnUp.lua'),
@('RSmidigrid_coldn', 32060, 'MIDI Grid: Next note below in this cell', 'MidiGrid_ColumnDown.lua'),
@('RSmidigrid_rownext',32060, 'MIDI Grid: Next note at this pitch', 'MidiGrid_RowNext.lua'),
@('RSmidigrid_rowprev',32060, 'MIDI Grid: Previous note at this pitch', 'MidiGrid_RowPrev.lua'),
@('RSmidigrid_notenext',32060,'MIDI Grid: Next note anywhere', 'MidiGrid_NoteNext.lua'),
@('RSmidigrid_noteprev',32060,'MIDI Grid: Previous note anywhere', 'MidiGrid_NotePrev.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'),
@('RSmidigrid_triad', 32060, 'MIDI Grid: Toggle diatonic triad', 'MidiGrid_ChordTriad.lua'),
@('RSmidigrid_seventh',32060, 'MIDI Grid: Toggle diatonic seventh chord', 'MidiGrid_ChordSeventh.lua'),
@('RSmidigrid_velup', 32060, 'MIDI Grid: Velocity up', 'MidiGrid_VelUp.lua'),
@('RSmidigrid_veldn', 32060, 'MIDI Grid: Velocity down', 'MidiGrid_VelDown.lua'),
@('RSmidigrid_longer', 32060, 'MIDI Grid: Lengthen note one cell', 'MidiGrid_Lengthen.lua'),
@('RSmidigrid_shorter',32060, 'MIDI Grid: Shorten note one cell', 'MidiGrid_Shorten.lua'),
@('RSmidigrid_join', 32060, 'MIDI Grid: Join run of repeated notes', 'MidiGrid_Join.lua'),
@('RSmidigrid_shl', 32060, 'MIDI Grid: Shift all notes one cell left', 'MidiGrid_ShiftLeft.lua'),
@('RSmidigrid_shr', 32060, 'MIDI Grid: Shift all notes one cell right', 'MidiGrid_ShiftRight.lua'),
@('RSmidigrid_shlf', 32060, 'MIDI Grid: Shift all notes left (force)', 'MidiGrid_ShiftLeftForce.lua'),
@('RSmidigrid_shrf', 32060, 'MIDI Grid: Shift all notes right (force)', 'MidiGrid_ShiftRightForce.lua'),
@('RSmidigrid_playbar',32060, 'MIDI Grid: Play from start of current bar', 'MidiGrid_PlayFromBar.lua'),
@('RSmidigrid_playone',32060, 'MIDI Grid: Play current bar only', 'MidiGrid_PlayBarOnly.lua'),
@('RSmidigrid_loopbar',32060, 'MIDI Grid: Loop current bar', 'MidiGrid_LoopBar.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'),
@(25, 32806, 32060, 'RSmidigrid_octup'),
@(25, 32808, 32060, 'RSmidigrid_octdn'),
@(17, 32806, 32060, 'RSmidigrid_colup'),
@(17, 32808, 32060, 'RSmidigrid_coldn'),
@(17, 32807, 32060, 'RSmidigrid_rownext'),
@(17, 32805, 32060, 'RSmidigrid_rowprev'),
@(25, 32807, 32060, 'RSmidigrid_notenext'),
@(25, 32805, 32060, 'RSmidigrid_noteprev'),
@(1, 13, 32060, 'RSmidigrid_cell'),
@(17, 72, 32060, 'RSmidigrid_hold'),
@(17, 65, 32060, 'RSmidigrid_audit'),
@(17, 83, 32060, 'RSmidigrid_scale'),
@(17, 51, 32060, 'RSmidigrid_triad'),
@(17, 55, 32060, 'RSmidigrid_seventh'),
@(21, 32806, 32060, 'RSmidigrid_velup'),
@(21, 32808, 32060, 'RSmidigrid_veldn'),
@(1, 76, 32060, 'RSmidigrid_longer'),
@(5, 76, 32060, 'RSmidigrid_shorter'),
@(17, 74, 32060, 'RSmidigrid_join'),
@(13, 32805, 32060, 'RSmidigrid_shl'),
@(13, 32807, 32060, 'RSmidigrid_shr'),
@(29, 32805, 32060, 'RSmidigrid_shlf'),
@(29, 32807, 32060, 'RSmidigrid_shrf'),
@(17, 32, 32060, 'RSmidigrid_playbar'),
@(25, 32, 32060, 'RSmidigrid_playone'),
@(29, 32, 32060, 'RSmidigrid_loopbar')
)
$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."