Files
midigrid/install.ps1
T
Talon 51dff21e85 Add whole-take time shifting
Ctrl+Shift+Left/Right move every note in the take by one grid cell, for
clips that landed off the beat.

Since the whole take moves together, notes cannot collide with each other;
the hazard is the item boundary. Notes pushed past either end stop sounding
but are not deleted, so the operation is reversible -- shifting back restores
them. The plain keys still refuse when that would happen and report the count,
because a clip that has silently lost its first bar is a bad thing to discover
later. Ctrl+Alt+Shift forces it and names the consequence.

Positions are converted through QN rather than offset in raw ticks, so shifts
stay musically correct across tempo changes.
2026-08-14 18:46:33 +02:00

137 lines
6.4 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_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')
)
# 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'),
@(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')
)
$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."