
Tim Henson Presets
- 2 installs
- 12 repo stars
- Updated July 21, 2026
- arjunmahishi/dotfiles
tim-henson-presets is a skill that generates Neural DSP Archetype Tim Henson X audio-plugin preset files from AI-supplied parameter overrides.
About
A niche skill that generates Neural DSP Archetype Tim Henson X audio-plugin preset files from AI-supplied parameter overrides. The AI decides the tone and exact parameter overrides while a bundled Python script writes the binary preset file safely, always creating a new file and rejecting unknown parameter keys. A user invokes it with a natural-language tone request to produce a new guitar-amp preset.
- Generates Neural DSP Archetype Tim Henson X guitar-amp preset files from AI-supplied JSON parameter overrides
- Separates responsibilities: the AI picks the tone, a Python script writes the binary preset safely
- Reads JSON overrides from stdin, rejects unknown keys, and never edits existing source presets
Tim Henson Presets by the numbers
- 2 all-time installs (skills.sh)
- Ranked #1,839 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
tim-henson-presets capabilities & compatibility
- Capabilities
- automation
- Platforms
- macOS
What tim-henson-presets says it does
- AI decides the tone and exact parameter overrides. - Script writes the binary preset file safely.
Never edit existing source presets.
npx skills add https://github.com/arjunmahishi/dotfiles --skill tim-henson-presetsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2 |
|---|---|
| repo stars | ★ 12 |
| Last updated | July 21, 2026 |
| Repository | arjunmahishi/dotfiles ↗ |
What it does
Generate a Neural DSP Tim Henson X guitar-amp preset file from a natural-language tone request via a template-copy Python workflow.
Who is it for?
Producing new Neural DSP Tim Henson X presets from a tone description without hand-editing binary files.
Skip if: Editing existing source presets, which the skill forbids in favor of creating a new file.
When should I use this skill?
When the user gives a natural-language tone request and wants a new Tim Henson X guitar-amp preset generated.
What you get
A new, safely written Tim Henson X preset file reflecting the requested tone.
- A new .xml preset file in the AI generated presets directory
By the numbers
- 5 rules governing preset generation
Files
Tim Henson Preset Generator
This skill separates responsibilities:
- AI decides the tone and exact parameter overrides.
- Script writes the binary preset file safely.
Rules
1. Never edit existing source presets. 2. Always create a new file in /Library/Audio/Presets/Neural DSP/Archetype Tim Henson X/AI generated/. 3. Use Default.xml as fallback template when no template path is provided. 4. JSON overrides are read from stdin. 5. Unknown parameter keys are rejected.
Commands
Generate from stdin JSON and optional template:
python3 "/Users/arjunmahishi/.config/opencode/skills/tim-henson-presets/tim_henson_skill.py" generate --name "my_preset" --template "/Library/Audio/Presets/Neural DSP/Archetype Tim Henson X/User/clean_fingerstyle.xml"Generate from fallback Default.xml:
python3 "/Users/arjunmahishi/.config/opencode/skills/tim-henson-presets/tim_henson_skill.py" generate --name "my_preset"Build index cache:
python3 "/Users/arjunmahishi/.config/opencode/skills/tim-henson-presets/tim_henson_skill.py" indexPrint authoritative parameter names from a template:
python3 "/Users/arjunmahishi/.config/opencode/skills/tim-henson-presets/tim_henson_skill.py" paramsJSON Input Shape
The script reads a JSON object from stdin where keys are parameter names and values are booleans, numbers, or strings.
{
"reverbActive": true,
"reverbMix": 0.42,
"delayTime": 500,
"leadGain": 0.73
}Parameter Names (Reference)
Use only real plugin parameter names. Common groups:
- Global:
inputGain,outputGain,transpose,gateActive,gateThreshold,doublerActive,doublerSpread - Pre FX:
boostActive,boostGain,boostBass,boostTreble,boostLevel,compActive,compCompression,compAttack,compLevel,overdriveActive,overdriveDrive,overdriveTone,overdriveLevel - Amp:
selectedAmp,leadGain,leadBass,leadMiddle,leadTreble,leadPresence,leadMaster,leadOutput,rhythmGain,rhythmBass,rhythmMiddle,rhythmTreble,rhythmPresence,rhythmOutput,rhythmChannel,acousticGain,acousticBlend,acousticBass,acousticMiddle,acousticTreble,acousticPresence,acousticOutput - EQ:
leadEQActive,leadEQBand1..leadEQBand9,leadEQHpf,leadEQLpf,rhythmEQActive,rhythmEQBand1..rhythmEQBand9,rhythmEQHpf,rhythmEQLpf,acousticEQActive,acousticEQBand1..acousticEQBand9,acousticEQHpf,acousticEQLpf - Cab:
cabStereo,leftCabActive,leftCab0MicType,leftCab1MicType,leftCabDistance,leftCabPosition,leftCabPhase,leftCabPan,leftCabMicLevel,leftRoomActive,leftRoomMicLevel,rightCabActive,rightCab0MicType,rightCab1MicType,rightCabDistance,rightCabPosition,rightCabPhase,rightCabPan,rightCabMicLevel,rightRoomActive,rightRoomMicLevel,leftCab0ChosenIRFilePath,leftCab1ChosenIRFilePath,rightCab0ChosenIRFilePath,rightCab1ChosenIRFilePath - Delay:
delayActive,delayTime,delayTempo,delaySync,delayNote,delayMode,delayType,delayFeedback,delayMix,delayAmount,delayHighCut,delayLowCut - Reverb:
reverbActive,reverbMix,reverbDecay,reverbHighCut,reverbLowCut,reverbShimmer - Chorus:
chorusActive,chorusMix - Multivoicer:
multivoicerMode,multivoicerRoot,multivoicerQuantize,multivoicerTuning,multivoicerTone,multivoicerWidth,multivoicerOutput,multivoicerMidiEnabled,multivoicerUnison,multivoicerVoice1Active,multivoicerVoice1Level,multivoicerVoice1Pan,multivoicerVoice1Delay,multivoicerVoice1Detune,multivoicerVoice1Interval,multivoicerVoice1Semitones,multivoicerVoice2Active,multivoicerVoice2Level,multivoicerVoice2Pan,multivoicerVoice2Delay,multivoicerVoice2Detune,multivoicerVoice2Interval,multivoicerVoice2Semitones,multivoicerVoice3Active,multivoicerVoice3Level,multivoicerVoice3Pan,multivoicerVoice3Delay,multivoicerVoice3Detune,multivoicerVoice3Interval,multivoicerVoice3Semitones,multivoicerVoice4Active,multivoicerVoice4Level,multivoicerVoice4Pan,multivoicerVoice4Delay,multivoicerVoice4Detune,multivoicerVoice4Interval,multivoicerVoice4Semitones
For full authoritative keys from your installed version, run the params command.
presets_db.json
__pycache__
*.pyc
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
import math
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Dict, List, Tuple
PRESET_ROOT = Path("/Library/Audio/Presets/Neural DSP/Archetype Tim Henson X")
DEFAULT_TEMPLATE = PRESET_ROOT / "Default.xml"
OUTPUT_DIR = PRESET_ROOT / "AI generated"
DB_PATH = Path(__file__).with_name("presets_db.json")
@dataclass
class ValueEntry:
key: str
len_pos: int
value_start: int
value_end: int
value: str
def _is_key_char(byte: int) -> bool:
return (
48 <= byte <= 57
or 65 <= byte <= 90
or 97 <= byte <= 122
or byte in (42, 45, 46, 95)
)
def parse_value_entries(data: bytes) -> List[ValueEntry]:
entries: List[ValueEntry] = []
i = 0
n = len(data)
while i < n:
key_start = i
while i < n and _is_key_char(data[i]):
i += 1
if i == key_start or i >= n or data[i] != 0:
i += 1
continue
key = data[key_start:i].decode("ascii", errors="ignore")
if not key:
i += 1
continue
if i + 3 >= n:
break
if data[i + 1] != 1 or data[i + 3] != 5:
i += 1
continue
len_pos = i + 2
value_start = i + 4
value_end = data.find(b"\x00", value_start)
if value_end < 0:
break
declared_len = data[len_pos]
actual_len = (value_end - value_start) + 2
if declared_len != actual_len:
i += 1
continue
raw = data[value_start:value_end]
try:
value = raw.decode("ascii")
except UnicodeDecodeError:
i = value_end + 1
continue
entries.append(
ValueEntry(
key=key,
len_pos=len_pos,
value_start=value_start,
value_end=value_end,
value=value,
)
)
i = value_end + 1
return entries
def parse_params(data: bytes) -> Dict[str, str]:
params: Dict[str, str] = {}
for entry in parse_value_entries(data):
params[entry.key] = entry.value
return params
def apply_updates(data: bytes, updates: Dict[str, str]) -> bytes:
entries = parse_value_entries(data)
by_key: Dict[str, ValueEntry] = {e.key: e for e in entries}
patches: List[Tuple[int, int, int, bytes]] = []
for key, value in updates.items():
entry = by_key.get(key)
if not entry:
continue
encoded = value.encode("ascii")
length_byte = len(encoded) + 2
if length_byte > 255:
raise ValueError(f"Value too long for key '{key}': {value}")
patches.append((entry.value_start, entry.value_end, entry.len_pos, encoded))
patched = data
for start, end, len_pos, encoded in sorted(patches, key=lambda x: x[0], reverse=True):
patched = patched[:len_pos] + bytes([len(encoded) + 2]) + patched[len_pos + 1 :]
patched = patched[:start] + encoded + patched[end:]
return patched
def _format_float(value: float) -> str:
txt = f"{value:.6f}".rstrip("0").rstrip(".")
if txt == "-0":
return "0"
return txt
def normalize_override_value(value: object) -> str:
if isinstance(value, bool):
return "true" if value else "false"
if isinstance(value, int):
return str(value)
if isinstance(value, float):
if not math.isfinite(value):
raise ValueError("Float values must be finite")
return _format_float(value)
if isinstance(value, str):
if "\x00" in value:
raise ValueError("String values cannot contain NUL characters")
value.encode("ascii")
return value
raise ValueError(f"Unsupported value type: {type(value).__name__}")
def sanitize_name(name: str) -> str:
clean = "".join(
ch if (ch.isascii() and (ch.isalnum() or ch in ("-", "_"))) else "_"
for ch in name.strip()
)
clean = clean.strip("_")
return clean or "ai_preset"
def ensure_output_dir() -> None:
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
def load_stdin_overrides() -> Dict[str, object]:
try:
payload = json.load(sys.stdin)
except json.JSONDecodeError as error:
raise ValueError(f"Invalid JSON on stdin: {error}") from error
if not isinstance(payload, dict):
raise ValueError("Stdin JSON must be an object of parameter overrides")
return payload
def do_generate(name: str, template: str | None) -> int:
template_path = Path(template) if template else DEFAULT_TEMPLATE
if not template_path.exists():
raise FileNotFoundError(f"Template does not exist: {template_path}")
template_data = template_path.read_bytes()
base_params = parse_params(template_data)
if not base_params:
raise RuntimeError(f"Could not parse parameters from template: {template_path}")
overrides = load_stdin_overrides()
normalized_overrides: Dict[str, str] = {}
unknown_keys: List[str] = []
for key, raw_value in overrides.items():
if key not in base_params:
unknown_keys.append(key)
continue
normalized_overrides[key] = normalize_override_value(raw_value)
if unknown_keys:
unknown_list = ", ".join(sorted(unknown_keys))
raise ValueError(f"Unknown parameter key(s): {unknown_list}")
merged_params = dict(base_params)
merged_params.update(normalized_overrides)
merged_params["name"] = sanitize_name(name)
changed_values: Dict[str, str] = {}
for key, value in merged_params.items():
if base_params.get(key) != value:
changed_values[key] = value
output_name = sanitize_name(name)
output_path = OUTPUT_DIR / f"{output_name}.xml"
if output_path.exists():
raise FileExistsError(f"Output already exists: {output_path}")
ensure_output_dir()
out_bytes = apply_updates(template_data, changed_values)
output_path.write_bytes(out_bytes)
print(f"Template: {template_path}")
print(f"Output: {output_path}")
print(f"Updated: {len(changed_values)} parameter(s)")
if normalized_overrides:
print("Overrides:")
for key in sorted(normalized_overrides.keys()):
print(f"- {key} = {normalized_overrides[key]}")
return 0
def do_index() -> int:
presets: List[Dict[str, object]] = []
for path in sorted(PRESET_ROOT.rglob("*.xml")):
if "AI generated" in path.parts:
continue
try:
params = parse_params(path.read_bytes())
except OSError:
continue
presets.append(
{
"path": str(path),
"name": path.stem,
"key_count": len(params),
"keys": sorted(params.keys()),
}
)
payload = {
"preset_root": str(PRESET_ROOT),
"count": len(presets),
"presets": presets,
}
DB_PATH.write_text(json.dumps(payload, indent=2), encoding="utf-8")
print(f"Indexed {len(presets)} presets -> {DB_PATH}")
return 0
def do_params(template: str | None) -> int:
template_path = Path(template) if template else DEFAULT_TEMPLATE
if not template_path.exists():
raise FileNotFoundError(f"Template does not exist: {template_path}")
params = parse_params(template_path.read_bytes())
if not params:
raise RuntimeError(f"Could not parse parameters from template: {template_path}")
print(f"Template: {template_path}")
print(f"Parameter count: {len(params)}")
for key in sorted(params.keys()):
print(key)
return 0
def build_arg_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
prog="tim_henson_skill.py",
description="Generate Tim Henson X presets from template + JSON overrides on stdin.",
)
sub = parser.add_subparsers(dest="command", required=True)
gen = sub.add_parser("generate", help="Generate a preset from JSON overrides on stdin")
gen.add_argument("--name", required=True, help="Output preset name (without extension)")
gen.add_argument("--template", default=None, help="Optional source template preset path")
sub.add_parser("index", help="Build preset key index cache")
params = sub.add_parser("params", help="Print all parameter keys from a template")
params.add_argument("--template", default=None, help="Template path; defaults to Default.xml")
return parser
def main() -> int:
parser = build_arg_parser()
args = parser.parse_args()
try:
if args.command == "generate":
return do_generate(args.name, args.template)
if args.command == "index":
return do_index()
if args.command == "params":
return do_params(args.template)
except (ValueError, FileNotFoundError, FileExistsError, OSError, RuntimeError, UnicodeError) as error:
print(f"Error: {error}", file=sys.stderr)
return 2
parser.print_help()
return 1
if __name__ == "__main__":
raise SystemExit(main())