sfx: start moving code into a new Player struct, which will eventually support mixing multiple audio streams
This commit is contained in:
+107
-72
@@ -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<f32>
|
||||
}
|
||||
|
||||
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();
|
||||
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<f32> = 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user