sfx: start moving code into a new Player struct, which will eventually support mixing multiple audio streams
This commit is contained in:
+84
-49
@@ -1,5 +1,7 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use rand::seq::IteratorRandom;
|
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;
|
use crate::audio::AudioOutStream;
|
||||||
|
|
||||||
@@ -19,63 +21,41 @@ impl SfxControl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn start_sfx(audio_sink: AudioOutStream) -> SfxControl {
|
struct Player {
|
||||||
let (event_sink, mut event_src) = tokio::sync::mpsc::channel(32);
|
audio_sink: AudioOutStream,
|
||||||
tokio::spawn(async move {
|
audio_out_buf: Vec<f32>
|
||||||
let sfx_dir = std::path::Path::new("./sfx");
|
}
|
||||||
|
|
||||||
loop {
|
impl Player {
|
||||||
while let Some(event) = event_src.recv().await {
|
async fn submit_buffer(&mut self) {
|
||||||
match event {
|
self.audio_sink.sink.send(std::mem::take(&mut self.audio_out_buf)).await.unwrap();
|
||||||
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");
|
|
||||||
|
|
||||||
|
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 sample_rate = decoder.codec_params().sample_rate.unwrap();
|
||||||
let channel_num = decoder.codec_params().channels.as_ref().unwrap().count();
|
let channel_num = decoder.codec_params().channels.as_ref().unwrap().count();
|
||||||
log::debug!("Resampling {} -> {}", sample_rate, audio_sink.sample_rate);
|
let mut bitrate_resample = resampler::ResamplerFir::new_from_hz(channel_num, sample_rate, self.audio_sink.sample_rate, Default::default(), Default::default());
|
||||||
// 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!("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 {
|
loop {
|
||||||
let packet = match format.next_packet() {
|
let packet = match format.next_packet() {
|
||||||
Ok(Some(packet)) => packet,
|
Ok(Some(packet)) => packet,
|
||||||
Ok(None) => break,
|
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()) {
|
match decoder.decode_ref(&packet.as_packet_ref()) {
|
||||||
Ok(samples) => {
|
Ok(samples) => {
|
||||||
channel_bufs.resize(samples.samples_interleaved(), 0.);
|
channel_bufs.resize(samples.samples_interleaved(), 0.);
|
||||||
samples.copy_to_slice_interleaved(&mut channel_bufs);
|
samples.copy_to_slice_interleaved(&mut channel_bufs);
|
||||||
|
|
||||||
let mut resampled = [0.; 2048];
|
let mut resampled = [0.; 4096];
|
||||||
let (_, write_count) = bitrate_resample.resample(&channel_bufs, &mut resampled).unwrap();
|
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
|
// First we convert the audio feed from stereo down to mono by simple average
|
||||||
// TODO: This should be something smarter, like a saturating add..?
|
// 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
|
// 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
|
// Once we have 1024 samples (jack default, I guess), we send it to the audio output
|
||||||
if audio_out_buf.len() >= 1024 {
|
if self.audio_out_buf.len() >= 1024 {
|
||||||
audio_sink.sink.send(audio_out_buf).await.unwrap();
|
self.submit_buffer().await;
|
||||||
audio_out_buf = vec![];
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
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");
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user