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

208 lines
8.2 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# MIDI Grid installer for macOS (and Linux REAPER).
#
# The macOS counterpart of install.ps1, and deliberately a line-for-line
# mirror of it: same actions, same key table, same idempotent rewrite of
# reaper-kb.ini. If you add an action, add it to *both* files.
#
# REAPER MUST BE CLOSED. It rewrites reaper-kb.ini on exit and will
# discard anything written while it is running.
#
# Usage: ./install.sh
# ./install.sh --uninstall
# ./install.sh --resource-path ~/Library/Application\ Support/REAPER
# ./install.sh --real-control # see "Modifiers on macOS" below
#
# Modifiers on macOS
# ------------------
# In reaper-kb.ini the modifier bit 8 means Ctrl on Windows but *Command*
# on macOS; the physical Control key is bit 32. By default this script
# keeps bit 8, so every "Ctrl+..." binding in the README is Command+... on
# a Mac, which is what Mac users expect. Pass --real-control to bind the
# physical Control key instead.
set -euo pipefail
script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
resource_path=""
uninstall=0
real_control=0
while [ $# -gt 0 ]; do
case "$1" in
--uninstall) uninstall=1 ;;
--real-control) real_control=1 ;;
--script-dir) script_dir="$2"; shift ;;
--resource-path) resource_path="$2"; shift ;;
-h|--help) sed -n '2,25p' "$0"; exit 0 ;;
*) echo "Unknown option: $1" >&2; exit 2 ;;
esac
shift
done
if [ -z "$resource_path" ]; then
if [ -d "$HOME/Library/Application Support/REAPER" ]; then
resource_path="$HOME/Library/Application Support/REAPER"
elif [ -d "$HOME/.config/REAPER" ]; then
resource_path="$HOME/.config/REAPER"
else
echo "Could not find your REAPER resource folder." >&2
echo "Find it via Options > Show REAPER resource path, then pass" >&2
echo " --resource-path '/path/to/REAPER'" >&2
exit 1
fi
fi
kb="$resource_path/reaper-kb.ini"
[ -f "$kb" ] || { echo "reaper-kb.ini not found at $kb" >&2; exit 1; }
if pgrep -x REAPER >/dev/null 2>&1 || pgrep -x reaper >/dev/null 2>&1; then
echo "REAPER is running. Close it completely and run this again." >&2
exit 1
fi
# 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 (Command on macOS), +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 [ ! -f "$backup" ]; then
cp "$kb" "$backup"
echo "Backed up original to $backup"
fi
tmp=$(mktemp)
trap 'rm -f "$tmp" "$tmp.2"' EXIT
# Drop any previous MIDI Grid lines so re-running is idempotent.
grep -v -i 'RSmidigrid' "$kb" > "$tmp" || true
if [ "$uninstall" -eq 1 ]; then
cat "$tmp" > "$kb"
echo "Removed MIDI Grid actions and bindings."
echo "Note: keys it had taken over (arrows, Enter) are now unbound in the"
echo "MIDI editor. Rebind them, or restore $backup."
exit 0
fi
# SCR lines go at the top, in table order.
: > "$tmp.2"
while IFS='|' read -r id section desc file; do
[ -n "${id:-}" ] || continue
path="$script_dir/$file"
[ -f "$path" ] || { echo "Missing script: $path" >&2; exit 1; }
printf 'SCR 4 %s %s "%s" %s\n' "$section" "$id" "$desc" "$path" >> "$tmp.2"
done <<EOF
$actions
EOF
cat "$tmp" >> "$tmp.2"
mv "$tmp.2" "$tmp"
# 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.
n_keys=0
while IFS='|' read -r flags code section id; do
[ -n "${flags:-}" ] || continue
if [ "$real_control" -eq 1 ] && [ $(( flags & 8 )) -ne 0 ]; then
flags=$(( flags - 8 + 32 ))
fi
# Replace an existing binding for the same key+section, else append.
awk -v flags="$flags" -v code="$code" -v section="$section" -v id="$id" '
BEGIN { pat = "^KEY " flags " " code " [^ ]+ " section "([ \t]|$)"
new = "KEY " flags " " code " _" id " " section
done = 0 }
!done && $0 ~ pat { print new; done = 1; next }
{ print }
END { if (!done) print new }
' "$tmp" > "$tmp.2"
mv "$tmp.2" "$tmp"
n_keys=$(( n_keys + 1 ))
done <<EOF
$keys
EOF
cat "$tmp" > "$kb"
n_actions=$(printf '%s' "$actions" | grep -c 'RSmidigrid')
echo "Installed $n_actions actions and $n_keys key bindings."
if [ "$real_control" -eq 1 ]; then
echo "Ctrl bindings use the physical Control key."
else
echo "On macOS the README's Ctrl+... bindings are Command+... here"
echo "(re-run with --real-control to use the Control key instead)."
fi
echo "Start REAPER, select a MIDI item, and press Alt+Shift+G (Option+Shift+G)."