diff --git a/docs/log.txt b/docs/log.txt index 2c03d63..5f5dde5 100644 --- a/docs/log.txt +++ b/docs/log.txt @@ -353,3 +353,30 @@ General Work 2023-04-22 - I'm back to trying to get the ym2612 emulation working properly for the Genesis +2023-04-23 +- I managed to get the idea of the envelope implemented but it's clearly very broken and I don't + know why exactly. It could easily be the waveform generators or bugs or anything else. I have + already found and fixed a number of bugs, so there are probably more + +2023-04-24 +- I added a print statement to blastem to see how the envelope value was varying over time based on + the envelope state and what update cycle it was in. In moa, it shows the envelope output varying + over many many update cycles which I thought was maybe wrong, and the decay was only lasting one + cycle +- It turned out blastem showed the same output varying over many cycles, so that wasn't a problem, + but it showed the decay lasting many cycles too +- it also showed the envelope output being up to 4092 instead of ~1024 and I didn't notice until now + but there's a comment that blastem is using a 12 bit number in two parts, 8 bits and 4 bits, but I + don't yet know how it uses those numbers to get the output +- the decay lasting only one cycle turned out to be a bug where I was masking the wrong bits and + shifting them away, so it was always 0. +- there was also a bug in that the raw values from the registers needs to be adjusted to make it + comparable to the envelope, and the total level also needs to be adjusted (which I was already + doing but it's a 10 bit value) +- it looks like the release rate also needs to be adjusted. It should be shifted left and + incremented to make it the same as the other rates + +- after messing with it, it still didn't seem to work, and it still doesn't work correctly, but + changing from a sine wave generator to a square wave generator made it go from being muddled and + mute sounding to sounding crisp and much closer to what it's supposed to sound like + diff --git a/emulator/core/src/clock.rs b/emulator/core/src/clock.rs index 17b1bdf..f4e15ae 100644 --- a/emulator/core/src/clock.rs +++ b/emulator/core/src/clock.rs @@ -1,5 +1,3 @@ -//! Clock time and duration types for simulation with femtosecond accurancy -//! use std::ops::{Add, AddAssign, Sub, SubAssign, Mul, MulAssign, Div, DivAssign}; @@ -230,7 +228,11 @@ impl AddAssign for ClockTime { } } - +/// Represents a frequency in Hz +/// +/// Clocks are usually given as a frequency, but durations are needed when dealing with clocks +/// and clock durations. This type makes it easier to create a clock of a given frequency and +/// convert it to a `ClockDuration` #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct Frequency { hertz: u32, diff --git a/emulator/peripherals/yamaha/src/ym2612.rs b/emulator/peripherals/yamaha/src/ym2612.rs index a811bef..8f3b636 100644 --- a/emulator/peripherals/yamaha/src/ym2612.rs +++ b/emulator/peripherals/yamaha/src/ym2612.rs @@ -6,7 +6,7 @@ //! //! Resources: //! - Registers: https://www.smspower.org/maxim/Documents/YM2612 -//! - Envelope and rates: http://gendev.spritesmind.net/forum/viewtopic.php?p=5716#5716 +//! - Envelope and rates: https://gendev.spritesmind.net/forum/viewtopic.php?t=386 [Nemesis] use std::num::NonZeroU8; use std::collections::VecDeque; @@ -14,7 +14,7 @@ use std::collections::VecDeque; 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::{SineWave, db_to_gain}; +use moa_audio::{SquareWave, db_to_gain}; /// Table of shift values for each possible rate angle @@ -117,7 +117,7 @@ const DEV_NAME: &str = "ym2612"; const CHANNELS: usize = 6; const OPERATORS: usize = 4; -const MAX_ENVELOPE: u16 = 0xFFC; +const MAX_ENVELOPE: u16 = 0x3FC; type EnvelopeClock = u64; @@ -200,12 +200,13 @@ impl EnvelopeGenerator { match self.envelope_state { EnvelopeState::Attack => { - let new_envelope = self.envelope + increment * (((1024 - self.envelope) / 16) + 1); - if new_envelope < MAX_ENVELOPE { - self.envelope = new_envelope; - } else { + //let new_envelope = self.envelope + increment * (((1024 - self.envelope) / 16) + 1); + let new_envelope = ((!self.envelope * increment) >> 4) & 0xFFFC; + if new_envelope > self.envelope { self.envelope_state = EnvelopeState::Decay; self.envelope = 0; + } else { + self.envelope = new_envelope.min(MAX_ENVELOPE); } }, EnvelopeState::Decay | @@ -214,16 +215,15 @@ impl EnvelopeGenerator { self.envelope = (self.envelope + increment).min(MAX_ENVELOPE); }, } + + 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 envelope = if self.envelope_state == EnvelopeState::Attack { - !self.envelope & 0x3FF - } else { - self.envelope - }; - let attenuation_10bit = (envelope + self.total_level).min(MAX_ENVELOPE); + let attenuation_10bit = (self.envelope + self.total_level).min(MAX_ENVELOPE); attenuation_10bit as f32 * 0.09375 } } @@ -245,7 +245,7 @@ enum OperatorAlgorithm { struct Operator { #[allow(dead_code)] debug_name: String, - wave: SineWave, + wave: SquareWave, frequency: f32, multiplier: f32, envelope: EnvelopeGenerator, @@ -255,7 +255,7 @@ impl Operator { fn new(debug_name: String, sample_rate: usize) -> Self { Self { debug_name: debug_name.clone(), - wave: SineWave::new(400.0, sample_rate), + wave: SquareWave::new(400.0, sample_rate), frequency: 400.0, multiplier: 1.0, envelope: EnvelopeGenerator::new(debug_name), @@ -474,7 +474,7 @@ impl Ym2612 { selected_reg_1: None, clock_frequency, - envelope_clock_period: clock_frequency.period_duration() * 351, + envelope_clock_period: clock_frequency.period_duration() * 144 * 3, // Nemesis shows * 351? Not sure why the difference channels: (0..CHANNELS).map(|i| Channel::new(format!("ch {}", i), sample_rate)).collect(), channel_frequencies: [(0, 0); CHANNELS], @@ -585,10 +585,10 @@ impl Ym2612 { reg if is_reg_range(reg, 0x40) => { let (ch, op) = get_ch_op(bank, reg); // The total level (attenuation) is 0-127, where 0 is the highest volume and 127 - // is the lowest, in 0.75 dB intervals. The 7-bit value is multiplied by 8 to + // is the lowest, in 0.75 dB intervals. The 7-bit value is shifted left to // convert it to a 10-bit attenuation for the envelope generator, which is an // attenuation value in 0.09375 dB intervals - self.channels[ch].operators[op].set_total_level((data & 0x7F) as u16 * 8); + self.channels[ch].operators[op].set_total_level(((data & 0x7F) as u16) << 3); }, reg if is_reg_range(reg, 0x50) @@ -641,18 +641,20 @@ impl Ym2612 { let (ch, op) = get_ch_op(bank, reg); let keycode = self.registers[0xA0 + get_ch_index(ch)] >> 1; let rate_scaling = self.registers[0x50 + index] & 0xC0 >> 6; + let attack_rate = self.registers[0x50 + index] & 0x1F; let first_decay_rate = self.registers[0x60 + index] & 0x1F; let second_decay_rate = self.registers[0x70 + index] & 0x1F; - let release_rate = self.registers[0x80 + index] & 0x0F; + let release_rate = ((self.registers[0x80 + index] & 0x0F) << 1) + 1; // register is only 4 bits, so it's adjusted to 5-bits with 1 in the LSB self.channels[ch].operators[op].set_rate(EnvelopeState::Attack, calculate_rate(attack_rate, rate_scaling, keycode)); self.channels[ch].operators[op].set_rate(EnvelopeState::Decay, calculate_rate(first_decay_rate, rate_scaling, keycode)); self.channels[ch].operators[op].set_rate(EnvelopeState::Sustain, calculate_rate(second_decay_rate, rate_scaling, keycode)); self.channels[ch].operators[op].set_rate(EnvelopeState::Release, calculate_rate(release_rate, rate_scaling, keycode)); - let sustain_level = (self.registers[0x80 + index] & 0x0F) >> 4; - self.channels[ch].operators[op].set_sustain_level(sustain_level as u16); + let sustain_level = (self.registers[0x80 + index] as u16 & 0xF0) << 2; // register is 4 bits, so it's adjusted to match total_level's scale + let sustain_level = if sustain_level == (0xF0 << 2) { MAX_ENVELOPE } else { sustain_level }; // adjust the maximum storable value to be the max attenuation + self.channels[ch].operators[op].set_sustain_level(sustain_level); } } diff --git a/emulator/systems/genesis/src/peripherals/controllers.rs b/emulator/systems/genesis/src/peripherals/controllers.rs index e3b8a09..f5d966b 100644 --- a/emulator/systems/genesis/src/peripherals/controllers.rs +++ b/emulator/systems/genesis/src/peripherals/controllers.rs @@ -1,4 +1,7 @@ +use std::sync::Arc; +use std::sync::atomic::{AtomicU16, Ordering}; + use moa_core::{warn, info}; use moa_core::{System, Error, ClockTime, ClockDuration, Address, Addressable, Steppable, Transmutable}; use moa_core::host::{Host, ControllerUpdater, HostData, ControllerDevice, ControllerEvent}; @@ -22,7 +25,7 @@ pub struct GenesisControllerPort { /// Data contains bits: /// 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 /// X | Y | Z | MODE | START | A | C | B | RIGHT | LEFT | DOWN | UP - buttons: HostData, + buttons: Arc, ctrl: u8, outputs: u8, @@ -34,7 +37,7 @@ pub struct GenesisControllerPort { impl Default for GenesisControllerPort { fn default() -> Self { Self { - buttons: HostData::new(0xffff), + buttons: Arc::new(AtomicU16::new(0xffff)), ctrl: 0, outputs: 0, th_count: 0, @@ -45,7 +48,7 @@ impl Default for GenesisControllerPort { impl GenesisControllerPort { pub fn get_data(&mut self) -> u8 { - let inputs = self.buttons.get(); + let inputs = self.buttons.load(Ordering::Relaxed); let th_state = (self.outputs & 0x40) != 0; match (th_state, self.th_count) { @@ -85,7 +88,7 @@ impl GenesisControllerPort { } } -pub struct GenesisControllersUpdater(HostData, HostData); +pub struct GenesisControllersUpdater(Arc, HostData); impl ControllerUpdater for GenesisControllersUpdater { fn update_controller(&mut self, event: ControllerEvent) { @@ -102,8 +105,8 @@ impl ControllerUpdater for GenesisControllersUpdater { _ => (0x0000, false), }; - let buttons = (self.0.get() & !mask) | (if !state { mask } else { 0 }); - self.0.set(buttons); + let buttons = (self.0.load(Ordering::Acquire) & !mask) | (if !state { mask } else { 0 }); + self.0.store(buttons, Ordering::Release); if buttons != 0 { self.1.set(true); } diff --git a/todo.txt b/todo.txt index ec5915e..b464e7c 100644 --- a/todo.txt +++ b/todo.txt @@ -1,11 +1,16 @@ -* remove the old timer stuff and replace with opentelemetry if it can be wasm compatible - +* 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 * maybe I should make ClockDuration use picos as the base instead, and use u64 since that gives like 212 days or something instead of 5h and should prevent split nanoseconds which is the main concern - * the audio needs to not run if nothing is using it or there's constant buffer underruns +* fix ym2612 sound generation +* sound doesn't work on a lot of games... is it a problem with the Z80 accessing the YM2612, or the lack of YM timers? or something else? + +* add opentelemetry if it can be wasm compatible, or some kind of timing for giving an average framerate + * add doc strings everywhere * get rustfmt, rustdoc, and clippy working in some kind of semi-automatic fashion @@ -25,16 +30,9 @@ * can you refactor the update timeout to put it in rust? Would that make it faster? (the tricky part is the closure) * re-enable sound on webassembly, see if it works (it does not. Very lagged and jittery with what sounds like repeated frames) - * can you somehow speed up the memory accessing through the sim? The dyn Addressable is causing a fair amount of overhead * can you somehow make devices have two step functions for running things at different times? (I'm thinking ym2612 audio gen vs timers) * should you rename devices.rs traits.rs? -* you could refactor the instruction loop into a series of functions, and test if there's a performance difference with and without #[inline(always)] -* move parser into its own utils/ or /libs/ directory - -* fix ym2612 sound generation -* need to re-add a mechanism for audio frame dialation, either based on speed, or somehow automatic -* sound doesn't work on a lot of games... is it a problem with the Z80 accessing the YM2612, or the lack of YM timers? or something else? * the pixel format idea didn't work because of window resizing and the fact that the frame needs to be adjusted in size because the window can't always be resized...