Skip to main content

rill_lofi/
config.rs

1use serde::{Deserialize, Serialize};
2
3/// Classic digital audio systems that inform the lo-fi emulation parameters.
4#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
5pub enum ClassicSystem {
6    /// Nintendo Entertainment System (7-bit).
7    Nes,
8    /// Commodore 64 (8-bit).
9    Commodore64,
10    /// Sega Genesis / Mega Drive (9-bit).
11    SegaGenesis,
12    /// Roland D-50 (16-bit, 32 kHz).
13    RolandD50,
14    /// Akai S900 (12-bit, 40 kHz, non-linear).
15    AkaiS900,
16    /// E-mu Emulator II (8-bit, 27.7 kHz).
17    EmulatorII,
18    /// Fairlight CMI (8-bit, 16 kHz).
19    FairlightCMI,
20    /// LinnDrum (8-bit).
21    LinnDrum,
22    /// User-defined system with custom parameters.
23    Custom {
24        /// Bit depth (1–16).
25        bit_depth: u8,
26        /// Sample rate in Hz.
27        sample_rate: f32,
28        /// Whether the system uses non-linear encoding.
29        nonlinear: bool,
30        /// Noise floor in dB.
31        noise_floor: f32,
32    },
33}
34
35impl ClassicSystem {
36    /// Returns the bit depth for this classic system.
37    pub fn get_bit_depth(&self) -> u8 {
38        match self {
39            ClassicSystem::Nes => 7,
40            ClassicSystem::Commodore64 => 8,
41            ClassicSystem::SegaGenesis => 9,
42            ClassicSystem::RolandD50 => 16,
43            ClassicSystem::AkaiS900 => 12,
44            ClassicSystem::EmulatorII => 8,
45            ClassicSystem::FairlightCMI => 8,
46            ClassicSystem::LinnDrum => 8,
47            ClassicSystem::Custom { bit_depth, .. } => *bit_depth,
48        }
49    }
50
51    /// Returns the sample rate in Hz for this classic system.
52    pub fn get_sample_rate(&self) -> f32 {
53        match self {
54            ClassicSystem::Nes => 44_100.0,
55            ClassicSystem::Commodore64 => 44_100.0,
56            ClassicSystem::SegaGenesis => 44_100.0,
57            ClassicSystem::RolandD50 => 32_000.0,
58            ClassicSystem::AkaiS900 => 40_000.0,
59            ClassicSystem::EmulatorII => 27_700.0,
60            ClassicSystem::FairlightCMI => 16_000.0,
61            ClassicSystem::LinnDrum => 44_100.0,
62            ClassicSystem::Custom { sample_rate, .. } => *sample_rate,
63        }
64    }
65
66    /// Returns `true` if this system uses non-linear encoding (e.g. Akai S900).
67    pub fn has_nonlinear_encoding(&self) -> bool {
68        matches!(
69            self,
70            ClassicSystem::AkaiS900
71                | ClassicSystem::Custom {
72                    nonlinear: true,
73                    ..
74                }
75        )
76    }
77
78    /// Returns the noise floor in dB for this classic system.
79    pub fn get_noise_floor_db(&self) -> f32 {
80        match self {
81            ClassicSystem::Nes => -42.0,
82            ClassicSystem::Commodore64 => -48.0,
83            ClassicSystem::AkaiS900 => -72.0,
84            ClassicSystem::FairlightCMI => -48.0,
85            ClassicSystem::Custom { noise_floor, .. } => *noise_floor,
86            _ => -90.0,
87        }
88    }
89}
90
91/// Parameters for emulating vintage hardware imperfections.
92#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct HardwareEmulation {
94    /// Bit depth of the DAC.
95    pub bit_depth: u8,
96    /// Sample rate in Hz.
97    pub sample_rate: f32,
98    /// Whether to simulate DAC non-linearity.
99    pub dac_nonlinearity: bool,
100    /// Clock drift factor.
101    pub clock_drift: f32,
102    /// Voltage drop factor.
103    pub voltage_drop: f32,
104    /// Channel crosstalk factor.
105    pub crosstalk: f32,
106    /// Thermal noise floor.
107    pub thermal_noise: f32,
108    /// Ageing effect factor.
109    pub ageing_effect: f32,
110}
111
112impl Default for HardwareEmulation {
113    fn default() -> Self {
114        Self {
115            bit_depth: 8,
116            sample_rate: 44_100.0,
117            dac_nonlinearity: true,
118            clock_drift: 0.1,
119            voltage_drop: 0.02,
120            crosstalk: 0.01,
121            thermal_noise: 0.001,
122            ageing_effect: 0.05,
123        }
124    }
125}
126
127impl HardwareEmulation {
128    /// Creates a `HardwareEmulation` configured for a given classic system.
129    pub fn for_system(system: ClassicSystem) -> Self {
130        let mut emulation = Self::default();
131
132        match system {
133            ClassicSystem::Nes => {
134                emulation.bit_depth = 7;
135                emulation.clock_drift = 0.5;
136                emulation.voltage_drop = 0.05;
137                emulation.thermal_noise = 0.005;
138            }
139            ClassicSystem::Commodore64 => {
140                emulation.bit_depth = 8;
141                emulation.dac_nonlinearity = true;
142                emulation.clock_drift = 0.3;
143                emulation.crosstalk = 0.03;
144            }
145            ClassicSystem::AkaiS900 => {
146                emulation.bit_depth = 12;
147                emulation.dac_nonlinearity = true;
148                emulation.sample_rate = 40_000.0;
149                emulation.thermal_noise = 0.001;
150            }
151            ClassicSystem::FairlightCMI => {
152                emulation.bit_depth = 8;
153                emulation.sample_rate = 16_000.0;
154                emulation.clock_drift = 1.0;
155                emulation.voltage_drop = 0.1;
156                emulation.thermal_noise = 0.01;
157            }
158            _ => {
159                emulation.bit_depth = system.get_bit_depth();
160                emulation.sample_rate = system.get_sample_rate();
161            }
162        }
163
164        emulation
165    }
166}
167
168/// Configuration for the lo-fi audio processor.
169#[derive(Debug, Clone, Serialize, Deserialize)]
170pub struct LofiConfig {
171    /// Target classic system to emulate.
172    pub system: ClassicSystem,
173    /// Hardware imperfection parameters.
174    pub hardware: HardwareEmulation,
175    /// Enable bitcrushing effect.
176    pub enable_bitcrush: bool,
177    /// Enable sample rate reduction.
178    pub enable_sr_reduction: bool,
179    /// Enable noise simulation.
180    pub enable_noise: bool,
181    /// Output gain (1.0 = unity).
182    pub output_gain: f32,
183    /// DC offset correction — subtracted from each sample after gain.
184    ///
185    /// Use `0.5` for AY-3-8910 (which produces [0.0, 1.0] — all positive).
186    /// Default: `0.0` (no correction).
187    pub dc_offset: f32,
188    /// Hard clamp ceiling applied after offset correction.
189    ///
190    /// Values are clamped to `[-ceiling, +ceiling]`. Default: `1.0` (no clamping).
191    /// Set to `0.8` for conservative headroom into downstream processors.
192    pub output_ceiling: f32,
193    /// Dry/wet mix (1.0 = fully wet).
194    pub dry_wet: f32,
195}
196
197impl Default for LofiConfig {
198    fn default() -> Self {
199        Self {
200            system: ClassicSystem::Custom {
201                bit_depth: 8,
202                sample_rate: 44_100.0,
203                nonlinear: false,
204                noise_floor: -48.0,
205            },
206            hardware: HardwareEmulation::default(),
207            enable_bitcrush: true,
208            enable_sr_reduction: true,
209            enable_noise: true,
210            output_gain: 1.0,
211            dc_offset: 0.0,
212            output_ceiling: 1.0,
213            dry_wet: 1.0,
214        }
215    }
216}
217
218impl LofiConfig {
219    /// Creates a `LofiConfig` pre-populated for a given classic system.
220    pub fn for_system(system: ClassicSystem) -> Self {
221        Self {
222            system,
223            hardware: HardwareEmulation::for_system(system),
224            ..Default::default()
225        }
226    }
227}