use crate::events::AudioBridgeRequest;
use bevy::prelude::*;
use std::collections::{HashMap, HashSet};
use std::f32::consts::FRAC_PI_2;
#[derive(Resource, Default)]
pub struct AudioBridgeTargets(pub HashSet<Entity>);
#[derive(Resource, Default)]
pub struct AudioBridgeBuffers(pub HashMap<Entity, Vec<f32>>);
pub fn handle_bridge_requests(
mut requests: MessageReader<AudioBridgeRequest>,
mut targets: ResMut<AudioBridgeTargets>,
) {
for request in requests.read() {
targets.0.insert(request.entity);
}
}
#[derive(Resource, Default)]
pub struct BridgeAudioDevice;
#[derive(Resource, Default)]
pub struct BridgeAudioSinks;
impl BridgeAudioSinks {
pub fn get(&self, _entity: Entity) -> Option<&()> {
None
}
pub fn get_mut(&mut self, _entity: Entity) -> Option<&mut ()> {
None
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct AudioBridgeMix {
pub volume: f32,
pub pan: f32,
}
impl AudioBridgeMix {
pub const CENTER: Self = Self {
volume: 1.0,
pan: 0.0,
};
pub const MUTE: Self = Self {
volume: 0.0,
pan: 0.0,
};
pub const LEFT: Self = Self {
volume: 1.0,
pan: -1.0,
};
pub const RIGHT: Self = Self {
volume: 1.0,
pan: 1.0,
};
pub fn db_to_gain(volume_db: f32) -> f32 {
10_f32.powf(volume_db / 20.0)
}
pub fn gain_to_db(volume: f32) -> f32 {
20.0 * volume.max(1e-6).log10()
}
pub fn volume_db(self) -> f32 {
Self::gain_to_db(self.volume)
}
pub(crate) fn gains(self) -> (f32, f32) {
let volume = self.volume.clamp(0.0, 2.0);
let pan = self.pan.clamp(-1.0, 1.0);
let angle = (pan + 1.0) * FRAC_PI_2 * 0.5;
let left = volume * angle.cos();
let right = volume * angle.sin();
(left, right)
}
}
#[derive(Resource, Default)]
pub struct AudioBridgeMixes(pub HashMap<Entity, AudioBridgeMix>);
impl AudioBridgeMixes {
pub fn set(&mut self, entity: Entity, mix: AudioBridgeMix) {
self.0.insert(entity, mix);
}
pub fn set_volume(&mut self, entity: Entity, volume: f32) {
self.0
.entry(entity)
.and_modify(|mix| mix.volume = volume)
.or_insert(AudioBridgeMix {
volume,
..Default::default()
});
}
pub fn set_volume_db(&mut self, entity: Entity, volume_db: f32) {
let volume = AudioBridgeMix::db_to_gain(volume_db);
self.set_volume(entity, volume);
}
pub fn set_pan(&mut self, entity: Entity, pan: f32) {
self.0
.entry(entity)
.and_modify(|mix| mix.pan = pan)
.or_insert(AudioBridgeMix {
pan,
..Default::default()
});
}
pub fn clear(&mut self, entity: Entity) {
self.0.remove(&entity);
}
pub fn get(&self, entity: Entity) -> AudioBridgeMix {
self.0.get(&entity).copied().unwrap_or_default()
}
}
pub fn drive_bridge_audio_buffers(
config: Res<crate::plugin::Ym2149PluginConfig>,
targets: Res<AudioBridgeTargets>,
mut buffers: ResMut<AudioBridgeBuffers>,
mixes: Res<AudioBridgeMixes>,
) {
if !config.bevy_audio_bridge {
return;
}
for (entity, samples) in buffers.0.iter_mut() {
if !targets.0.contains(entity) {
continue;
}
let mix = mixes.get(*entity);
let (left_gain, right_gain) = mix.gains();
for stereo_pair in samples.chunks_mut(2) {
if stereo_pair.len() == 2 {
stereo_pair[0] *= left_gain;
stereo_pair[1] *= right_gain;
}
}
}
}