use kira::{
manager::{
backend::{Backend, DefaultBackend},
AudioManager, AudioManagerSettings, Capacities,
},
sound::{Sound, SoundData},
};
use std::{convert::Infallible, time::Duration};
pub struct FundspAudioPlugin;
impl Plugin for FundspAudioPlugin {
fn build(&self, app: &mut App) {
app.init_non_send_resource::<FundspAudioOutput>();
}
}
#[derive(Resource, Default)]
pub struct FundspBackendSettings {}
impl From<FundspBackendSettings> for AudioManagerSettings<DefaultBackend> {
fn from(value: FundspBackendSettings) -> Self {
AudioManagerSettings {
capacities: Capacities {
// command_capacity: settings.command_capacity,
// sound_capacity: settings.sound_capacity,
..default()
},
..default()
}
}
}
pub struct FundspAudioOutput<B: Backend = DefaultBackend> {
manager: Option<AudioManager<B>>,
}
impl FromWorld for FundspAudioOutput {
fn from_world(world: &mut World) -> Self {
let settings = world
.remove_resource::<FundspBackendSettings>()
.unwrap_or_default();
let manager = AudioManager::new(settings.into());
if let Err(ref setup_err) = manager {
warn!("failed to setup up fundsp audio: {setup_err:?}")
}
Self {
manager: manager.ok(),
}
}
}
impl FundspAudioOutput {
pub(crate) fn play<T: AudioUnit32 + 'static>(&mut self, node: T, settings: AudioNodeSettings) {
if let Some(manager) = self.manager.as_mut() {
manager
.play(AudioNodeAdapter::<T>::new(node, settings))
.unwrap();
}
}
}
pub struct AudioNodeAdapter<T> {
node: T,
settings: AudioNodeSettings,
}
#[derive(Clone, Debug, Default)]
pub struct AudioNodeSettings {
pub output: kira::OutputDestination,
pub duration: Option<Duration>,
}
impl<T> AudioNodeAdapter<T>
where
T: AudioUnit32,
{
pub fn new(node: T, settings: AudioNodeSettings) -> Self {
Self { node, settings }
}
}
impl<T> SoundData for AudioNodeAdapter<T>
where
T: AudioUnit32 + 'static,
{
type Error = Infallible;
type Handle = ();
fn into_sound(mut self) -> Result<(Box<dyn Sound>, Self::Handle), Self::Error> {
self.node.allocate();
Ok((
Box::new(AudioNodeAdapterSound::new(self.node, self.settings)),
(),
))
}
}
pub struct AudioNodeAdapterSound<T> {
node: T,
settings: AudioNodeSettings,
elapsed: Duration,
rms_left: Shared<f32>,
rms_right: Shared<f32>,
monitor_left: An<Monitor<f32>>,
monitor_right: An<Monitor<f32>>,
}
impl<T> AudioNodeAdapterSound<T>
where
T: AudioUnit32,
{
pub fn new(node: T, settings: AudioNodeSettings) -> Self {
let rms_left = Shared::new(0.0);
let rms_right = Shared::new(0.0);
AudioNodeAdapterSound {
node,
settings,
elapsed: Duration::ZERO,
monitor_left: monitor(&rms_left, Meter::Rms(0.1)),
monitor_right: monitor(&rms_right, Meter::Rms(0.1)),
rms_left,
rms_right,
}
}
}
impl<T> Sound for AudioNodeAdapterSound<T>
where
T: AudioUnit32,
{
fn output_destination(&mut self) -> kira::OutputDestination {
self.settings.output.clone()
}
fn process(
&mut self,
dt: f64,
_clock_info_provider: &kira::clock::clock_info::ClockInfoProvider,
_modulator_value_provider: &kira::modulator::value_provider::ModulatorValueProvider,
) -> kira::dsp::Frame {
self.elapsed += Duration::from_secs_f64(dt);
let (left, right) = self.node.get_stereo();
// Process samples
let left = self.monitor_left.filter_mono(left);
let right = self.monitor_right.filter_mono(right);
kira::dsp::Frame { left, right }
}
fn finished(&self) -> bool {
let left_rms = self.rms_left.value();
let right_rms = self.rms_right.value();
debug!("RMS: {left_rms} {right_rms}");
// NOTE This is empirical rn.
const NOISE_FLOOR_RMS: f32 = 0.001;
self.settings
.duration
.is_some_and(|lim| self.elapsed >= lim)
// If we are quieter (on both channels!!) than the noise floor, assume we
// are finished an want to stop
|| (left_rms < NOISE_FLOOR_RMS && right_rms < NOISE_FLOOR_RMS)
}
}