#![expect(clippy::cast_possible_truncation)] use std::sync::{Arc, Mutex}; use raylib::{ audio::{AudioStream, RaylibAudio}, prelude::audio_stream_callback::set_audio_stream_callback, }; const SAMPLE_RATE: u32 = 44100; const SAMPLE_SIZE: u32 = i16::BITS; #[derive(Debug, Clone, Copy)] pub enum ChannelKind { Pulse { duty_cycle: u8 }, Triangle, Noise { mode: bool, lsr: i16 }, } impl ChannelKind { pub const fn pulse() -> Self { Self::Pulse { duty_cycle: 50 } } pub const fn triangle() -> Self { Self::Triangle } pub const fn noise() -> Self { Self::Noise { mode: false, lsr: 1, } } 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 tphase = if phase < 0.5 { phase } else { 1.0 - phase }; let step = (tphase * steps as f32).floor() as u32; let value = ((step as f32 / (steps - 1) as f32) * 4.0) - 1.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 } } } } #[derive(Debug)] pub struct Channel { pub volume: f32, pub pitch: f32, pub kind: ChannelKind, phase: f32, } impl Channel { const fn new(kind: ChannelKind) -> Self { Self { volume: 0.0, pitch: 1.0, kind, phase: 0.0, } } 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 Channels { pub pulse_a: Channel, pub pulse_b: Channel, pub triangle: Channel, pub noise: Channel, } 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, } } } pub struct Device<'s> { pub channels: Arc>, #[expect(dead_code)] stream: AudioStream<'s>, } #[expect(clippy::unwrap_used)] impl<'s> Device<'s> { pub(crate) fn load(handle: &'s RaylibAudio) -> crate::Result { 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 }) } }