2026-08-14 22:13:59 +02:00
|
|
|
#!/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
|
2026-08-24 01:15:57 +02:00
|
|
|
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
|
2026-08-14 22:13:59 +02:00
|
|
|
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
|
2026-08-24 01:15:57 +02:00
|
|
|
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
|
2026-08-14 22:13:59 +02:00
|
|
|
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)."
|