Skip to content

Commit a455049

Browse files
authored
Add pink noise generation functionality
1 parent 10f8c3f commit a455049

File tree

1 file changed

+261
-0
lines changed

1 file changed

+261
-0
lines changed

src-tauri/src/audio/mod.rs

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,39 @@ impl AudioEngine {
573573
Ok(())
574574
}
575575

576+
pub fn run_pink_noise(settings: AudioSettings, cancel: Arc<AtomicBool>) -> Result<(), AudioError> {
577+
let host = preferred_host()?;
578+
let output_entries = enumerate_output_devices(&host)?;
579+
let output_device =
580+
select_device(&host, &output_entries, settings.output_device_index, false)?;
581+
let (output_config, output_format) =
582+
choose_output_config(&output_device, settings.output_sample_rate)?;
583+
let channels = output_config.channels as usize;
584+
585+
let noise_state = Arc::new(Mutex::new(PinkNoiseState::new(0.25)));
586+
let err_fn = |err| {
587+
eprintln!("pink noise stream error: {err}");
588+
};
589+
590+
let stream = build_pink_output_stream(
591+
&output_device,
592+
&output_config,
593+
output_format,
594+
OutputRouting::Both,
595+
channels,
596+
noise_state,
597+
err_fn,
598+
)?;
599+
stream.play()?;
600+
601+
while !cancel.load(Ordering::SeqCst) {
602+
std::thread::sleep(Duration::from_millis(50));
603+
}
604+
605+
drop(stream);
606+
Ok(())
607+
}
608+
576609
pub fn run_sweep_fr_test(
577610
settings: AudioSettings,
578611
mut request: SweepFrRequest,
@@ -2502,6 +2535,234 @@ fn route_sample(sample: f32, routing: OutputRouting) -> (f32, f32) {
25022535
}
25032536
}
25042537

