aboutsummaryrefslogtreecommitdiff
path: root/lib/project/src/voiceforge/config.py
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-09-07 06:47:47 -0400
committerhistoria <historiavg@proton.me>2026-09-07 06:47:47 -0400
commit84dd2d068317998f6fb59400c534ef5be6b51b53 (patch)
tree025293e9d9229e02960374771ae522d9de2628ce /lib/project/src/voiceforge/config.py
parent39b0f2bbed74f6487a41b82501ae3c6799e4b5c4 (diff)
downloadproducer-main.tar.gz
slop rewriteHEADmain
Diffstat (limited to 'lib/project/src/voiceforge/config.py')
-rw-r--r--lib/project/src/voiceforge/config.py118
1 files changed, 118 insertions, 0 deletions
diff --git a/lib/project/src/voiceforge/config.py b/lib/project/src/voiceforge/config.py
new file mode 100644
index 0000000..9cbd59d
--- /dev/null
+++ b/lib/project/src/voiceforge/config.py
@@ -0,0 +1,118 @@
+"""Flat, strict TOML configuration; CLI overrides are applied last."""
+from dataclasses import asdict, dataclass, fields
+import math
+from pathlib import Path
+import tomllib
+
+
+@dataclass
+class Settings:
+ profile: str = "natural"
+ denoiser: str = "deepfilter"
+ device: str = "auto"
+ denoise_strength: float = 0.85
+ fft_reduction_db: float = 8.0
+ channel: str = "auto"
+ highpass: bool = True
+ highpass_hz: float = 70.0
+ lowpass: bool = False
+ lowpass_hz: float = 16000.0
+ hum_hz: int = 0
+ leveling: bool = True
+ level_target_db: float = -23.0
+ max_gain_db: float = 18.0
+ expansion: bool = False
+ expansion_threshold_db: float = -50.0
+ expansion_ratio: float = 1.5
+ expansion_range_db: float = 12.0
+ eq: bool = True
+ warmth_db: float = 0.5
+ mud_db: float = -1.5
+ presence_db: float = 1.0
+ compression: bool = True
+ compressor_threshold_db: float = -21.0
+ compressor_ratio: float = 2.0
+ compressor_attack_ms: float = 15.0
+ compressor_release_ms: float = 150.0
+ deess: bool = True
+ deess_intensity: float = 0.15
+ deess_amount: float = 0.4
+ normalize: bool = True
+ target_lufs: float = -19.0
+ loudness_range: float = 9.0
+ limiter: bool = True
+ true_peak_db: float = -1.5
+ sample_rate: int = 48000
+ mp3: bool = False
+ mp3_bitrate: int = 192
+
+
+PROFILES = {
+ "natural": {},
+ "narrator": {"compressor_ratio": 1.6, "compressor_attack_ms": 25.0,
+ "presence_db": 0.5, "warmth_db": 1.0, "loudness_range": 11.0},
+ "radio": {"compressor_ratio": 3.5, "compressor_threshold_db": -24.0,
+ "compressor_attack_ms": 8.0, "compressor_release_ms": 100.0,
+ "warmth_db": 2.0, "mud_db": -2.0, "presence_db": 2.0,
+ "deess_intensity": 0.25, "loudness_range": 6.0},
+ "cleanup-only": {"leveling": False, "eq": False, "compression": False,
+ "deess": False, "normalize": False, "limiter": False},
+}
+
+RANGES = {
+ "denoise_strength": (0, 1), "fft_reduction_db": (0.01, 30),
+ "highpass_hz": (20, 300), "lowpass_hz": (4000, 22000),
+ "level_target_db": (-40, -12), "max_gain_db": (0, 30),
+ "expansion_threshold_db": (-90, -20), "expansion_ratio": (1, 4),
+ "expansion_range_db": (0, 40), "warmth_db": (-12, 12),
+ "mud_db": (-12, 12), "presence_db": (-12, 12),
+ "compressor_threshold_db": (-50, -5), "compressor_ratio": (1, 10),
+ "compressor_attack_ms": (0.1, 200), "compressor_release_ms": (10, 2000),
+ "deess_intensity": (0, 1), "deess_amount": (0, 1),
+ "target_lufs": (-30, -12), "loudness_range": (1, 20),
+ "true_peak_db": (-9, -0.5),
+}
+
+
+def resolve(path: Path | None = None, profile: str | None = None,
+ overrides: dict | None = None) -> Settings:
+ values = tomllib.loads(path.read_text()) if path else {}
+ chosen = (overrides or {}).get("profile", profile or values.get("profile", "natural"))
+ if not isinstance(chosen, str) or chosen not in PROFILES:
+ raise ValueError(f"Unknown profile {chosen!r}; choose {', '.join(PROFILES)}")
+ merged = asdict(Settings()) | PROFILES[chosen] | values | {"profile": chosen}
+ merged.update(overrides or {})
+ defaults = asdict(Settings())
+ unknown = merged.keys() - defaults.keys()
+ if unknown:
+ raise ValueError(f"Unknown setting(s): {', '.join(sorted(unknown))}")
+ for key, value in merged.items():
+ expected = type(defaults[key])
+ if expected is float:
+ if type(value) not in (int, float) or not math.isfinite(value):
+ raise ValueError(f"{key} must be a finite number")
+ elif type(value) is not expected:
+ raise ValueError(f"{key} must be {expected.__name__}")
+ if key in RANGES and not RANGES[key][0] <= value <= RANGES[key][1]:
+ raise ValueError(f"{key} must be between {RANGES[key][0]} and {RANGES[key][1]}")
+ for key, options in {
+ "denoiser": ("deepfilter", "fft", "none"), "device": ("auto", "cpu", "cuda"),
+ "channel": ("auto", "left", "right", "mix"), "hum_hz": (0, 50, 60),
+ "sample_rate": (44100, 48000), "mp3_bitrate": (128, 160, 192, 224, 256, 320),
+ }.items():
+ if merged[key] not in options:
+ raise ValueError(f"{key} must be one of {options}")
+ if merged["lowpass"] and merged["lowpass_hz"] >= merged["sample_rate"] / 2:
+ raise ValueError("lowpass_hz must be below the output Nyquist frequency")
+ if merged["normalize"] and not merged["limiter"]:
+ raise ValueError("Normalization includes true-peak limiting; use --no-normalize to bypass it")
+ return Settings(**merged)
+
+
+def toml(settings: Settings) -> str:
+ lines = ["# VoiceForge configuration. CLI flags override these values."]
+ for field in fields(settings):
+ value = getattr(settings, field.name)
+ text = str(value).lower() if isinstance(value, bool) else repr(value)
+ lines.append(f"{field.name} = {text}")
+ return "\n".join(lines) + "\n"