Build IR convolution engine
- Full FFT overlap-add IR convolution in IRLoader (process(), set_mix(), toggle) - Lazy FFT computation — IR FFT padded to correct block+ir size on first process() - Wet/dry mix control, enabled/disabled toggle with tail clearing - Fixed pipeline._apply_ir_cab() to delegate to IRLoader.process() instead of poking internals (old code had array-size mismatch bug: IR FFT at ir_len vs block FFT at conv_size) - 46 tests: loading, convolution correctness, overlap-add state, mix, toggle, directory listing, performance budget (all <5ms even at 8192 taps), edge cases - scripts/download_irs.sh: free IR pack downloader (God's Cab, Seacow)
This commit is contained in:
Executable
+219
@@ -0,0 +1,219 @@
|
||||
#!/usr/bin/env bash
|
||||
# ──────────────────────────────────────────────────────────────────────
|
||||
# Pi Multi-FX Pedal — IR Pack Downloader
|
||||
# ──────────────────────────────────────────────────────────────────────
|
||||
# Downloads free impulse response packs for cabinet simulation.
|
||||
# Run: ./scripts/download_irs.sh # interactive choose & download
|
||||
# Run: ./scripts/download_irs.sh --list # list available packs
|
||||
# Run: ./scripts/download_irs.sh --all # download everything
|
||||
#
|
||||
# All IRs are Creative Commons / public domain / free for
|
||||
# personal use. Check each pack's license for commercial use.
|
||||
# ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
IR_DIR="${HOME}/.pedal/irs"
|
||||
|
||||
# ── Terminal colours ────────────────────────────────────────────────
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m' # No Colour
|
||||
|
||||
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; }
|
||||
|
||||
# ── Pre-flight ──────────────────────────────────────────────────────
|
||||
|
||||
CURL=""
|
||||
for cmd in curl wget; do
|
||||
if command -v "$cmd" &>/dev/null; then
|
||||
CURL="$cmd"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$CURL" ]; then
|
||||
err "Neither curl nor wget found. Install one of them first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
download() {
|
||||
local url="$1"
|
||||
local dest="$2"
|
||||
if [ "$CURL" = "curl" ]; then
|
||||
curl -fsSL -o "$dest" "$url"
|
||||
else
|
||||
wget -q -O "$dest" "$url"
|
||||
fi
|
||||
}
|
||||
|
||||
# Ensure IR directory exists
|
||||
mkdir -p "$IR_DIR"
|
||||
|
||||
# ── Pack definitions ────────────────────────────────────────────────
|
||||
# Each pack: "name|source_url|filename|description"
|
||||
# Source URLs verified working. Report broken ones at the project
|
||||
# issue tracker.
|
||||
|
||||
declare -a 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"
|
||||
)
|
||||
|
||||
# ── Actions ──────────────────────────────────────────────────────────
|
||||
|
||||
list_packs() {
|
||||
echo ""
|
||||
echo "Available free IR packs:"
|
||||
echo "────────────────────────"
|
||||
for i in "${!PACKS[@]}"; do
|
||||
IFS='|' read -r name url fname desc <<< "${PACKS[$i]}"
|
||||
printf " [%d] %s\n" "$((i+1))" "$name"
|
||||
printf " %s\n" "$desc"
|
||||
echo ""
|
||||
done
|
||||
echo "Manual download options (visit URLs in browser):"
|
||||
echo ""
|
||||
echo " • York Audio — Free 412 M25 IRS:"
|
||||
echo " https://www.yorkaudio.co/product-page/free-412-m25-ir"
|
||||
echo ""
|
||||
echo " • ML Sound Lab — Free IR pack:"
|
||||
echo " https://mlsoundlab.com/pages/free-stuff"
|
||||
echo ""
|
||||
echo " • OwnHammer — Free 412 GNR:"
|
||||
echo " https://www.ownhammer.com/free-gnr-ir/"
|
||||
echo ""
|
||||
echo " • ValhallIR — $1 free vintage cab IRs:"
|
||||
echo " https://valhallir.com/"
|
||||
echo ""
|
||||
echo " • Lancaster Audio — Free Bass IRs:"
|
||||
echo " https://www.lancasteraudio.com/free-irs"
|
||||
echo ""
|
||||
}
|
||||
|
||||
download_pack() {
|
||||
local idx="$1"
|
||||
if [ "$idx" -lt 0 ] || [ "$idx" -ge "${#PACKS[@]}" ]; then
|
||||
err "Invalid pack index: $idx"
|
||||
return 1
|
||||
fi
|
||||
|
||||
IFS='|' read -r name url fname desc <<< "${PACKS[$idx]}"
|
||||
local dest="${IR_DIR}/${fname}"
|
||||
|
||||
info "Downloading ${name}..."
|
||||
info " Source: ${url}"
|
||||
info " To: ${dest}"
|
||||
echo ""
|
||||
|
||||
if download "$url" "$dest"; then
|
||||
ok "Downloaded ${fname} (${name})"
|
||||
# Extract ZIP to IR directory
|
||||
if command -v unzip &>/dev/null; then
|
||||
info "Extracting ${fname} to ${IR_DIR}..."
|
||||
unzip -q -o "$dest" -d "$IR_DIR"
|
||||
rm -f "$dest"
|
||||
ok "Extracted IRs to ${IR_DIR}/"
|
||||
else
|
||||
warn "unzip not found — ZIP saved at ${dest}"
|
||||
warn "Manually extract: cd ${IR_DIR} && unzip ${fname}"
|
||||
fi
|
||||
else
|
||||
err "Failed to download ${name} from ${url}"
|
||||
info "The URL may have changed. Try downloading manually from the"
|
||||
info "source above, or check the project README for updated links."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
download_all() {
|
||||
for i in "${!PACKS[@]}"; do
|
||||
download_pack "$i"
|
||||
echo ""
|
||||
done
|
||||
}
|
||||
|
||||
show_count() {
|
||||
local count
|
||||
count=$(find "$IR_DIR" -maxdepth 1 -name '*.wav' 2>/dev/null | wc -l)
|
||||
info "IR directory: ${IR_DIR}"
|
||||
info "Installed IRs: ${count} .wav files"
|
||||
if [ "$count" -gt 0 ]; then
|
||||
echo ""
|
||||
find "$IR_DIR" -maxdepth 1 -name '*.wav' -printf ' • %f\n' | sort
|
||||
fi
|
||||
}
|
||||
|
||||
# ── CLI ──────────────────────────────────────────────────────────────
|
||||
|
||||
case "${1:-}" in
|
||||
--list|-l)
|
||||
list_packs
|
||||
show_count
|
||||
;;
|
||||
--all|-a)
|
||||
download_all
|
||||
show_count
|
||||
;;
|
||||
--help|-h)
|
||||
echo "Usage: $0 [OPTION]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --list, -l List available IR packs and installed IRs"
|
||||
echo " --all, -a Download all available IR packs"
|
||||
echo " --help, -h Show this help"
|
||||
echo ""
|
||||
echo "Without options: interactive selection"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
# Interactive
|
||||
list_packs
|
||||
echo ""
|
||||
echo "Download which pack? (1-${#PACKS[@]}, 'a' = all, 'q' = quit)"
|
||||
read -r -p "> " choice
|
||||
|
||||
case "$choice" in
|
||||
q|Q|quit|exit)
|
||||
info "Exiting."
|
||||
exit 0
|
||||
;;
|
||||
a|A|all)
|
||||
download_all
|
||||
;;
|
||||
*)
|
||||
if [[ "$choice" =~ ^[0-9]+$ ]]; then
|
||||
download_pack "$((choice - 1))"
|
||||
else
|
||||
err "Invalid choice: $choice"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
show_count
|
||||
;;
|
||||
esac
|
||||
|
||||
# ── Post-install instructions ───────────────────────────────────────
|
||||
|
||||
echo ""
|
||||
info "All done!"
|
||||
echo ""
|
||||
echo " IRs are in: ${IR_DIR}/"
|
||||
echo ""
|
||||
echo " To use an IR in a preset:"
|
||||
echo " 1. Set FXBlock.fx_type = FXType.IR_CAB"
|
||||
echo " 2. Set FXBlock.ir_file_path = \"${IR_DIR}/your_ir.wav\""
|
||||
echo " 3. Optionally set FXBlock.params = {\"wet\": 1.0, \"dry\": 0.0}"
|
||||
echo " 4. The IR loader will auto-detect sample rate and taps"
|
||||
echo ""
|
||||
echo " To verify IR loading:"
|
||||
echo " python -c \"from src.dsp.ir_loader import IRLoader;"
|
||||
echo " ir = IRLoader(); print(ir.get_irs())\""
|
||||
Reference in New Issue
Block a user