use std::sync::{ Arc, Mutex };
use std::sync::atomic::{ AtomicBool, AtomicU32, Ordering };
use std::collections::VecDeque;
use cpal::traits::{ DeviceTrait, HostTrait, StreamTrait };
use thiserror::Error;
#[derive( Debug, Error )]
pub enum OutputError {
#[error( "No output device available" )]
NoDevice,
#[error( "Failed to get default stream config: {0}" )]
StreamConfig( String ),
#[error( "Failed to build output stream: {0}" )]
BuildStream( String ),
#[error( "Failed to play stream: {0}" )]
PlayStream( String ),
}
pub const VIS_BARS: usize = 32;
pub struct SampleBuffer {
buffer: Mutex<VecDeque<f32>>,
capacity: usize,
paused: AtomicBool,
volume: AtomicU32,
source_channels: u16,
output_channels: u16,
vis_data: Mutex<[f32; VIS_BARS]>,
}
impl SampleBuffer {
pub fn new( capacity: usize, source_channels: u16, output_channels: u16 ) -> Self {
Self {
buffer: Mutex::new( VecDeque::with_capacity( capacity ) ),
vis_data: Mutex::new( [0.0; VIS_BARS] ),
capacity,
paused: AtomicBool::new( false ),
volume: AtomicU32::new( 1.0_f32.to_bits() ),
source_channels,
output_channels,
}
}
pub fn push( &self, samples: &[f32] ) -> usize {
let mut buf = self.buffer.lock().unwrap();
let available = self.capacity.saturating_sub( buf.len() );
let to_push = samples.len().min( available );
buf.extend( samples[ ..to_push ].iter().copied() );
if to_push >= VIS_BARS {
let mut vis = self.vis_data.lock().unwrap();
let samples_per_bar = to_push / VIS_BARS;
for ( bar_idx, bar ) in vis.iter_mut().enumerate() {
let start = bar_idx * samples_per_bar;
let end = ( start + samples_per_bar ).min( to_push );
let sum_sq: f32 = samples[ start..end ]
.iter()
.map( |s| s * s )
.sum();
let rms = ( sum_sq / ( end - start ) as f32 ).sqrt();
*bar = ( *bar * 0.7 ) + ( rms * 0.3 );
}
}
to_push
}
pub fn pop( &self, output: &mut [f32] ) -> usize {
if self.paused.load( Ordering::Relaxed ) {
for sample in output.iter_mut() {
*sample = 0.0;
}
return 0;
}
let volume = f32::from_bits( self.volume.load( Ordering::Relaxed ) );
let mut buf = self.buffer.lock().unwrap();
let src_ch = self.source_channels as usize;
let out_ch = self.output_channels as usize;
let written = if src_ch == out_ch {
let to_pop = output.len().min( buf.len() );
for i in 0..to_pop {
output[ i ] = buf.pop_front().unwrap();
}
for i in to_pop..output.len() {
output[ i ] = 0.0;
}
to_pop
} else if src_ch == 1 && out_ch == 2 {
let output_frames = output.len() / out_ch;
let available_frames = buf.len() / src_ch;
let frames_to_process = output_frames.min( available_frames );
for i in 0..frames_to_process {
let sample = buf.pop_front().unwrap();
output[ i * 2 ] = sample;
output[ i * 2 + 1 ] = sample;
}
for i in ( frames_to_process * out_ch )..output.len() {
output[ i ] = 0.0;
}
frames_to_process * out_ch
} else if src_ch == 2 && out_ch == 1 {
let output_frames = output.len() / out_ch;
let available_frames = buf.len() / src_ch;
let frames_to_process = output_frames.min( available_frames );
for i in 0..frames_to_process {
let left = buf.pop_front().unwrap();
let right = buf.pop_front().unwrap();
output[ i ] = ( left + right ) * 0.5;
}
for i in frames_to_process..output.len() {
output[ i ] = 0.0;
}
frames_to_process
} else {
let output_frames = output.len() / out_ch;
let available_frames = buf.len() / src_ch;
let frames_to_process = output_frames.min( available_frames );
for frame in 0..frames_to_process {
let mut src_samples = Vec::with_capacity( src_ch );
for _ in 0..src_ch {
src_samples.push( buf.pop_front().unwrap() );
}
for ch in 0..out_ch {
if ch < src_ch {
output[ frame * out_ch + ch ] = src_samples[ ch ];
} else {
output[ frame * out_ch + ch ] = src_samples[ src_ch - 1 ];
}
}
}
for i in ( frames_to_process * out_ch )..output.len() {
output[ i ] = 0.0;
}
frames_to_process * out_ch
};
if volume != 1.0 {
for sample in output[ ..written ].iter_mut() {
*sample *= volume;
}
}
written
}
pub fn len( &self ) -> usize {
self.buffer.lock().unwrap().len()
}
pub fn is_empty( &self ) -> bool {
self.buffer.lock().unwrap().is_empty()
}
pub fn clear( &self ) {
self.buffer.lock().unwrap().clear();
}
pub fn set_paused( &self, paused: bool ) {
self.paused.store( paused, Ordering::Relaxed );
}
pub fn is_paused( &self ) -> bool {
self.paused.load( Ordering::Relaxed )
}
pub fn vis_data( &self ) -> [f32; VIS_BARS] {
*self.vis_data.lock().unwrap()
}
pub fn set_volume( &self, volume: f32 ) {
self.volume.store( volume.to_bits(), Ordering::Relaxed );
}
pub fn volume( &self ) -> f32 {
f32::from_bits( self.volume.load( Ordering::Relaxed ) )
}
}
pub struct AudioOutput {
stream: cpal::Stream,
sample_rate: u32,
channels: u16,
}
impl AudioOutput {
pub fn new(
source_sample_rate: u32,
source_channels: u16,
) -> Result<( Self, Arc<SampleBuffer> ), OutputError> {
let host = cpal::default_host();
let device = host
.default_output_device()
.ok_or( OutputError::NoDevice )?;
tracing::info!( "Using output device: {:?}", device.name() );
let supported_configs: Vec<_> = device
.supported_output_configs()
.map_err( |e| OutputError::StreamConfig( e.to_string() ) )?
.collect();
let config = if let Some( supported_config ) = supported_configs.iter().find( |c| {
c.channels() == source_channels
&& c.min_sample_rate().0 <= source_sample_rate
&& c.max_sample_rate().0 >= source_sample_rate
}) {
supported_config.clone()
.with_sample_rate( cpal::SampleRate( source_sample_rate ) )
.config()
}
else if let Some( supported_config ) = supported_configs.iter().find( |c| {
c.min_sample_rate().0 <= source_sample_rate
&& c.max_sample_rate().0 >= source_sample_rate
}) {
tracing::info!(
"Channel conversion: file has {} channels, device using {} channels",
source_channels,
supported_config.channels()
);
supported_config.clone()
.with_sample_rate( cpal::SampleRate( source_sample_rate ) )
.config()
}
else {
let default_config = device
.default_output_config()
.map_err( |e| OutputError::StreamConfig( e.to_string() ) )?;
tracing::warn!(
"Sample rate mismatch: file is {} Hz, device defaulting to {} Hz - playback speed may be incorrect!",
source_sample_rate,
default_config.sample_rate().0
);
default_config.config()
};
tracing::info!(
"Audio output config: {} Hz, {} channels",
config.sample_rate.0,
config.channels
);
let buffer_capacity = ( source_sample_rate as usize ) * ( source_channels as usize ) / 2;
let sample_buffer = Arc::new( SampleBuffer::new(
buffer_capacity,
source_channels,
config.channels,
));
let sample_buffer_clone = Arc::clone( &sample_buffer );
let stream = device
.build_output_stream(
&config,
move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
sample_buffer_clone.pop( data );
},
|err| {
tracing::error!( "Audio output error: {}", err );
},
None,
)
.map_err( |e| OutputError::BuildStream( e.to_string() ) )?;
Ok((
Self {
stream,
sample_rate: config.sample_rate.0,
channels: config.channels,
},
sample_buffer,
))
}
pub fn play( &self ) -> Result<(), OutputError> {
self.stream
.play()
.map_err( |e| OutputError::PlayStream( e.to_string() ) )
}
pub fn pause( &self ) -> Result<(), OutputError> {
self.stream
.pause()
.map_err( |e| OutputError::PlayStream( e.to_string() ) )
}
pub fn sample_rate( &self ) -> u32 {
self.sample_rate
}
pub fn channels( &self ) -> u16 {
self.channels
}
}