pub const FREQUENCY: i32 = 22050;
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>;
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>,
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) {
self.devices.clear();
}
}
impl SoundDriver for Sound {
fn add_channel(&mut self, channel: usize) -> Result<String, String> {
if self.devices.contains_key(&channel) {
return Err(format!("cannot add same channel {}", channel));
}
let desired_spec = AudioSpec {
freq: Some(FREQUENCY),
channels: Some(1), 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))?;
let _ = audio_queue.stream.resume();
Ok("sound play".to_string())
}
}