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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
//! Noise configuration and type definitions
use serde::{Deserialize, Serialize};
use super::{NoiseError, NoiseResult};
/// Predefined noise color profiles based on spectral slope
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum NoiseType {
/// Flat frequency spectrum (0 dB/octave)
White,
/// -3 dB/octave roll-off (1/f noise)
Pink,
/// -6 dB/octave roll-off (1/f² noise, Brownian motion)
Brown,
/// +3 dB/octave (differentiated pink)
Blue,
/// +6 dB/octave (differentiated brown)
Violet,
/// User-defined spectral slope in dB/octave
Custom(f32),
}
impl NoiseType {
/// Returns the spectral slope in dB/octave
#[must_use]
pub fn spectral_slope(&self) -> f32 {
match self {
NoiseType::White => 0.0,
NoiseType::Pink => -3.0,
NoiseType::Brown => -6.0,
NoiseType::Blue => 3.0,
NoiseType::Violet => 6.0,
NoiseType::Custom(slope) => *slope,
}
}
/// Parse from string name
pub fn from_name(name: &str) -> Option<Self> {
match name.to_lowercase().as_str() {
"white" => Some(NoiseType::White),
"pink" => Some(NoiseType::Pink),
"brown" | "brownian" | "red" => Some(NoiseType::Brown),
"blue" | "azure" => Some(NoiseType::Blue),
"violet" | "purple" => Some(NoiseType::Violet),
_ => None,
}
}
/// Get the name of this noise type
#[must_use]
pub fn name(&self) -> &'static str {
match self {
NoiseType::White => "white",
NoiseType::Pink => "pink",
NoiseType::Brown => "brown",
NoiseType::Blue => "blue",
NoiseType::Violet => "violet",
NoiseType::Custom(_) => "custom",
}
}
}
impl Default for NoiseType {
fn default() -> Self {
NoiseType::Brown // Best for sleep
}
}
/// Brainwave entrainment frequency presets for binaural beats
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BinauralPreset {
/// Delta waves: 0.5-4 Hz (deep sleep, healing)
Delta,
/// Theta waves: 4-8 Hz (meditation, creativity)
Theta,
/// Alpha waves: 8-13 Hz (relaxation, calm focus)
Alpha,
/// Beta waves: 13-30 Hz (active thinking, focus)
Beta,
/// Gamma waves: 30-100 Hz (high cognition, peak awareness)
Gamma,
}
impl BinauralPreset {
/// Returns the center frequency for this brainwave band in Hz
#[must_use]
pub fn frequency(&self) -> f32 {
match self {
BinauralPreset::Delta => 2.0,
BinauralPreset::Theta => 6.0,
BinauralPreset::Alpha => 10.0,
BinauralPreset::Beta => 20.0,
BinauralPreset::Gamma => 40.0,
}
}
/// Returns the frequency range for this brainwave band
#[must_use]
pub fn frequency_range(&self) -> (f32, f32) {
match self {
BinauralPreset::Delta => (0.5, 4.0),
BinauralPreset::Theta => (4.0, 8.0),
BinauralPreset::Alpha => (8.0, 13.0),
BinauralPreset::Beta => (13.0, 30.0),
BinauralPreset::Gamma => (30.0, 100.0),
}
}
}
/// Configuration for noise generation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NoiseConfig {
/// Base noise type/color
pub noise_type: NoiseType,
/// Spectral slope override in dB/octave (-12.0 to +12.0)
/// If None, uses noise_type.spectral_slope()
pub spectral_slope: Option<f32>,
/// Texture/grain control (0.0 = smooth, 1.0 = grainy)
pub texture: f32,
/// LFO modulation depth (0.0 = none, 1.0 = full)
pub modulation_depth: f32,
/// LFO modulation rate in Hz (0.1 to 10.0)
pub modulation_rate: f32,
/// Sample rate in Hz (typically 44100 or 48000)
pub sample_rate: u32,
/// FFT/buffer size (256, 512, 1024, 2048)
pub buffer_size: usize,
}
impl NoiseConfig {
/// Create a new configuration with validation
pub fn new(noise_type: NoiseType) -> Self {
Self {
noise_type,
spectral_slope: None,
texture: 0.5,
modulation_depth: 0.0,
modulation_rate: 1.0,
sample_rate: 44100,
buffer_size: 1024,
}
}
/// Preset for brown noise (optimal for sleep)
#[must_use]
pub fn brown() -> Self {
Self::new(NoiseType::Brown)
}
/// Preset for white noise
#[must_use]
pub fn white() -> Self {
Self::new(NoiseType::White)
}
/// Preset for pink noise
#[must_use]
pub fn pink() -> Self {
Self::new(NoiseType::Pink)
}
/// Preset for blue noise
#[must_use]
pub fn blue() -> Self {
Self::new(NoiseType::Blue)
}
/// Preset for violet noise
#[must_use]
pub fn violet() -> Self {
Self::new(NoiseType::Violet)
}
/// Get the effective spectral slope (override or from type)
#[must_use]
pub fn effective_slope(&self) -> f32 {
self.spectral_slope
.unwrap_or_else(|| self.noise_type.spectral_slope())
}
/// Validate configuration parameters
pub fn validate(&self) -> NoiseResult<()> {
// Validate spectral slope if overridden
if let Some(slope) = self.spectral_slope {
if !(-12.0..=12.0).contains(&slope) {
return Err(NoiseError::InvalidConfig(format!(
"spectral_slope must be in [-12, 12], got {slope}"
)));
}
}
// Validate texture
if !(0.0..=1.0).contains(&self.texture) {
return Err(NoiseError::InvalidConfig(
format!("texture must be in [0, 1], got {}", self.texture), // Can't use inline format with self
));
}
// Validate modulation depth
if !(0.0..=1.0).contains(&self.modulation_depth) {
return Err(NoiseError::InvalidConfig(format!(
"modulation_depth must be in [0, 1], got {}",
self.modulation_depth
)));
}
// Validate modulation rate
if !(0.1..=10.0).contains(&self.modulation_rate) {
return Err(NoiseError::InvalidConfig(format!(
"modulation_rate must be in [0.1, 10], got {}",
self.modulation_rate
)));
}
// Validate sample rate
if self.sample_rate == 0 {
return Err(NoiseError::InvalidConfig(
"sample_rate must be > 0".to_string(),
));
}
// Validate buffer size
if ![256, 512, 1024, 2048].contains(&self.buffer_size) {
return Err(NoiseError::InvalidConfig(format!(
"buffer_size must be 256, 512, 1024, or 2048, got {}",
self.buffer_size
)));
}
Ok(())
}
/// Set spectral slope override with validation
pub fn with_spectral_slope(mut self, slope: f32) -> NoiseResult<Self> {
if !(-12.0..=12.0).contains(&slope) {
return Err(NoiseError::InvalidConfig(format!(
"spectral_slope must be in [-12, 12], got {}",
slope
)));
}
self.spectral_slope = Some(slope);
Ok(self)
}
/// Set texture with validation
pub fn with_texture(mut self, texture: f32) -> NoiseResult<Self> {
if !(0.0..=1.0).contains(&texture) {
return Err(NoiseError::InvalidConfig(format!(
"texture must be in [0, 1], got {}",
texture
)));
}
self.texture = texture;
Ok(self)
}
/// Set modulation with validation
pub fn with_modulation(mut self, depth: f32, rate: f32) -> NoiseResult<Self> {
if !(0.0..=1.0).contains(&depth) {
return Err(NoiseError::InvalidConfig(format!(
"modulation_depth must be in [0, 1], got {}",
depth
)));
}
if !(0.1..=10.0).contains(&rate) {
return Err(NoiseError::InvalidConfig(format!(
"modulation_rate must be in [0.1, 10], got {}",
rate
)));
}
self.modulation_depth = depth;
self.modulation_rate = rate;
Ok(self)
}
/// Set sample rate
pub fn with_sample_rate(mut self, sample_rate: u32) -> NoiseResult<Self> {
if sample_rate == 0 {
return Err(NoiseError::InvalidConfig(
"sample_rate must be > 0".to_string(),
));
}
self.sample_rate = sample_rate;
Ok(self)
}
/// Set buffer size with validation
pub fn with_buffer_size(mut self, buffer_size: usize) -> NoiseResult<Self> {
if ![256, 512, 1024, 2048].contains(&buffer_size) {
return Err(NoiseError::InvalidConfig(format!(
"buffer_size must be 256, 512, 1024, or 2048, got {}",
buffer_size
)));
}
self.buffer_size = buffer_size;
Ok(self)
}
/// Encode config as normalized f32 vector for MLP input
#[must_use]
pub fn encode(&self, time: f64) -> Vec<f32> {
let slope = self.effective_slope();
let lfo_phase = time * f64::from(self.modulation_rate) * std::f64::consts::TAU;
vec![
slope / 12.0, // Normalized slope [-1, 1]
self.texture, // Already [0, 1]
self.modulation_depth, // Already [0, 1]
self.modulation_rate / 10.0, // Normalized rate [0, 1]
lfo_phase.sin() as f32, // LFO sin component
lfo_phase.cos() as f32, // LFO cos component
self.buffer_size as f32 / 2048.0, // Normalized buffer size
self.sample_rate as f32 / 48000.0, // Normalized sample rate
]
}
}
impl Default for NoiseConfig {
fn default() -> Self {
Self::brown()
}
}
#[cfg(test)]
#[path = "config_tests.rs"]
mod tests;