2538+
struct PinkNoiseState {
2539+
b0: f32,
2540+
b1: f32,
2541+
b2: f32,
2542+
b3: f32,
2543+
b4: f32,
2544+
b5: f32,
2545+
b6: f32,
2546+
seed: u64,
2547+
gain: f32,
2548+
}
2549+
2550+
impl PinkNoiseState {
2551+
fn new(gain: f32) -> Self {
2552+
let seed = SystemTime::now()
2553+
.duration_since(SystemTime::UNIX_EPOCH)
2554+
.unwrap_or_default()
2555+
.as_nanos() as u64
2556+
^ 0x9E37_79B9_7F4A_7C15;
2557+
2558+
Self {
2559+
b0: 0.0,
2560+
b1: 0.0,
2561+
b2: 0.0,
2562+
b3: 0.0,
2563+
b4: 0.0,
2564+
b5: 0.0,
2565+
b6: 0.0,
2566+
seed: if seed == 0 { 0xA5A5_A5A5_A5A5_A5A5 } else { seed },
2567+
gain: gain.clamp(0.0, 1.0),
2568+
}
2569+
}
2570+
2571+
fn next_white(&mut self) -> f32 {
2572+
let mut x = self.seed;
2573+
x ^= x << 13;
2574+
x ^= x >> 7;
2575+
x ^= x << 17;
2576+
self.seed = x;
2577+
let unit = (x as f64) / (u64::MAX as f64);
2578+
(unit as f32) * 2.0 - 1.0
2579+
}
2580+
2581+
fn next_sample(&mut self) -> f32 {
2582+
let x = self.next_white();
2583+
self.b0 = 0.99886 * self.b0 + x * 0.055_517_9;
2584+
self.b1 = 0.99332 * self.b1 + x * 0.075_075_9;
2585+
self.b2 = 0.96900 * self.b2 + x * 0.153_852_0;
2586+
self.b3 = 0.86650 * self.b3 + x * 0.310_485_6;
2587+
self.b4 = 0.55000 * self.b4 + x * 0.532_952_2;
2588+
self.b5 = -0.7616 * self.b5 - x * 0.016_898_0;
2589+
let y = self.b0 + self.b1 + self.b2 + self.b3 + self.b4 + self.b5 + self.b6 + x * 0.5362;
2590+
self.b6 = x * 0.115_926;
2591+
2592+
// 0.11 keeps the Paul Kellet filter output in a comfortable playback range.
2593+
(y * 0.11 * self.gain).clamp(-1.0, 1.0)
2594+
}
2595+
}
2596+
2597+
fn build_pink_output_stream(
2598+
device: &Device,
2599+
config: &StreamConfig,
2600+
format: SampleFormat,
2601+
routing: OutputRouting,
2602+
channels: usize,
2603+
noise_state: Arc<Mutex<PinkNoiseState>>,
2604+
err_fn: impl Fn(cpal::StreamError) + Send + 'static + Copy,
2605+
) -> Result<Stream, AudioError> {
2606+
match format {
2607+
SampleFormat::F32 => {
2608+
let state = noise_state.clone();
2609+
Ok(device.build_output_stream(
2610+
config,
2611+
move |data: &mut [f32], _| {
2612+
write_pink_f32(data, channels, routing, &state);
2613+
},
2614+
err_fn,
2615+
None,
2616+
)?)
2617+
}
2618+
SampleFormat::I16 => {
2619+
let state = noise_state.clone();
2620+
Ok(device.build_output_stream(
2621+
config,
2622+
move |data: &mut [i16], _| {
2623+
write_pink_i16(data, channels, routing, &state);
2624+
},
2625+
err_fn,
2626+
None,
2627+
)?)
2628+
}
2629+
SampleFormat::U16 => {
2630+
let state = noise_state.clone();
2631+
Ok(device.build_output_stream(
2632+
config,
2633+
move |data: &mut [u16], _| {
2634+
write_pink_u16(data, channels, routing, &state);
2635+
},
2636+
err_fn,
2637+
None,
2638+
)?)
2639+
}
2640+
SampleFormat::U8 => {
2641+
let state = noise_state.clone();
2642+
Ok(device.build_output_stream(
2643+
config,
2644+
move |data: &mut [u8], _| {
2645+
write_pink_u8(data, channels, routing, &state);
2646+
},
2647+
err_fn,
2648+
None,
2649+
)?)
2650+
}
2651+
other => Err(AudioError::UnsupportedSampleFormat(format!("{other:?}"))),
2652+
}
2653+
}
2654+
2655+
fn write_pink_f32(
2656+
data: &mut [f32],
2657+
channels: usize,
2658+
routing: OutputRouting,
2659+
noise_state: &Arc<Mutex<PinkNoiseState>>,
2660+
) {
2661+
if let Ok(mut state) = noise_state.lock() {
2662+
for frame in data.chunks_mut(channels.max(1)) {
2663+
let mono = state.next_sample();
2664+
let (left, right) = route_sample(mono, routing);
2665+
for (ch, sample) in frame.iter_mut().enumerate() {
2666+
*sample = if ch == 0 {
2667+
left
2668+
} else if ch == 1 {
2669+
right
2670+
} else if ch % 2 == 0 {
2671+
left
2672+
} else {
2673+
right
2674+
};
2675+
}
2676+
}
2677+
} else {
2678+
data.fill(0.0);
2679+
}
2680+
}
2681+
2682+
fn write_pink_i16(
2683+
data: &mut [i16],
2684+
channels: usize,
2685+
routing: OutputRouting,
2686+
noise_state: &Arc<Mutex<PinkNoiseState>>,
2687+
) {
2688+
if let Ok(mut state) = noise_state.lock() {
2689+
for frame in data.chunks_mut(channels.max(1)) {
2690+
let mono = state.next_sample();
2691+
let (left, right) = route_sample(mono, routing);
2692+
for (ch, sample) in frame.iter_mut().enumerate() {
2693+
let value = if ch == 0 {
2694+
left
2695+
} else if ch == 1 {
2696+
right
2697+
} else if ch % 2 == 0 {
2698+
left
2699+
} else {
2700+
right
2701+
};
2702+
*sample = (value * i16::MAX as f32) as i16;
2703+
}
2704+
}
2705+
} else {
2706+
data.fill(0);
2707+
}
2708+
}
2709+
2710+
fn write_pink_u16(
2711+
data: &mut [u16],
2712+
channels: usize,
2713+
routing: OutputRouting,
2714+
noise_state: &Arc<Mutex<PinkNoiseState>>,
2715+
) {
2716+
if let Ok(mut state) = noise_state.lock() {
2717+
for frame in data.chunks_mut(channels.max(1)) {
2718+
let mono = state.next_sample();
2719+
let (left, right) = route_sample(mono, routing);
2720+
for (ch, sample) in frame.iter_mut().enumerate() {
2721+
let value = if ch == 0 {
2722+
left
2723+
} else if ch == 1 {
2724+
right
2725+
} else if ch % 2 == 0 {
2726+
left
2727+
} else {
2728+
right
2729+
};
2730+
*sample = ((value * 0.5 + 0.5) * u16::MAX as f32) as u16;
2731+
}
2732+
}
2733+
} else {
2734+
data.fill(u16::MAX / 2);
2735+
}
2736+
}
2737+
2738+
fn write_pink_u8(
2739+
data: &mut [u8],
2740+
channels: usize,
2741+
routing: OutputRouting,
2742+
noise_state: &Arc<Mutex<PinkNoiseState>>,
2743+
) {
2744+
if let Ok(mut state) = noise_state.lock() {
2745+
for frame in data.chunks_mut(channels.max(1)) {
2746+
let mono = state.next_sample();
2747+
let (left, right) = route_sample(mono, routing);
2748+
for (ch, sample) in frame.iter_mut().enumerate() {
2749+
let value = if ch == 0 {
2750+
left
2751+
} else if ch == 1 {
2752+
right
2753+
} else if ch % 2 == 0 {
2754+
left
2755+
} else {
2756+
right
2757+
};
2758+
*sample = ((value * 0.5 + 0.5) * u8::MAX as f32) as u8;
2759+
}
2760+
}
2761+
} else {
2762+
data.fill(u8::MAX / 2);
2763+
}
2764+
}
2765+
25052766
struct MonitorStats {
25062767
current_dbfs: f32,
25072768
peak_dbfs: f32,

0 commit comments

Comments
 (0)