#!/bin/bash # # Drives the firmware's calibration patterns from the PC over ALSA MIDI. # See "Calibration" in README.md for the procedure. # # Usage: # tools/calibrate.sh list list MIDI ports # tools/calibrate.sh ends pixel 0 (red) and last pixel (green) # tools/calibrate.sh octaves every C, middle C in red # tools/calibrate.sh keys every key, white green / black blue # tools/calibrate.sh walk light pixel n only # tools/calibrate.sh sweep [ms] walk every pixel in turn # tools/calibrate.sh all every pixel, for voltage droop # tools/calibrate.sh off back to normal operation # tools/calibrate.sh note [vel] play one MIDI note # tools/calibrate.sh chromatic [ms] play every key, low to high # # Set PORT to the device's ALSA port (e.g. PORT=24:0). If unset, the script # picks the first port whose name matches PORT_MATCH. # set -e PORT_MATCH=${PORT_MATCH:-Piano LED} CC_PATTERN=20 CC_INDEX_HI=21 CC_INDEX_LO=22 PAT_OFF=0; PAT_ENDS=1; PAT_OCTAVES=2; PAT_KEYS=3; PAT_WALK=4; PAT_ALL=5 need() { command -v "$1" >/dev/null 2>&1 || { echo "error: $1 not found (install alsa-utils)" >&2; exit 1; } } find_port() { if [ -n "$PORT" ]; then echo "$PORT"; return; fi local p p=$(aconnect -l | awk -v m="$PORT_MATCH" ' /^client /{ cl=$2; sub(":","",cl); name=$0 } /^ +[0-9]+ / { if (name ~ m) { port=$1; print cl ":" port; exit } }') if [ -z "$p" ]; then echo "error: no MIDI port matching \"$PORT_MATCH\"." >&2 echo " Run '$0 list', then set PORT=client:port" >&2 exit 1 fi echo "$p" } # Send raw MIDI bytes to the port. send() { need amidi local hex="$*" amidi -p "$(alsa_rawmidi_port)" -S "$hex" 2>/dev/null && return 0 # amidi needs a rawmidi device; fall back to the sequencer via aplaymidi echo "error: could not send. Set PORT and ensure the device is connected." >&2 exit 1 } # amidi addresses rawmidi (hw:X,Y), not sequencer ports. alsa_rawmidi_port() { if [ -n "$RAWMIDI" ]; then echo "$RAWMIDI"; return; fi need amidi local p p=$(amidi -l | awk -v m="$PORT_MATCH" '$0 ~ m { print $2; exit }') if [ -z "$p" ]; then echo "error: no rawmidi device matching \"$PORT_MATCH\"." >&2 echo " Run 'amidi -l', then set RAWMIDI=hw:X,Y" >&2 exit 1 fi echo "$p" } cc() { printf 'B0 %02X %02X' "$1" "$2"; } pattern() { send "$(cc $CC_PATTERN $1)"; } walk() { local n=$1 send "$(cc $CC_PATTERN $PAT_WALK) $(cc $CC_INDEX_HI $((n >> 7))) $(cc $CC_INDEX_LO $((n & 127)))" } case "${1:-}" in list) echo "--- sequencer ports (aconnect -l) ---"; aconnect -l || true echo; echo "--- rawmidi devices (amidi -l) ---"; amidi -l || true ;; ends) pattern $PAT_ENDS; echo "pattern: ends" ;; octaves) pattern $PAT_OCTAVES; echo "pattern: octaves" ;; keys) pattern $PAT_KEYS; echo "pattern: keys" ;; all) pattern $PAT_ALL; echo "pattern: all (watch the far end for warm colour)" ;; off) pattern $PAT_OFF; echo "pattern: off" ;; walk) [ -n "${2:-}" ] || { echo "usage: $0 walk " >&2; exit 1; } walk "$2"; echo "pixel $2" ;; sweep) MS=${2:-120} COUNT=${LED_COUNT:-176} echo "sweeping 0..$((COUNT-1)); Ctrl-C to stop" for ((i=0; i [velocity]" >&2; exit 1; } V=${3:-100} send "$(printf '90 %02X %02X' "$2" "$V")" echo "note $2 on (velocity $V); '$0 off' or send note-off to clear" ;; chromatic) MS=${2:-150} echo "playing notes 21..108; Ctrl-C to stop" for ((n=21; n<=108; n++)); do send "$(printf '90 %02X 64' "$n")" printf '\rnote %3d' "$n" sleep "$(echo "$MS/1000" | bc -l)" send "$(printf '80 %02X 00' "$n")" done echo ;; *) sed -n "2,/^# Set PORT/p" "$0" | sed 's/^# \{0,1\}//' exit 1 ;; esac