aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDeterminant <ted.sybil@gmail.com>2017-11-26 13:44:12 -0500
committerDeterminant <ted.sybil@gmail.com>2017-11-26 13:44:12 -0500
commit933ed6873df7bd6c1bf4e762d9ac2956e8aecd8d (patch)
treee840e5e17e649752e81fe519a188a6d4fbc4f86f
parent3fac7315288e066b9c31b85d55ec7751159b958e (diff)
...
-rw-r--r--calc_mixer.py18
-rw-r--r--src/apu.rs414
2 files changed, 432 insertions, 0 deletions
diff --git a/calc_mixer.py b/calc_mixer.py
new file mode 100644
index 0000000..e1faba1
--- /dev/null
+++ b/calc_mixer.py
@@ -0,0 +1,18 @@
+def chunks(l, n):
+ """Yield successive n-sized chunks from l."""
+ for i in range(0, len(l), n):
+ yield l[i:i + n]
+
+pulse_table = list(map(lambda n: 0 if n == 0 else 95.52 / (8128.0 / n + 100), range(31)))
+tnd_table = list(map(lambda n: 0 if n == 0 else 163.67 / (24329.0 / n + 100), range(203)))
+
+print(pulse_table, len(pulse_table))
+print(tnd_table, len(tnd_table))
+
+max = (1 << 16) - 1
+pulse_table = list(map(lambda n: "{0:#06x}".format(int(n * max)), pulse_table))
+tnd_table = list(map(lambda n: "{0:#06x}".format(int(n * max)), tnd_table))
+
+
+print(',\n'.join([', '.join(l) for l in chunks(pulse_table, 6)]))
+print(',\n'.join([', '.join(l) for l in chunks(tnd_table, 6)]))
diff --git a/src/apu.rs b/src/apu.rs
new file mode 100644
index 0000000..57525bf
--- /dev/null
+++ b/src/apu.rs
@@ -0,0 +1,414 @@
+#![allow(dead_code)]
+use mos6502;
+
+pub trait Speaker {
+ fn queue(&mut self, sample: u16);
+ fn push(&mut self);
+}
+
+const CPU_SAMPLE_FREQ: u32 = 240;
+pub const AUDIO_SAMPLE_FREQ: u32 = 44100;
+
+const LEN_TABLE: [u8; 32] = [
+ 10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14,
+ 12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30,
+];
+
+const DUTY_TABLE: [u8; 4] = [
+ 0b00000010,
+ 0b00000110,
+ 0b00011110,
+ 0b11111001,
+];
+
+const PULSE_TABLE: [u16; 31] = [
+ 0x0000, 0x02f8, 0x05df, 0x08b4, 0x0b78, 0x0e2b,
+ 0x10cf, 0x1363, 0x15e9, 0x1860, 0x1ac9, 0x1d25,
+ 0x1f75, 0x21b7, 0x23ee, 0x2618, 0x2837, 0x2a4c,
+ 0x2c55, 0x2e54, 0x3049, 0x3234, 0x3416, 0x35ee,
+ 0x37be, 0x3985, 0x3b43, 0x3cf9, 0x3ea7, 0x404d,
+ 0x41ec
+];
+
+const TND_TABLE: [u16; 203] = [
+ 0x0000, 0x01b7, 0x036a, 0x051a, 0x06c6, 0x086f,
+ 0x0a15, 0x0bb7, 0x0d56, 0x0ef2, 0x108a, 0x121f,
+ 0x13b1, 0x1540, 0x16cc, 0x1855, 0x19da, 0x1b5d,
+ 0x1cdd, 0x1e59, 0x1fd3, 0x214a, 0x22be, 0x2430,
+ 0x259e, 0x270a, 0x2874, 0x29da, 0x2b3e, 0x2c9f,
+ 0x2dfe, 0x2f5a, 0x30b4, 0x320b, 0x335f, 0x34b2,
+ 0x3601, 0x374f, 0x389a, 0x39e2, 0x3b29, 0x3c6d,
+ 0x3dae, 0x3eee, 0x402b, 0x4166, 0x429f, 0x43d6,
+ 0x450a, 0x463d, 0x476d, 0x489c, 0x49c8, 0x4af2,
+ 0x4c1b, 0x4d41, 0x4e65, 0x4f87, 0x50a8, 0x51c6,
+ 0x52e3, 0x53fe, 0x5517, 0x562e, 0x5743, 0x5856,
+ 0x5968, 0x5a78, 0x5b86, 0x5c93, 0x5d9d, 0x5ea6,
+ 0x5fae, 0x60b3, 0x61b7, 0x62ba, 0x63bb, 0x64ba,
+ 0x65b7, 0x66b3, 0x67ae, 0x68a7, 0x699e, 0x6a94,
+ 0x6b88, 0x6c7b, 0x6d6d, 0x6e5d, 0x6f4b, 0x7038,
+ 0x7124, 0x720e, 0x72f7, 0x73de, 0x74c4, 0x75a9,
+ 0x768c, 0x776e, 0x784f, 0x792e, 0x7a0d, 0x7ae9,
+ 0x7bc5, 0x7c9f, 0x7d78, 0x7e50, 0x7f26, 0x7ffc,
+ 0x80d0, 0x81a3, 0x8274, 0x8345, 0x8414, 0x84e2,
+ 0x85af, 0x867b, 0x8746, 0x880f, 0x88d8, 0x899f,
+ 0x8a65, 0x8b2b, 0x8bef, 0x8cb2, 0x8d74, 0x8e35,
+ 0x8ef4, 0x8fb3, 0x9071, 0x912e, 0x91ea, 0x92a4,
+ 0x935e, 0x9417, 0x94cf, 0x9586, 0x963c, 0x96f0,
+ 0x97a4, 0x9857, 0x990a, 0x99bb, 0x9a6b, 0x9b1a,
+ 0x9bc9, 0x9c76, 0x9d23, 0x9dcf, 0x9e7a, 0x9f24,
+ 0x9fcd, 0xa075, 0xa11c, 0xa1c3, 0xa269, 0xa30e,
+ 0xa3b2, 0xa455, 0xa4f7, 0xa599, 0xa63a, 0xa6da,
+ 0xa779, 0xa818, 0xa8b5, 0xa952, 0xa9ef, 0xaa8a,
+ 0xab25, 0xabbe, 0xac58, 0xacf0, 0xad88, 0xae1f,
+ 0xaeb5, 0xaf4a, 0xafdf, 0xb073, 0xb107, 0xb199,
+ 0xb22b, 0xb2bd, 0xb34d, 0xb3dd, 0xb46c, 0xb4fb,
+ 0xb589, 0xb616, 0xb6a3, 0xb72f, 0xb7ba, 0xb845,
+ 0xb8cf, 0xb958, 0xb9e1, 0xba69, 0xbaf1, 0xbb78,
+ 0xbbfe, 0xbc84, 0xbd09, 0xbd8d, 0xbe11
+];
+
+struct Sampler {
+ ticks_remain: u32,
+ ticks_now: u32,
+ ticks_unit: u32,
+ ticks_all: u32,
+ ticks_extra: u32,
+ ticks_extra_all: u32,
+}
+
+impl Sampler {
+ fn new(freq1: u32, freq2: u32) -> Self {
+ let unit = freq1 / freq2;
+ let extra = freq1 - unit * freq2;
+ Sampler {
+ ticks_remain: freq1 - extra,
+ ticks_now: 0,
+ ticks_unit: unit,
+ ticks_all: freq1 - extra,
+ ticks_extra: extra,
+ ticks_extra_all: extra
+ }
+ }
+
+ fn tick(&mut self) -> (bool, bool) {
+ let unit = self.ticks_unit;
+ if self.ticks_now == 0 {
+ self.ticks_now = unit;
+ self.ticks_remain -= unit;
+ if self.ticks_remain == 0 {
+ /* compensate to last exactly 1 sec */
+ self.ticks_now += self.ticks_remain;
+ /* reload for the next second */
+ self.ticks_remain = self.ticks_all;
+ self.ticks_extra = self.ticks_extra_all;
+ }
+ if self.ticks_extra > 0 {
+ self.ticks_extra -= 1;
+ self.ticks_now += 1;
+ }
+ }
+ self.ticks_now -= 1;
+ (self.ticks_now == 0, self.ticks_remain == self.ticks_all)
+ }
+}
+
+pub struct Pulse {
+ /* envelope */
+ env_period: u8,
+ env_lvl: u8,
+ decay_lvl: u8,
+ env_start: bool,
+ env_loop: bool,
+ env_const: bool,
+ env_vol: u8,
+ /* sweep */
+ swp_count: u8,
+ swp_period: u8,
+ swp_lvl: u8,
+ swp_en: bool,
+ swp_neg: bool,
+ swp_rld: bool,
+ muted: bool,
+ /* length counter */
+ len_lvl: u8,
+ /* timer */
+ timer_period: u16,
+ timer_lvl: u16,
+ /* sequencer */
+ seq_wave: u8,
+ seq_cnt: u8,
+ /* channel */
+ enabled: bool,
+ comple: bool,
+}
+
+impl Pulse {
+ fn tick_env(&mut self) {
+ /* should be clocked by frame counter */
+ if !self.env_start {
+ if self.env_lvl == 0 {
+ self.env_lvl = self.env_period;
+ if self.decay_lvl == 0 {
+ if self.env_loop {
+ self.decay_lvl = 15;
+ }
+ } else {
+ self.decay_lvl -= 1;
+ }
+ } else {
+ self.env_lvl -= 1;
+ }
+ } else {
+ self.decay_lvl = 15;
+ self.env_start = false;
+ self.env_lvl = self.env_period;
+ }
+ }
+
+ fn tick_sweep(&mut self) {
+ let mut reload = self.swp_rld;
+ if self.swp_lvl == 0 {
+ reload = true;
+ let p = self.timer_period;
+ let mut delta = p >> self.swp_count;
+ if self.swp_neg {
+ delta = !delta;
+ if self.comple { delta += 1; } /* two's complement */
+ }
+ p.wrapping_add(delta);
+ self.muted = self.timer_period < 8 || (self.timer_period >> 11 != 0);
+ if !self.muted && self.swp_en && self.swp_count != 0 {
+ self.timer_period = p;
+ }
+ } else {
+ self.swp_lvl -= 1;
+ }
+ if reload {
+ self.swp_lvl = self.swp_period;
+ self.swp_rld = false;
+ }
+ }
+
+ fn disable(&mut self) {
+ self.len_lvl = 0;
+ self.enabled = false;
+ }
+
+ fn enable(&mut self) { self.enabled = true }
+
+ fn tick_length(&mut self) {
+ if self.len_lvl > 0 && !self.env_loop {
+ self.len_lvl -= 1
+ }
+ }
+
+ fn tick_timer(&mut self) {
+ if self.timer_lvl == 0 {
+ self.timer_lvl = self.timer_period;
+ if self.seq_cnt == 7 {
+ self.seq_cnt = 0
+ } else {
+ self.seq_cnt += 1
+ }
+ } else {
+ self.timer_lvl -= 1
+ }
+ }
+
+ fn get_len(&self) -> u8 { self.len_lvl }
+ fn set_duty(&mut self, b: u8) { self.seq_wave = DUTY_TABLE[b as usize] }
+ fn set_loop(&mut self, b: bool) { self.env_loop = b }
+ fn set_const(&mut self, b: bool) { self.env_const = b }
+ fn set_env_period(&mut self, p: u8) { self.env_period = p } /* 4 bits */
+ fn set_env_vol(&mut self, p: u8) { self.env_vol = p }
+ fn set_sweep(&mut self, d: u8) {
+ self.swp_en = (d >> 7) == 1;
+ self.swp_period = (d >> 4) & 7;
+ self.swp_neg = d & 0x8 == 0x8;
+ self.swp_count = d & 7;
+ self.swp_rld = true;
+ }
+
+ fn set_timer_period(&mut self, p: u16) {
+ self.muted = p < 8;
+ self.timer_period = p;
+ }
+
+ fn set_len(&mut self, d: u8) {
+ if self.enabled {
+ self.len_lvl = LEN_TABLE[d as usize]
+ }
+ }
+
+ pub fn write_reg1(&mut self, data: u8) {
+ self.set_duty(data >> 6);
+ self.set_loop(data & 0x20 == 0x20);
+ self.set_const(data & 0x10 == 0x10);
+ self.set_env_period(data & 0xf);
+ self.set_env_vol(data & 0xf);
+ self.env_start = true;
+ }
+
+ pub fn write_reg2(&mut self, data: u8) { self.set_sweep(data) }
+
+ pub fn write_reg3(&mut self, data: u8) {
+ self.timer_period = (self.timer_period & 0xff00) | data as u16
+ }
+
+ pub fn write_reg4(&mut self, data: u8) {
+ self.set_len(data >> 3);
+ self.timer_period = (self.timer_period & 0x00ff) | ((data as u16 & 7) << 8);
+ self.seq_cnt = 0;
+ self.env_start = true;
+ }
+
+ pub fn output(&self) -> u8 {
+ let env = if self.env_const { self.env_vol } else { self.decay_lvl };
+ let swp = !self.muted;
+ let seq = (self.seq_wave >> self.seq_cnt) & 1 == 1;
+ let len = self.len_lvl > 0;
+ if swp && seq && len { env } else { 0 }
+ }
+
+ pub fn new(comple: bool) -> Self {
+ Pulse {env_period: 0, env_lvl: 0, decay_lvl: 0,
+ env_start: false, env_loop: false, env_const: false, env_vol: 0,
+ swp_count: 0, swp_period: 0, swp_lvl: 0,
+ swp_en: false, swp_neg: false, swp_rld: false, muted: true,
+ len_lvl: 0, timer_period: 0, timer_lvl: 0,
+ seq_wave: 0, seq_cnt: 0, enabled: false, comple}
+ }
+}
+
+pub struct APU<'a> {
+ pub pulse1: Pulse,
+ pub pulse2: Pulse,
+ frame_lvl: u8,
+ frame_mode: bool, /* true for 5-step mode */
+ frame_inh: bool,
+ frame_int: bool,
+ cpu_sampler: Sampler,
+ audio_sampler: Sampler,
+ cycle_even: bool,
+ spkr: &'a mut Speaker
+}
+
+impl<'a> APU<'a> {
+ fn tick_env(&mut self) {
+ self.pulse1.tick_env();
+ self.pulse2.tick_env();
+ }
+
+ fn tick_len_swp(&mut self) {
+ self.pulse1.tick_length();
+ self.pulse1.tick_sweep();
+ self.pulse2.tick_length();
+ self.pulse2.tick_sweep();
+ }
+
+ pub fn new(spkr: &'a mut Speaker) -> Self {
+ APU {
+ pulse1: Pulse::new(false), pulse2: Pulse::new(true),
+ frame_lvl: 0, frame_mode: false, frame_int: false, frame_inh: false,
+ cpu_sampler: Sampler::new(mos6502::CPU_FREQ, CPU_SAMPLE_FREQ),
+ audio_sampler: Sampler::new(mos6502::CPU_FREQ, AUDIO_SAMPLE_FREQ),
+ cycle_even: false,
+ spkr
+ }
+ }
+
+ pub fn output(&self) -> u16 {
+ let pulse_out = PULSE_TABLE[(self.pulse1.output() +
+ self.pulse2.output()) as usize];
+ let tnd_out = TND_TABLE[0];
+ pulse_out + tnd_out
+ }
+
+ pub fn read_status(&mut self) -> u8 {
+ let res = if self.pulse1.get_len() > 0 { 1 } else { 0 } |
+ (if self.pulse1.get_len() > 0 { 1 } else { 0 }) << 1 |
+ (if self.frame_int { 1 } else { 0 }) << 6;
+ if self.frame_lvl != 3 {
+ self.frame_int = false; /* clear interrupt flag */
+ }
+ res
+ }
+
+ pub fn write_status(&mut self, data: u8) {
+ match data & 0x1 {
+ 0 => self.pulse1.disable(),
+ _ => self.pulse1.enable()
+ }
+ match data & 0x2 {
+ 0 => self.pulse2.disable(),
+ _ => self.pulse2.enable()
+ }
+ }
+
+ pub fn write_frame_counter(&mut self, data: u8) {
+ self.frame_inh = data & 0x40 == 1;
+ self.frame_mode = data >> 7 == 1;
+ }
+
+ pub fn tick_timer(&mut self) {
+ if self.cycle_even {
+ self.pulse1.tick_timer();
+ self.pulse2.tick_timer();
+ }
+ }
+
+ fn tick_frame_counter(&mut self) -> bool {
+ let f = self.frame_lvl;
+ match self.frame_mode {
+ false => {
+ match f {
+ 0 | 2 => self.tick_env(),
+ 1 => {
+ self.tick_env();
+ self.tick_len_swp();
+ },
+ _ => {
+ self.tick_env();
+ self.tick_len_swp();
+ if !self.frame_inh {
+ self.frame_int = true;
+ }
+ },
+ };
+ self.frame_lvl = if f == 3 { 0 } else { f + 1 }
+ },
+ true => {
+ match f {
+ 0 | 2 => self.tick_env(),
+ 1 | 4 => {
+ self.tick_env();
+ self.tick_len_swp();
+ },
+ _ => ()
+ }
+ self.frame_lvl = if f == 4 { 0 } else { f + 1 }
+ }
+ }
+ self.frame_int
+ }
+
+ pub fn tick(&mut self) -> bool {
+ let mut irq = false;
+ if let (true, _) = self.cpu_sampler.tick() {
+ irq = self.tick_frame_counter();
+ //print!("+");
+ }
+ if let (true, sec) = self.audio_sampler.tick() {
+ let sample = self.output();
+ self.spkr.queue(sample);
+ //print!(".");
+ if sec {
+ self.spkr.push();
+ //println!("ok");
+ }
+ }
+ self.tick_timer();
+ self.cycle_even = !self.cycle_even;
+ irq
+ }
+}