#!/usr/bin/env python3 """Generate factory presets for Keys, Vocals, and Backing Tracks channels. Creates 3 banks × 4 presets per channel under presets/factory//. Bank numbers start at 21 for Keys, 24 for Vocals, 27 for Backing Tracks to avoid collisions with the existing 0-20 factory bank range. """ import json import os import sys FACTORY_DIR = os.path.join( os.path.dirname(os.path.dirname(__file__)), "presets", "factory" ) # ── IR path shortcuts ─────────────────────────────────────────────────── IR = { "vintage": "~/.pedal/irs/vintage-1x12.wav", "british": "~/.pedal/irs/british-4x12.wav", "american": "~/.pedal/irs/american-2x12.wav", "modern": "~/.pedal/irs/modern-4x12.wav", "jazz": "~/.pedal/irs/jazz-1x15.wav", "boutique": "~/.pedal/irs/boutique-1x12.wav", "mini": "~/.pedal/irs/mini-1x8.wav", "bass_thick": "~/.pedal/irs/bass-thick_classic.wav", } # ── Helper builders ──────────────────────────────────────────────────── def noise_gate(threshold=0.01): return {"fx_type": "noise_gate", "enabled": True, "bypass": False, "params": {"threshold": threshold}, "nam_model_path": "", "ir_file_path": ""} def compressor(threshold=0.3, ratio=4.0, gain=0.8): return {"fx_type": "compressor", "enabled": True, "bypass": False, "params": {"threshold": threshold, "ratio": ratio, "gain": gain}, "nam_model_path": "", "ir_file_path": ""} def boost(gain_db=6.0): return {"fx_type": "boost", "enabled": True, "bypass": False, "params": {"gain_db": gain_db}, "nam_model_path": "", "ir_file_path": ""} def overdrive(drive=0.5, gain=0.7, tone=0.5): return {"fx_type": "overdrive", "enabled": True, "bypass": False, "params": {"drive": drive, "tone": tone, "gain": gain}, "nam_model_path": "", "ir_file_path": ""} def eq(bass=0.5, mid=0.5, treble=0.5): return {"fx_type": "eq", "enabled": True, "bypass": False, "params": {"bass": bass, "mid": mid, "treble": treble}, "nam_model_path": "", "ir_file_path": ""} def ir_cab(ir_path): return {"fx_type": "ir_cab", "enabled": True, "bypass": False, "params": {"wet": 1.0, "dry": 0.0}, "nam_model_path": "", "ir_file_path": ir_path} def delay(time=400.0, feedback=0.3, mix=0.25): return {"fx_type": "delay", "enabled": True, "bypass": False, "params": {"time": time, "feedback": feedback, "mix": mix}, "nam_model_path": "", "ir_file_path": ""} def reverb(decay=0.4, mix=0.25, subtype=""): fx = {"fx_type": "reverb", "enabled": True, "bypass": False, "nam_model_path": "", "ir_file_path": ""} params = {"decay": decay, "mix": mix} if subtype: params["subtype"] = subtype fx["params"] = params return fx def chorus(rate=0.35, depth=0.5, mix=0.3): return {"fx_type": "chorus", "enabled": True, "bypass": False, "params": {"rate": rate, "depth": depth, "mix": mix}, "nam_model_path": "", "ir_file_path": ""} def phaser(rate=0.3, depth=0.5, feedback=0.3): return {"fx_type": "phaser", "enabled": True, "bypass": False, "params": {"rate": rate, "depth": depth, "feedback": feedback}, "nam_model_path": "", "ir_file_path": ""} def tremolo(rate=3.0, depth=0.4): return {"fx_type": "tremolo", "enabled": True, "bypass": False, "params": {"rate": rate, "depth": depth}, "nam_model_path": "", "ir_file_path": ""} def volume(level=0.85): return {"fx_type": "volume", "enabled": True, "bypass": False, "params": {"level": level}, "nam_model_path": "", "ir_file_path": ""} def pitch_shifter(semitones=0, mix=0.5): return {"fx_type": "pitch_shifter", "enabled": True, "bypass": False, "params": {"semitones": semitones, "mix": mix}, "nam_model_path": "", "ir_file_path": ""} def stereo_widener(width=0.5, mix=0.5): return {"fx_type": "stereo_widener", "enabled": True, "bypass": False, "params": {"width": width, "mix": mix}, "nam_model_path": "", "ir_file_path": ""} def auto_pan(rate=0.3, depth=0.5): return {"fx_type": "auto_pan", "enabled": True, "bypass": False, "params": {"rate": rate, "depth": depth}, "nam_model_path": "", "ir_file_path": ""} def high_pass_filter(cutoff=80.0): return {"fx_type": "high_pass_filter", "enabled": True, "bypass": False, "params": {"cutoff": cutoff}, "nam_model_path": "", "ir_file_path": ""} def low_pass_filter(cutoff=8000.0): return {"fx_type": "low_pass_filter", "enabled": True, "bypass": False, "params": {"cutoff": cutoff}, "nam_model_path": "", "ir_file_path": ""} def de_esser(threshold=0.5, reduction=0.4): return {"fx_type": "de_esser", "enabled": True, "bypass": False, "params": {"threshold": threshold, "reduction": reduction}, "nam_model_path": "", "ir_file_path": ""} def shimmer_reverb(decay=0.6, mix=0.3): return {"fx_type": "shimmer_reverb", "enabled": True, "bypass": False, "params": {"decay": decay, "mix": mix}, "nam_model_path": "", "ir_file_path": ""} def multi_tap_delay(time=400.0, feedback=0.3, mix=0.25, taps=3): return {"fx_type": "multi_tap_delay", "enabled": True, "bypass": False, "params": {"time": time, "feedback": feedback, "mix": mix, "taps": taps}, "nam_model_path": "", "ir_file_path": ""} def rotary_speaker(rate=1.5, mix=0.4): return {"fx_type": "rotary_speaker", "enabled": True, "bypass": False, "params": {"rate": rate, "mix": mix}, "nam_model_path": "", "ir_file_path": ""} def expander(threshold=0.2, ratio=2.0): return {"fx_type": "expander", "enabled": True, "bypass": False, "params": {"threshold": threshold, "ratio": ratio}, "nam_model_path": "", "ir_file_path": ""} # ── Channel definitions ───────────────────────────────────────────────── CHANNELS = [ { "slug": "keys", "label": "Keys", "bank_start": 21, "banks": [ { "name": "Piano & Electric", "presets": [ { "name": "Grand Piano Clean", "master_volume": 0.82, "chain": [ noise_gate(0.005), compressor(0.2, 5.0, 0.6), eq(0.5, 0.5, 0.55), reverb(0.35, 0.2, "hall"), volume(0.85), ], }, { "name": "Warm Rhodes", "master_volume": 0.8, "chain": [ noise_gate(0.005), compressor(0.25, 4.0, 0.65), chorus(0.3, 0.4, 0.25), eq(0.45, 0.55, 0.5), reverb(0.4, 0.22, "hall"), volume(0.82), ], }, { "name": "Wurli Punch", "master_volume": 0.78, "chain": [ noise_gate(0.008), compressor(0.3, 4.5, 0.7), tremolo(4.0, 0.3), eq(0.55, 0.4, 0.6), reverb(0.3, 0.18, "room"), volume(0.8), ], }, { "name": "Stage EP Layer", "master_volume": 0.82, "chain": [ noise_gate(0.005), compressor(0.2, 5.0, 0.55), stereo_widener(0.4, 0.3), eq(0.4, 0.5, 0.6), chorus(0.25, 0.35, 0.2), reverb(0.45, 0.28, "hall"), volume(0.84), ], }, ], }, { "name": "Synth Pads & Leads", "presets": [ { "name": "Analog Pad", "master_volume": 0.78, "chain": [ noise_gate(0.005), compressor(0.25, 4.5, 0.65), stereo_widener(0.6, 0.4), chorus(0.2, 0.45, 0.35), auto_pan(0.15, 0.3), reverb(0.55, 0.35, "hall"), volume(0.8), ], }, { "name": "Brass Synth Lead", "master_volume": 0.85, "chain": [ noise_gate(0.01), compressor(0.3, 3.5, 0.85), boost(3.0), eq(0.45, 0.35, 0.7), delay(420, 0.25, 0.2), reverb(0.4, 0.22, "hall"), volume(0.88), ], }, { "name": "Arp Texture", "master_volume": 0.76, "chain": [ noise_gate(0.005), compressor(0.2, 5.0, 0.55), eq(0.4, 0.55, 0.65), multi_tap_delay(300, 0.2, 0.3, 3), reverb(0.5, 0.3, "hall"), volume(0.78), ], }, { "name": "Wobble Bass Synth", "master_volume": 0.72, "chain": [ noise_gate(0.012), compressor(0.35, 6.0, 0.6), low_pass_filter(2000.0), eq(0.65, 0.25, 0.4), reverb(0.2, 0.1, "room"), volume(0.74), ], }, ], }, { "name": "Organ & Vintage Keys", "presets": [ { "name": "B3 Rotary Slow", "master_volume": 0.8, "chain": [ noise_gate(0.005), compressor(0.2, 4.5, 0.65), rotary_speaker(0.8, 0.4), eq(0.5, 0.5, 0.55), reverb(0.3, 0.15, "room"), volume(0.82), ], }, { "name": "B3 Rotary Fast", "master_volume": 0.82, "chain": [ noise_gate(0.005), compressor(0.2, 4.5, 0.7), rotary_speaker(5.0, 0.5), eq(0.45, 0.55, 0.5), reverb(0.25, 0.12, "room"), volume(0.84), ], }, { "name": "Clavinet Funk", "master_volume": 0.76, "chain": [ noise_gate(0.008), compressor(0.35, 5.0, 0.6), eq(0.6, 0.3, 0.7), phaser(0.2, 0.35, 0.2), reverb(0.2, 0.1, "room"), volume(0.78), ], }, { "name": "Vox Continental", "master_volume": 0.82, "chain": [ noise_gate(0.005), compressor(0.25, 4.0, 0.65), eq(0.5, 0.45, 0.6), tremolo(3.5, 0.3), reverb(0.35, 0.18, "hall"), volume(0.84), ], }, ], }, ], }, { "slug": "vocals", "label": "Vocals", "bank_start": 24, "banks": [ { "name": "Lead Vocals", "presets": [ { "name": "Vocal Standard", "master_volume": 0.82, "chain": [ high_pass_filter(80.0), de_esser(0.5, 0.35), compressor(0.25, 4.0, 0.7), eq(0.45, 0.55, 0.55), reverb(0.35, 0.22, "hall"), volume(0.85), ], }, { "name": "Warm Lead", "master_volume": 0.85, "chain": [ high_pass_filter(75.0), de_esser(0.45, 0.4), compressor(0.2, 4.5, 0.75), eq(0.5, 0.5, 0.45), reverb(0.4, 0.25, "hall"), delay(380, 0.2, 0.15), volume(0.88), ], }, { "name": "Big Ballad", "master_volume": 0.84, "chain": [ high_pass_filter(70.0), de_esser(0.5, 0.3), compressor(0.22, 5.0, 0.8), eq(0.45, 0.5, 0.5), shimmer_reverb(0.55, 0.3), delay(450, 0.3, 0.2), volume(0.86), ], }, { "name": "Dry & Present", "master_volume": 0.82, "chain": [ high_pass_filter(90.0), de_esser(0.55, 0.35), compressor(0.28, 3.5, 0.85), eq(0.4, 0.6, 0.55), expander(0.15, 1.5), volume(0.84), ], }, ], }, { "name": "Harmony & Backing", "presets": [ { "name": "Airy Harmonies", "master_volume": 0.78, "chain": [ high_pass_filter(100.0), de_esser(0.5, 0.3), compressor(0.2, 4.0, 0.6), eq(0.4, 0.5, 0.65), stereo_widener(0.5, 0.35), reverb(0.45, 0.25, "hall"), volume(0.8), ], }, { "name": "Doubler Effect", "master_volume": 0.8, "chain": [ high_pass_filter(90.0), compressor(0.25, 4.0, 0.65), eq(0.45, 0.5, 0.55), chorus(0.2, 0.3, 0.35), delay(220, 0.05, 0.25), reverb(0.3, 0.18, "room"), volume(0.82), ], }, { "name": "Wide Backing Stack", "master_volume": 0.76, "chain": [ high_pass_filter(80.0), de_esser(0.45, 0.35), compressor(0.2, 4.5, 0.55), eq(0.4, 0.55, 0.6), stereo_widener(0.7, 0.4), reverb(0.5, 0.28, "hall"), volume(0.78), ], }, { "name": "Soft Pad Vox", "master_volume": 0.74, "chain": [ high_pass_filter(120.0), compressor(0.2, 5.0, 0.5), eq(0.4, 0.5, 0.6), chorus(0.18, 0.25, 0.4), shimmer_reverb(0.5, 0.35), volume(0.76), ], }, ], }, { "name": "Vocal FX", "presets": [ { "name": "Slap Delay", "master_volume": 0.82, "chain": [ high_pass_filter(80.0), de_esser(0.5, 0.3), compressor(0.25, 4.0, 0.7), eq(0.45, 0.5, 0.55), delay(120, 0.15, 0.35), reverb(0.25, 0.12, "room"), volume(0.84), ], }, { "name": "Ping Pong Echo", "master_volume": 0.8, "chain": [ high_pass_filter(80.0), de_esser(0.5, 0.3), compressor(0.25, 4.0, 0.65), eq(0.45, 0.5, 0.55), delay(380, 0.35, 0.3), reverb(0.3, 0.15, "hall"), volume(0.82), ], }, { "name": "Shimmer Wash", "master_volume": 0.78, "chain": [ high_pass_filter(100.0), de_esser(0.55, 0.25), compressor(0.2, 4.0, 0.6), eq(0.4, 0.5, 0.6), shimmer_reverb(0.65, 0.35), delay(500, 0.25, 0.2), volume(0.8), ], }, { "name": "Radio Filter", "master_volume": 0.76, "chain": [ high_pass_filter(300.0), low_pass_filter(3500.0), compressor(0.3, 6.0, 0.65), eq(0.3, 0.1, 0.2), reverb(0.2, 0.08, "room"), volume(0.78), ], }, ], }, ], }, { "slug": "backing_tracks", "label": "Backing Tracks", "bank_start": 27, "banks": [ { "name": "Mix Ready", "presets": [ { "name": "Stereo Master Bus", "master_volume": 0.82, "chain": [ high_pass_filter(30.0), low_pass_filter(18000.0), compressor(0.2, 4.0, 0.6), eq(0.5, 0.5, 0.5), stereo_widener(0.3, 0.25), volume(0.85), ], }, { "name": "Kick & Bass Focus", "master_volume": 0.8, "chain": [ high_pass_filter(25.0), compressor(0.25, 5.0, 0.55), eq(0.65, 0.35, 0.45), volume(0.82), ], }, { "name": "Full Mix Compressed", "master_volume": 0.78, "chain": [ high_pass_filter(30.0), compressor(0.15, 6.0, 0.5), eq(0.5, 0.5, 0.55), multi_tap_delay(200, 0.08, 0.1, 2), reverb(0.15, 0.08, "room"), volume(0.8), ], }, { "name": "Transparent Mix", "master_volume": 0.85, "chain": [ high_pass_filter(25.0), compressor(0.2, 3.5, 0.65), eq(0.5, 0.5, 0.5), stereo_widener(0.2, 0.15), volume(0.88), ], }, ], }, { "name": "Click & Guide", "presets": [ { "name": "Click Track + Backing", "master_volume": 0.78, "chain": [ high_pass_filter(40.0), compressor(0.3, 4.0, 0.55), eq(0.5, 0.4, 0.6), reverb(0.2, 0.08, "room"), volume(0.8), ], }, { "name": "Guide Vox Monitor", "master_volume": 0.8, "chain": [ high_pass_filter(80.0), de_esser(0.5, 0.3), compressor(0.25, 4.0, 0.65), eq(0.45, 0.5, 0.5), reverb(0.3, 0.15, "room"), volume(0.82), ], }, { "name": "Scratch Guitar Mix", "master_volume": 0.76, "chain": [ noise_gate(0.008), compressor(0.3, 4.0, 0.6), eq(0.5, 0.45, 0.55), reverb(0.2, 0.1, "room"), volume(0.78), ], }, { "name": "Stereo Reference", "master_volume": 0.82, "chain": [ high_pass_filter(25.0), low_pass_filter(20000.0), compressor(0.18, 4.5, 0.55), eq(0.5, 0.5, 0.55), stereo_widener(0.4, 0.25), volume(0.84), ], }, ], }, { "name": "Ambient Pads", "presets": [ { "name": "Wash Pad", "master_volume": 0.74, "chain": [ noise_gate(0.005), compressor(0.2, 5.0, 0.5), eq(0.4, 0.55, 0.5), chorus(0.15, 0.5, 0.4), stereo_widener(0.6, 0.4), shimmer_reverb(0.6, 0.35), volume(0.76), ], }, { "name": "Texture Swell", "master_volume": 0.72, "chain": [ noise_gate(0.005), expander(0.1, 3.0), eq(0.4, 0.55, 0.55), auto_pan(0.12, 0.35), reverb(0.65, 0.4, "hall"), volume(0.74), ], }, { "name": "Drone Bed", "master_volume": 0.7, "chain": [ noise_gate(0.005), low_pass_filter(1000.0), eq(0.55, 0.4, 0.35), chorus(0.12, 0.4, 0.45), reverb(0.7, 0.45, "hall"), volume(0.72), ], }, { "name": "Cinematic Swell", "master_volume": 0.76, "chain": [ noise_gate(0.005), compressor(0.15, 5.0, 0.5), eq(0.4, 0.5, 0.6), stereo_widener(0.7, 0.35), shimmer_reverb(0.55, 0.4), delay(600, 0.2, 0.15), volume(0.78), ], }, ], }, ], }, ] def generate(): """Write all factory preset JSON files to presets/factory//bank_N/.""" total_presets = 0 total_banks = 0 for channel in CHANNELS: ch_dir = os.path.join(FACTORY_DIR, channel["slug"]) bn = channel["bank_start"] for bank_idx, bank_def in enumerate(channel["banks"]): bank_num = bn + bank_idx bank_dir = os.path.join(ch_dir, f"bank_{bank_num}") os.makedirs(bank_dir, exist_ok=True) # bank.json bank_meta = { "number": bank_num, "name": bank_def["name"], "preset_count": len(bank_def["presets"]), } with open(os.path.join(bank_dir, "bank.json"), "w") as f: json.dump(bank_meta, f, indent=2) f.write("\n") # presets for prog, preset_data in enumerate(bank_def["presets"]): preset_json = { "name": preset_data["name"], "bank": bank_num, "program": prog, "master_volume": preset_data["master_volume"], "chain": preset_data["chain"], "midi_mappings": {}, } path = os.path.join(bank_dir, f"preset_{prog}.json") with open(path, "w") as f: json.dump(preset_json, f, indent=2) f.write("\n") total_presets += 1 total_banks += 1 print(f"✅ {channel['label']}: {total_banks} banks so far, {total_presets} presets") print(f"\n✅ Generated {total_presets} presets across {total_banks} banks in 3 channels:") for channel in CHANNELS: print(f" {channel['label']}: {len(channel['banks'])} banks × {len(channel['banks'][0]['presets'])} presets = {len(channel['banks']) * len(channel['banks'][0]['presets'])} presets") if __name__ == "__main__": # Guard against running from wrong cwd if not os.path.isdir(FACTORY_DIR): # Try resolving relative to the script location script_dir = os.path.dirname(os.path.abspath(__file__)) FACTORY_DIR_ALT = os.path.join( os.path.dirname(os.path.dirname(script_dir)), "presets", "factory" ) if os.path.isdir(FACTORY_DIR_ALT): FACTORY_DIR = FACTORY_DIR_ALT else: print(f"Error: factory directory not found at {FACTORY_DIR} or {FACTORY_DIR_ALT}") sys.exit(1) generate()