From b243aa991070e5ae0ab385dbabbc0912ddc57de0 Mon Sep 17 00:00:00 2001 From: transistor Date: Fri, 28 Apr 2023 20:22:33 -0700 Subject: [PATCH] Added a not-yet-properly-working version of the phase generator --- Cargo.lock | 1 + emulator/peripherals/yamaha/Cargo.toml | 1 + emulator/peripherals/yamaha/src/ym2612.rs | 332 +++++++++++++++------- todo.txt | 3 + 4 files changed, 229 insertions(+), 108 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e8f6d7d..9652f1b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -700,6 +700,7 @@ dependencies = [ name = "moa_peripherals_yamaha" version = "0.1.0" dependencies = [ + "lazy_static", "moa_audio", "moa_core", ] diff --git a/emulator/peripherals/yamaha/Cargo.toml b/emulator/peripherals/yamaha/Cargo.toml index 1796a68..1873f24 100644 --- a/emulator/peripherals/yamaha/Cargo.toml +++ b/emulator/peripherals/yamaha/Cargo.toml @@ -6,3 +6,4 @@ edition = "2021" [dependencies] moa_core = { path = "../../core" } moa_audio = { path = "../../libraries/audio" } +lazy_static = "1.4.0" diff --git a/emulator/peripherals/yamaha/src/ym2612.rs b/emulator/peripherals/yamaha/src/ym2612.rs index 387ea4a..536dfae 100644 --- a/emulator/peripherals/yamaha/src/ym2612.rs +++ b/emulator/peripherals/yamaha/src/ym2612.rs @@ -6,15 +6,22 @@ //! //! Resources: //! - Registers: https://www.smspower.org/maxim/Documents/YM2612 -//! - Envelope and rates: https://gendev.spritesmind.net/forum/viewtopic.php?t=386 [Nemesis] +//! - Internal Implementation: https://gendev.spritesmind.net/forum/viewtopic.php?t=386 [Nemesis] +//! * Envelope Generator and Corrections: +//! http://gendev.spritesmind.net/forum/viewtopic.php?p=5716#5716 +//! http://gendev.spritesmind.net/forum/viewtopic.php?t=386&postdays=0&postorder=asc&start=417 +//! * Phase Generator and Output: +//! http://gendev.spritesmind.net/forum/viewtopic.php?f=24&t=386&start=150 +//! http://gendev.spritesmind.net/forum/viewtopic.php?p=6224#6224 +use std::f32; use std::num::NonZeroU8; use std::collections::VecDeque; +use lazy_static::lazy_static; use moa_core::{debug, warn}; use moa_core::{System, Error, ClockTime, ClockDuration, Frequency, Address, Addressable, Steppable, Transmutable}; use moa_core::host::{Host, Audio}; -use moa_audio::{SquareWave, db_to_gain}; /// Table of shift values for each possible rate angle @@ -113,11 +120,41 @@ const RATE_TABLE: &[u16] = &[ 8, 8, 8, 8, 8, 8, 8, 8, ]; +const SIN_TABLE_SIZE: usize = 512; +const POW_TABLE_SIZE: usize = 1 << 13; + +lazy_static! { + static ref SIN_TABLE: Vec = (0..SIN_TABLE_SIZE) + .map(|i| { + let sine = (((i * 2 + 1) as f32 / SIN_TABLE_SIZE as f32) * f32::consts::PI / 2.0).sin(); + let log_sine = -1.0 * sine.log2(); + (log_sine * (1 << 8) as f32) as u16 + }) + .collect(); + + static ref POW_TABLE: Vec = (0..POW_TABLE_SIZE) + .map(|i| { + let linear = 2.0_f32.powf(-1.0 * (((i & 0xFF) + 1) as f32 / 256.0)); + let linear_fixed = (linear * (1 << 11) as f32) as u16; + let shift = (i as i32 >> 8) - 2; + if shift < 0 { + linear_fixed << (0 - shift) + } else if shift < 16 { + linear_fixed >> shift + } else { + 0 + } + }) + .collect(); +} + const DEV_NAME: &str = "ym2612"; const CHANNELS: usize = 6; const OPERATORS: usize = 4; -const MAX_ENVELOPE: u16 = 0x3FC; + +const MAX_ENVELOPE: u16 = 0xFFC; +const MAX_PHASE: u32 = 0x000FFFFF; type FmClock = u64; @@ -141,7 +178,6 @@ struct EnvelopeGenerator { rates: [usize; 4], envelope_state: EnvelopeState, - next_envelope_clock: EnvelopeClock, envelope: u16, } @@ -154,7 +190,6 @@ impl EnvelopeGenerator { rates: [0; 4], envelope_state: EnvelopeState::Attack, - next_envelope_clock: 0, envelope: 0, } } @@ -164,7 +199,8 @@ impl EnvelopeGenerator { } fn set_sustain_level(&mut self, level: u16) { - self.sustain_level = level; + // Convert it to a fixed point decimal number of 4 bit : 8 bits, which will be the output + self.sustain_level = level << 2; } fn set_rate(&mut self, etype: EnvelopeState, rate: usize) { @@ -173,7 +209,6 @@ impl EnvelopeGenerator { fn notify_key_change(&mut self, state: bool, envelope_clock: EnvelopeClock) { if state { - self.next_envelope_clock = envelope_clock; self.envelope = 0; if self.rates[EnvelopeState::Attack as usize] < 62 { self.envelope_state = EnvelopeState::Attack; @@ -186,13 +221,6 @@ impl EnvelopeGenerator { } fn update_envelope(&mut self, envelope_clock: EnvelopeClock) { - for clock in self.next_envelope_clock..=envelope_clock { - self.do_cycle(clock); - } - self.next_envelope_clock = envelope_clock + 1; - } - - fn do_cycle(&mut self, envelope_clock: EnvelopeClock) { if self.envelope_state == EnvelopeState::Decay && self.envelope >= self.sustain_level { self.envelope_state = EnvelopeState::Sustain; } @@ -216,20 +244,88 @@ impl EnvelopeGenerator { EnvelopeState::Decay | EnvelopeState::Sustain | EnvelopeState::Release => { - self.envelope = (self.envelope + increment).min(MAX_ENVELOPE); + // Convert it to a fixed point decimal number of 4 bit : 8 bits, which will be the output + self.envelope = (self.envelope + increment << 2).min(MAX_ENVELOPE); }, } - - // TODO remove this - //if self.debug_name == "ch 3, op 2" { - // println!("{}: {:?} {} {}", update_cycle, self.envelope_state, self.envelope, self.sustain_level); - //} } } - fn get_db_at(&mut self) -> f32 { - let attenuation_10bit = (self.envelope + self.total_level).min(MAX_ENVELOPE); - attenuation_10bit as f32 * 0.09375 + fn get_last_attenuation(&mut self) -> u16 { + (self.envelope + self.total_level).min(MAX_ENVELOPE) + } +} + +#[derive(Clone, Debug)] +struct PhaseGenerator { + #[allow(dead_code)] + debug_name: String, + + block: u8, + fnumber: u16, + detune: u8, + multiple: u32, + + counter: u32, + increment: u32, +} + +impl PhaseGenerator { + fn new(debug_name: String) -> Self { + Self { + debug_name, + + block: 0, + fnumber: 0, + detune: 0, + multiple: 1, + + counter: 0, + increment: 0, + } + } + + fn reset(&mut self) { + self.counter = 0; + } + + fn set_block_and_fnumber(&mut self, block: u8, fnumber: u16) { + self.block = block; + self.fnumber = fnumber; + self.calculate_phase_increment(); + } + + fn set_detune_and_multiple(&mut self, detune: u8, multiple: u8) { + self.detune = detune; + self.multiple = multiple as u32; + self.calculate_phase_increment(); + } + + fn calculate_phase_increment(&mut self) { + let increment = self.fnumber as u32; + + let increment = if self.block == 0 { + increment >> 1 + } else { + increment << self.block - 1 + }; + + // TODO detune + //let inc = + + let increment = if self.multiple == 0 { + increment >> 1 + } else { + (increment * self.multiple).min(MAX_PHASE) + }; + + self.increment = increment; + } + + fn update_phase(&mut self, _fm_clock: FmClock) -> u16 { + let phase = ((self.counter >> 10) & 0x3FF) as u16; + self.counter += self.increment; + phase } } @@ -250,42 +346,25 @@ enum OperatorAlgorithm { struct Operator { #[allow(dead_code)] debug_name: String, - wave: SquareWave, - block: u8, - fnumber: u16, - detune: u8, - multiplier: f32, + phase: PhaseGenerator, envelope: EnvelopeGenerator, } impl Operator { - fn new(debug_name: String, sample_rate: usize) -> Self { + fn new(debug_name: String) -> Self { Self { debug_name: debug_name.clone(), - wave: SquareWave::new(400.0, sample_rate), - block: 0, - fnumber: 0, - detune: 0, - multiplier: 1.0, + phase: PhaseGenerator::new(debug_name.clone()), envelope: EnvelopeGenerator::new(debug_name), } } - fn reset(&mut self) { - self.wave.reset(); - } - fn set_block_and_fnumber(&mut self, block: u8, fnumber: u16) { - self.block = block; - self.fnumber = fnumber; + self.phase.set_block_and_fnumber(block, fnumber); } - fn set_detune(&mut self, detune: u8) { - self.detune = detune; - } - - fn set_multiplier(&mut self, multiplier: u16) { - self.multiplier = if multiplier == 0 { 0.5 } else { multiplier as f32 }; + fn set_detune_and_multiple(&mut self, detune: u8, multiple: u8) { + self.phase.set_detune_and_multiple(detune, multiple); } fn set_total_level(&mut self, level: u16) { @@ -302,17 +381,26 @@ impl Operator { fn notify_key_change(&mut self, state: bool, envelope_clock: EnvelopeClock) { self.envelope.notify_key_change(state, envelope_clock); + self.phase.reset(); } - fn get_sample(&mut self, modulator: f32, envelope_clock: EnvelopeClock) -> f32 { - let frequency = fnumber_to_frequency(self.block, self.fnumber); - self.wave.set_frequency((frequency * self.multiplier) as f32 + modulator); - let sample = self.wave.next().unwrap(); - + fn get_output(&mut self, modulator: u16, clocks: (FmClock, EnvelopeClock)) -> u16 { + let (fm_clock, envelope_clock) = clocks; self.envelope.update_envelope(envelope_clock); - let gain = db_to_gain(self.envelope.get_db_at()); + let envelope = self.envelope.get_last_attenuation(); + let phase = self.phase.update_phase(fm_clock); - sample / gain + let mod_phase = phase + modulator; + + let mut output = POW_TABLE[(SIN_TABLE[(mod_phase & 0x1FF) as usize] + envelope) as usize]; +//if self.debug_name == "ch 3, op 1" { +//print!("{:4x}", output); +//} + if mod_phase & 0x200 != 0 { + output = (output as i16 * -1) as u16 + } + + output } } @@ -334,10 +422,10 @@ struct Channel { } impl Channel { - fn new(debug_name: String, sample_rate: usize) -> Self { + fn new(debug_name: String) -> Self { Self { debug_name: debug_name.clone(), - operators: (0..OPERATORS).map(|i| Operator::new(format!("{}, op {}", debug_name, i), sample_rate)).collect(), + operators: (0..OPERATORS).map(|i| Operator::new(format!("{}, op {}", debug_name, i))).collect(), key_state: 0, next_key_clock: 0, next_key_state: 0, @@ -351,72 +439,78 @@ impl Channel { self.next_key_state = key; } - fn check_key_change(&mut self, fm_clock: FmClock, envelope_clock: EnvelopeClock) { + fn check_key_change(&mut self, clocks: (FmClock, EnvelopeClock)) { + let (fm_clock, envelope_clock) = clocks; if self.key_state != self.next_key_state && fm_clock >= self.next_key_clock { self.key_state = self.next_key_state; for (i, operator) in self.operators.iter_mut().enumerate() { operator.notify_key_change(((self.key_state >> i) & 0x01) != 0, envelope_clock); - operator.reset(); } } } - fn get_sample(&mut self, fm_clock: FmClock, envelope_clock: EnvelopeClock) -> f32 { - self.check_key_change(fm_clock, envelope_clock); + fn get_sample(&mut self, clocks: (FmClock, EnvelopeClock)) -> f32 { + self.check_key_change(clocks); + let mut output = self.get_algorithm_output(clocks); - if self.key_state != 0 { - self.get_algorithm_sample(envelope_clock) + let output = if output & 0x2000 == 0 { + output as i16 } else { - 0.0 - } + (output | 0xC000) as i16 + }; +//if self.debug_name == "ch 0" { +//println!("{}", output); +//} + let output = output as f32 / (1 << 14) as f32; + output } - fn get_algorithm_sample(&mut self, envelope_clock: EnvelopeClock) -> f32 { + fn get_algorithm_output(&mut self, clocks: (FmClock, EnvelopeClock)) -> u16 { match self.algorithm { OperatorAlgorithm::A0 => { - let modulator0 = self.operators[0].get_sample(0.0, envelope_clock); - let modulator1 = self.operators[1].get_sample(modulator0, envelope_clock); - let modulator2 = self.operators[2].get_sample(modulator1, envelope_clock); - self.operators[3].get_sample(modulator2, envelope_clock) + let modulator0 = self.operators[0].get_output(0, clocks); + let modulator1 = self.operators[1].get_output(modulator0, clocks); + let modulator2 = self.operators[2].get_output(modulator1, clocks); + self.operators[3].get_output(modulator2, clocks) }, OperatorAlgorithm::A1 => { - let sample1 = self.operators[0].get_sample(0.0, envelope_clock) + self.operators[1].get_sample(0.0, envelope_clock); - let sample2 = self.operators[2].get_sample(sample1, envelope_clock); - self.operators[3].get_sample(sample2, envelope_clock) + let output1 = self.operators[0].get_output(0, clocks) + self.operators[1].get_output(0, clocks); + let output2 = self.operators[2].get_output(output1, clocks); + self.operators[3].get_output(output2, clocks) }, OperatorAlgorithm::A2 => { - let sample1 = self.operators[1].get_sample(0.0, envelope_clock); - let sample2 = self.operators[2].get_sample(sample1, envelope_clock); - let sample3 = self.operators[0].get_sample(0.0, envelope_clock) + sample2; - self.operators[3].get_sample(sample3, envelope_clock) + let output1 = self.operators[1].get_output(0, clocks); + let output2 = self.operators[2].get_output(output1, clocks); + let output3 = self.operators[0].get_output(0, clocks) + output2; + self.operators[3].get_output(output3, clocks) }, OperatorAlgorithm::A3 => { - let sample1 = self.operators[0].get_sample(0.0, envelope_clock); - let sample2 = self.operators[1].get_sample(sample1, envelope_clock); - let sample3 = self.operators[2].get_sample(0.0, envelope_clock); - self.operators[3].get_sample(sample2 + sample3, envelope_clock) + let output1 = self.operators[0].get_output(0, clocks); + let output2 = self.operators[1].get_output(output1, clocks); + let output3 = self.operators[2].get_output(0, clocks); + self.operators[3].get_output(output2 + output3, clocks) }, OperatorAlgorithm::A4 => { - let sample1 = self.operators[0].get_sample(0.0, envelope_clock); - let sample2 = self.operators[1].get_sample(sample1, envelope_clock); - let sample3 = self.operators[2].get_sample(0.0, envelope_clock); - let sample4 = self.operators[3].get_sample(sample3, envelope_clock); - sample2 + sample4 + let output1 = self.operators[0].get_output(0, clocks); + let output2 = self.operators[1].get_output(output1, clocks); + let output3 = self.operators[2].get_output(0, clocks); + let output4 = self.operators[3].get_output(output3, clocks); + output2 + output4 }, OperatorAlgorithm::A5 => { - let sample1 = self.operators[0].get_sample(0.0, envelope_clock); - self.operators[1].get_sample(sample1, envelope_clock) + self.operators[2].get_sample(sample1, envelope_clock) + self.operators[3].get_sample(sample1, envelope_clock) + let output1 = self.operators[0].get_output(0, clocks); + self.operators[1].get_output(output1, clocks) + self.operators[2].get_output(output1, clocks) + self.operators[3].get_output(output1, clocks) }, OperatorAlgorithm::A6 => { - let sample1 = self.operators[0].get_sample(0.0, envelope_clock); - let sample2 = self.operators[1].get_sample(sample1, envelope_clock); - sample2 + self.operators[2].get_sample(0.0, envelope_clock) + self.operators[3].get_sample(0.0, envelope_clock) + let output1 = self.operators[0].get_output(0, clocks); + let output2 = self.operators[1].get_output(output1, clocks); + output2 + self.operators[2].get_output(0, clocks) + self.operators[3].get_output(0, clocks) }, OperatorAlgorithm::A7 => { - self.operators[0].get_sample(0.0, envelope_clock) - + self.operators[1].get_sample(0.0, envelope_clock) - + self.operators[2].get_sample(0.0, envelope_clock) - + self.operators[3].get_sample(0.0, envelope_clock) + self.operators[0].get_output(0, clocks) + + self.operators[1].get_output(0, clocks) + + self.operators[2].get_output(0, clocks) + + self.operators[3].get_output(0, clocks) }, } } @@ -459,7 +553,9 @@ pub struct Ym2612 { clock_frequency: Frequency, fm_clock_period: ClockDuration, - envelope_clock_period: ClockDuration, + next_fm_clock: FmClock, + envelope_clock: EnvelopeClock, + channels: Vec, dac: Dac, @@ -479,10 +575,8 @@ pub struct Ym2612 { impl Ym2612 { pub fn create(host: &mut H, clock_frequency: Frequency) -> Result { let source = host.create_audio_source()?; - let sample_rate = source.samples_per_second(); - let fm_clock = clock_frequency / 6 / 24; + let fm_clock = clock_frequency / (6 * 24); let fm_clock_period = fm_clock.period_duration(); - let envelope_clock_period = (fm_clock / 3).period_duration(); Ok(Self { source, @@ -491,9 +585,10 @@ impl Ym2612 { clock_frequency, fm_clock_period, - envelope_clock_period, - channels: (0..CHANNELS).map(|i| Channel::new(format!("ch {}", i), sample_rate)).collect(), + next_fm_clock: 0, + envelope_clock: 0, + channels: (0..CHANNELS).map(|i| Channel::new(format!("ch {}", i))).collect(), dac: Dac::default(), timer_a_enable: false, @@ -519,23 +614,22 @@ impl Steppable for Ym2612 { let sample_duration = ClockDuration::from_secs(1) / rate as u64; //if self.source.space_available() >= samples { + let mut sample = 0.0; let mut buffer = vec![0.0; samples]; for (i, buffered_sample) in buffer.iter_mut().enumerate().take(samples) { let sample_clock = system.clock + (sample_duration * i as u64); let fm_clock = sample_clock.as_duration() / self.fm_clock_period; - let envelope_clock = sample_clock.as_duration() / self.envelope_clock_period; - let mut sample = 0.0; - for ch in 0..(CHANNELS - 1) { - sample += self.channels[ch].get_sample(fm_clock, envelope_clock); + // Simulate each clock cycle, even if we skip one due to aliasing from the unequal sampling rate of 53,267 Hz + for clock in self.next_fm_clock..=fm_clock { + sample = self.get_sample(clock); } + self.next_fm_clock = fm_clock + 1; + // The DAC uses an 8000 Hz sample rate, so we don't want to skip clocks if self.dac.enabled { sample += self.dac.get_sample(); - } else { - sample += self.channels[CHANNELS - 1].get_sample(fm_clock, envelope_clock); } - *buffered_sample = sample.clamp(-1.0, 1.0); } self.source.write_samples(system.clock, &buffer); @@ -545,6 +639,27 @@ impl Steppable for Ym2612 { } } +impl Ym2612 { + fn get_sample(&mut self, fm_clock: FmClock) -> f32 { + if fm_clock % 3 == 0 { + self.envelope_clock += 1; + } + let clocks = (fm_clock, self.envelope_clock); + + let mut sample = 0.0; + + for ch in 0..(CHANNELS - 1) { + sample += self.channels[ch].get_sample(clocks); + } + + if !self.dac.enabled { + sample += self.channels[CHANNELS - 1].get_sample(clocks); + } + + sample + } +} + impl Ym2612 { pub fn set_register(&mut self, clock: ClockTime, bank: u8, reg: u8, data: u8) { // Keep a copy for debugging purposes, and if the original values are needed @@ -596,8 +711,9 @@ impl Ym2612 { reg if is_reg_range(reg, 0x30) => { let (ch, op) = get_ch_op(bank, reg); - self.channels[ch].operators[op].set_detune((data & 0xF0) >> 4); - self.channels[ch].operators[op].set_multiplier((data & 0x0F) as u16); + let detune = (data & 0xF0) >> 4; + let multiple = data & 0x0F; + self.channels[ch].operators[op].set_detune_and_multiple(detune, multiple); }, reg if is_reg_range(reg, 0x40) => { diff --git a/todo.txt b/todo.txt index 94e40e1..acd11bb 100644 --- a/todo.txt +++ b/todo.txt @@ -1,4 +1,7 @@ +* the first 512 entries are 0 for some reason, in the log table, but otherwise seems ok +* you need to scale the output sample to be +/- 1.0 instead of 0-1.0 + * can you make the frontend more adaptive to the input that the devices are using * change the name of the functions that take Host to be `with_host` or `register` or something * need to re-add a mechanism for audio frame dialation, either based on speed, or somehow automatic, use clocks and make them aligned