sfx: start moving code into a new Player struct, which will eventually support mixing multiple audio streams

This commit is contained in:
2026-06-22 21:55:47 +02:00
parent bcf37182fe
commit 646ea5bca3
+84 -49
View File
@@ -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,63 +21,41 @@ impl SfxControl {
}
}
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 sfx_dir = std::path::Path::new("./sfx");
struct Player {
audio_sink: AudioOutStream,
audio_out_buf: Vec<f32>
}
loop {
while let Some(event) = event_src.recv().await {
match event {
SfxRequest::RandomAmbient => {
let avail_files = std::fs::read_dir(sfx_dir).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");
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<dyn FormatReader>, mut decoder: Box<dyn AudioDecoder>) -> Result<(), symphonia::core::errors::Error> {
let mut channel_bufs: Vec<f32> = vec![];
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());
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);
log::debug!("Starting stream");
let mut audio_out_buf = vec![];
let mut channel_bufs: Vec<f32> = vec![];
loop {
let packet = match format.next_packet() {
Ok(Some(packet)) => packet,
Ok(None) => break,
Err(err) => panic!()
Err(err) => return Err(err)
};
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();
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..?
@@ -85,23 +65,78 @@ pub async fn start_sfx(audio_sink: AudioOutStream) -> SfxControl {
});
// Then we write out the resampled audio to our staging buffer
audio_out_buf.extend(mono_stream);
self.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![];
if self.audio_out_buf.len() >= 1024 {
self.submit_buffer().await;
}
},
Err(err) => panic!()
Err(err) => {
// Dump the audio buffer on failure
self.submit_buffer().await;
return Err(err)
}
}
if !audio_out_buf.is_empty() {
audio_sink.sink.send(audio_out_buf).await.unwrap();
}
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 => {
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();
player.play_sample(&chosen_file.path()).await;
}
}
}
}
});