librsmsx 0.4.6

a MSX emulator written in rust, a port from gomsx
Documentation
pub const FREQUENCY: i32 = 22050;
// pub const FREQUENCY: i32 = 44100;

use std::{
    cell::RefCell,
    collections::HashMap,
    rc::Rc,
    sync::{Arc, Mutex},
};

use sdl3::{
    audio::{AudioCallback, AudioSpec, AudioStreamWithCallback},
    AudioSubsystem,
};

use super::base_system::BaseSystem;

pub enum SoundType {
    None,
    Normal,
}
impl SoundType {
    pub fn create(self, sys: Option<&mut BaseSystem>) -> Rc<RefCell<dyn SoundDriver>> {
        match self {
            SoundType::None => Rc::new(RefCell::new(NullSound::new())),
            SoundType::Normal => Rc::new(RefCell::new(Sound::new(sys))),
        }
    }
}

pub trait SoundDriver {
    fn add_channel(&mut self, channel: usize) -> Result<String, String>;
    // pub fn feed_samples(&mut self, channel: usize, wave: &[i16]) -> Result<String, String> {
    fn feed_samples(&mut self, channel: usize, freq: f32, volume: f32) -> Result<String, String>;
    fn pause(&mut self, channel: usize) -> Result<String, String>;
    fn play(&mut self, channel: usize) -> Result<String, String>;
}

pub struct NullSound {}

impl Default for NullSound {
    fn default() -> Self {
        Self::new()
    }
}

impl NullSound {
    pub fn new() -> Self {
        Self {}
    }
}
impl SoundDriver for NullSound {
    fn add_channel(&mut self, _channel: usize) -> Result<String, String> {
        Ok("null sound driver".to_string())
    }
    fn feed_samples(
        &mut self,
        _channel: usize,
        _freq: f32,
        _volume: f32,
    ) -> Result<String, String> {
        Ok("null sound driver".to_string())
    }
    fn pause(&mut self, _channel: usize) -> Result<String, String> {
        Ok("null sound driver".to_string())
    }
    fn play(&mut self, _channel: usize) -> Result<String, String> {
        Ok("null sound driver".to_string())
    }
}

#[allow(dead_code)]
enum Tone {
    Square,
    Sin,
}

struct TuneData {
    rate: f32,
    volume: f32,
}

struct Oscillator {
    current_step: f32,
    tune_data: Arc<Mutex<TuneData>>,
    mode: Tone,
}
impl Oscillator {
    pub fn new(tune_data: Arc<Mutex<TuneData>>, mode: Tone) -> Self {
        Self {
            current_step: 0.0,
            tune_data,
            mode,
        }
    }
    pub fn next(&mut self) -> f32 {
        let Ok(data) = self.tune_data.lock() else {
            return 0.0;
        };
        if data.rate == 0.0 || data.volume == 0.0 {
            return 0.0;
        }
        let step_size = 2.0 * std::f32::consts::PI / data.rate;
        self.current_step += step_size;
        match self.mode {
            Tone::Square => {
                if f32::sin(self.current_step) > 0.0 {
                    data.volume
                } else {
                    -data.volume
                }
            }
            Tone::Sin => f32::sin(self.current_step) * data.volume,
        }
    }
}

impl AudioCallback<f32> for Oscillator {
    fn callback(&mut self, out: &mut [f32]) {
        for x in out.iter_mut() {
            *x = self.next();
        }
    }
}

struct SoundDevice {
    stream: AudioStreamWithCallback<Oscillator>,
    // https://github.com/revmischa/sdl3-rs/blob/master/examples/audio-whitenoise.rs
    // AudioStreamWithCallback<> does not have lock()
    tune_data: Arc<Mutex<TuneData>>,
}

pub struct Sound {
    audio_subsystem: AudioSubsystem,
    devices: HashMap<usize, SoundDevice>,
}

impl Default for Sound {
    fn default() -> Self {
        Self::new(None)
    }
}

impl Sound {
    pub fn new(sys: Option<&mut BaseSystem>) -> Self {
        let sys = sys.unwrap();
        let audio_subsystem = sys.get_sdl_context().audio().unwrap();
        Self {
            audio_subsystem,
            devices: HashMap::new(),
        }
    }
}

impl Drop for Sound {
    fn drop(&mut self) {
        // prevent STATUS_HEAP_CORRUPTION
        self.devices.clear();
    }
}

impl SoundDriver for Sound {
    fn add_channel(&mut self, channel: usize) -> Result<String, String> {
        // log::info!("add_channel channel:{}", channel);
        if self.devices.contains_key(&channel) {
            return Err(format!("cannot add same channel {}", channel));
        }
        let desired_spec = AudioSpec {
            freq: Some(FREQUENCY),
            channels: Some(1), // mono
            format: Some(sdl3::audio::AudioFormat::F32LE),
        };

        let tune_data = Arc::new(Mutex::new(TuneData {
            rate: 1.0,
            volume: 0.0,
        }));
        let oscillator = Oscillator::new(tune_data.clone(), Tone::Sin);
        let stream = self
            .audio_subsystem
            .open_playback_stream(&desired_spec, oscillator)
            .unwrap();
        self.devices
            .insert(channel, SoundDevice { stream, tune_data });
        Ok("channel added".to_string())
    }
    fn feed_samples(&mut self, channel: usize, freq: f32, vol: f32) -> Result<String, String> {
        let audio_queue = self
            .devices
            .get_mut(&channel)
            .ok_or(format!("no such channel {}", channel))?;

        {
            let Ok(mut data) = audio_queue.tune_data.lock() else {
                return Ok("feed samples failed".to_string());
            };
            data.rate = FREQUENCY as f32 / freq;
            data.volume = vol / 15.0;
        }

        Ok("sound queue".to_string())
    }
    fn pause(&mut self, channel: usize) -> Result<String, String> {
        let audio_queue = self
            .devices
            .get(&channel)
            .ok_or(format!("no such channel {}", channel))?;
        let _ = audio_queue.stream.pause();
        Ok("sound pause".to_string())
    }
    fn play(&mut self, channel: usize) -> Result<String, String> {
        let audio_queue = self
            .devices
            .get_mut(&channel)
            .ok_or(format!("no such channel {}", channel))?;

        // Start playback
        let _ = audio_queue.stream.resume();
        Ok("sound play".to_string())
    }
}