#![expect(clippy::cast_possible_truncation)] use atomic::Atomic; use bytemuck::NoUninit; use raylib::{ audio::{AudioStream, RaylibAudio}, prelude::audio_stream_callback::set_audio_stream_callback, }; use std::sync::{Arc, atomic::Ordering}; const SAMPLE_RATE: u32 = 44100; const SAMPLE_SIZE: u32 = i16::BITS; fn map_pitch(pitch: u8) -> f32 { const HALF_STEP: f32 = 1.059_463_1; HALF_STEP.powf(pitch as f32 - 128.0) } const fn map_volume(volume: u8) -> f32 { if volume > 100 { 1.0 } else { (volume as f32) / 100.0 } } #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq, NoUninit)] pub enum ChannelKind { Pulse { duty_cycle: u8 }, Triangle { _pad: u8 }, Noise { mode: bool }, } impl ChannelKind { pub const fn pulse() -> Self { Self::Pulse { duty_cycle: 50 } } pub const fn triangle() -> Self { Self::Triangle { _pad: 0 } } pub const fn noise() -> Self { Self::Noise { mode: false } } const fn on_phase(self, lsr_ptr: &mut i16) { if let Self::Noise { mode } = self { let mut lsr = *lsr_ptr; let feedback = if mode { (lsr & 1) ^ ((lsr >> 1) & 1) } else { (lsr & 1) ^ ((lsr >> 6) & 1) }; lsr >>= 1; lsr |= feedback << 14; *lsr_ptr = lsr; } } fn sample(self, phase: f32, lsr: i16) -> 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 { .. } => { if (lsr & 0x1) == 1 { i16::MAX } else { i16::MIN } } } } } #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, Eq, NoUninit)] pub struct Channel { pub volume: u8, pub pitch: u8, pub kind: ChannelKind, } impl Channel { const fn new(kind: ChannelKind) -> Self { Self { volume: 0, pitch: 128, kind, } } fn sample(self, buffer: &mut [i16], phase_ptr: &mut f32, lsr_ptr: &mut i16) { let freq = map_pitch(self.pitch) * 440.0; let volume = map_volume(self.volume); let step = freq / SAMPLE_RATE as f32; let mut phase = *phase_ptr; for sample in buffer { phase += step; if phase >= 1.0 { phase -= 1.0; self.kind.on_phase(lsr_ptr); } let real_note = self.kind.sample(phase, *lsr_ptr); let note = (real_note as f32 * volume) as i16; *sample += note / 4; } *phase_ptr = phase; } } #[derive(Debug, Clone, Copy)] struct RuntimeState { pulse_a_phase: f32, pulse_b_phase: f32, triangle_phase: f32, noise_phase: f32, noise_lsr: i16, } impl RuntimeState { const fn new() -> Self { Self { pulse_a_phase: 0.0, pulse_b_phase: 0.0, triangle_phase: 0.0, noise_phase: 0.0, noise_lsr: 1, } } } #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, Eq, NoUninit)] pub struct Channels { pub pulse_a: Channel, pub pulse_b: Channel, pub triangle: Channel, pub noise: Channel, } impl Channels { pub 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 Default for Channels { fn default() -> Self { Self::new() } } pub struct Device<'s> { pub channels: Arc>, #[expect(dead_code)] stream: AudioStream<'s>, } impl<'s> Device<'s> { pub(crate) fn load(handle: &'s RaylibAudio) -> crate::Result { let channels = Arc::new(Atomic::new(Channels::new())); let stream = handle.new_audio_stream(SAMPLE_RATE, SAMPLE_SIZE, 1); let cb_data = Arc::clone(&channels); let mut state = RuntimeState::new(); set_audio_stream_callback(&stream, move |buffer| { let chan = cb_data.load(Ordering::Relaxed); chan.pulse_a .sample(buffer, &mut state.pulse_a_phase, &mut state.noise_lsr); chan.pulse_b .sample(buffer, &mut state.pulse_b_phase, &mut state.noise_lsr); chan.triangle .sample(buffer, &mut state.triangle_phase, &mut state.noise_lsr); chan.noise .sample(buffer, &mut state.noise_phase, &mut state.noise_lsr); })?; stream.set_volume(1.0); stream.set_pitch(1.0); stream.play(); Ok(Self { channels, stream }) } }