use std::sync::{Arc, Mutex};
use bevy::prelude::*;
use itertools::Itertools;
use midix_synth::prelude::{Synthesizer, SynthesizerSettings};
use tinyaudio::run_output_device;
use crate::asset::{SoundFont, SoundFontLoader};
use super::{Synth, SynthState};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SynthParams {
pub channel_count: usize,
pub channel_sample_count: usize,
pub sample_rate: usize,
}
impl Default for SynthParams {
fn default() -> Self {
Self {
channel_count: 2,
sample_rate: 44100,
channel_sample_count: 441,
}
}
}
#[derive(Default, Clone, Copy)]
pub struct SynthPlugin {
params: SynthParams,
}
impl Plugin for SynthPlugin {
fn build(&self, app: &mut App) {
app.init_asset::<SoundFont>()
.init_asset_loader::<SoundFontLoader>()
.init_state::<SynthStatus>()
.insert_resource(Synth::new(self.params))
.add_systems(
PreUpdate,
(
load_audio_font.run_if(in_state(SynthStatus::ShouldLoad)),
sync_states.run_if(state_out_of_sync),
)
.chain(),
);
}
}
#[derive(States, Debug, Clone, PartialEq, Eq, Hash, Copy, Default)]
enum SynthStatus {
#[default]
NotLoaded,
ShouldLoad,
Loaded,
}
impl SynthState {
fn status_should_be(&self) -> SynthStatus {
match self {
Self::NotLoaded => SynthStatus::NotLoaded,
Self::LoadHandle { .. } => SynthStatus::ShouldLoad,
Self::Loaded(..) => SynthStatus::Loaded,
}
}
}
impl Synth {
fn status_should_be(&self) -> SynthStatus {
self.synthesizer.status_should_be()
}
}
fn load_audio_font(mut synth: ResMut<Synth>, assets: Res<Assets<SoundFont>>) {
let SynthState::LoadHandle { sound_font } = &synth.synthesizer else {
warn!(
"loading the audio font is out of sync. This is an issue with bevy_midix. Please file an issue!"
);
return;
};
let Some(sound_font) = assets.get(sound_font) else {
return;
};
let sound_font = Arc::clone(&sound_font.file);
let synth_settings = SynthesizerSettings::new(synth.params.sample_rate as i32);
let synthesizer = Arc::new(Mutex::new(
Synthesizer::new(&sound_font, &synth_settings).unwrap(),
));
let device_synth_ref = synthesizer.clone();
let mut left = vec![0f32; synth.params.channel_sample_count];
let mut right = vec![0f32; synth.params.channel_sample_count];
let _device = run_output_device(synth.params, {
move |data| {
let mut synth = device_synth_ref.lock().unwrap();
synth.render(&mut left[..], &mut right[..]);
for (i, value) in left.iter().interleave(right.iter()).enumerate() {
data[i] = *value;
}
}
})
.unwrap();
synth.synthesizer = SynthState::Loaded(synthesizer);
synth._device = Some(Mutex::new(_device));
}
fn state_out_of_sync(synth: Res<Synth>, current_state: Res<State<SynthStatus>>) -> bool {
&synth.status_should_be() != current_state.get()
}
fn sync_states(synth: Res<Synth>, mut next_state: ResMut<NextState<SynthStatus>>) {
next_state.set(synth.status_should_be());
}