Last active
April 19, 2026 20:10
-
-
Save torgeir/e1fc51da45d3491dd4b08ebf35710ebc to your computer and use it in GitHub Desktop.
Raspberry pi 4 guitar pedal setup with neural-amp-modeler-lv2 and ImpulseLoader.lv2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -eo pipefail | |
| # ----------------------- | |
| # Raspberry PI guitar rig | |
| # ----------------------- | |
| # functions to log statements in color | |
| g="\033[32m" | |
| r="\033[31m" | |
| o="\033[33m" | |
| w="\033[37m" | |
| c="\033[0m" | |
| function log() { | |
| echo -e "${g}$1$c" | |
| } | |
| function log_warn() { | |
| echo -e "${r}$1$c" | |
| } | |
| function log_os() { | |
| echo -e "${w}os: ${o}$1$c" | |
| } | |
| sound_cards() { | |
| cat /proc/asound/cards \ | |
| | awk -F'[][]' '{print $2}' \ | |
| | awk 'NF' \ | |
| | tr -d ' ' | |
| } | |
| # First available audio input card that isn't the USB gadget output. | |
| detect_input_card() { | |
| sound_cards | grep -v '^UAC2Gadget$' | head -1 | |
| } | |
| # First available audio output card that isn't UAC2Gadget and isn't the input card. | |
| # Falls back to the input card if no dedicated output exists. | |
| detect_output_card() { | |
| local input_card | |
| input_card=$(cat ~/.preferred-sound-card-in 2>/dev/null) | |
| sound_cards | grep -v '^UAC2Gadget$' | grep -v "^${input_card}$" | head -1 \ | |
| || echo "$input_card" | |
| } | |
| update_os() { | |
| log_os "running apt update.." | |
| sudo apt update | |
| log_os "running apt upgrade.." | |
| sudo apt upgrade | |
| } | |
| ensure_deps() { | |
| log_os "install alsa and deps" | |
| sudo apt install -y libasound2-dev g++ cmake alsa-tools alsa-utils | |
| log_os "lv2 plugins$c enough to build them" | |
| sudo apt install -y build-essential git lv2-dev pkg-config libsndfile1-dev libfftw3-dev libcairo2-dev libx11-dev | |
| log_os "install mod-host deps" | |
| sudo apt install -y libreadline-dev liblilv-dev lilv-utils libjack-jackd2-dev | |
| log_os "install mod-ui deps" | |
| sudo apt install -y libjpeg-dev zlib1g-dev python3-setuptools python3-dev libfreetype6-dev authbind | |
| log_os "install useful apps" | |
| sudo apt install -y wget tmux git vim netcat-openbsd exa htop fzf fd-find | |
| log_os "install ImpulseLoaderStereo's xxd$c nescessary for building it" | |
| # https://github.com/brummer10/ImpulseLoaderStereo.lv2/issues/4 | |
| sudo apt install xxd | |
| log_os "install lv2ls$c to list installed lv2 plugins" | |
| sudo apt install -y lilv-utils | |
| # lv2 plugin host https://github.com/drobilla/jalv/ | |
| #sudo apt install -y jalv | |
| log_os "guitarix plugins lv2" | |
| sudo apt install guitarix-lv2 | |
| # git clone https://github.com/x42/darc.lv2.git | |
| # sudo apt-get install libcairo2-dev libpango1.0-dev libglu1-mesa-dev libgl1-mesa-dev | |
| log_os "jack2 + jack-tools" | |
| sudo apt install -y --no-install-recommends jackd2 jack-tools | |
| sudo apt install linux-cpupower | |
| log_os "disabling and stopping all unnescessary processing to prevent xruns.." | |
| sudo systemctl disable dphys-swapfile cron bluetooth hciuart triggerhappy prometheus-node-exporter ModemManager nvmf-autoconnect openipmi smartmontools | |
| sudo systemctl stop dphys-swapfile cron bluetooth hciuart triggerhappy prometheus-node-exporter ModemManager nvmf-autoconnect openipmi smartmontools | |
| sudo dphys-swapfile swapoff | |
| } | |
| realtime_jack() { | |
| log "configuring realtime jack" | |
| DEBIAN_FRONTEND=noninteractive \ | |
| DEBCONF_NONINTERACTIVE_SEEN=true \ | |
| sudo dpkg-reconfigure --frontend=noninteractive -p high jackd2 | |
| echo | |
| log_warn "you should log out and log in again after this (adjusted rtprio), or just reboot" | |
| echo | |
| } | |
| deps_cleanup() { | |
| log "deps cleanup:$c remove no longer needed deps" | |
| sudo apt autoremove | |
| } | |
| create_folders() { | |
| log "creating ~/.bin:$c scripts to run everything will be put here" | |
| mkdir -p ~/bin | |
| mkdir -p ~/plugs | |
| log "creating ~/.plugs:$c plugins will be downloaded here" | |
| mkdir -p ~/.lv2 | |
| log "creating ~/.lv2:$c plugins will be installed here" | |
| log "$(cat <<EOF | |
| creating ~/mod-user-files:$c the folders where you will put models and IRs go $g | |
| # ~/mod-user-files | |
| # |- "Aida DSP Models" | |
| # |- "Audio Loops" | |
| # |- "Audio Recordings" | |
| # |- "Audio Samples" | |
| # |- "Audio Tracks" | |
| # |- "Hydrogen Drumkits" | |
| # |- "MIDI Clips" | |
| # |- "MIDI Songs" | |
| # |- "NAM Models" $c <-- put models here $g | |
| # |- "Reverb IRs" | |
| # |- "SF2 Instruments" | |
| # |- "SFZ Instruments" | |
| # |- "Speaker Cabinets IRs" $c <-- put IRs here $c | |
| EOF | |
| )" | |
| # mod-ui expects these folders | |
| mkdir -p mod-user-files/Aida\ DSP\ Models | |
| mkdir -p mod-user-files/Audio\ Loops | |
| mkdir -p mod-user-files/Audio\ Recordings | |
| mkdir -p mod-user-files/Audio\ Samples | |
| mkdir -p mod-user-files/Audio\ Tracks | |
| mkdir -p mod-user-files/Hydrogen\ Drumkits | |
| mkdir -p mod-user-files/MIDI\ Clips | |
| mkdir -p mod-user-files/MIDI\ Songs | |
| mkdir -p mod-user-files/NAM\ Models | |
| mkdir -p mod-user-files/Reverb\ IRs | |
| mkdir -p mod-user-files/SF2\ Instruments | |
| mkdir -p mod-user-files/SFZ\ Instruments | |
| mkdir -p mod-user-files/Speaker\ Cabinets\ IRs | |
| } | |
| install_mod_host() { | |
| if [ -d mod-host ]; then | |
| log "mod-host:$c already installed" | |
| return | |
| fi | |
| log "mod-host:$c downloading" | |
| git clone --recurse-submodules -j4 https://github.com/mod-audio/mod-host.git | |
| pushd mod-host | |
| log "mod-host:$c compiling" | |
| make | |
| log "mod-host:$c installing" | |
| sudo make install | |
| popd | |
| } | |
| install_mod_ui() { | |
| if [ -d mod-ui ]; then | |
| log "mod-ui:$c already installed" | |
| return | |
| fi | |
| log "mod-ui:$c downloading" | |
| git clone https://github.com/moddevices/mod-ui | |
| pushd mod-ui | |
| log "mod-ui:$c creating venv" | |
| python -m venv modui-env | |
| log "mod-ui:$c activating venv" | |
| source modui-env/bin/activate | |
| log "mod-ui:$c installing dependencies in venv" | |
| pip install -r requirements.txt | |
| log "mod-ui:$c compiling" | |
| make -C utils | |
| log "mod-ui:$c apply crazy python3.11 tornado hack, mod-ui seems to expect python3.10" | |
| sed -i -e 's/collections.MutableMapping/collections.abc.MutableMapping/' \ | |
| modui-env/lib/python3.11/site-packages/tornado/httputil.py | |
| popd | |
| } | |
| install_mod_plugins() { | |
| if [ ! -d plugs/mod-lv2-data ]; then | |
| log "mod plugins:$c downloading" | |
| git clone https://github.com/mod-audio/mod-lv2-data.git plugs/mod-lv2-data | |
| else | |
| log "mod plugins:$c already downloaded" | |
| fi | |
| # needs apt install guitarix-lv2 | |
| if [ ! -d ~/.lv2/gx_amp.lv2 ]; then | |
| log "mod plugins:$c copy already compiled plugins into place" | |
| # TODO | |
| #cp -r plugs/mod-lv2-data/plugins/gx_*.lv2 ~/.lv2/ | |
| # merge and preserve existing files in ~/.lv2/ | |
| rsync -av --ignore-existing plugs/mod-lv2-data/plugins-fixed/*.lv2 ~/.lv2/ | |
| else | |
| log "mod plugins:$c plugins already installed" | |
| fi | |
| } | |
| install_impulse_loader() { | |
| if [ ! -d plugs/ImpulseLoader.lv2 ]; then | |
| log "impulse loader:$c downloading" | |
| git clone --recurse-submodules -j4 https://github.com/brummer10/ImpulseLoader.lv2.git plugs/ImpulseLoader.lv2 | |
| else | |
| log "impulse loader:$c already downloaded" | |
| fi | |
| if [ ! -d ~/.lv2/ImpulseLoader.lv2 ]; then | |
| pushd plugs/ImpulseLoader.lv2 | |
| log "impulse loader:$c compiling" | |
| make | |
| log "impulse loader:$c installing" | |
| cp -r bin/ImpulseLoader.lv2 ~/.lv2/ | |
| log "impulse loader:$c make mod-ui show cabsims (i.e. wav files) from 'Speaker Cabinets IRs' in the ImpulseLoader plugin" | |
| sed -i '/^ *mod:fileTypes/s/"wav,audio"/"wav,audio,cabsim"/' ~/.lv2/ImpulseLoader.lv2/ImpulseLoader.ttl | |
| popd | |
| else | |
| log "impulse loader:$c already installed" | |
| fi | |
| } | |
| install_ratatouille() { | |
| if [ ! -d plugs/Ratatouille.lv2 ]; then | |
| log "ratatouille:$c downloading" | |
| git clone --recurse-submodules -j4 https://github.com/brummer10/Ratatouille.lv2 plugs/Ratatouille.lv2 | |
| else | |
| log "ratatouille:$c already downloaded" | |
| fi | |
| if [ ! -d ~/.lv2/Ratatouille.lv2 ]; then | |
| pushd plugs/Ratatouille.lv2 | |
| log "ratatouille:$c compiling" | |
| make | |
| log "ratatouille:$c installing" | |
| cp -r bin/Ratatouille.lv2 ~/.lv2/ | |
| popd | |
| else | |
| log "ratatouille:$c already installed" | |
| fi | |
| } | |
| install_neural_amp_modeler() { | |
| if [ ! -d plugs/neural-amp-modeler-lv2 ]; then | |
| log "neural amp modeler:$c downloading" | |
| git clone --recurse-submodules -j4 https://github.com/mikeoliphant/neural-amp-modeler-lv2 plugs/neural-amp-modeler-lv2 | |
| else | |
| log "neural amp modeler:$c already downloaded" | |
| fi | |
| if [ ! -d ~/.lv2/neural_amp_modeler.lv2 ] || ! cmp -s ~/.preferred-period-frames ~/.preferred-period-frames-compiled ; then | |
| pushd plugs/neural-amp-modeler-lv2/build | |
| log "neural amp modeler:$c compiling" | |
| cmake .. -DCMAKE_BUILD_TYPE="Release" -DWAVENET_FRAMES=$(cat ~/.preferred-period-frames) | |
| make -j4 | |
| cat ~/.preferred-period-frames > ~/.preferred-period-frames-compiled | |
| log "neural amp modeler:$c installing" | |
| rm -rf ~/.lv2/neural_amp_modeler.lv2 1>/dev/null 2>&1 | |
| cp -r neural_amp_modeler.lv2 ~/.lv2/ | |
| popd | |
| else | |
| log "neural amp modeler:$c already installed" | |
| fi | |
| } | |
| install_neural_rack() { | |
| if [ ! -d plugs/NeuralRack ]; then | |
| log "neural rack:$c downloading" | |
| git clone --recurse-submodules -j4 https://github.com/brummer10/NeuralRack.git plugs/NeuralRack | |
| else | |
| log "neural rack:$c already downloaded" | |
| fi | |
| if [ ! -d ~/.lv2/Neuralrack.lv2 ]; then # sic | |
| pushd plugs/NeuralRack | |
| log "neural rack:$c submodules" | |
| git submodule update --init --recursive | |
| log "neural rack:$c compiling" | |
| make lv2 | |
| log "neural rack:$c installing" | |
| make install | |
| popd | |
| else | |
| log "neural rack:$c already installed" | |
| fi | |
| } | |
| install_dragonfly() { | |
| if [ ! -d plugs/dragonfly-reverb ]; then | |
| log "dragonfly:$c downloading" | |
| git clone --recurse-submodules -j4 https://github.com/pedalboard/dragonfly-reverb.git plugs/dragonfly-reverb | |
| else | |
| log "dragonfly:$c already downloaded" | |
| fi | |
| if [ ! -d ~/.lv2/DragonflyHallReverb.lv2 ]; then | |
| pushd plugs/dragonfly-reverb | |
| log "dragonfly:$c compiling" | |
| # make | |
| log "dragonfly:$c patching" | |
| for manifest in "bin/*.lv2/manifest.ttl"; do | |
| sed -i 's/rdfs:seeAlso/rdfs:seeAlso <modgui.ttl>,/' $manifest | |
| done | |
| log "dragonfly:$c installing" | |
| cp -r bin/*.lv2 ~/.lv2/ | |
| popd | |
| else | |
| log "dragonfly:$c already installed" | |
| fi | |
| } | |
| # console.log([].slice.apply(document.querySelectorAll(".item-title")).map(a => a.href.replace("https://patchstorage.com/", "").replace(/\/$/, "")).join("\n")) | |
| patchstorage_plugs=( | |
| #setbfree-dsp-tonewheel-organ | |
| #setbfree-whirl-speaker-old-version | |
| #neural-record | |
| #aida-x | |
| cabinet-loader | |
| #convolution-loader | |
| #rumor | |
| #bollie-delay-xt | |
| #portal-lv2-bundle | |
| #collisiondrive | |
| #harmonic-exciter | |
| #bluesbreaker | |
| #neural-amp-modeler | |
| #chowcentaur | |
| #schrammel-ojd | |
| #audiotocv-pitch | |
| #audio-to-cv | |
| zamtube | |
| #zamulticompx2 | |
| #zamulticomp | |
| #zamaximx2 | |
| #zamheadx2 | |
| zamgeq31 | |
| #zamgatex2 | |
| zamgate | |
| zameq2 | |
| zamdelay | |
| #zamautosat | |
| #no-delay-line | |
| #stereo-balance-control | |
| #soul-force | |
| #mapitchshift | |
| #magigaverb | |
| #mafreeverb | |
| #mabitcrush | |
| #amplitude-imposer | |
| #3-band-splitter | |
| 3-band-eq | |
| #edisdim | |
| #vibrato-2 | |
| #spiral-2 | |
| #sidepass | |
| #pressure5 | |
| #pocketverbs | |
| #nikola | |
| #mojo-plugin | |
| #midside | |
| #mackity | |
| #galactic | |
| #everytrim | |
| #dyno-plugin | |
| #debess | |
| #console7crunch | |
| #console7channel | |
| #console7cascade | |
| #console7buss | |
| #cliponly2 | |
| #cliponly | |
| #channel8 | |
| #capacitor2 | |
| #capacitor | |
| #baxandall | |
| #adclip7 | |
| #acceleration2 | |
| #rezonateur | |
| #aether | |
| #amp-profiler | |
| #preamptubes | |
| #preampimpulses | |
| #poweramptubes | |
| #powerampimpulses | |
| #zamcompx2 | |
| zamcomp | |
| midi-display | |
| #simsam | |
| #pitchotto | |
| #setbfree-midi-controller | |
| #paranoia | |
| #mud-plugin | |
| floaty | |
| avocado | |
| molot-lite-mono | |
| #hardware-bypass | |
| compressor-advanced | |
| compressor-2 | |
| noise-gate-advanced | |
| noise-gate | |
| #spectrum-analyzer | |
| #level-meter | |
| #mixer-stereo | |
| #mixer | |
| #arpeggiator | |
| #mod-ams-lv2-bundle | |
| #fatfrog | |
| #samplv1 | |
| #dragonfly-room-reverb | |
| dragonfly-plate-reverb | |
| dragonfly-hall-reverb | |
| #dragonfly-early-reflections | |
| #die-fluid-synth | |
| #dexed | |
| #carla-audiogain-lv2-bundle | |
| #carla-files-lv2-bundle | |
| #bitta | |
| alo-plugin | |
| #starchild | |
| #abgate | |
| #gxsd1 | |
| #gxsaturator | |
| #gxquack | |
| #gxliquiddrive | |
| #gxknightfuzz | |
| #gxhyperion | |
| #gxhotbox | |
| #gxguvnor | |
| #gxepic | |
| #gxbottlerocket | |
| #gxbajatubedriver | |
| #gxmuff | |
| #gxmole | |
| #gxcrybabygcb95 | |
| #gxwahwah | |
| #metaltone | |
| #sc_record-lv2-bundle | |
| #gxtubescreamer | |
| #ir-loader-cabsim | |
| #gxultracab | |
| #gxvbasspreamp | |
| #zeroconvo-lv2-bundle | |
| #instrument-tuner | |
| #tinygain-lv2-bundle | |
| #toggleswitch | |
| switchtrigger4 | |
| #mda-vocinput | |
| #mda-transient | |
| #mda-tracker | |
| #mda-testtone | |
| #mda-talkbox | |
| #mda-stereo | |
| #mda-splitter | |
| #mda-rezfilter | |
| #mda-multiband | |
| #mda-loudness | |
| #mda-limiter | |
| #mda-image | |
| #mda-dynamics | |
| #mda-dither | |
| #mda-delay | |
| #mda-de-ess | |
| #mda-combo | |
| #mda-bandisto | |
| lowpassfilter | |
| highpassfilter | |
| gain-2x2 | |
| gain-plugin | |
| #crossover-3 | |
| #crossover-2 | |
| #c-wider-stereo-image-synthesis | |
| #c-tonestack-tone-stack-emulation | |
| #c-spicex2 | |
| #c-spice | |
| #c-sin-sine-wave-generator | |
| #c-scape-stereo-delay-filters | |
| #c-saturate | |
| #c-platex2-stereo-in-out-versatile-plate-reverb | |
| #c-plate-versatile-plate-reverb | |
| #c-phaserii-mono-phaser-modulated-by-a-lorenz-fractal | |
| #c-noisegate-attenuate-noise-resident-in-silence | |
| #c-narrower-stereo-image-width-reduction | |
| #c-fractal-audio-stream-from-deterministic-chaos | |
| #c-eq10x2-10-band-equalizer | |
| #c-eq10-10-band-equalizer | |
| #c-compressx2-stereo-compressor | |
| #c-compress-mono-compressor | |
| #c-click-metronome | |
| #c-chorusi-mono-chorus-flanger | |
| #c-cabinetiii-idealised-loudspeaker-cabinet-emulation | |
| #c-autofilter | |
| #c-ampvts-tube-amp-tone-stack | |
| #bandpassfilter | |
| #open-big-muff | |
| #gxautowah | |
| #stereo-x-fade | |
| #tap-vibrato | |
| #tap-tubewarmth | |
| #tap-tremolo | |
| #tap-sigmoid-booster | |
| #tap-rotary-speaker | |
| #tap-reverberator | |
| #tap-reflector | |
| #tap-pitch-shifter | |
| #tap-pink-fractal-noise | |
| #tap-scaling-limiter | |
| #tap-equalizer-bw | |
| tap-equalizer | |
| #tap-stereo-echo | |
| #tap-mono-dynamics | |
| #tap-stereo-dynamics | |
| #tap-fractal-doubler | |
| #tap-deesser | |
| #tap-chorus-flanger | |
| #tap-autopanner | |
| #stuck-lv2-bundle | |
| #midi-step-sequencer8x8 | |
| #midi-step-sequencer8x4 | |
| #midi-step-sequencer8x16 | |
| #midi-step-sequencer32x8 | |
| #midi-step-sequencer16x8 | |
| sooperlooper | |
| #shiroverb | |
| #rkr-lv2-bundle | |
| #prefreak | |
| #the-infamous-power-cut | |
| #ping-pong-pan | |
| #nekobi | |
| #mverb | |
| #midi-timecode-mtc-generator | |
| #modulay | |
| #ds1-plugin | |
| #cv-round | |
| #cv-range-divider | |
| #random-generator | |
| #cv-parameter-modulation | |
| #cv-meter | |
| #cv-gate | |
| #control-to-cv | |
| #attenuverter-booster | |
| #cv-abs | |
| #c-white-white-noise-generator | |
| #c-ceo-chief-executive-oscillator | |
| #c-cabinetiv-idealised-loudspeaker-cabinet-emulation | |
| #multi-button-to-cv | |
| the-infamous-mindi | |
| #midi-generator | |
| #midi-clock-generator | |
| #larynx | |
| #kars-plugin | |
| #invada-lv2-bundle | |
| #the-infamous-hip2b | |
| #harmless | |
| #gxswitched_tremolo | |
| #gxsustainer | |
| #gx-studio-preamp-stereo | |
| #gxalembic | |
| #gxslowgear | |
| #gxshimmizita | |
| #gxscreamingbird | |
| #gxroom_simulator | |
| #gxreverb-stereo | |
| #gx_redeye-lv2-bundle | |
| #gxrangemaster | |
| #gxphaser | |
| #gxoc_2 | |
| #gxmultibanddelay | |
| #gxjcm800prest | |
| #gxjcm800pre | |
| #gxhornet | |
| #gxhogsfoot | |
| #gxhf_brightener | |
| #gxfuzzfacefuller | |
| #gxfuzzfacejh2 | |
| #gxfuzz | |
| #gxfuzzmaster | |
| #gxflanger | |
| #gxexpander | |
| #gxecho-stereo | |
| gxduck_delay_st | |
| #gxduck_delay | |
| gxdigital_delay_st | |
| #gxdigital_delay | |
| #gxdetune | |
| gxdelay-stereo | |
| #gxcolorsound-tonebender | |
| gxcompressor | |
| #gxcabinet | |
| #gxamplifier-stereo-x | |
| #gxamplifier-x | |
| #gxtilttone | |
| #gxswitchlesswah | |
| #gxbooster | |
| #granulator-2 | |
| #freaktail | |
| #freakclip | |
| #fomp-lv2-bundle | |
| #fil4-lv2-bundle | |
| #autotune-2 | |
| #the-infamous-ewham | |
| #calf-lv2-bundle | |
| #setbfree-organ-reverb | |
| #setbfree-organ-overdrive | |
| bollie-delay | |
| #artyfx-lv2-bundle | |
| #amsynth | |
| #mda-dubdelay | |
| #mda-dx10 | |
| #mda-epiano | |
| #mda-jx10 | |
| #mda-leslie | |
| #mda-overdrive | |
| #mda-piano | |
| #mda-repsycho | |
| #mda-ringmod | |
| #mda-roundpan | |
| #mda-shepard | |
| #mda-subsynth | |
| #mda-ambience | |
| #mda-beatbox | |
| #mda-degrade | |
| #mda-detune | |
| #mda-thruzero | |
| #mda-vocoder | |
| #switchbox-1-2 | |
| #gxsvt | |
| ) | |
| install_patchstorage_plugs() { | |
| local url filename cache_dir=~/plugs/ | |
| for plug in "${patchstorage_plugs[@]}"; do | |
| echo $plug | |
| if [ -f "$cache_dir/$plug" ]; then | |
| echo " Already installed: $plug" | |
| continue | |
| fi | |
| curl -s "https://patchstorage.com/$plug/" \ | |
| | grep -o 'href="[^"]*"[^>]*>.*rpi-aarch64' \ | |
| | sed 's/.*href="\([^"]*\)".*/\1/' \ | |
| | while read url; do | |
| echo "Downloading and extracting: $url" | |
| filename=$(basename "$url") | |
| # Check if file already exists in cache | |
| echo " Downloading: $url" | |
| wget "$url" -O "$cache_dir/$plug" && \ | |
| tar xvfz "$cache_dir/$plug" -C ~/.lv2 | |
| done | |
| done | |
| } | |
| install_noisegate() { | |
| if [ ! -d plugs/noisegate ]; then | |
| log "install noisegate:$c downloading" | |
| git clone https://github.com/VeJa-Plugins/Noise-Gate.git plugs/noisegate | |
| else | |
| log "install noisegate:$c already installed" | |
| fi | |
| if [ ! -d ~/.lv2/noisegate.lv2 ]; then | |
| pushd plugs/noisegate | |
| log "noisegate:$c compiling" | |
| make clean | |
| make | |
| log "noisegate:$c installing" | |
| cp -r noisegate.lv2 ~/.lv2/ | |
| popd | |
| else | |
| log "noisegate:$c already installed" | |
| fi | |
| } | |
| preset_model() { | |
| if [ ! -f mod-user-files/Speaker\ Cabinets\ IRs/mesa-os-57.wav ]; then | |
| log "neural_amp_modeler:$c fetching m100-cln.nam amp model" | |
| wget https://github.com/torgeir/raspberry-pi-guitar/raw/refs/heads/main/m100-cln.nam \ | |
| -O mod-user-files/NAM\ Models/m100-cln.nam | |
| else | |
| log "neural_amp_modeler:$c already got m100-cln.nam amp model" | |
| fi | |
| log "neural_amp_modeler:$c creating preset model" | |
| cat <<EOF > ~/.lv2/neural_amp_modeler.lv2/manifest.ttl | |
| @prefix atom: <http://lv2plug.in/ns/ext/atom#> . | |
| @prefix lv2: <http://lv2plug.in/ns/lv2core#> . | |
| @prefix pset: <http://lv2plug.in/ns/ext/presets#> . | |
| @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . | |
| @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . | |
| @prefix state: <http://lv2plug.in/ns/ext/state#> . | |
| @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . | |
| <http://github.com/mikeoliphant/neural-amp-modeler-lv2> | |
| a lv2:Plugin; | |
| lv2:binary <neural_amp_modeler.so>; | |
| rdfs:seeAlso <modgui.ttl>, <neural_amp_modeler.ttl>. | |
| <model> | |
| a pset:Preset ; | |
| rdfs:label "model" ; | |
| lv2:appliesTo <http://github.com/mikeoliphant/neural-amp-modeler-lv2> ; | |
| state:state [ <http://github.com/mikeoliphant/neural-amp-modeler-lv2#model> "/home/$user/mod-user-files/NAM Models/m100-cln.nam" ] . | |
| EOF | |
| } | |
| preset_ir() { | |
| if [ ! -f mod-user-files/Speaker\ Cabinets\ IRs/mesa-os-57.wav ]; then | |
| log "ImpulseLoader:$c fetching mesa-os-57.wav cabinet ir" | |
| wget https://github.com/torgeir/raspberry-pi-guitar/raw/refs/heads/main/mesa-os-57.wav \ | |
| -O mod-user-files/Speaker\ Cabinets\ IRs/mesa-os-57.wav | |
| else | |
| log "ImpulseLoader:$c already got mesa-os-57.wav cabinet ir" | |
| fi | |
| log "ImpulseLoader:$c creating preset ir" | |
| cat <<EOF > ~/.lv2/ImpulseLoader.lv2/manifest.ttl | |
| @prefix atom: <http://lv2plug.in/ns/ext/atom#> . | |
| @prefix lv2: <http://lv2plug.in/ns/lv2core#> . | |
| @prefix pset: <http://lv2plug.in/ns/ext/presets#> . | |
| @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . | |
| @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . | |
| @prefix state: <http://lv2plug.in/ns/ext/state#> . | |
| @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . | |
| <urn:brummer:ImpulseLoader> | |
| a lv2:Plugin ; | |
| lv2:binary <ImpulseLoader.so> ; | |
| rdfs:seeAlso <ImpulseLoader.ttl>. | |
| <urn:brummer:ImpulseLoader#ir> | |
| a pset:Preset ; | |
| lv2:appliesTo <urn:brummer:ImpulseLoader> ; | |
| rdfs:label "ir" ; | |
| lv2:port [ | |
| lv2:symbol "Bypass" ; | |
| pset:value 0.0 | |
| ] , [ | |
| lv2:symbol "DRY_WET" ; | |
| pset:value 100.0 | |
| ] , [ | |
| lv2:symbol "INPUT" ; | |
| pset:value 0.0 | |
| ] ; | |
| state:state [ | |
| atom:Path "/home/$user/mod-user-files/Speaker Cabinets IRs/mesa-os-57.wav" | |
| ] . | |
| EOF | |
| } | |
| # https://torgeir.dev/2023/08/pwm-fan-on-raspberry-pi-4/ | |
| setup_fan() { | |
| if [ ! -d ~/fan ]; then | |
| log "fan:$c creating ~/fan" | |
| mkdir -p ~/fan | |
| cd fan | |
| log "fan:$c creating venv" | |
| python -m venv fan-env | |
| log "fan:$c activating venv" | |
| source fan-env/bin/activate | |
| log "fan:$c installing RPi.GPIO" | |
| pip install RPi.GPIO | |
| else | |
| log "fan:$c already installed" | |
| fi | |
| log "fan:$c writing fan.py" | |
| cat <<'EOF' > ~/fan/fan.py | |
| import signal | |
| import time | |
| import RPi.GPIO as gpio | |
| gpio.setmode(gpio.BCM) | |
| gpio.setup(14, gpio.OUT) | |
| pwm = gpio.PWM(14, 50) | |
| pwm.start(0) | |
| def duty_for_temp(temp): | |
| if temp < 45: return 0 | |
| if temp < 55: return 25 | |
| if temp < 65: return 50 | |
| if temp < 75: return 75 | |
| return 100 | |
| def read_temp(): | |
| with open("/sys/class/thermal/thermal_zone0/temp") as f: | |
| return int(f.read()) / 1000.0 | |
| def signal_handler(sig, frame): | |
| pwm.stop() | |
| gpio.cleanup() | |
| raise SystemExit(0) | |
| signal.signal(signal.SIGINT, signal_handler) | |
| signal.signal(signal.SIGTERM, signal_handler) | |
| while True: | |
| temp = read_temp() | |
| duty = duty_for_temp(temp) | |
| pwm.ChangeDutyCycle(duty) | |
| time.sleep(5) | |
| EOF | |
| log "fan:$c creating ~/bin/launch-fan" | |
| cat <<EOF > ~/bin/launch-fan | |
| #!/usr/bin/env bash | |
| cd /home/$user/fan | |
| source fan-env/bin/activate | |
| python fan.py | |
| EOF | |
| sudo tee /etc/systemd/system/fan.service &> /dev/null <<EOF | |
| [Unit] | |
| Description=pwm-fancontrol | |
| [Service] | |
| ExecStart=/home/$user/bin/launch-fan | |
| Restart=always | |
| RestartSec=5 | |
| [Install] | |
| WantedBy=multi-user.target | |
| EOF | |
| log "fan:$c making it runnable" | |
| chmod u+x ~/bin/launch-fan | |
| } | |
| setup_midi() { | |
| if [ ! -d ~/midi ]; then | |
| log "midi:$c creating ~/midi" | |
| mkdir -p ~/midi | |
| cd midi | |
| log "midi:$c creating venv" | |
| python -m venv midi-env | |
| log "midi:$c activating venv" | |
| source midi-env/bin/activate | |
| log "midi:$c intalling RPi:GPIO" | |
| pip install RPi.GPIO python-rtmidi evdev | |
| cat <<EOF > midi.py | |
| #!/usr/bin/env python | |
| import rtmidi | |
| #keypad | |
| import evdev | |
| from evdev import ecodes | |
| import sys | |
| cc=176 # control change | |
| pc=192 # program change | |
| note_off=128 | |
| note_off=144 | |
| pitch_bend=224 | |
| ## see /home/nam/mod-ui/data/profile5.json, channel is set: | |
| ## "midiChannelForSnapshotsNavigation": 14, | |
| device_path = "/dev/input/event0" | |
| # debug keys | |
| keys = [attr for attr in dir(ecodes) if attr.startswith('KEY_')] | |
| #print([k for k in keys if 'BACK' in k or 'DEL' in k or 'KP' in k]) | |
| #print([k for k in keys if 'NUM' in k]) | |
| keys={ | |
| ecodes.KEY_KPPLUS: { "state": False, "send": [pc + 13, 0] }, | |
| ecodes.KEY_KPMINUS: { "state": False, "send": [pc + 13, 1] }, | |
| ecodes.KEY_KP0: { "state": False, "send": [cc + 0, 0] }, | |
| ecodes.KEY_KP1: { "state": False, "send": [cc + 0, 1] }, | |
| ecodes.KEY_KP2: { "state": False, "send": [cc + 0, 2] }, | |
| ecodes.KEY_KP3: { "state": False, "send": [cc + 0, 3] }, | |
| ecodes.KEY_KP4: { "state": False, "send": [cc + 0, 4] }, | |
| ecodes.KEY_KP5: { "state": False, "send": [cc + 0, 5] }, | |
| ecodes.KEY_KP6: { "state": False, "send": [cc + 0, 6] }, | |
| ecodes.KEY_KP7: { "state": False, "send": [cc + 0, 7] }, | |
| ecodes.KEY_KP8: { "state": False, "send": [cc + 0, 8] }, | |
| ecodes.KEY_KP9: { "state": False, "send": [cc + 0, 9] }, | |
| ecodes.KEY_KPENTER: { "state": False, "send": [cc + 0, 10] }, | |
| ecodes.KEY_KPDOT: { "state": False, "send": [cc + 0, 11] }, | |
| ecodes.KEY_KPSLASH: { "state": False, "send": [cc + 0, 12] }, | |
| ecodes.KEY_KPASTERISK: { "state": False, "send": [cc + 0, 13] }, | |
| ecodes.KEY_BACKSPACE: { "state": False, "send": [cc + 0, 14] }, | |
| ecodes.KEY_NUMLOCK: { "state": False, "send": [cc + 0, 15] } | |
| } | |
| def send_midi(midiout, state, send): | |
| """Send MIDI""" | |
| if send[0] >= pc: | |
| msg=send | |
| midiout.send_message(msg) | |
| print(f"Sent pc msg: {msg}") | |
| elif send[0] >= cc: | |
| if state: | |
| value=0 | |
| else: | |
| value=127 | |
| msg=[*send, value] | |
| midiout.send_message(msg) | |
| print(f"Sent cc msg: {msg}") | |
| def main(): | |
| try: | |
| device = evdev.InputDevice(device_path) | |
| print(f"Connected to: {device.name}") | |
| midiout = rtmidi.MidiOut() | |
| if not midiout.get_ports(): | |
| midiout.open_virtual_port("Pi MIDI") | |
| else: | |
| midiout.open_port(0) | |
| print("Listening for keys... Press Ctrl+C to stop") | |
| device.grab() # Prevent normal key function | |
| for event in device.read_loop(): | |
| print(f"event: {event}") | |
| # is key, is handled, is up event | |
| if event.type == ecodes.EV_KEY and event.code in keys and event.value == 0: | |
| msg = keys[event.code] | |
| send_midi(midiout, msg["state"], msg["send"]) | |
| msg["state"] = not msg["state"] | |
| except KeyboardInterrupt: | |
| print("\nStopping...") | |
| except Exception as e: | |
| print(f"Error: {e}") | |
| finally: | |
| try: | |
| device.ungrab() | |
| device.close() | |
| midiout.close_port() | |
| except: | |
| pass | |
| if __name__ == "__main__": | |
| main() | |
| EOF | |
| else | |
| log "midi:$c already installed" | |
| fi | |
| log "midi:$c creating ~/bin/launch-midi " | |
| cat <<EOF > ~/bin/launch-midi | |
| #!/usr/bin/env bash | |
| cd /home/$user/midi | |
| source midi-env/bin/activate | |
| python midi.py | |
| EOF | |
| sudo tee /etc/systemd/system/midi.service &> /dev/null <<EOF | |
| [Unit] | |
| Description=midi | |
| [Service] | |
| User=$user | |
| ExecStart=/home/$user/bin/launch-midi | |
| [Install] | |
| WantedBy=multi-user.target | |
| EOF | |
| log "midi:$c making it runnable" | |
| chmod u+x ~/bin/launch-midi | |
| } | |
| create_jack() { | |
| log "create jack.service:$c the system service that will run jack on boot" | |
| sudo tee /etc/systemd/system/jack.service &>/dev/null <<EOF | |
| [Unit] | |
| Description=JACK Audio Server | |
| After=network.target irq-affinity.service | |
| Wants=irq-affinity.service | |
| Before=mod-host.service mod-ui.service | |
| [Service] | |
| User=$user | |
| Environment="JACK_NO_AUDIO_RESERVATION=1" | |
| Environment="JACK_NO_DBUS=1" | |
| Environment="JACK_DEFAULT_SERVER=default" | |
| LimitRTPRIO=95 | |
| LimitMEMLOCK=infinity | |
| CPUAffinity=2 | |
| CPUSchedulingPolicy=fifo | |
| CPUSchedulingPriority=95 | |
| ExecStartPre=/bin/bash -c 'rm -rf /dev/shm/jack* /dev/shm/sem.jack* /tmp/jack* 2>/dev/null || true' | |
| ExecStart=/home/$user/bin/launch-jack | |
| ExecStop=/bin/bash -c 'killall -TERM jackd; sleep 2; killall -9 jackd 2>/dev/null || true' | |
| ExecStopPost=/bin/bash -c 'rm -rf /dev/shm/jack* /dev/shm/sem.jack* /tmp/jack* 2>/dev/null || true' | |
| StandardOutput=journal | |
| StandardError=journal | |
| Restart=always | |
| RestartSec=5 | |
| [Install] | |
| WantedBy=multi-user.target | |
| EOF | |
| log "create ~/bin/launch-jack:$c that will launch jack, to be able to route audio between plugins using mod-ui, through mod-host" | |
| cat <<EOF > ~/bin/launch-jack | |
| #!/usr/bin/env bash | |
| sleep 1 | |
| hw_in=\$(cat ~/.preferred-sound-card-in) | |
| period=\$(cat ~/.preferred-period-frames) | |
| # Use UAC2Gadget directly when a USB host (PC) is connected and has configured | |
| # the gadget. Fall back to preferred output (e.g. Device/speakers) otherwise. | |
| test_is_usbgadget() { | |
| cat /sys/class/udc/*/state 2>/dev/null | grep -q "configured" | |
| } | |
| if test_is_usbgadget; then | |
| hw_out="UAC2Gadget" | |
| echo "USB host connected - using UAC2Gadget as output" | |
| else | |
| hw_out=\$(cat ~/.preferred-sound-card-out) | |
| echo "No USB host - using \$hw_out as output" | |
| fi | |
| echo "Starting jack with:" | |
| echo "- input device \$hw_in" | |
| echo "- output device \$hw_out" | |
| echo "- period \$period" | |
| nperiods=\$(cat ~/.preferred-nperiods 2>/dev/null || echo 3) | |
| echo "- nperiods \$nperiods" | |
| exec jackd -P95 -R -t4999 -d alsa -Chw:\$hw_in -Phw:\$hw_out -i 1 -o 2 -p\$period -n\$nperiods -Xseq -H -M -r48000 | |
| EOF | |
| chmod u+x ~/bin/launch-jack | |
| } | |
| create_mod_services() { | |
| sudo tee /etc/systemd/system/mod-host.service &>/dev/null <<EOF | |
| [Unit] | |
| Description=MOD Host Service | |
| After=jack.service | |
| Requires=jack.service | |
| BindsTo=jack.service | |
| [Service] | |
| User=$user | |
| LimitRTPRIO=95 | |
| LimitMEMLOCK=infinity | |
| CPUAffinity=3 | |
| CPUSchedulingPolicy=fifo | |
| CPUSchedulingPriority=95 | |
| ExecStartPre=/bin/sleep 4 | |
| ExecStart=mod-host -n -p 5555 -f 5556 | |
| ExecStop=/bin/bash -c 'killall -9 mod-host || true' | |
| StandardOutput=journal | |
| StandardError=journal | |
| Restart=always | |
| RestartSec=5 | |
| [Install] | |
| WantedBy=multi-user.target | |
| EOF | |
| log "create ~/bin/launch-mod-ui:$c that will launch mod-ui, to you can build your pedalboard in the browser" | |
| cat <<EOF > ~/bin/launch-mod-ui | |
| #!/usr/bin/env bash | |
| cd /home/$user/mod-ui | |
| source modui-env/bin/activate | |
| # https://patchstorage.com/docs/api/beta/ | |
| # PATCHSTORAGE_PLATFORM_ID=8046 is LV2 Plugins | |
| # PATCHSTORAGE_TARGET_ID=8280 is rpi-aarch64 | |
| MOD_SOUNDCARD=\$(cat ~/.preferred-sound-card-in) \ | |
| MOD_DEV_ENVIRONMENT=0 \ | |
| MOD_USER_FILES_DIR=/home/$user/mod-user-files \ | |
| PATCHSTORAGE_API_URL=https://patchstorage.com/api/beta/patches \ | |
| PATCHSTORAGE_PLATFORM_ID=8046 \ | |
| PATCHSTORAGE_TARGET_ID=8280 \ | |
| python ./server.py | |
| EOF | |
| chmod u+x ~/bin/launch-mod-ui | |
| log "run authbind:$c to allow mod-ui to start on port 80, so you can reach it without specifying the port" | |
| sudo touch /etc/authbind/byport/80 | |
| sudo chown $user /etc/authbind/byport/80 | |
| sudo chmod 755 /etc/authbind/byport/80 | |
| sed -i "/MOD_DEVICE_WEBSERVER_PORT/s/8888/80/" ~/mod-ui/server.py | |
| # https://github.com/SolsticeFX/pi-stomp-bookworm/blob/aeff925037e3318b078fc1418ddd86ed88d37d25/setup/mod/mod-ui.service#L25 | |
| sudo tee /etc/systemd/system/mod-ui.service &>/dev/null <<EOF | |
| [Unit] | |
| Description=MOD UI Service | |
| After=network.target jack.service mod-host.service | |
| Requires=jack.service mod-host.service | |
| BindsTo=jack.service mod-host.service | |
| [Service] | |
| User=$user | |
| ExecStartPre=/bin/sleep 3 | |
| ExecStart=/usr/bin/authbind --deep /home/$user/bin/launch-mod-ui | |
| StandardOutput=journal | |
| StandardError=journal | |
| Restart=always | |
| RestartSec=5 | |
| [Install] | |
| WantedBy=multi-user.target | |
| EOF | |
| } | |
| less_logging() { | |
| log "create /etc/systemd/journald.conf:$c to configure less logging" | |
| sudo tee /etc/systemd/journald.conf > /dev/null << 'EOF' | |
| [Journal] | |
| Storage=volatile | |
| RuntimeMaxUse=32M | |
| RuntimeMaxFileSize=8M | |
| ForwardToConsole=yes | |
| TTYPath=/dev/null | |
| #MaxLevelStore=emerg | |
| #MaxLevelSyslog=emerg | |
| #MaxLevelKMsg=emerg | |
| #MaxLevelConsole=emerg | |
| #MaxLevelWall=emerg | |
| ReadKMsg=no | |
| Audit=no | |
| EOF | |
| } | |
| usb_gadget_mode() { | |
| log "usb gadget:$c clean up previous setup" | |
| # Clean up any existing gadget | |
| sudo sh -c 'echo "" > /sys/kernel/config/usb_gadget/*/UDC' 2>/dev/null || true | |
| sudo rm -rf /sys/kernel/config/usb_gadget/* 2>/dev/null || true | |
| sudo modprobe -r g_ether g_serial g_mass_storage g_audio libcomposite 2>/dev/null || true | |
| log "usb gadget: create ~/bin/usb-gadget-audio:$c that will make the raspberry pi appear as a soundcard" | |
| # Create the script (note: this will be run with sudo) | |
| cat > ~/bin/usb-gadget-audio <<'EOF' | |
| #!/bin/bash | |
| # Function to clean up existing gadget | |
| cleanup_gadget() { | |
| echo "Cleaning up existing USB gadget..." | |
| # Disable existing gadget | |
| if [ -f /sys/kernel/config/usb_gadget/pi-audio/UDC ]; then | |
| echo "" > /sys/kernel/config/usb_gadget/pi-audio/UDC 2>/dev/null || true | |
| fi | |
| # Remove existing gadget configuration | |
| if [ -d /sys/kernel/config/usb_gadget/pi-audio ]; then | |
| rm -f /sys/kernel/config/usb_gadget/pi-audio/configs/c.1/uac2.usb0 2>/dev/null || true | |
| rmdir /sys/kernel/config/usb_gadget/pi-audio/configs/c.1/strings/0x409 2>/dev/null || true | |
| rmdir /sys/kernel/config/usb_gadget/pi-audio/configs/c.1 2>/dev/null || true | |
| rmdir /sys/kernel/config/usb_gadget/pi-audio/functions/uac2.usb0 2>/dev/null || true | |
| rmdir /sys/kernel/config/usb_gadget/pi-audio/strings/0x409 2>/dev/null || true | |
| rmdir /sys/kernel/config/usb_gadget/pi-audio 2>/dev/null || true | |
| fi | |
| # Clean up any other gadgets | |
| for gadget in /sys/kernel/config/usb_gadget/*; do | |
| if [ -d "$gadget" ]; then | |
| echo "" > "$gadget/UDC" 2>/dev/null || true | |
| fi | |
| done | |
| # Unload modules | |
| modprobe -r g_audio libcomposite 2>/dev/null || true | |
| # Wait a moment for cleanup | |
| sleep 2 | |
| } | |
| # Function to setup gadget | |
| setup_gadget() { | |
| echo "Setting up USB gadget..." | |
| # Load required modules | |
| modprobe libcomposite | |
| cd /sys/kernel/config/usb_gadget/ | |
| mkdir -p pi-audio | |
| cd pi-audio | |
| echo 0x1d6b > idVendor # linux foundation | |
| echo 0x0104 > idProduct # multifunction composite gadget | |
| echo 0x0100 > bcdDevice # v1.0.0 | |
| echo 0x0200 > bcdUSB # USB 2.0 | |
| mkdir -p strings/0x409 | |
| echo "fedcba9876543210" > strings/0x409/serialnumber | |
| echo "Raspberry Pi" > strings/0x409/manufacturer | |
| echo "Pi Audio Interface" > strings/0x409/product | |
| mkdir -p configs/c.1/strings/0x409 | |
| echo "Audio" > configs/c.1/strings/0x409/configuration | |
| mkdir -p functions/uac2.usb0 | |
| echo 48000 > functions/uac2.usb0/c_srate | |
| echo 48000 > functions/uac2.usb0/p_srate | |
| #capture,playback channel mask (binary) | |
| echo 1 > functions/uac2.usb0/c_chmask | |
| echo 3 > functions/uac2.usb0/p_chmask | |
| # Increase USB request pipeline depth: more URBs queued = smoother delivery, | |
| # less chance of ALSA buffer underrun when JACK is momentarily late. | |
| echo 8 > functions/uac2.usb0/req_number | |
| # Widen feedback envelope: allows larger clock-rate corrections to the host, | |
| # reducing buffer drift between the input clock (xhci) and UAC2Gadget (PC USB clock). | |
| echo 40 > functions/uac2.usb0/fb_max | |
| ln -s functions/uac2.usb0 configs/c.1/ | |
| # Find and activate UDC | |
| UDC=$(ls /sys/class/udc | head -1) | |
| if [ -n "$UDC" ]; then | |
| echo "$UDC" > UDC | |
| echo "USB gadget activated with UDC: $UDC" | |
| else | |
| echo "Error: No UDC found" | |
| exit 1 | |
| fi | |
| } | |
| # Main execution | |
| cleanup_gadget | |
| setup_gadget | |
| echo "USB gadget setup complete" | |
| EOF | |
| chmod +x ~/bin/usb-gadget-audio | |
| # Create cleanup script | |
| cat > ~/bin/usb-gadget-audio-cleanup <<'EOF' | |
| #!/bin/bash | |
| echo "Cleaning up USB gadget on shutdown..." | |
| # Disable all gadgets | |
| for gadget in /sys/kernel/config/usb_gadget/*; do | |
| if [ -d "$gadget" ] && [ -f "$gadget/UDC" ]; then | |
| echo "" > "$gadget/UDC" 2>/dev/null || true | |
| fi | |
| done | |
| # Remove gadget configuration | |
| if [ -d /sys/kernel/config/usb_gadget/pi-audio ]; then | |
| rm -f /sys/kernel/config/usb_gadget/pi-audio/configs/c.1/uac2.usb0 2>/dev/null || true | |
| rmdir /sys/kernel/config/usb_gadget/pi-audio/configs/c.1/strings/0x409 2>/dev/null || true | |
| rmdir /sys/kernel/config/usb_gadget/pi-audio/configs/c.1 2>/dev/null || true | |
| rmdir /sys/kernel/config/usb_gadget/pi-audio/functions/uac2.usb0 2>/dev/null || true | |
| rmdir /sys/kernel/config/usb_gadget/pi-audio/strings/0x409 2>/dev/null || true | |
| rmdir /sys/kernel/config/usb_gadget/pi-audio 2>/dev/null || true | |
| fi | |
| # Unload modules | |
| modprobe -r g_audio libcomposite 2>/dev/null || true | |
| echo "USB gadget cleanup complete" | |
| EOF | |
| chmod +x ~/bin/usb-gadget-audio-cleanup | |
| log "create usb-gadget-audio.service:$c that will set up the pi as a usb gadget on boot" | |
| sudo tee /etc/systemd/system/usb-gadget-audio.service > /dev/null <<EOF | |
| [Unit] | |
| Description=USB Gadget Audio | |
| After=network.target | |
| Before=jack.service | |
| [Service] | |
| Type=oneshot | |
| ExecStart=/home/$user/bin/usb-gadget-audio | |
| ExecStop=/home/$user/bin/usb-gadget-audio-cleanup | |
| RemainAfterExit=yes | |
| TimeoutStartSec=30 | |
| [Install] | |
| WantedBy=multi-user.target | |
| EOF | |
| sudo systemctl enable usb-gadget-audio.service | |
| # Run the setup immediately with sudo | |
| log "usb gadget:$c running initial setup" | |
| sudo /home/$user/bin/usb-gadget-audio | |
| } | |
| performance () { | |
| sudo tee /etc/systemd/system/performance.service &> /dev/null <<EOF | |
| [Unit] | |
| Description=Performance cpu mode | |
| After=network.target | |
| [Service] | |
| Type=oneshot | |
| ExecStart=cpupower frequency-set --governor performance | |
| RemainAfterExit=no | |
| [Install] | |
| WantedBy=multi-user.target | |
| EOF | |
| sudo systemctl enable performance.service | |
| sudo systemctl start performance.service | |
| } | |
| setup_irq_affinity() { | |
| log "irq affinity:$c pin interrupts off JACK CPUs (2-3) to prevent xruns" | |
| cat > ~/bin/irq-affinity <<'EOF' | |
| #!/bin/bash | |
| # Pin interrupts to CPUs 0-1, keeping CPUs 2-3 clean for JACK (isolcpus=2-3). | |
| # Runs as root before jack.service. | |
| pin_irq() { | |
| local name="$1" cpus="$2" pattern="$3" | |
| for irq in $(grep -P "$pattern" /proc/interrupts | grep -oP "^\s*\K\d+"); do | |
| echo "$cpus" > /proc/irq/$irq/smp_affinity_list \ | |
| && echo "Pinned IRQ $irq ($name) to CPUs $cpus" | |
| done | |
| } | |
| # USB gadget (dwc2/UAC2Gadget output) — on CPU 0, fires on every audio output frame | |
| pin_irq "dwc2/UAC2Gadget" "0" "fe980000\.usb|dwc2" | |
| # xhci USB host controller (guitar input) — on CPU 1, separate from dwc2 on CPU 0 | |
| # so the two USB controllers never compete for the same CPU simultaneously | |
| pin_irq "xhci" "1" "xhci_hcd" | |
| # Ethernet — CPUs 0-1 | |
| pin_irq "eth0" "0-1" " eth0" | |
| # PCIe PME — CPU 0 | |
| pin_irq "PCIe" "0" "PCIe PME|aerdrv" | |
| # VideoCore mailbox + VCHIQ doorbell — CPUs 0-1 | |
| pin_irq "VC mailbox/VCHIQ" "0-1" "mailbox|VCHIQ doorbell" | |
| # USB hub autosuspend — keep hub powered so audio input never drops mid-phrase | |
| echo "on" > /sys/bus/usb/devices/1-1/power/control 2>/dev/null \ | |
| && echo "USB hub 1-1 autosuspend disabled" || true | |
| # Raise USB audio IRQ thread priorities above JACK (RT 95). | |
| # IRQ threads default to RT 50 — JACK preempts them, delaying DMA refills → pops. | |
| # Raising them to RT 98 ensures DMA is never starved by JACK's audio callback. | |
| for pattern in "irq/29-xhci_hcd" "irq/39-fe980000.usb" "irq/39-dwc2"; do | |
| for pid in $(pgrep -f "$pattern" 2>/dev/null); do | |
| chrt -f -p 98 "$pid" && echo "Set RT 98: $pattern (pid $pid)" | |
| done | |
| done | |
| EOF | |
| chmod +x ~/bin/irq-affinity | |
| sudo tee /etc/systemd/system/irq-affinity.service > /dev/null <<EOF | |
| [Unit] | |
| Description=Pin IRQs to CPUs 0-1, keeping CPUs 2-3 clean for JACK | |
| After=network.target | |
| Before=jack.service | |
| [Service] | |
| Type=oneshot | |
| ExecStart=/home/$user/bin/irq-affinity | |
| RemainAfterExit=yes | |
| StandardOutput=journal | |
| StandardError=journal | |
| [Install] | |
| WantedBy=multi-user.target | |
| EOF | |
| sudo systemctl enable irq-affinity.service | |
| sudo systemctl restart irq-affinity.service | |
| } | |
| # Configures /boot/firmware/config.txt for USB audio gadget functionality, | |
| # and removes the default audio output and adds USB gadget, performance, and bluetooth settings. | |
| setup_config_txt() { | |
| log "setup /boot/firmware/config.txt:$c configuring boot settings for USB gadget audio" | |
| sudo cp /boot/firmware/config.txt /boot/firmware/config.txt.bak | |
| sudo sed -i.bak \ | |
| -e '/dtparam=audio=/d' \ | |
| -e '/force_turbo=/d' \ | |
| -e '/dtoverlay=/d' \ | |
| -e '/dtoverlay=disable-bt/d' \ | |
| -e '/gpu_mem=/d' \ | |
| /boot/firmware/config.txt | |
| sudo sed -i.bak \ | |
| -e '$a dtparam=audio=off' \ | |
| -e '$a force_turbo=1' \ | |
| -e '$a dtoverlay=dwc2' \ | |
| -e '$a dtoverlay=disable-bt' \ | |
| -e '$a gpu_mem=16' \ | |
| /boot/firmware/config.txt | |
| log_warn "/boot/firmware/config.txt:$c - reboot required for changes to take effect" | |
| } | |
| # Configures boot parameters /boot/firmware/cmdline.txt to enable USB audio gadget mode, | |
| # and sets options to dont pause usb devices, and force controller speed to limit interrupts | |
| # to improve realtime audio. | |
| # Adds these to the default: | |
| # modules-load=dwc2,g_audio usbcore.autosuspend=-1 dwc_otg.speed=1 | |
| setup_cmdline_txt() { | |
| sudo cp /boot/firmware/cmdline.txt /boot/firmware/cmdline.txt.bak | |
| sudo sed -i.bak \ | |
| -e 's/modules-load=dwc2,g_audio//g' \ | |
| -e 's/usbcore\.autosuspend=-1//g' \ | |
| -e 's/dwc_otg\.speed=1//g' \ | |
| -e 's/threadirqs//g' \ | |
| -e 's/ */ /g' \ | |
| -e 's/rootwait/rootwait modules-load=dwc2,g_audio usbcore.autosuspend=-1 dwc_otg.speed=1 threadirqs/' \ | |
| /boot/firmware/cmdline.txt | |
| } | |
| # not nescessary with mod-ui, it does this | |
| #create_connect() { | |
| # cat <<EOF > launch-connect | |
| ##!/usr/bin/env bash | |
| #sleep 4 | |
| #jack_connect system:capture_1 effect_1:input | |
| #jack_connect effect_1:output effect_2:in0 | |
| #jack_connect effect_2:out0 system:playback_2 | |
| #jack_lsp -c | |
| #EOF | |
| # chmod u+x launch-connect | |
| #} | |
| log "$(cat <<EOF | |
| ${o}:: Raspberry PI neural amp modeller guitar rig :: | |
| $r | |
| This script will $g | |
| - update and prepare the os | |
| - install nescessary dependencies | |
| - configure jack for realtime audio | |
| - download, compile and install a bunch of plugins | |
| - mod-host | |
| - mod-ui | |
| - nam | |
| - impulse loader | |
| - effects++ | |
| - create script to launch everything | |
| - bundle everything into a service that starts on boot | |
| - configure the pi as an usb gadget "soundcard" | |
| EOF | |
| ) | |
| " | |
| user=$USER | |
| cd | |
| log "-$r need:$c sudo" | |
| log "-$r run as:$c $user" | |
| log "-$r install a bunch of things in:$c $PWD\n" | |
| # Only prompt when running interactively | |
| if [ -t 0 ]; then | |
| read -p "Press enter to proceed.." | |
| fi | |
| if [ ! -f ~/.pedal.deps ]; then | |
| log_warn "Preparing OS. Remove the$c ~/.pedal.deps$r file to update or do it again.\n" | |
| update_os | |
| ensure_deps | |
| realtime_jack | |
| deps_cleanup | |
| touch ~/.pedal.deps | |
| setup_cmdline_txt | |
| setup_config_txt | |
| fi | |
| usb_gadget_mode | |
| if [ ! -f ~/.preferred-period-frames ] || [ -t 0 ]; then | |
| log 'Jack number of period frames. 96 seems to work fine on a raspberry pi 4, | |
| with low enough latency, without many pops and clicks (this also depends on | |
| how many effects you run). You can raise this value to e.g. 128 or more if | |
| you experience pops and clicks or look into tuning the os for less xruns. | |
| Read more: https://wiki.linuxaudio.org/wiki/list_of_jack_frame_period_settings_ideal_for_usb_interface' | |
| if [ -t 0 ]; then | |
| log_warn "Choose number of jack period frames:" | |
| echo -e "96\n128\n144\n192\n256\n512" \ | |
| | fzf --height 10% --reverse \ | |
| | tee ~/.preferred-period-frames | |
| else | |
| echo "96" | tee ~/.preferred-period-frames | |
| log "Defaulting jack period frames to 96 (non-interactive)" | |
| fi | |
| else | |
| log "Using existing jack period frames: $(cat ~/.preferred-period-frames)" | |
| fi | |
| if [ ! -f ~/.preferred-nperiods ] || [ -t 0 ]; then | |
| log 'Jack number of periods (nperiods). Lower = less latency, higher = fewer xruns. | |
| 2 is very aggressive (~4ms buffer). 3 is a good balance (~6ms). 5 is the safe default.' | |
| if [ -t 0 ]; then | |
| log_warn "Choose number of jack periods:" | |
| echo -e "2\n3\n4\n5" \ | |
| | fzf --height 10% --reverse \ | |
| | tee ~/.preferred-nperiods | |
| else | |
| echo "3" | tee ~/.preferred-nperiods | |
| log "Defaulting jack nperiods to 3 (non-interactive)" | |
| fi | |
| else | |
| log "Using existing jack nperiods: $(cat ~/.preferred-nperiods)" | |
| fi | |
| log "Found the following usb sound cards (cat /proc/asound/cards):" | |
| cat /proc/asound/cards | |
| if [ ! -s ~/.preferred-sound-card-in ] || [ -t 0 ]; then | |
| if [ -t 0 ]; then | |
| log_warn "Choose preferred input sound card:" | |
| echo -e "$(sound_cards)" \ | |
| | fzf --height 10% --reverse \ | |
| | tr -d ' ' \ | |
| | tee ~/.preferred-sound-card-in | |
| else | |
| detect_input_card | tee ~/.preferred-sound-card-in | |
| log "Defaulting input sound card to $(cat ~/.preferred-sound-card-in) (non-interactive, first non-gadget card)" | |
| fi | |
| else | |
| log "Using existing input sound card: $(cat ~/.preferred-sound-card-in)" | |
| fi | |
| if [ ! -s ~/.preferred-sound-card-out ] || [ -t 0 ]; then | |
| if [ -t 0 ]; then | |
| log_warn "Choose preferred output sound card (they can be the same):" | |
| echo -e "$(sound_cards)" \ | |
| | fzf --height 10% --reverse \ | |
| | tr -d ' ' \ | |
| | tee ~/.preferred-sound-card-out | |
| else | |
| detect_output_card | tee ~/.preferred-sound-card-out | |
| log "Defaulting output sound card to $(cat ~/.preferred-sound-card-out) (non-interactive, first non-gadget non-input card)" | |
| fi | |
| else | |
| log "Using existing output sound card: $(cat ~/.preferred-sound-card-out)" | |
| fi | |
| create_folders | |
| install_mod_host | |
| install_mod_ui | |
| # amp+cab | |
| install_impulse_loader | |
| install_neural_amp_modeler | |
| install_neural_rack | |
| install_ratatouille | |
| # plugins | |
| # install_noisegate | |
| install_patchstorage_plugs | |
| #install_mod_plugins | |
| preset_model | |
| preset_ir | |
| performance | |
| setup_irq_affinity | |
| setup_fan | |
| setup_midi | |
| create_jack | |
| create_mod_services | |
| less_logging | |
| log "reloading existing service definitions.." | |
| sudo systemctl daemon-reload | |
| log "making services start on boot as well.." | |
| sudo systemctl enable fan.service | |
| sudo systemctl enable midi.service | |
| sudo systemctl enable irq-affinity.service | |
| sudo systemctl enable jack.service | |
| sudo systemctl enable mod-host.service | |
| sudo systemctl enable mod-ui.service | |
| sudo systemctl enable performance.service | |
| log "setting performance mode" | |
| sudo systemctl restart performance.service | |
| log "restarting fan" | |
| sudo systemctl restart fan.service | |
| log "restarting midi" | |
| sudo systemctl restart midi.service | |
| log "stopping mod-ui" | |
| sudo systemctl stop mod-ui.service | |
| log "stopping mod-host" | |
| sudo systemctl stop mod-host.service | |
| log "stopping jack" | |
| sudo systemctl stop jack.service | |
| log "starting jack" | |
| sudo systemctl start jack.service | |
| log "starting mod-host" | |
| sudo systemctl start mod-host.service | |
| log "starting mod-ui" | |
| sudo systemctl start mod-ui.service | |
| log "for each core, cpu governor is set to:" | |
| cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor | |
| log "$(cat <<EOF | |
| Done. | |
| $oNow what?$c | |
| $g Reboot, if this is your first time running the script: | |
| $c sudo reboot | |
| $g Copy models, so mod-ui will show them in the ui, e.g. in Neural Amp Modeler: | |
| $c scp model.nam nam@pedal.lan:/home/$user/mod-user-files/NAM\ Models/ | |
| $g Copy IRs, so mod-ui will show them in the ui, e.g. in ImpulseLoader: | |
| $c scp cab-ir.wav nam@pedal.lan:/home/$user/mod-user-files/Speaker\ Cabinet\ IRs/ | |
| $g Stop service: | |
| $c sudo systemctl restart pedal.service | |
| $g Restart service: | |
| $c sudo systemctl restart pedal.service | |
| $g Show last logs: | |
| $c journalctl -u pedal.service -b0 | |
| $g Tail logs: | |
| $c journalctl -u pedal.service -f | |
| $g Adjust overall volume: | |
| $c alsamixer | |
| $g Store set volume: | |
| $c sudo alsactl store | |
| $g It is restored on boot, or using: | |
| $c sudo alsactl restore | |
| Visit$c http://pedal.lan$g | |
| Enjoy! | |
| EOF | |
| )\n" | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment