diff options
| author | Freya Murphy <freya@freyacat.org> | 2025-11-23 14:50:31 -0500 |
|---|---|---|
| committer | Freya Murphy <freya@freyacat.org> | 2025-11-23 16:18:34 -0500 |
| commit | bb670049840e64e96b9bf0bf72897d6f3a928194 (patch) | |
| tree | 93adb7011a450b693e8be0c23707ad9813842a63 /audio/src/channel.rs | |
| parent | audio: some changes (diff) | |
| download | DungeonCrawl-bb670049840e64e96b9bf0bf72897d6f3a928194.tar.gz DungeonCrawl-bb670049840e64e96b9bf0bf72897d6f3a928194.tar.bz2 DungeonCrawl-bb670049840e64e96b9bf0bf72897d6f3a928194.zip | |
audio: refactor everything
Diffstat (limited to 'audio/src/channel.rs')
| -rw-r--r-- | audio/src/channel.rs | 279 |
1 files changed, 109 insertions, 170 deletions
diff --git a/audio/src/channel.rs b/audio/src/channel.rs index 7ed61a6..fa0bbbd 100644 --- a/audio/src/channel.rs +++ b/audio/src/channel.rs @@ -1,199 +1,138 @@ -use raylib::audio::RaylibAudio; +#![expect(clippy::cast_possible_truncation)] -type Music = raylib::audio::Music<'static>; +use std::sync::{Arc, Mutex}; -macro_rules! load_audio { - ($handle:expr, $filepath:expr) => {{ - let mut audio = if cfg!(any(feature = "static", target_arch = "wasm32")) { - let bytes = include_bytes!(concat!("../../", $filepath)); - let vec = Vec::from(bytes); - $handle.new_music_from_memory(".wav", &vec)? - } else { - $handle.new_music($filepath)? - }; - audio.looping = true; - audio - }}; -} +use raylib::{ + audio::{AudioStream, RaylibAudio}, + prelude::audio_stream_callback::set_audio_stream_callback, +}; -pub trait Channel { - fn stop(&self); - fn play(&self); - fn update(&self); - fn is_playing(&self) -> bool; - fn set_volume(&self, volume: f32); - fn set_pitch(&self, pitch: f32); -} +const SAMPLE_RATE: u32 = 44100; +const SAMPLE_SIZE: u32 = i16::BITS; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum DutyCycle { - Percent12, - Percent25, - Percent50, - Percent25Neg, +#[derive(Debug, Clone, Copy)] +pub enum ChannelKind { + Pulse { duty_cycle: u8 }, + Triangle, + Noise { mode: bool, lsr: i16 }, } - -pub struct PulseChannel { - pulse_12: Music, - pulse_25: Music, - pulse_50: Music, - pulse_75: Music, - duty: DutyCycle, -} -impl PulseChannel { - pub fn load(handle: &'static RaylibAudio) -> crate::Result<Self> { - Ok(Self { - pulse_12: load_audio!(handle, "assets/wav/pulse_12.wav"), - pulse_25: load_audio!(handle, "assets/wav/pulse_25.wav"), - pulse_50: load_audio!(handle, "assets/wav/pulse_50.wav"), - pulse_75: load_audio!(handle, "assets/wav/pulse_50.wav"), - duty: DutyCycle::Percent50, - }) +impl ChannelKind { + pub const fn pulse() -> Self { + Self::Pulse { duty_cycle: 50 } } - pub fn set_duty(&mut self, duty: DutyCycle) { - self.duty = duty; - if self.is_playing() { - self.stop(); - self.play(); - } - } -} -impl Channel for PulseChannel { - fn stop(&self) { - self.pulse_12.stop_stream(); - self.pulse_25.stop_stream(); - self.pulse_50.stop_stream(); - self.pulse_75.stop_stream(); + pub const fn triangle() -> Self { + Self::Triangle } - fn play(&self) { - use DutyCycle as D; - match self.duty { - D::Percent12 => self.pulse_12.play_stream(), - D::Percent25 => self.pulse_25.play_stream(), - D::Percent50 => self.pulse_50.play_stream(), - D::Percent25Neg => self.pulse_75.play_stream(), + pub const fn noise() -> Self { + Self::Noise { + mode: false, + lsr: 1, } } - fn update(&self) { - self.pulse_12.update_stream(); - self.pulse_25.update_stream(); - self.pulse_50.update_stream(); - self.pulse_75.update_stream(); - } - - fn is_playing(&self) -> bool { - self.pulse_12.is_stream_playing() - || self.pulse_25.is_stream_playing() - || self.pulse_50.is_stream_playing() - || self.pulse_75.is_stream_playing() - } - - fn set_volume(&self, volume: f32) { - self.pulse_12.set_volume(volume); - self.pulse_25.set_volume(volume); - self.pulse_50.set_volume(volume); - self.pulse_75.set_volume(volume); - } - - fn set_pitch(&self, pitch: f32) { - self.pulse_12.set_pitch(pitch); - self.pulse_25.set_pitch(pitch); - self.pulse_50.set_pitch(pitch); - self.pulse_75.set_pitch(pitch); + fn sample(&mut self, phase: f32) -> i16 { + match self { + Self::Pulse { duty_cycle } => { + let duty = *duty_cycle as f32 / 100.0; + if phase < duty { i16::MAX } else { i16::MIN } + } + Self::Triangle => { + let steps = 32; + let step = (phase * steps as f32).floor() as u32; + let value = (((step as f32 / (steps - 1) as f32) * 2.0) - 0.5) * 2.0; + (value * i16::MAX as f32) as i16 + } + Self::Noise { mode, lsr } => { + let feedback = if *mode { + (*lsr & 1) ^ ((*lsr >> 1) & 1) + } else { + (*lsr & 1) ^ ((*lsr >> 6) & 1) + }; + *lsr >>= 1; + *lsr |= feedback << 14; + *lsr + } + } } } -pub struct TriangleChannel { - inner: Music, +#[derive(Debug)] +pub struct Channel { + pub volume: f32, + pub pitch: f32, + pub kind: ChannelKind, + phase: f32, } -impl TriangleChannel { - pub fn load(handle: &'static RaylibAudio) -> crate::Result<Self> { - Ok(Self { - inner: load_audio!(handle, "assets/wav/triangle.wav"), - }) - } -} -impl Channel for TriangleChannel { - fn stop(&self) { - self.inner.stop_stream(); - } - - fn play(&self) { - self.inner.play_stream(); - } - - fn update(&self) { - self.inner.update_stream(); - } - - fn is_playing(&self) -> bool { - self.inner.is_stream_playing() - } - - fn set_volume(&self, volume: f32) { - self.inner.set_volume(volume); +impl Channel { + const fn new(kind: ChannelKind) -> Self { + Self { + volume: 0.0, + pitch: 1.0, + kind, + phase: 0.0, + } } - fn set_pitch(&self, pitch: f32) { - self.inner.set_pitch(pitch); + fn sample(&mut self, buffer: &mut [i16]) { + let freq = self.pitch * 440.0; + let step = freq / SAMPLE_RATE as f32; + for sample in buffer { + self.phase += step; + if self.phase >= 1.0 { + self.phase -= 1.0; + } + let real_note = self.kind.sample(self.phase); + let note = (real_note as f32 * self.volume) as i16; + *sample += note / 4; + } } } -pub struct NoiseChannel { - noise_0: Music, - noise_1: Music, - mode: bool, +pub struct Channels { + pub pulse_a: Channel, + pub pulse_b: Channel, + pub triangle: Channel, + pub noise: Channel, } -impl NoiseChannel { - pub fn load(handle: &'static RaylibAudio) -> crate::Result<Self> { - Ok(Self { - noise_0: load_audio!(handle, "assets/wav/noise_0.wav"), - noise_1: load_audio!(handle, "assets/wav/noise_1.wav"), - mode: false, - }) - } - - pub fn set_mode(&mut self, mode: bool) { - self.mode = mode; - if self.is_playing() { - self.stop(); - self.play(); +impl Channels { + const fn new() -> Self { + let pulse_a = Channel::new(ChannelKind::pulse()); + let pulse_b = Channel::new(ChannelKind::pulse()); + let triangle = Channel::new(ChannelKind::triangle()); + let noise = Channel::new(ChannelKind::noise()); + Self { + pulse_a, + pulse_b, + triangle, + noise, } } } -impl Channel for NoiseChannel { - fn stop(&self) { - self.noise_0.stop_stream(); - self.noise_1.stop_stream(); - } - - fn play(&self) { - if self.mode { - self.noise_1.play_stream(); - } else { - self.noise_0.play_stream(); - } - } - - fn update(&self) { - self.noise_0.update_stream(); - self.noise_0.update_stream(); - } - - fn is_playing(&self) -> bool { - self.noise_0.is_stream_playing() || self.noise_1.is_stream_playing() - } - fn set_volume(&self, volume: f32) { - self.noise_0.set_volume(volume); - self.noise_1.set_volume(volume); - } +pub struct Device<'s> { + pub channels: Arc<Mutex<Channels>>, + #[expect(dead_code)] + stream: AudioStream<'s>, +} - fn set_pitch(&self, pitch: f32) { - self.noise_0.set_pitch(pitch); - self.noise_1.set_pitch(pitch); +#[expect(clippy::unwrap_used)] +impl<'s> Device<'s> { + pub(crate) fn load(handle: &'s RaylibAudio) -> crate::Result<Self> { + let channels = Arc::new(Mutex::new(Channels::new())); + let stream = handle.new_audio_stream(SAMPLE_RATE, SAMPLE_SIZE, 1); + let cb_data = Arc::clone(&channels); + set_audio_stream_callback(&stream, move |buffer| { + let mut state = cb_data.lock().unwrap(); + state.pulse_a.sample(buffer); + state.pulse_b.sample(buffer); + state.triangle.sample(buffer); + state.noise.sample(buffer); + })?; + stream.set_volume(1.0); + stream.set_pitch(1.0); + stream.play(); + Ok(Self { channels, stream }) } } |