1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
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"
|