diff --git a/src/sfx.rs b/src/sfx.rs index ae888fe..d071b9e 100644 --- a/src/sfx.rs +++ b/src/sfx.rs @@ -1,5 +1,7 @@ +use std::path::Path; + use rand::seq::IteratorRandom; -use symphonia::core::{formats::{TrackType, probe::Hint}, io::MediaSourceStream}; +use symphonia::core::{codecs::audio::AudioDecoder, formats::{FormatReader, TrackType, probe::Hint}, io::MediaSourceStream}; use crate::audio::AudioOutStream; @@ -19,87 +21,120 @@ impl SfxControl { } } +struct Player { + audio_sink: AudioOutStream, + audio_out_buf: Vec +} + +impl Player { + async fn submit_buffer(&mut self) { + self.audio_sink.sink.send(std::mem::take(&mut self.audio_out_buf)).await.unwrap(); + } + + async fn play_stream(&mut self, mut format: Box, mut decoder: Box) -> Result<(), symphonia::core::errors::Error> { + let mut channel_bufs: Vec = vec![]; + let sample_rate = decoder.codec_params().sample_rate.unwrap(); + let channel_num = decoder.codec_params().channels.as_ref().unwrap().count(); + let mut bitrate_resample = resampler::ResamplerFir::new_from_hz(channel_num, sample_rate, self.audio_sink.sample_rate, Default::default(), Default::default()); + + log::debug!("Resampling {} -> {}", sample_rate, self.audio_sink.sample_rate); + + loop { + let packet = match format.next_packet() { + Ok(Some(packet)) => packet, + Ok(None) => break, + Err(err) => return Err(err) + }; + + match decoder.decode_ref(&packet.as_packet_ref()) { + Ok(samples) => { + channel_bufs.resize(samples.samples_interleaved(), 0.); + samples.copy_to_slice_interleaved(&mut channel_bufs); + + let mut resampled = [0.; 4096]; + let (read_count, write_count) = bitrate_resample.resample(&channel_bufs, &mut resampled).unwrap(); + if read_count < channel_bufs.len() { + log::error!("Resampling buffer is too small for a {}Hz file! We need an additional {}", sample_rate, read_count); + } + + // First we convert the audio feed from stereo down to mono by simple average + // TODO: This should be something smarter, like a saturating add..? + let mono_stream = resampled[..write_count].chunks(channel_num).map(|channels| { + let total_volume = channels.iter().cloned().reduce(|a, b| a + b).unwrap_or_default(); + total_volume / (channel_num as f32) + }); + + // Then we write out the resampled audio to our staging buffer + self.audio_out_buf.extend(mono_stream); + + // Once we have 1024 samples (jack default, I guess), we send it to the audio output + if self.audio_out_buf.len() >= 1024 { + self.submit_buffer().await; + } + }, + Err(err) => { + // Dump the audio buffer on failure + self.submit_buffer().await; + return Err(err) + } + } + } + if !self.audio_out_buf.is_empty() { + self.submit_buffer().await; + } + Ok(()) + } + + + // cw-m03 kwhz - morse code, 18:03 + // amateur radio station - spanish, 2:30 + // data transmission sound - 00:27 + // NOAA report - 00:50 + // sideband voice - 00:17 + + async fn play_sample(&mut self, path: &Path) { + log::debug!("Queuing sound playback for {:?}", path); + let sfx_fd = std::fs::File::open(path).unwrap(); + let mss = MediaSourceStream::new(Box::new(sfx_fd), Default::default()); + let meta_opts = Default::default(); + let fmt_opts = Default::default(); + let mut hint = Hint::new(); + // FIXME: use actual file extension + hint.with_extension(".mp3"); + let format = symphonia::default::get_probe() + .probe(&hint, mss, fmt_opts, meta_opts) + .expect("Unsupported audio format"); + let track = format.default_track(TrackType::Audio).expect("No audio track"); + let dec_opts = Default::default(); + let decoder = symphonia::default::get_codecs() + .make_audio_decoder( + track.codec_params.as_ref().expect("codec params missing").audio().unwrap(), + &dec_opts + ).expect("Unsupported audio codec"); + + + log::debug!("Starting stream"); + if let Err(err) = self.play_stream(format, decoder).await { + log::error!("Audio playback error: {:?}", err); + } + log::debug!("Playback complete"); + } +} + pub async fn start_sfx(audio_sink: AudioOutStream) -> SfxControl { let (event_sink, mut event_src) = tokio::sync::mpsc::channel(32); tokio::spawn(async move { + let mut player = Player { audio_sink, audio_out_buf: vec![] }; let sfx_dir = std::path::Path::new("./sfx"); loop { while let Some(event) = event_src.recv().await { match event { SfxRequest::RandomAmbient => { - let avail_files = std::fs::read_dir(sfx_dir).unwrap(); + log::debug!("Playing random audio sample"); + let avail_files = std::fs::read_dir(sfx_dir.join("ambient")).unwrap(); let chosen_file = avail_files.choose(&mut rand::rng()).unwrap().unwrap(); - log::debug!("Queuing ambient sound playback with {:?}", chosen_file); - let sfx_fd = std::fs::File::open(chosen_file.path()).unwrap(); - let mss = MediaSourceStream::new(Box::new(sfx_fd), Default::default()); - let meta_opts = Default::default(); - let fmt_opts = Default::default(); - let mut hint = Hint::new(); - hint.with_extension(".mp3"); - let mut format = symphonia::default::get_probe() - .probe(&hint, mss, fmt_opts, meta_opts) - .expect("Unsupported audio format"); - let track = format.default_track(TrackType::Audio).expect("No audio track"); - let track_id = track.id; - let dec_opts = Default::default(); - let mut decoder = symphonia::default::get_codecs() - .make_audio_decoder( - track.codec_params.as_ref().expect("codec params missing").audio().unwrap(), - &dec_opts - ).expect("Unsupported audio codec"); - - let sample_rate = decoder.codec_params().sample_rate.unwrap(); - let channel_num = decoder.codec_params().channels.as_ref().unwrap().count(); - log::debug!("Resampling {} -> {}", sample_rate, audio_sink.sample_rate); - // Our resampler works on a mono input - let mut bitrate_resample = resampler::ResamplerFir::new_from_hz(channel_num, sample_rate, audio_sink.sample_rate, Default::default(), Default::default()); - - log::debug!("Starting stream"); - let mut audio_out_buf = vec![]; - let mut channel_bufs: Vec = vec![]; - loop { - let packet = match format.next_packet() { - Ok(Some(packet)) => packet, - Ok(None) => break, - Err(err) => panic!() - }; - - if packet.track_id != track_id { - continue - } - - match decoder.decode_ref(&packet.as_packet_ref()) { - Ok(samples) => { - channel_bufs.resize(samples.samples_interleaved(), 0.); - samples.copy_to_slice_interleaved(&mut channel_bufs); - - let mut resampled = [0.; 2048]; - let (_, write_count) = bitrate_resample.resample(&channel_bufs, &mut resampled).unwrap(); - - // First we convert the audio feed from stereo down to mono by simple average - // TODO: This should be something smarter, like a saturating add..? - let mono_stream = resampled[..write_count].chunks(channel_num).map(|channels| { - let total_volume = channels.iter().cloned().reduce(|a, b| a + b).unwrap_or_default(); - total_volume / (channel_num as f32) - }); - - // Then we write out the resampled audio to our staging buffer - audio_out_buf.extend(mono_stream); - - // Once we have 1024 samples (jack default, I guess), we send it to the audio output - if audio_out_buf.len() >= 1024 { - audio_sink.sink.send(audio_out_buf).await.unwrap(); - audio_out_buf = vec![]; - } - }, - Err(err) => panic!() - } - } - if !audio_out_buf.is_empty() { - audio_sink.sink.send(audio_out_buf).await.unwrap(); - } - log::debug!("Playback complete"); + player.play_sample(&chosen_file.path()).await; } } }