60ms was too blunt: at 180 BPM a 32nd note is 42ms, so the lead swallowed the final 32nd of the bar, and it swallowed a whole 32nd from 125 BPM upward. Testing against GetPlayPosition2 rather than GetPlayPosition removes the render-ahead from the equation -- it reports the block about to be rendered rather than what is currently audible -- so the lead only has to cover the daemon's polling interval, not both delays. 35ms suffices, which is safe for 32nds to about 214 BPM. Polling cannot do better than its own interval, so for genuine precision Ctrl+Alt+Shift+Space loops the bar via the loop points instead, letting the audio engine own the boundary. That is sample accurate at any tempo or grid size. It borrows loop range and repeat state; the daemon restores both once transport stops, including when stopped with Space.
143 lines
6.9 KiB
PowerShell
143 lines
6.9 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'),
|
|
@('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'),
|
|
@(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."
|