summaryrefslogtreecommitdiff
path: root/audio/src/channel.rs
diff options
context:
space:
mode:
authorFreya Murphy <freya@freyacat.org>2025-11-23 14:50:31 -0500
committerFreya Murphy <freya@freyacat.org>2025-11-23 16:18:34 -0500
commitbb670049840e64e96b9bf0bf72897d6f3a928194 (patch)
tree93adb7011a450b693e8be0c23707ad9813842a63 /audio/src/channel.rs
parentaudio: some changes (diff)
downloadDungeonCrawl-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.rs279
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 })
}
}