resonance 0.1.0

A modular game engine. Heavy work in progress.
Documentation

use bevy_ecs::prelude::*;
use rodio::{OutputStream, Sink, SpatialSink, Source};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};

pub enum AudioSinkType {
    Regular(Sink),
    Spatial(SpatialSink),
}

impl AudioSinkType {
    pub fn set_volume(&self, volume: f32) {
        match self {
            AudioSinkType::Regular(sink) => sink.set_volume(volume),
            AudioSinkType::Spatial(sink) => sink.set_volume(volume),
        }
    }

    pub fn is_empty(&self) -> bool {
        match self {
            AudioSinkType::Regular(sink) => sink.empty(),
            AudioSinkType::Spatial(sink) => sink.empty(),
        }
    }

    pub fn pause(&self) {
        match self {
            AudioSinkType::Regular(sink) => sink.pause(),
            AudioSinkType::Spatial(sink) => sink.pause(),
        }
    }

    pub fn play(&self) {
        match self {
            AudioSinkType::Regular(sink) => sink.play(),
            AudioSinkType::Spatial(sink) => sink.play(),
        }
    }

    pub fn stop(&self) {
        match self {
            AudioSinkType::Regular(sink) => sink.stop(),
            AudioSinkType::Spatial(sink) => sink.stop(),
        }
    }

    pub fn append<S>(&self, source: S)
    where
        S: Source<Item = f32> + Send + 'static,
    {
        match self {
            AudioSinkType::Regular(sink) => sink.append(source),
            AudioSinkType::Spatial(sink) => sink.append(source),
        }
    }
}

struct SendOutputStream(Arc<OutputStream>);

unsafe impl Send for SendOutputStream {}
unsafe impl Sync for SendOutputStream {}

#[derive(Resource)]
pub struct AudioBackend {

    stream: SendOutputStream,

    sinks: Arc<Mutex<HashMap<Entity, AudioSinkType>>>,
}

impl AudioBackend {
    pub fn new() -> Result<Self, String> {

        let stream = rodio::OutputStreamBuilder::open_default_stream()
            .map_err(|e| format!("Failed to initialize audio output: {}", e))?;

        log::info!("Audio backend initialized successfully");

        Ok(Self {
            stream: SendOutputStream(Arc::new(stream)),
            sinks: Arc::new(Mutex::new(HashMap::new())),
        })
    }

    pub fn create_sink(&self, entity: Entity) -> Result<(), String> {
        let sink = rodio::Sink::connect_new(self.stream.0.mixer());
        let mut sinks = self.sinks.lock().unwrap();
        sinks.insert(entity, AudioSinkType::Regular(sink));
        Ok(())
    }

    pub fn create_spatial_sink(&self, entity: Entity, emitter_pos: [f32; 3]) -> Result<(), String> {

        let left_ear = [-1.0, 0.0, 0.0];
        let right_ear = [1.0, 0.0, 0.0];

        let sink = rodio::SpatialSink::connect_new(
            self.stream.0.mixer(),
            emitter_pos,
            left_ear,
            right_ear,
        );

        let mut sinks = self.sinks.lock().unwrap();
        sinks.insert(entity, AudioSinkType::Spatial(sink));
        Ok(())
    }

    pub fn set_emitter_position(&self, entity: Entity, position: [f32; 3]) {
        let sinks = self.sinks.lock().unwrap();
        if let Some(AudioSinkType::Spatial(sink)) = sinks.get(&entity) {
            sink.set_emitter_position(position);
        }
    }

    pub fn has_sink(&self, entity: Entity) -> bool {
        let sinks = self.sinks.lock().unwrap();
        sinks.contains_key(&entity)
    }

    pub fn remove_sink(&self, entity: Entity) {
        let mut sinks = self.sinks.lock().unwrap();
        if let Some(sink) = sinks.remove(&entity) {
            sink.stop();
        }
    }

    pub fn play_audio<S>(&self, entity: Entity, source: S, volume: f32) -> Result<(), String>
    where
        S: Source<Item = f32> + Send + 'static,
    {
        let sinks = self.sinks.lock().unwrap();
        if let Some(sink) = sinks.get(&entity) {
            sink.set_volume(volume);
            sink.append(source);
            Ok(())
        } else {
            Err(format!("No sink found for entity {:?}", entity))
        }
    }

    pub fn set_volume(&self, entity: Entity, volume: f32) {
        let sinks = self.sinks.lock().unwrap();
        if let Some(sink) = sinks.get(&entity) {
            sink.set_volume(volume.clamp(0.0, 1.0));
        }
    }

    pub fn is_playing(&self, entity: Entity) -> bool {
        let sinks = self.sinks.lock().unwrap();
        sinks
            .get(&entity)
            .map(|sink| !sink.is_empty())
            .unwrap_or(false)
    }

    pub fn pause(&self, entity: Entity) {
        let sinks = self.sinks.lock().unwrap();
        if let Some(sink) = sinks.get(&entity) {
            sink.pause();
        }
    }

    pub fn resume(&self, entity: Entity) {
        let sinks = self.sinks.lock().unwrap();
        if let Some(sink) = sinks.get(&entity) {
            sink.play();
        }
    }

    pub fn stop(&self, entity: Entity) {
        let sinks = self.sinks.lock().unwrap();
        if let Some(sink) = sinks.get(&entity) {
            sink.stop();
        }
    }

    pub fn active_count(&self) -> usize {
        let sinks = self.sinks.lock().unwrap();
        sinks.len()
    }

    pub fn cleanup_finished(&self) {
        let mut sinks = self.sinks.lock().unwrap();
        sinks.retain(|_, sink| !sink.is_empty());
    }
}

impl Default for AudioBackend {
    fn default() -> Self {
        Self::new().expect("Failed to create default audio backend")
    }
}

pub struct MemorySource {
    data: Arc<Vec<f32>>,
    position: usize,
    sample_rate: u32,
    channels: u16,
}

impl MemorySource {
    pub fn new(data: Vec<f32>, sample_rate: u32, channels: u16) -> Self {
        Self {
            data: Arc::new(data),
            position: 0,
            sample_rate,
            channels,
        }
    }
}

impl Iterator for MemorySource {
    type Item = f32;

    fn next(&mut self) -> Option<Self::Item> {
        if self.position < self.data.len() {
            let sample = self.data[self.position];
            self.position += 1;
            Some(sample)
        } else {
            None
        }
    }
}

impl Source for MemorySource {
    fn current_span_len(&self) -> Option<usize> {
        None
    }

    fn channels(&self) -> u16 {
        self.channels
    }

    fn sample_rate(&self) -> u32 {
        self.sample_rate
    }

    fn total_duration(&self) -> Option<std::time::Duration> {
        let total_samples = self.data.len() as u64;
        let frames = total_samples / self.channels as u64;
        let duration_secs = frames as f64 / self.sample_rate as f64;
        Some(std::time::Duration::from_secs_f64(duration_secs))
    }
}