summaryrefslogtreecommitdiff
path: root/graphics/src/audio/program.rs
diff options
context:
space:
mode:
Diffstat (limited to 'graphics/src/audio/program.rs')
-rw-r--r--graphics/src/audio/program.rs173
1 files changed, 173 insertions, 0 deletions
diff --git a/graphics/src/audio/program.rs b/graphics/src/audio/program.rs
new file mode 100644
index 0000000..f31be06
--- /dev/null
+++ b/graphics/src/audio/program.rs
@@ -0,0 +1,173 @@
+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,
+ 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,
+ tempo: u32,
+ pause: u32,
+}
+impl Program {
+ pub const fn new(ins: Vec<Instruction>, looping: bool) -> Self {
+ let pc = ins.len();
+ Self {
+ ins,
+ pc,
+ looping,
+ tempo: 120,
+ pause: 0,
+ }
+ }
+
+ 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 l_ins = self.ins;
+ let r_ins = other.ins;
+ let mut l = 0;
+ let mut r = 0;
+ loop {
+ if r >= r_ins.len() {
+ break;
+ }
+ let ins = r_ins[r];
+ if ins == Instruction::Pause {
+ // inc l until even
+ loop {
+ if l == l_ins.len() {
+ l_ins.push(Instruction::Pause);
+ break;
+ }
+ if l_ins[l] == Instruction::Pause {
+ l += 1;
+ break;
+ }
+ l += 1;
+ }
+ } else {
+ l_ins.insert(l, ins);
+ }
+ r += 1;
+ }
+
+ Self::new(l_ins, self.looping)
+ }
+
+ fn exec_ins(&mut self, channels: &mut Channels, ins: Instruction) {
+ use Instruction as I;
+ match ins {
+ I::Pause => {
+ // pause execution until next `exec` call
+ self.pause = self.tempo / 60;
+ }
+ 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 > 0 {
+ self.pause -= 1;
+ return;
+ }
+ loop {
+ if self.pause > 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;
+ }
+}