#!/usr/bin/env bash # ── NAM Model & Cabinet IR Downloader ───────────────────────────────── # # Downloads real NAM amp captures (WaveNet/architecture=2) from the # pelennor2170/NAM_models community collection on GitHub, plus free # cabinet IR packs from Archive.org. # # Models are organized by genre to match the factory presets: # Bank 0 — Clean & Edge (Clean Jazz, Edge of Breakup, Clean w/ Chorus, Dynamic Fingerstyle) # Bank 1 — Drive & Lead (Classic Rock, Lead Solo, Metal Rhythm, Ambient Wash) # Bank 2 — FX Bench (reference presets) # # Usage: # ./scripts/download_nam_and_irs.sh # Download everything # ./scripts/download_nam_and_irs.sh --list # List what would be downloaded # ./scripts/download_nam_and_irs.sh --models # Models only # ./scripts/download_nam_and_irs.sh --irs # IRs only # # Environment: # NAM_DIR: target directory for .nam files (default: ~/.pedal/models) # IR_DIR: target directory for .wav IRs (default: ~/.pedal/irs) # # ────────────────────────────────────────────────────────────────────── set -euo pipefail NAM_DIR="${NAM_DIR:-$HOME/.pedal/models}" IR_DIR="${IR_DIR:-$HOME/.pedal/irs}" TMPDIR="${TMPDIR:-/tmp}/pedal-download-$$" BASE_URL="https://raw.githubusercontent.com/pelennor2170/NAM_models/main" # ── Colours ────────────────────────────────────────────────────────── GREEN='\033[0;32m' YELLOW='\033[1;33m' RED='\033[0;31m' CYAN='\033[0;36m' NC='\033[0m' info() { echo -e "${CYAN}[INFO]${NC} $*"; } ok() { echo -e "${GREEN}[OK]${NC} $*"; } warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } err() { echo -e "${RED}[ERR]${NC} $*" >&2; } # ── NAM Models by Genre ────────────────────────────────────────────── # # Format: "local_filename|github_filename|description|architecture|size_kb" # All are WaveNet models from the community capture collection. # # GENRE MAP (matching factory presets): # Clean Jazz, Dynamic Fingerstyle → Fender Twin, Vox AC15 cleans # Edge of Breakup → Magnatone pushed, JCM2000 crunch # Clean with Chorus → Fender Twin clean # Classic Rock → Orange Rockerverb, JCM2000, JCM900 # Lead Solo → 5150, Engl Savage # Metal Rhythm → 5150, Splawn # Ambient Wash → Fender Twin clean (with reverb/mod FX) # declare -a MODELS=() # ── Bank 0: Clean & Edge ───────────────────────────────────────────── MODELS+=( "Fender_TwinVerb_Clean.nam|Tim R Fender TwinVerb Norm Bright.nam|Fender Twin Reverb Normal Bright channel — clean jazz/fingerstyle|WaveNet|400" "Fender_TwinVerb_Vibrato.nam|Tim R Fender TwinVerb Vibrato Bright.nam|Fender Twin Reverb Vibrato Bright channel — sparkling clean|WaveNet|400" "Vox_AC15_TopBoost.nam|Phillipe P VOXAC15-TopBoost.nam|Vox AC15 Top Boost channel — chimey edge-of-breakup|WaveNet|400" "Magnatone_Super59_Pushed.nam|Tim R Magnatone Super 59 Mkii Pushed.nam|Magnatone Super 59 pushed — bluesy breakup|WaveNet|400" "JCM2000_Clean.nam|Tim R JCM2000 Clean.nam|Marshall JCM2000 clean — crisp rhythm|WaveNet|400" ) # ── Bank 1: Drive & Lead ───────────────────────────────────────────── MODELS+=( "Orange_Rockerverb.nam|Tom C Axe FX 2 Orange Rockerverb.nam|Orange Rockerverb — classic rock crunch|WaveNet|400" "JCM2000_Crunch.nam|Tim R JCM2000 Crunch.nam|Marshall JCM2000 crunch — hard rock rhythm|WaveNet|400" "JCM900_Lead.nam|Tim R JCM90050WDualVerbCHAG6.nam|Marshall JCM900 Dual Verb channel A gain 6 — lead|WaveNet|400" "5150_BlockLetter_Boosted.nam|Helga B 5150 BlockLetter - Boosted.nam|EVH 5150 Block Letter boosted — high-gain lead|WaveNet|400" "6505_Red_NoBoost.nam|Helga B 6505+ Red ch - NoBoost.nam|Peavey 6505+ Red channel no boost — metal rhythm|WaveNet|400" "Engl_Savage.nam|Tom C Axe FX 2 Engl Savage.nam|ENGL Savage 120 — modern high-gain lead|WaveNet|400" "Splawn_ProMod_3rd.nam|Tim R Splawn Pro Mod 3rd Gear - G12.nam|Splawn Pro Mod 3rd gear — tight high-gain|WaveNet|400" "5152_Lead_TS9.nam|Tim R 5152 Lead + TS9.nam|5152 Lead with Tube Screamer — boosted solo tone|WaveNet|400" ) # ── Pedal captures (for FX chain flexibility) ──────────────────────── # These work as stand-in for the BOOST/OD/DIST blocks or as # preamp-into-IR tones. MODELS+=( "Klon_Centaur_High.nam|Keith B Klone_plus_BB_highGain.nam|Klon Centaur-style overdrive high gain — boost/overdrive|WaveNet|400" "TubeScreamer_Maxed.nam|Tudor N Ibanez Tube Screamer Mini Tone@10_Level@10_Overdrive@0 ESR_0.0006_lite.nam|Ibanez TS808 Mini maxed — clean boost|WaveNet|200" "Mesa_MarkIV_Lead.nam|Roman A LT_MESA_MARKIV_1.nam|Mesa Boogie Mark IV lead — singing sustain|WaveNet|400" "Bogner_Ecstasy.nam|Tudor N Ceriatone Molecular 50.nam|Bogner Ecstasy-style — versatile overdrive range|WaveNet|400" ) # ── Feather models (smallest footprint, best for RPi 4B) ──────────── MODELS+=( "BOSS_SD1_Feather.nam|Phillipe P BOSS-SD1-Feather-ESR0,001.nam|BOSS SD-1 overdrive feather model — 93 KB, ultra-low CPU|WaveNet|93" "MXR_M77_Feather.nam|Phillipe P MXR-M77-Feather-ESR0,001.nam|MXR M77 Custom Badass OD feather model — 93 KB|WaveNet|93" "TS808_Feather.nam|Phillipe P Maxon-OD808-Feather-ESR0,001.nam|Maxon OD808 Tube Screamer feather — 93 KB|WaveNet|93" "Precision_Drive_Feather.nam|Phillipe P Precision-Drive-Feather-ESR0,001.nam|Precision Drive overdrive feather — 93 KB|WaveNet|93" "Friedman_PowerAmp_Feather.nam|Sascha S FriedmanDSM_PowerAmpEL84_MV10_feather.nam|Friedman DSM power amp feather — 93 KB|WaveNet|93" ) # ── Bank 2: Blues (Tone3000 captures — clean, edge-of-breakup, pushed) ── # Downloaded from ToneHunt/Tone3000 Supabase storage. "remote_name" is the # storage key (not a filename), which the Python script resolves to a # Tone3000 storage URL. The shell script downloads these with direct # Tone3000 storage URLs via curl. # Covers: Fender Princeton, Tweed Deluxe, Twin Reverb, Dumble ODS, # Two-Rock, Marshall Bluesbreaker. MODELS+=( "Fender_Princeton_Clone_Clean.nam|4f81e2b088149c04|Fender Hand-wired Princeton Clone Clean — blues clean (WaveNet A1, 289 KB)|WaveNet|289" "Fender_Princeton_Clone_EOB.nam|68a5f3484e1759ed_a2|Fender Hand-wired Princeton Clone Edge-of-Breakup — blues edge (Slimmable A2, 288 KB)|SlimmableContainer|288" "Fender_Tweed_Dlx_Edge.nam|ga5rj9sxlnk|Fender Tweed Deluxe The Edge (Celestion Blue) — pushed clean (Slimmable A2, 288 KB)|SlimmableContainer|288" "Fender_Twin_Reverb_Clean_A2.nam|tg81uadutjo_a2|Fender Twin Reverb Clean — sparkling blues clean (Slimmable A2, 290 KB)|SlimmableContainer|290" "TwoRock_JTM_Signature.nam|qj3hkgbcwb9|Two-Rock John Mayer Signature Prototype — clean/edge bloom (WaveNet A1, 276 KB)|WaveNet|276" "Dumble_ODS_102_Rock_A1.nam|5bac98f136812852|Dumble ODS #102 Clone Rock setting — pushed blues overdrive (WaveNet A1, 87 KB feather)|WaveNet|87" "Dumble_ODS_102_Rock_A2.nam|5bac98f136812852_a2|Dumble ODS #102 Clone Rock setting — pushed blues OD (Slimmable A2, 289 KB)|SlimmableContainer|289" "Marshall_Bluesbreaker_1962_A2.nam|bbc41ecccdc730fb_a2|Marshall Bluesbreaker 1962 — classic blues rock (Slimmable A2, 289 KB)|SlimmableContainer|289" "Marshall_Bluesbreaker_Pedal.nam|31aeae1256b36839|Marshall Bluesbreaker Pedal (Original) Setting 1 — pedal OD (WaveNet A1, 290 KB)|WaveNet|290" ) # ── Total: 31 models (22 core + 9 blues) ────────────────────────── # ── IR Packs ───────────────────────────────────────────────────────── # Free cabinet IR collections from Archive.org (CC0 / free for personal use) declare -a IR_PACKS=( "God's Cab|https://archive.org/download/godscab-ir-pack/GodsCab_IR_Pack.zip|godscab.zip|100+ cabinet IRs (412, 1960, V30, Greenback, etc.) — CC0 / Public Domain" "Seacow Cabs|https://archive.org/download/seacow-cabs-free-ir-2023/Seacow_Cabs_Free_IRs_2023.zip|seacow.zip|Boutique cab IR pack, 15+ cabs — Free for personal use" ) # ── Helpers ────────────────────────────────────────────────────────── _list_models() { echo -e "\n${CYAN}NAM Models by Genre:${NC}" echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo "" local current_bank="" for entry in "${MODELS[@]}"; do IFS='|' read -r local_name remote_name desc arch size <<< "$entry" printf " %-35s %s\n" "$local_name" "$desc" printf " %-35s %s\n" "" "($arch, ~${size} KB)" echo "" done echo -e "${CYAN}Total: ${#MODELS[@]} models${NC}" } _download_model() { local local_name="$1" remote_name="$2" desc="$3" arch="$4" size="$5" local outfile="$NAM_DIR/$local_name" if [[ -f "$outfile" ]]; then local existing_size existing_size=$(stat -c%s "$outfile" 2>/dev/null || echo 0) if [[ $existing_size -gt 1000 ]]; then ok "SKIP $local_name (already exists, $(( existing_size / 1024 )) KB)" return 0 fi fi info "DL $local_name ($desc)" # URL-encode the remote filename for GitHub raw URL local encoded_name encoded_name=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$remote_name'))" 2>/dev/null || echo "$remote_name") local url="$BASE_URL/$encoded_name" local http_code http_code=$(curl -sL -o "$outfile" -w "%{http_code}" --connect-timeout 10 --max-time 60 "$url" 2>/dev/null || echo "000") if [[ "$http_code" == "200" ]]; then local actual_size actual_size=$(stat -c%s "$outfile" 2>/dev/null || echo 0) if [[ $actual_size -lt 1000 ]]; then err "Downloaded file too small (${actual_size} bytes) — removing" rm -f "$outfile" return 1 fi # Verify it's valid JSON / .nam if python3 -c "import json; json.load(open('$outfile'))" 2>/dev/null; then ok "Downloaded ${local_name} ($(( actual_size / 1024 )) KB)" return 0 else err "Invalid .nam file (not valid JSON) — removing" rm -f "$outfile" return 1 fi else err "HTTP $http_code for $url" rm -f "$outfile" return 1 fi } _download_all_models() { echo -e "\n${CYAN}Downloading ${#MODELS[@]} NAM models to $NAM_DIR${NC}" echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" mkdir -p "$NAM_DIR" local success=0 fail=0 for entry in "${MODELS[@]}"; do IFS='|' read -r local_name remote_name desc arch size <<< "$entry" if _download_model "$local_name" "$remote_name" "$desc" "$arch" "$size"; then ((success++)) else ((fail++)) fi done echo "" info "Models: $success downloaded, $fail failed" return "$fail" } _download_ir_pack() { local name="$1" url="$2" fname="$3" desc="$4" local dest="${IR_DIR}/${fname}" if [[ -d "$IR_DIR/GodsCab_IR_Pack" ]] || [[ -d "$IR_DIR/Seacow_Cabs_Free_IRs_2023" ]]; then local wav_count wav_count=$(find "$IR_DIR" -maxdepth 2 -name '*.wav' 2>/dev/null | wc -l) if [[ $wav_count -gt 10 ]]; then ok "SKIP $name (IRs already extracted, $wav_count .wav files present)" return 0 fi fi info "Downloading $name..." info " Source: $url" if curl -sL -o "$dest" --connect-timeout 10 --max-time 120 "$url" 2>/dev/null; then local size size=$(stat -c%s "$dest" 2>/dev/null || echo 0) if [[ $size -lt 10000 ]]; then err "Downloaded file too small (${size} bytes)" rm -f "$dest" return 1 fi ok "Downloaded ${fname} ($(( size / 1024 / 1024 )) MB)" # Extract ZIP if command -v unzip &>/dev/null; then info "Extracting ${fname} to ${IR_DIR}..." unzip -q -o "$dest" -d "$IR_DIR" 2>/dev/null && { rm -f "$dest" ok "Extracted to ${IR_DIR}/" local wav_count wav_count=$(find "$IR_DIR" -maxdepth 2 -name '*.wav' 2>/dev/null | wc -l) info "IR files installed: $wav_count" } || { warn "Extraction had issues — ZIP saved at ${dest}" } else warn "unzip not found — ZIP saved at ${dest}" fi return 0 else err "Failed to download $name from $url" return 1 fi } _download_all_irs() { echo -e "\n${CYAN}Downloading IR packs to $IR_DIR${NC}" echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" mkdir -p "$IR_DIR" local success=0 fail=0 for i in "${!IR_PACKS[@]}"; do IFS='|' read -r name url fname desc <<< "${IR_PACKS[$i]}" if _download_ir_pack "$name" "$url" "$fname" "$desc"; then ((success++)) else ((fail++)) fi done echo "" info "IR packs: $success downloaded, $fail failed" # Count installed IRs local wav_count wav_count=$(find "$IR_DIR" -maxdepth 2 -name '*.wav' 2>/dev/null | wc -l) info "Total .wav IR files in $IR_DIR: $wav_count" } _show_counts() { echo "" local model_count nam_count ir_count model_count=$(find "$NAM_DIR" -maxdepth 1 -name '*.nam' 2>/dev/null | wc -l) nam_count=$(find "$HOME/.pedal/nam" -maxdepth 1 -name '*.nam' 2>/dev/null | wc -l) ir_count=$(find "$IR_DIR" -maxdepth 2 -name '*.wav' 2>/dev/null | wc -l) info "Models in $NAM_DIR: $model_count .nam files" info "Models in ~/.pedal/nam: $nam_count .nam files (legacy)" info "IRs in $IR_DIR: $ir_count .wav files" } # ── Main ───────────────────────────────────────────────────────────── main() { mkdir -p "$NAM_DIR" "$IR_DIR" case "${1:-}" in --list|-l) _list_models _show_counts ;; --models|-m) _download_all_models _show_counts ;; --irs|-i) _download_all_irs _show_counts ;; ""|--all|-a) _download_all_models echo "" _download_all_irs echo "" _show_counts echo "" echo -e "${GREEN}╔══════════════════════════════════════════════════════╗${NC}" echo -e "${GREEN}║ Download complete! ║${NC}" echo -e "${GREEN}╚══════════════════════════════════════════════════════╝${NC}" echo "" echo " Next steps:" echo " 1. Run the inventory report:" echo " python3 scripts/models/nam/build_inventory.py" echo "" echo " 2. Verify models load correctly:" echo " python3 -c \"from src.dsp.nam_host import NAMHost;" echo " h = NAMHost(models_dir='$NAM_DIR');" echo " for m in h.list_available_models(): print(m.name, m.architecture, f'{m.size_mb:.1f}MB')\"" echo "" echo " 3. Check IRs:" echo " python3 -c \"from pathlib import Path;" echo " irs = list(Path('$IR_DIR').rglob('*.wav'));" echo " print(f'{len(irs)} IRs loaded')\"" echo "" ;; --help|-h) echo "Usage: $0 [OPTION]" echo "" echo "Options:" echo " --list, -l List models and IRs (without downloading)" echo " --models, -m Download NAM models only" echo " --irs, -i Download IR packs only" echo " --all, -a Download everything (default)" echo " --help, -h Show this help" echo "" echo "Environment:" echo " NAM_DIR target for .nam files (default: ~/.pedal/models)" echo " IR_DIR target for .wav IRs (default: ~/.pedal/irs)" ;; *) err "Unknown option: $1" echo "Usage: $0 [--list|--models|--irs|--all|--help]" exit 1 ;; esac } main "$@"