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.
208 lines
8.2 KiB
Bash
Executable File
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)."
|