use std::f32::consts::PI;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::Arc;
use std::thread::{self, JoinHandle};
use std::time::{Duration, Instant};
use flexaudio_core::backend::{CaptureBackend, RawSink};
use flexaudio_core::types::Result;
const BLOCK_MS: u32 = 10;
pub struct MockBackend {
sample_rate: u32,
channels: u16,
freq_hz: f32,
running: Arc<AtomicBool>,
handle: Option<JoinHandle<()>>,
}
impl MockBackend {
pub fn new(sample_rate: u32, channels: u16, freq_hz: f32) -> Self {
Self {
sample_rate: sample_rate.max(1),
channels: channels.max(1),
freq_hz,
running: Arc::new(AtomicBool::new(false)),
handle: None,
}
}
}
impl CaptureBackend for MockBackend {
fn native_format(&self) -> (u32, u16) {
(self.sample_rate, self.channels)
}
fn start(&mut self, mut sink: RawSink) -> Result<()> {
if self.running.load(Ordering::SeqCst) {
return Ok(());
}
self.running.store(true, Ordering::SeqCst);
let running = self.running.clone();
let sample_rate = self.sample_rate;
let channels = self.channels as usize;
let freq = self.freq_hz;
let handle = thread::Builder::new()
.name("flexaudio-mock-gen".into())
.spawn(move || {
let frames_per_block =
((sample_rate as u64 * BLOCK_MS as u64) / 1000).max(1) as usize;
let block_dur = Duration::from_millis(BLOCK_MS as u64);
let two_pi_f_over_sr = 2.0 * PI * freq / sample_rate as f32;
let mut phase_frame: u64 = 0;
let start = Instant::now();
let mut scratch: Vec<f32> = Vec::with_capacity(frames_per_block * channels);
while running.load(Ordering::SeqCst) {
scratch.clear();
for _ in 0..frames_per_block {
let s = if freq > 0.0 {
(two_pi_f_over_sr * phase_frame as f32).sin() * 0.5
} else {
0.0
};
for _ in 0..channels {
scratch.push(s);
}
phase_frame = phase_frame.wrapping_add(1);
}
let pts_ns = start.elapsed().as_nanos() as i64;
sink.push(&scratch, pts_ns);
thread::sleep(block_dur);
}
})
.map_err(|e| {
flexaudio_core::types::Error::Backend(format!("spawn mock thread: {e}"))
})?;
self.handle = Some(handle);
Ok(())
}
fn stop(&mut self) {
self.running.store(false, Ordering::SeqCst);
if let Some(h) = self.handle.take() {
let _ = h.join();
}
}
}
impl Drop for MockBackend {
fn drop(&mut self) {
self.stop();
}
}
#[doc(hidden)]
pub struct StallableMockBackend {
sample_rate: u32,
channels: u16,
freq_hz: f32,
stall_after: Duration,
running: Arc<AtomicBool>,
start_count: Arc<AtomicU32>,
handle: Option<JoinHandle<()>>,
}
impl StallableMockBackend {
pub fn new(sample_rate: u32, channels: u16, freq_hz: f32, stall_after: Duration) -> Self {
Self {
sample_rate: sample_rate.max(1),
channels: channels.max(1),
freq_hz,
stall_after,
running: Arc::new(AtomicBool::new(false)),
start_count: Arc::new(AtomicU32::new(0)),
handle: None,
}
}
pub fn start_count(&self) -> u32 {
self.start_count.load(Ordering::SeqCst)
}
}
impl CaptureBackend for StallableMockBackend {
fn native_format(&self) -> (u32, u16) {
(self.sample_rate, self.channels)
}
fn start(&mut self, mut sink: RawSink) -> Result<()> {
if self.running.load(Ordering::SeqCst) {
return Ok(());
}
self.running.store(true, Ordering::SeqCst);
let generation = self.start_count.fetch_add(1, Ordering::SeqCst);
let running = self.running.clone();
let sample_rate = self.sample_rate;
let channels = self.channels as usize;
let freq = self.freq_hz;
let stall_after = self.stall_after;
let handle = thread::Builder::new()
.name("flexaudio-stallable-mock-gen".into())
.spawn(move || {
let frames_per_block =
((sample_rate as u64 * BLOCK_MS as u64) / 1000).max(1) as usize;
let block_dur = Duration::from_millis(BLOCK_MS as u64);
let two_pi_f_over_sr = 2.0 * PI * freq / sample_rate as f32;
let mut phase_frame: u64 = 0;
let session_start = Instant::now();
let mut scratch: Vec<f32> = Vec::with_capacity(frames_per_block * channels);
while running.load(Ordering::SeqCst) {
let stalled = generation == 0 && session_start.elapsed() >= stall_after;
if !stalled {
scratch.clear();
for _ in 0..frames_per_block {
let s = if freq > 0.0 {
(two_pi_f_over_sr * phase_frame as f32).sin() * 0.5
} else {
0.0
};
for _ in 0..channels {
scratch.push(s);
}
phase_frame = phase_frame.wrapping_add(1);
}
let pts_ns = session_start.elapsed().as_nanos() as i64;
sink.push(&scratch, pts_ns);
}
thread::sleep(block_dur);
}
})
.map_err(|e| {
flexaudio_core::types::Error::Backend(format!("spawn stallable mock thread: {e}"))
})?;
self.handle = Some(handle);
Ok(())
}
fn stop(&mut self) {
self.running.store(false, Ordering::SeqCst);
if let Some(h) = self.handle.take() {
let _ = h.join();
}
}
}
impl Drop for StallableMockBackend {
fn drop(&mut self) {
self.stop();
}
}
#[doc(hidden)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PanicMode {
Start,
Stop,
}
#[doc(hidden)]
pub struct PanickingMockBackend {
sample_rate: u32,
channels: u16,
freq_hz: f32,
mode: PanicMode,
running: Arc<AtomicBool>,
handle: Option<JoinHandle<()>>,
}
impl PanickingMockBackend {
pub fn new(sample_rate: u32, channels: u16, freq_hz: f32, mode: PanicMode) -> Self {
Self {
sample_rate: sample_rate.max(1),
channels: channels.max(1),
freq_hz,
mode,
running: Arc::new(AtomicBool::new(false)),
handle: None,
}
}
}
impl CaptureBackend for PanickingMockBackend {
fn native_format(&self) -> (u32, u16) {
(self.sample_rate, self.channels)
}
fn start(&mut self, mut sink: RawSink) -> Result<()> {
if self.mode == PanicMode::Start {
panic!("PanickingMockBackend: intentional panic in start()");
}
if self.running.load(Ordering::SeqCst) {
return Ok(());
}
self.running.store(true, Ordering::SeqCst);
let running = self.running.clone();
let sample_rate = self.sample_rate;
let channels = self.channels as usize;
let freq = self.freq_hz;
let handle = thread::Builder::new()
.name("flexaudio-panicking-mock-gen".into())
.spawn(move || {
let frames_per_block =
((sample_rate as u64 * BLOCK_MS as u64) / 1000).max(1) as usize;
let block_dur = Duration::from_millis(BLOCK_MS as u64);
let two_pi_f_over_sr = 2.0 * PI * freq / sample_rate as f32;
let mut phase_frame: u64 = 0;
let start = Instant::now();
let mut scratch: Vec<f32> = Vec::with_capacity(frames_per_block * channels);
while running.load(Ordering::SeqCst) {
scratch.clear();
for _ in 0..frames_per_block {
let s = if freq > 0.0 {
(two_pi_f_over_sr * phase_frame as f32).sin() * 0.5
} else {
0.0
};
for _ in 0..channels {
scratch.push(s);
}
phase_frame = phase_frame.wrapping_add(1);
}
let pts_ns = start.elapsed().as_nanos() as i64;
sink.push(&scratch, pts_ns);
thread::sleep(block_dur);
}
})
.map_err(|e| {
flexaudio_core::types::Error::Backend(format!("spawn panicking mock thread: {e}"))
})?;
self.handle = Some(handle);
Ok(())
}
fn stop(&mut self) {
self.running.store(false, Ordering::SeqCst);
if let Some(h) = self.handle.take() {
let _ = h.join();
}
if self.mode == PanicMode::Stop {
panic!("PanickingMockBackend: intentional panic in stop()");
}
}
}
impl Drop for PanickingMockBackend {
fn drop(&mut self) {
self.running.store(false, Ordering::SeqCst);
if let Some(h) = self.handle.take() {
let _ = h.join();
}
}
}
#[doc(hidden)]
pub struct StallThenPanicOnReopenBackend {
sample_rate: u32,
channels: u16,
freq_hz: f32,
stall_after: Duration,
running: Arc<AtomicBool>,
start_count: Arc<AtomicU32>,
handle: Option<JoinHandle<()>>,
}
impl StallThenPanicOnReopenBackend {
pub fn new(sample_rate: u32, channels: u16, freq_hz: f32, stall_after: Duration) -> Self {
Self {
sample_rate: sample_rate.max(1),
channels: channels.max(1),
freq_hz,
stall_after,
running: Arc::new(AtomicBool::new(false)),
start_count: Arc::new(AtomicU32::new(0)),
handle: None,
}
}
}
impl CaptureBackend for StallThenPanicOnReopenBackend {
fn native_format(&self) -> (u32, u16) {
(self.sample_rate, self.channels)
}
fn start(&mut self, mut sink: RawSink) -> Result<()> {
if self.running.load(Ordering::SeqCst) {
return Ok(());
}
let generation = self.start_count.fetch_add(1, Ordering::SeqCst);
if generation >= 1 {
panic!("StallThenPanicOnReopenBackend: intentional panic on reopen start()");
}
self.running.store(true, Ordering::SeqCst);
let running = self.running.clone();
let sample_rate = self.sample_rate;
let channels = self.channels as usize;
let freq = self.freq_hz;
let stall_after = self.stall_after;
let handle = thread::Builder::new()
.name("flexaudio-stall-then-panic-gen".into())
.spawn(move || {
let frames_per_block =
((sample_rate as u64 * BLOCK_MS as u64) / 1000).max(1) as usize;
let block_dur = Duration::from_millis(BLOCK_MS as u64);
let two_pi_f_over_sr = 2.0 * PI * freq / sample_rate as f32;
let mut phase_frame: u64 = 0;
let session_start = Instant::now();
let mut scratch: Vec<f32> = Vec::with_capacity(frames_per_block * channels);
while running.load(Ordering::SeqCst) {
let stalled = session_start.elapsed() >= stall_after;
if !stalled {
scratch.clear();
for _ in 0..frames_per_block {
let s = if freq > 0.0 {
(two_pi_f_over_sr * phase_frame as f32).sin() * 0.5
} else {
0.0
};
for _ in 0..channels {
scratch.push(s);
}
phase_frame = phase_frame.wrapping_add(1);
}
let pts_ns = session_start.elapsed().as_nanos() as i64;
sink.push(&scratch, pts_ns);
}
thread::sleep(block_dur);
}
})
.map_err(|e| {
flexaudio_core::types::Error::Backend(format!(
"spawn stall-then-panic mock thread: {e}"
))
})?;
self.handle = Some(handle);
Ok(())
}
fn stop(&mut self) {
self.running.store(false, Ordering::SeqCst);
if let Some(h) = self.handle.take() {
let _ = h.join();
}
}
}
impl Drop for StallThenPanicOnReopenBackend {
fn drop(&mut self) {
self.running.store(false, Ordering::SeqCst);
if let Some(h) = self.handle.take() {
let _ = h.join();
}
}
}