summaryrefslogtreecommitdiff
path: root/graphics
diff options
context:
space:
mode:
authorFreya Murphy <freya@freyacat.org>2025-11-22 13:29:10 -0500
committerFreya Murphy <freya@freyacat.org>2025-11-22 13:29:10 -0500
commit2274d33e469aca544a7aeb899a10769b973ae374 (patch)
tree7ff10964234fd5e8917624508e249265a3d0b19c /graphics
parentaudio: move 'asm' files out of data segment (diff)
downloadDungeonCrawl-2274d33e469aca544a7aeb899a10769b973ae374.tar.gz
DungeonCrawl-2274d33e469aca544a7aeb899a10769b973ae374.tar.bz2
DungeonCrawl-2274d33e469aca544a7aeb899a10769b973ae374.zip
audio: refactor into seperate crate
Diffstat (limited to 'graphics')
-rw-r--r--graphics/Cargo.toml3
-rw-r--r--graphics/src/audio/channel.rs199
-rw-r--r--graphics/src/audio/data.rs30
-rw-r--r--graphics/src/audio/mod.rs92
-rw-r--r--graphics/src/audio/parse/lex.rs218
-rw-r--r--graphics/src/audio/parse/macros.rs103
-rw-r--r--graphics/src/audio/parse/mod.rs12
-rw-r--r--graphics/src/audio/parse/parser.rs104
-rw-r--r--graphics/src/audio/program.rs184
-rw-r--r--graphics/src/lib.rs8
10 files changed, 7 insertions, 946 deletions
diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml
index 0240745..0687272 100644
--- a/graphics/Cargo.toml
+++ b/graphics/Cargo.toml
@@ -8,6 +8,7 @@ publish.workspace = true
rust-version.workspace = true
[dependencies]
+audio.workspace = true
dungeon.workspace = true
raylib.workspace = true
@@ -19,7 +20,7 @@ default = []
x11 = ["raylib/GLFW_BUILD_X11"]
wayland = ["raylib/wayland", "raylib/GLFW_BUILD_WAYLAND"]
sdl = ["raylib/sdl", "raylib/SUPPORT_MODULE_RTEXT"]
-static = []
+static = ["audio/static"]
# Individual features seem to currently be
# broken on windows
diff --git a/graphics/src/audio/channel.rs b/graphics/src/audio/channel.rs
deleted file mode 100644
index a1ad144..0000000
--- a/graphics/src/audio/channel.rs
+++ /dev/null
@@ -1,199 +0,0 @@
-use raylib::audio::RaylibAudio;
-
-type Music = raylib::audio::Music<'static>;
-
-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
- }};
-}
-
-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);
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum DutyCycle {
- Percent12,
- Percent25,
- Percent50,
- Percent25Neg,
-}
-
-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,
- })
- }
-
- 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();
- }
-
- 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(),
- }
- }
-
- 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);
- }
-}
-
-pub struct TriangleChannel {
- inner: Music,
-}
-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);
- }
-
- fn set_pitch(&self, pitch: f32) {
- self.inner.set_pitch(pitch);
- }
-}
-
-pub struct NoiseChannel {
- noise_0: Music,
- noise_1: Music,
- mode: bool,
-}
-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 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);
- }
-
- fn set_pitch(&self, pitch: f32) {
- self.noise_0.set_pitch(pitch);
- self.noise_1.set_pitch(pitch);
- }
-}
diff --git a/graphics/src/audio/data.rs b/graphics/src/audio/data.rs
deleted file mode 100644
index 9c68523..0000000
--- a/graphics/src/audio/data.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-use crate::audio::program::Program;
-use std::fs;
-
-macro_rules! load_asm {
- ($path:tt) => {{
- if cfg!(any(feature = "static", target_arch = "wasm32")) {
- let src = include_str!(concat!("../../../", $path));
- Program::parse(src, true)?
- } else {
- let src = fs::read_to_string($path)?;
- Program::parse(&src, true)?
- }
- }};
- ($first:tt, $($arg:tt)*) => {
- load_asm!($first)$(.merge(load_asm!($arg)))*
- };
-}
-
-pub struct Data {
- pub megalovania: Program,
-}
-impl Data {
- pub fn load() -> crate::Result<Self> {
- let megalovania = load_asm!(
- "assets/asm/megalovania_melody.asm",
- "assets/asm/megalovania_base.asm"
- );
- Ok(Self { megalovania })
- }
-}
diff --git a/graphics/src/audio/mod.rs b/graphics/src/audio/mod.rs
deleted file mode 100644
index 2b85a0f..0000000
--- a/graphics/src/audio/mod.rs
+++ /dev/null
@@ -1,92 +0,0 @@
-//! The `audio` crate stores all audio assets that need to be loaded during runtime
-
-use std::time::{Duration, Instant};
-
-use channel::{Channel, NoiseChannel, PulseChannel, TriangleChannel};
-use raylib::audio::RaylibAudio;
-
-use crate::audio::data::Data;
-
-mod channel;
-mod data;
-mod parse;
-mod program;
-
-const AUDIO_FPS: u32 = 60;
-const TIME_SLICE: Duration = Duration::from_millis(1000 / AUDIO_FPS as u64);
-
-/// Holds each audio channel
-pub struct Channels {
- pub pulse_a: PulseChannel,
- pub pulse_b: PulseChannel,
- pub triangle: TriangleChannel,
- pub noise: NoiseChannel,
-}
-impl Channels {
- pub(crate) fn load() -> crate::Result<Self> {
- // Phantom handle to the raylib audio subsystem
- // Raylib doesnt use a handle, but the rust bindings
- // have one to ensure memory safety.
- //
- // We must leak this handle after allocating it,
- // if we dont then all audio will be unloaded :(
- //
- // NOTE: would this cause issues if `Audio::load` was
- // called multiple times?
- let handle = Box::leak(Box::new(RaylibAudio::init_audio_device()?));
-
- let pulse_a = PulseChannel::load(handle)?;
- pulse_a.set_volume(0.0);
- pulse_a.play();
- let pulse_b = PulseChannel::load(handle)?;
- pulse_b.set_volume(0.0);
- pulse_b.play();
- let triangle = TriangleChannel::load(handle)?;
- triangle.set_volume(0.0);
- triangle.play();
- let noise = NoiseChannel::load(handle)?;
- noise.set_volume(0.0);
- noise.play();
-
- Ok(Self {
- pulse_a,
- pulse_b,
- triangle,
- noise,
- })
- }
-}
-
-/// The `Audio` container initalizes the audio subsystem
-/// for raylib, leaks it (to gurentee audio is statically loaded),
-/// then loads all needed audio samples
-pub struct Audio {
- channels: Channels,
- last: Instant,
- pub data: Data,
-}
-impl Audio {
- pub(crate) fn load() -> crate::Result<Self> {
- let channels = Channels::load()?;
- let last = Instant::now();
- let data = Data::load()?;
-
- Ok(Self {
- channels,
- last,
- data,
- })
- }
-
- pub fn update(&mut self) {
- if self.last.elapsed() >= TIME_SLICE {
- self.data.megalovania.exec(&mut self.channels);
- self.last = Instant::now();
- }
-
- self.channels.pulse_a.update();
- self.channels.pulse_b.update();
- self.channels.triangle.update();
- self.channels.noise.update();
- }
-}
diff --git a/graphics/src/audio/parse/lex.rs b/graphics/src/audio/parse/lex.rs
deleted file mode 100644
index 9969c9e..0000000
--- a/graphics/src/audio/parse/lex.rs
+++ /dev/null
@@ -1,218 +0,0 @@
-use std::{
- fmt::Display,
- iter::Peekable,
- str::{Chars, FromStr},
-};
-
-use super::Result;
-use crate::audio::{channel::DutyCycle, program::ChanSpec};
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub enum Token {
- Eof,
- LineSeparator,
- Pause(usize),
- PauseLen(u32),
- Jump(usize),
- ChanSpec(ChanSpec),
- SetVolume(u8),
- SetPitch(u8),
- SetNoiseMode(bool),
- SetPulseDuty(DutyCycle),
-}
-impl Token {
- pub const fn is_eol(self) -> bool {
- matches!(self, Self::Eof | Self::LineSeparator)
- }
-}
-
-pub struct Lexer<'s> {
- src: &'s str,
- chars: Peekable<Chars<'s>>,
- pos: usize,
-}
-impl<'s> Lexer<'s> {
- pub fn new(src: &'s str) -> Self {
- let chars = src.chars().peekable();
- Self { src, chars, pos: 0 }
- }
-
- fn filter_char(&mut self, ch: Option<char>, advance: bool) -> Result<char> {
- match ch {
- Some(c) if c.is_control() && !matches!(c, '\n' | '\r' | '\t') => {
- Err(invalid_char(c))
- }
- Some(c) => {
- if advance {
- self.pos += c.len_utf8();
- }
- Ok(c)
- }
- None => Ok('\0'),
- }
- }
-
- fn peek(&mut self) -> Result<char> {
- let c = self.chars.peek().copied();
- self.filter_char(c, false)
- }
-
- fn next(&mut self) -> Result<char> {
- let c = self.chars.next();
- self.filter_char(c, true)
- }
-
- fn next_int<T>(&mut self) -> Result<T>
- where
- T: FromStr,
- <T as FromStr>::Err: Display,
- {
- let start = self.pos;
- while self.peek()?.is_ascii_digit() {
- self.next()?;
- }
- let str = &self.src[start..self.pos];
- str.parse::<T>().map_err(|e| e.to_string())
- }
-
- fn next_note(&mut self) -> Result<u8> {
- if self.peek()?.is_ascii_digit() {
- return self.next_int();
- }
-
- let note = match self.next()? {
- 'a' => 80,
- 'b' => 82,
- 'c' => 83,
- 'd' => 85,
- 'e' => 87,
- 'f' => 88,
- 'g' => 90,
- c => unexpected(c)?,
- };
-
- let octave = {
- let c = self.next()?;
- c.to_digit(10).ok_or_else(|| format!("invalid octave: {c}"))
- }?;
-
- let off = match self.peek()? {
- '#' => {
- self.next()?;
- 1
- }
- 'b' => {
- self.next()?;
- -1
- }
- _ => 0,
- };
-
- let pitch_u32 = (note + octave * 12).wrapping_add_signed(off);
- let pitch = u8::try_from(pitch_u32).map_err(|e| format!("{e}"))?;
-
- Ok(pitch)
- }
-
- fn next_bool(&mut self) -> Result<bool> {
- match self.next_int()? {
- 0 => Ok(false),
- _ => Ok(true),
- }
- }
-
- fn next_duty_cycle(&mut self) -> Result<DutyCycle> {
- match self.next_int()? {
- 12 => Ok(DutyCycle::Percent12),
- 25 => Ok(DutyCycle::Percent25),
- 50 => Ok(DutyCycle::Percent50),
- 75 => Ok(DutyCycle::Percent25Neg),
- n => Err(format!("invalid duty cycle: {n}")),
- }
- }
-
- fn skip_comment(&mut self) -> Result<()> {
- loop {
- let next = self.next()?;
- if next == '\n' || next == '\0' {
- break;
- }
- }
- Ok(())
- }
-
- fn next_pause(&mut self) -> Result<Token> {
- let mut count = 1;
- if self.peek()?.is_ascii_digit() {
- count = self.next_int()?;
- }
- Ok(Token::Pause(count))
- }
-
- pub fn next_token(&mut self) -> Result<Token> {
- use Token as T;
- loop {
- let peek = self.peek()?;
- if peek == ';' {
- self.skip_comment()?;
- } else if matches!(peek, ' ' | '\t' | '\r') {
- self.next()?;
- } else {
- break;
- }
- }
- let token = match self.next()? {
- // chan spec
- 'a' => Token::ChanSpec(ChanSpec::PulseA),
- 'b' => Token::ChanSpec(ChanSpec::PulseB),
- 't' => Token::ChanSpec(ChanSpec::Triangle),
- 'n' => Token::ChanSpec(ChanSpec::Noise),
- // volume
- 'v' => T::SetVolume(self.next_int()?),
- // pitch
- 'p' => T::SetPitch(self.next_note()?),
- // duty cycle
- 'd' => T::SetPulseDuty(self.next_duty_cycle()?),
- // noise mode
- 'm' => T::SetNoiseMode(self.next_bool()?),
- // pause
- '-' => self.next_pause()?,
- // jump
- 'j' => T::Jump(self.next_int()?),
- // pause len
- 'P' => T::PauseLen(self.next_int()?),
- // eof
- '\0' => T::Eof,
- // new line
- '\n' => T::LineSeparator,
- // unexpected
- c => unexpected(c)?,
- };
- Ok(token)
- }
-}
-impl Iterator for Lexer<'_> {
- type Item = Result<Token>;
-
- fn next(&mut self) -> Option<Self::Item> {
- Some(self.next_token())
- }
-}
-
-fn invalid_char(ch: char) -> String {
- match ch as u32 {
- c @ 0x00..=0x7f => format!("invalid character (codepoint 0x{c:2x})"),
- c => format!("invalid character (codepoint U+{c:04x})"),
- }
-}
-
-fn unexpected<T>(c: char) -> Result<T> {
- let msg = match c {
- '\0' => "unexpected end of file".to_owned(),
- '\n' => "unexpected newline character".to_owned(),
- '\t' => "unexpected tab character".to_owned(),
- '\r' => "unexpected return character".to_owned(),
- c => format!("unexpected character {c}"),
- };
- Err(msg)
-}
diff --git a/graphics/src/audio/parse/macros.rs b/graphics/src/audio/parse/macros.rs
deleted file mode 100644
index 1dc33eb..0000000
--- a/graphics/src/audio/parse/macros.rs
+++ /dev/null
@@ -1,103 +0,0 @@
-use std::{borrow::Cow, collections::HashMap, str::Lines};
-
-struct PreProcessor<'s> {
- src: &'s str,
- pos: usize,
- lines: Lines<'s>,
- macros: HashMap<&'s str, &'s str>,
-}
-impl<'s> PreProcessor<'s> {
- fn new(src: &'s str) -> Self {
- let lines = src.lines();
- let macros = HashMap::new();
- Self {
- src,
- pos: 0,
- lines,
- macros,
- }
- }
-
- fn next(&mut self) -> Option<&'s str> {
- self.lines.next().map(|line| {
- self.pos += line.len() + 1;
- line.trim()
- })
- }
-
- fn read_macro(&mut self, full_name: &'s str) {
- let name = &full_name[8..];
- let start = self.pos;
- let mut end = start;
- while let Some(line) = self.next()
- && !matches!(line, "%end")
- {
- end = self.pos;
- }
- let str = &self.src[start..end];
- self.macros.insert(name, str);
- }
-
- fn read_macros(&mut self) -> String {
- let mut buf = String::new();
- while let Some(line) = self.next() {
- if line.starts_with("%define ") {
- self.read_macro(line);
- } else {
- buf.push_str(line);
- buf.push('\n');
- }
- }
- buf
- }
-
- fn process(&mut self) -> String {
- let rest = self.read_macros();
- let mut lines = rest.lines().map(Cow::Borrowed).collect::<Vec<_>>();
- loop {
- let mut count = 0;
- for (name, body) in &self.macros {
- count += fill_macro(&mut lines, name, body);
- }
- if count == 0 {
- break;
- }
- }
- lines.join("\n")
- }
-}
-
-fn fill_macro(contents: &mut Vec<Cow<'_, str>>, name: &str, body: &str) -> usize {
- let mut count = 0;
- let mut idx = 0;
- loop {
- if idx >= contents.len() {
- break;
- }
- let line = &contents[idx];
- if line.starts_with(name) {
- fill_macro_once(contents, idx, body);
- count += 1;
- }
- idx += 1;
- }
- count
-}
-
-fn fill_macro_once(contents: &mut Vec<Cow<'_, str>>, idx: usize, body: &str) {
- let invoke_line = contents.remove(idx);
- let args = invoke_line.split_whitespace().skip(1).collect::<Vec<_>>();
-
- for line in body.lines().rev() {
- let mut buf = String::from(line);
- for (idx, arg) in args.iter().enumerate() {
- let key = format!("${}", idx + 1);
- buf = buf.replace(&key, arg);
- }
- contents.insert(idx, Cow::Owned(buf));
- }
-}
-
-pub fn process(src: &str) -> String {
- PreProcessor::new(src).process()
-}
diff --git a/graphics/src/audio/parse/mod.rs b/graphics/src/audio/parse/mod.rs
deleted file mode 100644
index 9ff243d..0000000
--- a/graphics/src/audio/parse/mod.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-use crate::audio::{parse::parser::Parser, program::Instruction};
-
-pub type Result<T> = std::result::Result<T, String>;
-
-mod lex;
-mod macros;
-mod parser;
-
-pub fn parse(raw_src: &str) -> Result<Vec<Instruction>> {
- let src = macros::process(raw_src);
- Parser::new(&src).parse()
-}
diff --git a/graphics/src/audio/parse/parser.rs b/graphics/src/audio/parse/parser.rs
deleted file mode 100644
index 5157fa1..0000000
--- a/graphics/src/audio/parse/parser.rs
+++ /dev/null
@@ -1,104 +0,0 @@
-use std::iter::Peekable;
-
-use crate::audio::program::{ChanSpec, Instruction};
-
-use super::{
- Result,
- lex::{Lexer, Token},
-};
-
-pub struct Parser<'s> {
- lexer: Peekable<Lexer<'s>>,
-}
-impl<'s> Parser<'s> {
- pub fn new(src: &'s str) -> Self {
- Self {
- lexer: Lexer::new(src).peekable(),
- }
- }
-
- fn next(&mut self) -> Result<Token> {
- self.lexer
- .next()
- .unwrap_or_else(|| Err("should not happen".to_owned()))
- }
-
- fn peek(&mut self) -> Result<Token> {
- self.lexer
- .peek()
- .map_or_else(|| Err("should not happen".to_owned()), Result::clone)
- }
-
- fn parse_chan_spec(&mut self) -> Result<ChanSpec> {
- match self.next()? {
- Token::ChanSpec(spec) => Ok(spec),
- t => Err(format!("expected channel specifier, got {t:?}")),
- }
- }
-
- fn parse_ins(&mut self, spec: ChanSpec) -> Result<Instruction> {
- use Token as T;
- let t = self.next()?;
- let ins = match t {
- T::SetPitch(pitch) => Instruction::SetPitch(spec, pitch),
- T::SetVolume(volume) => Instruction::SetVolume(spec, volume),
- T::SetNoiseMode(mode) if spec == ChanSpec::Noise => {
- Instruction::SetNoiseMode(mode)
- }
- T::SetPulseDuty(duty_cycle) if spec == ChanSpec::PulseA => {
- Instruction::SetPulseDutyA(duty_cycle)
- }
- T::SetPulseDuty(duty_cycle) if spec == ChanSpec::PulseB => {
- Instruction::SetPulseDutyB(duty_cycle)
- }
- _ => unexpected(t)?,
- };
- Ok(ins)
- }
-
- fn parse_line(&mut self, prog: &mut Vec<Instruction>) -> Result<()> {
- let spec = self.parse_chan_spec()?;
- loop {
- prog.push(self.parse_ins(spec)?);
- let peek = self.peek()?;
- if peek.is_eol() || matches!(peek, Token::Pause(_)) {
- break;
- }
- }
- Ok(())
- }
-
- pub fn parse(&mut self) -> Result<Vec<Instruction>> {
- let mut prog = vec![];
- loop {
- let t = self.peek()?;
- match t {
- Token::Eof => break,
- Token::LineSeparator => {
- self.next()?;
- }
- Token::Pause(count) => {
- self.next()?;
- for _ in 0..count {
- prog.push(Instruction::Pause);
- }
- }
- Token::Jump(pc) => {
- self.next()?;
- prog.push(Instruction::Jump(pc));
- }
- Token::PauseLen(pause_len) => {
- self.next()?;
- prog.push(Instruction::PauseLen(pause_len));
- }
- _ => self.parse_line(&mut prog)?,
- }
- }
- Ok(prog)
- }
-}
-
-fn unexpected<T>(t: Token) -> Result<T> {
- let msg = format!("unexpected token: {t:?}");
- Err(msg)
-}
diff --git a/graphics/src/audio/program.rs b/graphics/src/audio/program.rs
deleted file mode 100644
index f5decd6..0000000
--- a/graphics/src/audio/program.rs
+++ /dev/null
@@ -1,184 +0,0 @@
-use crate::audio::{
- Channels,
- channel::{Channel, DutyCycle},
- parse,
-};
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum ChanSpec {
- PulseA,
- PulseB,
- Triangle,
- Noise,
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub enum Instruction {
- Pause,
- PauseLen(u32),
- Jump(usize),
- SetVolume(ChanSpec, u8),
- SetPitch(ChanSpec, u8),
- SetPulseDutyA(DutyCycle),
- SetPulseDutyB(DutyCycle),
- SetNoiseMode(bool),
-}
-
-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
- }
-}
-
-#[derive(Debug, Clone)]
-pub struct Program {
- ins: Vec<Instruction>,
- pc: usize,
- looping: bool,
- pause_cnt: u32,
- pause_len: u32,
-}
-impl Program {
- pub const fn new(ins: Vec<Instruction>, looping: bool) -> Self {
- let pc = ins.len();
- Self {
- ins,
- pc,
- looping,
- pause_cnt: 0,
- pause_len: 4,
- }
- }
-
- pub fn parse(src: &str, looping: bool) -> parse::Result<Self> {
- let ins = parse::parse(src)?;
- Ok(Self::new(ins, looping))
- }
-
- pub fn merge(self, other: Self) -> Self {
- let mut res = vec![];
-
- let mut l = 0;
- let mut r = 0;
- let l_ins = self.ins;
- let r_ins = other.ins;
-
- loop {
- if l >= l_ins.len() && r >= r_ins.len() {
- // were done here
- break;
- }
-
- let mut has_pause = false;
- while l < l_ins.len() {
- let ins = l_ins[l];
- l += 1;
- if matches!(ins, Instruction::Pause) {
- has_pause = true;
- break;
- }
- res.push(ins);
- }
- while r < r_ins.len() {
- let ins = r_ins[r];
- r += 1;
- if matches!(ins, Instruction::Pause) {
- has_pause = true;
- break;
- }
- res.push(ins);
- }
- if has_pause {
- res.push(Instruction::Pause);
- }
- }
-
- Self::new(res, self.looping)
- }
-
- fn exec_ins(&mut self, channels: &mut Channels, ins: Instruction) {
- use Instruction as I;
- match ins {
- I::Pause => {
- self.pause_cnt = self.pause_len;
- }
- I::PauseLen(pause_len) => {
- self.pause_len = pause_len;
- }
- I::Jump(pc) => {
- self.pc = pc;
- }
- I::SetVolume(chan_spec, volume) => {
- // set the volume (amplitude) on a given channel
- use ChanSpec as C;
- let v = map_volume(volume);
- match chan_spec {
- C::PulseA => channels.pulse_a.set_volume(v),
- C::PulseB => channels.pulse_b.set_volume(v),
- C::Triangle => channels.triangle.set_volume(v),
- C::Noise => channels.noise.set_volume(v),
- }
- }
- I::SetPitch(chan_spec, pitch) => {
- // set the pitch (freq) on a given channel
- use ChanSpec as C;
- let p = map_pitch(pitch);
- match chan_spec {
- C::PulseA => channels.pulse_a.set_pitch(p),
- C::PulseB => channels.pulse_b.set_pitch(p),
- C::Triangle => channels.triangle.set_pitch(p),
- C::Noise => channels.noise.set_pitch(p),
- }
- }
- I::SetPulseDutyA(duty_cycle) => {
- channels.pulse_a.set_duty(duty_cycle);
- }
- I::SetPulseDutyB(duty_cycle) => {
- channels.pulse_b.set_duty(duty_cycle);
- }
- I::SetNoiseMode(mode) => {
- channels.noise.set_mode(mode);
- }
- }
- }
-
- pub fn exec(&mut self, channels: &mut Channels) {
- if self.pause_cnt > 0 {
- self.pause_cnt -= 1;
- }
- loop {
- if self.pause_cnt > 0 {
- break;
- }
- if self.finished() {
- if self.looping {
- self.pc = 0;
- } else {
- break;
- }
- }
- let ins = self.ins[self.pc];
- self.exec_ins(channels, ins);
- self.pc += 1;
- }
- }
-
- pub const fn finished(&self) -> bool {
- self.pc >= self.ins.len()
- }
-
- pub const fn play(&mut self) {
- self.pc = 0;
- }
-
- pub const fn set_looping(&mut self, looping: bool) {
- self.looping = looping;
- }
-}
diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs
index 63233f5..fa83b98 100644
--- a/graphics/src/lib.rs
+++ b/graphics/src/lib.rs
@@ -3,13 +3,15 @@
use std::time::Duration;
+use audio::Audio;
use dungeon::Dungeon;
-use raylib::prelude::*;
+use raylib::{
+ RaylibHandle, RaylibThread,
+ ffi::{KeyboardKey, TraceLogLevel},
+};
-use crate::audio::Audio;
use crate::render::Renderer;
-mod audio;
mod render;
mod timer;