mirror of
https://github.com/transistorfet/moa.git
synced 2024-11-21 19:30:52 +00:00
Non-working implementation of the ym2612 envelope
It does something, but doesn't work as it should. It could be a few things including the on/off signal being too slow due to how time works in the sample generation, but I wanted to at least commit what I have. It seems to work roughly right according to the forum post that describes the chip's operation in detail, but there could still be some glaring bugs
This commit is contained in:
parent
10ef61784a
commit
5740550c95
@ -11,3 +11,7 @@ exclude = [
|
||||
"emulator/frontends/macroquad",
|
||||
]
|
||||
default-members = ["emulator/frontends/minifb"]
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
|
||||
|
135
docs/log.txt
135
docs/log.txt
@ -1,4 +1,9 @@
|
||||
|
||||
[Note: until the dashed line is logs pieced together after the fact]
|
||||
|
||||
Project Start
|
||||
=============
|
||||
|
||||
- 2021-09-28: started the project, computie monitor working after a week
|
||||
- 2021-10-08: I think the Computie OS could boot by this point
|
||||
- 2021-10-14: I think I started working on the genesis at this point, or maybe a bit before
|
||||
@ -14,8 +19,6 @@
|
||||
Genesis
|
||||
=======
|
||||
|
||||
(pieced together after the fact)
|
||||
|
||||
before 2021-10-25
|
||||
- controllers and coproc controls aren't an issue as dummies until much later
|
||||
- implement dma
|
||||
@ -65,7 +68,7 @@ before 2021-10-25
|
||||
that caused some of the issues
|
||||
|
||||
|
||||
-----
|
||||
-----------------------------
|
||||
|
||||
2021-10-31
|
||||
- at this point I had finally gotten the scrolls to work enough to print the SEGA logo at the start
|
||||
@ -75,6 +78,55 @@ before 2021-10-25
|
||||
I think I was doing for most of November (instead of working on the Genesis impl)
|
||||
|
||||
|
||||
Macintosh
|
||||
=========
|
||||
|
||||
- ram self test is run by jumping to 0x400694, which returns by jumping to %a6 which contains 0x4000f0
|
||||
if it returns with eq set, it will jump to the system initialization at 0x40026c
|
||||
it doesn't have eq set, so it ends up in an infinite loop.
|
||||
- turns out MOVEM was broken such that it was incrementing instead of decrementing address (found by
|
||||
inspecting the first byte of memory when trying to get 0x5555AAAA to cancel itself out
|
||||
|
||||
2021-11-19
|
||||
- still not working, calling 0x4000f0 to fail. Turns out this is where the dead mac is supposed to
|
||||
be printed, but it's not (found that from another blog post)
|
||||
|
||||
2021-11-23
|
||||
- finally got dead mac screen showing with 0F0003 as the error. It wasn't showing because the 0 colour
|
||||
used by the genesis, which is a mask colour, causes nothing to appear
|
||||
- then the issue was the indexing of the memory page (x * 2) * y instead of (x * 2) + (y * 512/8)
|
||||
|
||||
- so the 0F0003 dead mac was caused by the first trap instruction in the ROM, appearing at 4002adc
|
||||
It causes an illegal instruction, which then jumps to the exception entry points start at 4001aa
|
||||
in rom which all jump to the same function at 4001d2, which then ends up in the failure
|
||||
- trying to find why, i traced the program to find where the trap table was being set up, which
|
||||
turned out to be around 400448 which is where I'd been debugging the last issue. It sets location
|
||||
0x28 to 401018 which is the trap handler...
|
||||
- 0x28 is not the illegal instruction handler, it's the 1010 line emulator exception... 1010 as it A
|
||||
as in the A line traps... So the m68k is specifically not handling the a000 instructions correctly
|
||||
|
||||
- it's now getting past the dead mac, but instead it causes two writes to read only memory when it
|
||||
tries to set some bits indirectly to an address in rom (0x4034ae), which is something do with the
|
||||
drivers. The Sound and Disk drivers are opened and it's during each of the open functions that the
|
||||
write occurs
|
||||
- it's possible to continue (which might be in broken state) and it then attempts to write to
|
||||
sequential addresses in upper ram which eventually spills over into 0x100000 which isn't mapped...
|
||||
No idea if this is just because of something going wrong earlier, or if this is also an emulator bug
|
||||
|
||||
- the writes to rom issues happens during InitIO (0x400614), when initializing the first two drivers,
|
||||
the Sound and Disk (I think). There is a pointer to a pointer to a value that contains 0x4034ae,
|
||||
which seems to be calculated from a jump table, presumably pointing to something in rom that contains
|
||||
the driver, but for some reason the code is bit setting that value. I'm not sure if the data is wrong
|
||||
and it should normally see a 0 instead of the rom addr, or if there's a branch that shouldn't/should
|
||||
happen that leads it to that code when it shouldn't
|
||||
- c4c and c7c seem to be driver descriptors of some kind, each with a rom addr and what seems to be some
|
||||
flags after that. They also have 0xfffb and 0xfffc. That said, if there's a bug in the code that
|
||||
creates that data, it would likely be broken for both
|
||||
|
||||
|
||||
Back to Genesis
|
||||
===============
|
||||
|
||||
2021-11-30
|
||||
- after getting things working somewhat with the scrolls, but not the sprites, I finally found
|
||||
ComradeOj's demo, which I'm now using to test with, and I've also got BlastEm setup so that I
|
||||
@ -270,6 +322,10 @@ before 2021-10-25
|
||||
check before the audio devices write only account for one channel of audio instead of two, so the
|
||||
buffer was over filling. Dividing the available buffer size by 2 fixed it
|
||||
|
||||
|
||||
General Work
|
||||
============
|
||||
|
||||
2022-10-08
|
||||
- I replaced the circular buffer with queues of audio frames, which might be less able to handle time
|
||||
dialation, but which uses fewer locks for less time, which seems to improve the occasional dropout
|
||||
@ -280,7 +336,6 @@ before 2021-10-25
|
||||
probably shouldn't be. The other audio sounds distorted but still sort of sounds like what it should,
|
||||
so I don't think it's too far off
|
||||
|
||||
|
||||
2023-03-25
|
||||
- trying to improve the performance. It seems to be at about 20 to 30 fps on firefox but 60 fps on
|
||||
chrome and I'm not sure why the difference.
|
||||
@ -295,74 +350,6 @@ before 2021-10-25
|
||||
individually, with steps in between so the game can do those tricks... and I don't see a lot of
|
||||
issues with colour glitches and stuff
|
||||
|
||||
|
||||
---------------------------------------------------------------------------------------------------
|
||||
|
||||
Macintosh
|
||||
=========
|
||||
|
||||
- ram self test is run by jumping to 0x400694, which returns by jumping to %a6 which contains 0x4000f0
|
||||
if it returns with eq set, it will jump to the system initialization at 0x40026c
|
||||
it doesn't have eq set, so it ends up in an infinite loop.
|
||||
- turns out MOVEM was broken such that it was incrementing instead of decrementing address (found by
|
||||
inspecting the first byte of memory when trying to get 0x5555AAAA to cancel itself out
|
||||
|
||||
2021-11-19
|
||||
- still not working, calling 0x4000f0 to fail. Turns out this is where the dead mac is supposed to
|
||||
be printed, but it's not (found that from another blog post)
|
||||
|
||||
2021-11-23
|
||||
- finally got dead mac screen showing with 0F0003 as the error. It wasn't showing because the 0 colour
|
||||
used by the genesis, which is a mask colour, causes nothing to appear
|
||||
- then the issue was the indexing of the memory page (x * 2) * y instead of (x * 2) + (y * 512/8)
|
||||
|
||||
- so the 0F0003 dead mac was caused by the first trap instruction in the ROM, appearing at 4002adc
|
||||
It causes an illegal instruction, which then jumps to the exception entry points start at 4001aa
|
||||
in rom which all jump to the same function at 4001d2, which then ends up in the failure
|
||||
- trying to find why, i traced the program to find where the trap table was being set up, which
|
||||
turned out to be around 400448 which is where I'd been debugging the last issue. It sets location
|
||||
0x28 to 401018 which is the trap handler...
|
||||
- 0x28 is not the illegal instruction handler, it's the 1010 line emulator exception... 1010 as it A
|
||||
as in the A line traps... So the m68k is specifically not handling the a000 instructions correctly
|
||||
|
||||
- it's now getting past the dead mac, but instead it causes two writes to read only memory when it
|
||||
tries to set some bits indirectly to an address in rom (0x4034ae), which is something do with the
|
||||
drivers. The Sound and Disk drivers are opened and it's during each of the open functions that the
|
||||
write occurs
|
||||
- it's possible to continue (which might be in broken state) and it then attempts to write to
|
||||
sequential addresses in upper ram which eventually spills over into 0x100000 which isn't mapped...
|
||||
No idea if this is just because of something going wrong earlier, or if this is also an emulator bug
|
||||
|
||||
- the writes to rom issues happens during InitIO (0x400614), when initializing the first two drivers,
|
||||
the Sound and Disk (I think). There is a pointer to a pointer to a value that contains 0x4034ae,
|
||||
which seems to be calculated from a jump table, presumably pointing to something in rom that contains
|
||||
the driver, but for some reason the code is bit setting that value. I'm not sure if the data is wrong
|
||||
and it should normally see a 0 instead of the rom addr, or if there's a branch that shouldn't/should
|
||||
happen that leads it to that code when it shouldn't
|
||||
- c4c and c7c seem to be driver descriptors of some kind, each with a rom addr and what seems to be some
|
||||
flags after that. They also have 0xfffb and 0xfffc. That said, if there's a bug in the code that
|
||||
creates that data, it would likely be broken for both
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
2023-04-22
|
||||
- I'm back to trying to get the ym2612 emulation working properly for the Genesis
|
||||
|
||||
|
@ -1,3 +1,12 @@
|
||||
/// Emulate the YM2612 FM Sound Synthesizer (used by the Sega Genesis)
|
||||
///
|
||||
/// This implementation is mostly based on online references to the YM2612's registers and their
|
||||
/// function, forum posts that describe the details of operation of the chip, and looking at
|
||||
/// source code that emulates the chip. It is still very much a work in progress
|
||||
///
|
||||
/// Resources:
|
||||
/// - Registers: https://www.smspower.org/maxim/Documents/YM2612
|
||||
/// - Envelope and rates: http://gendev.spritesmind.net/forum/viewtopic.php?p=5716#5716
|
||||
|
||||
use std::num::NonZeroU8;
|
||||
use std::collections::VecDeque;
|
||||
@ -7,18 +16,216 @@ use moa_core::{System, Error, Clock, ClockElapsed, Address, Addressable, Steppab
|
||||
use moa_core::host::{Host, Audio};
|
||||
use moa_core::host::audio::{SineWave, db_to_gain};
|
||||
|
||||
|
||||
/// Table of shift values for each possible rate angle
|
||||
///
|
||||
/// The value here is used to shift a bit to get the number of global cycles between each increment
|
||||
/// of the envelope attenuation, based on the rate that's currently active
|
||||
const COUNTER_SHIFT_VALUES: &[u16] = &[
|
||||
11, 11, 11, 11,
|
||||
10, 10, 10, 10,
|
||||
9, 9, 9, 9,
|
||||
8, 8, 8, 8,
|
||||
7, 7, 7, 7,
|
||||
6, 6, 6, 6,
|
||||
5, 5, 5, 5,
|
||||
4, 4, 4, 4,
|
||||
3, 3, 3, 3,
|
||||
2, 2, 2, 2,
|
||||
1, 1, 1, 1,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
];
|
||||
|
||||
/// Table of attenuation increments for each possible rate angle
|
||||
///
|
||||
/// The envelope rates (Attack, Decay, Sustain, and Release) are expressed as an "angle" rather
|
||||
/// than attenuation, and the values will always be below 64. This table maps each of the 64
|
||||
/// possible angle values to a sequence of 8 cycles, and the amount to increment the attenuation
|
||||
/// at each point in that cycle
|
||||
const RATE_TABLE: &[u16] = &[
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 1, 0, 1, 0, 1, 0, 1,
|
||||
0, 1, 0, 1, 0, 1, 0, 1,
|
||||
0, 1, 0, 1, 0, 1, 0, 1,
|
||||
0, 1, 0, 1, 0, 1, 0, 1,
|
||||
0, 1, 1, 1, 0, 1, 1, 1,
|
||||
0, 1, 1, 1, 0, 1, 1, 1,
|
||||
0, 1, 0, 1, 0, 1, 0, 1,
|
||||
0, 1, 0, 1, 1, 1, 0, 1,
|
||||
0, 1, 1, 1, 0, 1, 1, 1,
|
||||
0, 1, 1, 1, 1, 1, 1, 1,
|
||||
0, 1, 0, 1, 0, 1, 0, 1,
|
||||
0, 1, 0, 1, 1, 1, 0, 1,
|
||||
0, 1, 1, 1, 0, 1, 1, 1,
|
||||
0, 1, 1, 1, 1, 1, 1, 1,
|
||||
0, 1, 0, 1, 0, 1, 0, 1,
|
||||
0, 1, 0, 1, 1, 1, 0, 1,
|
||||
0, 1, 1, 1, 0, 1, 1, 1,
|
||||
0, 1, 1, 1, 1, 1, 1, 1,
|
||||
0, 1, 0, 1, 0, 1, 0, 1,
|
||||
0, 1, 0, 1, 1, 1, 0, 1,
|
||||
0, 1, 1, 1, 0, 1, 1, 1,
|
||||
0, 1, 1, 1, 1, 1, 1, 1,
|
||||
0, 1, 0, 1, 0, 1, 0, 1,
|
||||
0, 1, 0, 1, 1, 1, 0, 1,
|
||||
0, 1, 1, 1, 0, 1, 1, 1,
|
||||
0, 1, 1, 1, 1, 1, 1, 1,
|
||||
0, 1, 0, 1, 0, 1, 0, 1,
|
||||
0, 1, 0, 1, 1, 1, 0, 1,
|
||||
0, 1, 1, 1, 0, 1, 1, 1,
|
||||
0, 1, 1, 1, 1, 1, 1, 1,
|
||||
0, 1, 0, 1, 0, 1, 0, 1,
|
||||
0, 1, 0, 1, 1, 1, 0, 1,
|
||||
0, 1, 1, 1, 0, 1, 1, 1,
|
||||
0, 1, 1, 1, 1, 1, 1, 1,
|
||||
0, 1, 0, 1, 0, 1, 0, 1,
|
||||
0, 1, 0, 1, 1, 1, 0, 1,
|
||||
0, 1, 1, 1, 0, 1, 1, 1,
|
||||
0, 1, 1, 1, 1, 1, 1, 1,
|
||||
0, 1, 0, 1, 0, 1, 0, 1,
|
||||
0, 1, 0, 1, 1, 1, 0, 1,
|
||||
0, 1, 1, 1, 0, 1, 1, 1,
|
||||
0, 1, 1, 1, 1, 1, 1, 1,
|
||||
0, 1, 0, 1, 0, 1, 0, 1,
|
||||
0, 1, 0, 1, 1, 1, 0, 1,
|
||||
0, 1, 1, 1, 0, 1, 1, 1,
|
||||
0, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 2, 1, 1, 1, 2,
|
||||
1, 2, 1, 2, 1, 2, 1, 2,
|
||||
1, 2, 2, 2, 1, 2, 2, 2,
|
||||
2, 2, 2, 2, 2, 2, 2, 2,
|
||||
2, 2, 2, 4, 2, 2, 2, 4,
|
||||
2, 4, 2, 4, 2, 4, 2, 4,
|
||||
2, 4, 4, 4, 2, 4, 4, 4,
|
||||
4, 4, 4, 4, 4, 4, 4, 4,
|
||||
4, 4, 4, 8, 4, 4, 4, 8,
|
||||
4, 8, 4, 8, 4, 8, 4, 8,
|
||||
4, 8, 8, 8, 4, 8, 8, 8,
|
||||
8, 8, 8, 8, 8, 8, 8, 8,
|
||||
8, 8, 8, 8, 8, 8, 8, 8,
|
||||
8, 8, 8, 8, 8, 8, 8, 8,
|
||||
8, 8, 8, 8, 8, 8, 8, 8,
|
||||
];
|
||||
|
||||
const DEV_NAME: &str = "ym2612";
|
||||
|
||||
const CHANNELS: usize = 8;
|
||||
const MAX_ENVELOPE: u16 = 0xFFC;
|
||||
const SSG_CENTER: u16 = 0x800;
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
|
||||
#[repr(usize)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
enum EnvelopeState {
|
||||
Attack,
|
||||
Decay1,
|
||||
Decay2,
|
||||
Decay,
|
||||
Sustain,
|
||||
Release,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct EnvelopeGenerator {
|
||||
debug_name: String,
|
||||
total_level: f32,
|
||||
sustain_level: u16,
|
||||
rates: [usize; 4],
|
||||
|
||||
envelope_state: EnvelopeState,
|
||||
last_state_change: Clock,
|
||||
last_envelope_clock: Clock,
|
||||
envelope: u16,
|
||||
}
|
||||
|
||||
impl EnvelopeGenerator {
|
||||
fn new(debug_name: String) -> Self {
|
||||
Self {
|
||||
debug_name,
|
||||
total_level: 0.0,
|
||||
sustain_level: 0,
|
||||
rates: [0; 4],
|
||||
|
||||
envelope_state: EnvelopeState::Attack,
|
||||
last_state_change: 0,
|
||||
last_envelope_clock: 0,
|
||||
envelope: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_total_level(&mut self, db: f32) {
|
||||
self.total_level = db_to_gain(db);
|
||||
}
|
||||
|
||||
fn set_sustain_level(&mut self, level: u16) {
|
||||
self.sustain_level = level;
|
||||
}
|
||||
|
||||
fn set_rate(&mut self, etype: EnvelopeState, rate: usize) {
|
||||
self.rates[etype as usize] = rate;
|
||||
}
|
||||
|
||||
fn notify_state_change(&mut self, state: bool, envelope_clock: Clock) {
|
||||
self.last_state_change = envelope_clock;
|
||||
if state {
|
||||
self.envelope_state = EnvelopeState::Attack;
|
||||
self.envelope = 0;
|
||||
} else {
|
||||
self.envelope_state = EnvelopeState::Release;
|
||||
}
|
||||
}
|
||||
|
||||
fn update_envelope(&mut self, envelope_clock: Clock) {
|
||||
for clock in self.last_envelope_clock..=envelope_clock {
|
||||
self.do_cycle(clock);
|
||||
}
|
||||
self.last_envelope_clock = envelope_clock;
|
||||
}
|
||||
|
||||
fn do_cycle(&mut self, envelope_clock: Clock) {
|
||||
if self.envelope_state == EnvelopeState::Decay && self.envelope >= self.sustain_level {
|
||||
self.envelope_state = EnvelopeState::Sustain;
|
||||
}
|
||||
|
||||
let rate = self.rates[self.envelope_state as usize];
|
||||
let counter_shift = COUNTER_SHIFT_VALUES[rate];
|
||||
if envelope_clock % (1 << counter_shift) == 0 {
|
||||
let update_cycle = (envelope_clock >> counter_shift) & 0x07;
|
||||
let increment = RATE_TABLE[rate * 8 + update_cycle as usize];
|
||||
|
||||
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 {
|
||||
self.envelope_state = EnvelopeState::Decay;
|
||||
self.envelope = 0;
|
||||
}
|
||||
},
|
||||
EnvelopeState::Decay |
|
||||
EnvelopeState::Sustain |
|
||||
EnvelopeState::Release => {
|
||||
self.envelope = (self.envelope + increment).min(MAX_ENVELOPE);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_db_at(&mut self) -> f32 {
|
||||
let envelope = if self.envelope_state == EnvelopeState::Attack {
|
||||
!self.envelope
|
||||
} else {
|
||||
self.envelope
|
||||
};
|
||||
envelope as f32 * 0.09375
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
enum OperatorAlgorithm {
|
||||
A0,
|
||||
@ -34,39 +241,21 @@ enum OperatorAlgorithm {
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Operator {
|
||||
debug_name: String,
|
||||
wave: SineWave,
|
||||
frequency: f32,
|
||||
multiplier: f32,
|
||||
total_level: f32,
|
||||
|
||||
attack_rate: usize,
|
||||
first_decay_rate: usize,
|
||||
first_decay_level: usize,
|
||||
second_decay_rate: usize,
|
||||
release_rate: usize,
|
||||
|
||||
envelope_state: EnvelopeState,
|
||||
last_event: Clock,
|
||||
envelope_gain: f32,
|
||||
envelope: EnvelopeGenerator,
|
||||
}
|
||||
|
||||
impl Operator {
|
||||
fn new(sample_rate: usize) -> Self {
|
||||
fn new(debug_name: String, sample_rate: usize) -> Self {
|
||||
Self {
|
||||
debug_name: debug_name.clone(),
|
||||
wave: SineWave::new(400.0, sample_rate),
|
||||
frequency: 400.0,
|
||||
multiplier: 1.0,
|
||||
total_level: 0.0,
|
||||
|
||||
attack_rate: 0,
|
||||
first_decay_rate: 0,
|
||||
first_decay_level: 0,
|
||||
second_decay_rate: 0,
|
||||
release_rate: 0,
|
||||
|
||||
envelope_state: EnvelopeState::Attack,
|
||||
last_event: 0,
|
||||
envelope_gain: 0.0,
|
||||
envelope: EnvelopeGenerator::new(debug_name),
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,40 +263,6 @@ impl Operator {
|
||||
self.frequency = frequency;
|
||||
}
|
||||
|
||||
fn set_total_level(&mut self, db: f32) {
|
||||
self.total_level = db_to_gain(db);
|
||||
}
|
||||
|
||||
fn set_attack_rate(&mut self, rate: usize) {
|
||||
self.attack_rate = rate;
|
||||
}
|
||||
|
||||
fn set_first_decay_rate(&mut self, rate: usize) {
|
||||
self.first_decay_rate = rate;
|
||||
}
|
||||
|
||||
fn set_first_decay_level(&mut self, rate: usize) {
|
||||
self.first_decay_level = rate;
|
||||
}
|
||||
|
||||
fn set_second_decay_rate(&mut self, rate: usize) {
|
||||
self.second_decay_rate = rate;
|
||||
}
|
||||
|
||||
fn set_release_rate(&mut self, rate: usize) {
|
||||
self.release_rate = rate;
|
||||
}
|
||||
|
||||
fn notify_state_change(&mut self, state: bool, event_clock: Clock) {
|
||||
self.last_event = event_clock;
|
||||
if state {
|
||||
self.envelope_state = EnvelopeState::Attack;
|
||||
self.envelope_gain = 0.0;
|
||||
} else {
|
||||
self.envelope_state = EnvelopeState::Release;
|
||||
}
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.wave.reset();
|
||||
}
|
||||
@ -116,47 +271,41 @@ impl Operator {
|
||||
self.multiplier = multiplier;
|
||||
}
|
||||
|
||||
fn get_sample(&mut self, modulator: f32, event_clock: Clock) -> f32 {
|
||||
fn set_total_level(&mut self, db: f32) {
|
||||
self.envelope.set_total_level(db)
|
||||
}
|
||||
|
||||
fn set_sustain_level(&mut self, level: u16) {
|
||||
self.envelope.set_sustain_level(level)
|
||||
}
|
||||
|
||||
fn set_rate(&mut self, etype: EnvelopeState, rate: usize) {
|
||||
self.envelope.set_rate(etype, rate)
|
||||
}
|
||||
|
||||
fn notify_state_change(&mut self, state: bool, envelope_clock: Clock) {
|
||||
self.envelope.notify_state_change(state, envelope_clock)
|
||||
}
|
||||
|
||||
fn get_sample(&mut self, modulator: f32, envelope_clock: Clock) -> f32 {
|
||||
self.wave.set_frequency((self.frequency * self.multiplier) + modulator);
|
||||
let sample = self.wave.next().unwrap();
|
||||
|
||||
/*
|
||||
let time_since_last = event_clock - self.last_event;
|
||||
match self.envelope_state {
|
||||
EnvelopeState::Attack => {
|
||||
let gain = rate_to_gain(self.attack_rate, time_since_last).min(self.total_level);
|
||||
if gain == self.total_level {
|
||||
self.envelope_state = EnvelopeState::Decay1;
|
||||
}
|
||||
sample / gain
|
||||
},
|
||||
EnvelopeState::Decay1 => {
|
||||
let gain = (self.total_level - rate_to_gain(self.first_decay_rate, time_since_last)).max(self.total_level / 2.0);
|
||||
if gain == self.total_level / 2.0 {
|
||||
self.envelope_state = EnvelopeState::Decay2;
|
||||
}
|
||||
sample / gain
|
||||
},
|
||||
EnvelopeState::Decay2 => {
|
||||
let gain = (self.total_level / 2.0 - rate_to_gain(self.second_decay_rate, time_since_last)).max(0.0);
|
||||
sample / gain
|
||||
},
|
||||
EnvelopeState::Release => {
|
||||
let gain = (self.total_level / 2.0 - rate_to_gain(self.release_rate, time_since_last)).max(0.0);
|
||||
sample / gain
|
||||
},
|
||||
}
|
||||
*/
|
||||
sample
|
||||
self.envelope.update_envelope(envelope_clock);
|
||||
let gain = db_to_gain(self.envelope.get_db_at());
|
||||
//let gain = self.envelope.total_level;
|
||||
|
||||
sample / gain
|
||||
}
|
||||
}
|
||||
|
||||
fn rate_to_gain(rate: usize, event_clock: Clock) -> f32 {
|
||||
event_clock as f32 * rate as f32
|
||||
fn rate_to_gain(rate: usize, envelope_clock: Clock) -> f32 {
|
||||
envelope_clock as f32 * rate as f32
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Channel {
|
||||
debug_name: String,
|
||||
operators: Vec<Operator>,
|
||||
on_state: u8,
|
||||
next_on_state: u8,
|
||||
@ -165,9 +314,10 @@ struct Channel {
|
||||
}
|
||||
|
||||
impl Channel {
|
||||
fn new(sample_rate: usize) -> Self {
|
||||
fn new(debug_name: String, sample_rate: usize) -> Self {
|
||||
Self {
|
||||
operators: vec![Operator::new(sample_rate); 4],
|
||||
debug_name: debug_name.clone(),
|
||||
operators: (0..4).map(|i| Operator::new(format!("{}, op {}", debug_name, i), sample_rate)).collect(),
|
||||
on_state: 0,
|
||||
next_on_state: 0,
|
||||
base_frequency: 0.0,
|
||||
@ -188,67 +338,67 @@ impl Channel {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_sample(&mut self, event_clock: Clock) -> f32 {
|
||||
fn get_sample(&mut self, envelope_clock: Clock) -> f32 {
|
||||
if self.on_state != self.next_on_state {
|
||||
self.on_state = self.next_on_state;
|
||||
for (i, operator) in self.operators.iter_mut().enumerate() {
|
||||
operator.notify_state_change(((self.on_state >> i) & 0x01) != 0, event_clock);
|
||||
operator.notify_state_change(((self.on_state >> i) & 0x01) != 0, envelope_clock);
|
||||
}
|
||||
}
|
||||
|
||||
if self.on_state != 0 {
|
||||
self.get_algorithm_sample(event_clock)
|
||||
self.get_algorithm_sample(envelope_clock)
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
fn get_algorithm_sample(&mut self, event_clock: Clock) -> f32 {
|
||||
fn get_algorithm_sample(&mut self, envelope_clock: Clock) -> f32 {
|
||||
match self.algorithm {
|
||||
OperatorAlgorithm::A0 => {
|
||||
let modulator0 = self.operators[0].get_sample(0.0, event_clock);
|
||||
let modulator1 = self.operators[1].get_sample(modulator0, event_clock);
|
||||
let modulator2 = self.operators[2].get_sample(modulator1, event_clock);
|
||||
self.operators[3].get_sample(modulator2, event_clock)
|
||||
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)
|
||||
},
|
||||
OperatorAlgorithm::A1 => {
|
||||
let sample1 = self.operators[0].get_sample(0.0, event_clock) + self.operators[1].get_sample(0.0, event_clock);
|
||||
let sample2 = self.operators[2].get_sample(sample1, event_clock);
|
||||
self.operators[3].get_sample(sample2, event_clock)
|
||||
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)
|
||||
},
|
||||
OperatorAlgorithm::A2 => {
|
||||
let sample1 = self.operators[1].get_sample(0.0, event_clock);
|
||||
let sample2 = self.operators[2].get_sample(sample1, event_clock);
|
||||
let sample3 = self.operators[0].get_sample(0.0, event_clock) + sample2;
|
||||
self.operators[3].get_sample(sample3, event_clock)
|
||||
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)
|
||||
},
|
||||
OperatorAlgorithm::A3 => {
|
||||
let sample1 = self.operators[0].get_sample(0.0, event_clock);
|
||||
let sample2 = self.operators[1].get_sample(sample1, event_clock);
|
||||
let sample3 = self.operators[2].get_sample(0.0, event_clock);
|
||||
self.operators[3].get_sample(sample2 + sample3, event_clock)
|
||||
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)
|
||||
},
|
||||
OperatorAlgorithm::A4 => {
|
||||
let sample1 = self.operators[0].get_sample(0.0, event_clock);
|
||||
let sample2 = self.operators[1].get_sample(sample1, event_clock);
|
||||
let sample3 = self.operators[2].get_sample(0.0, event_clock);
|
||||
let sample4 = self.operators[3].get_sample(sample3, event_clock);
|
||||
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
|
||||
},
|
||||
OperatorAlgorithm::A5 => {
|
||||
let sample1 = self.operators[0].get_sample(0.0, event_clock);
|
||||
self.operators[1].get_sample(sample1, event_clock) + self.operators[2].get_sample(sample1, event_clock) + self.operators[3].get_sample(sample1, event_clock)
|
||||
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)
|
||||
},
|
||||
OperatorAlgorithm::A6 => {
|
||||
let sample1 = self.operators[0].get_sample(0.0, event_clock);
|
||||
let sample2 = self.operators[1].get_sample(sample1, event_clock);
|
||||
sample2 + self.operators[2].get_sample(0.0, event_clock) + self.operators[3].get_sample(0.0, event_clock)
|
||||
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)
|
||||
},
|
||||
OperatorAlgorithm::A7 => {
|
||||
self.operators[0].get_sample(0.0, event_clock)
|
||||
+ self.operators[1].get_sample(0.0, event_clock)
|
||||
+ self.operators[2].get_sample(0.0, event_clock)
|
||||
+ self.operators[3].get_sample(0.0, event_clock)
|
||||
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)
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -290,7 +440,7 @@ pub struct Ym2612 {
|
||||
selected_reg_1: Option<NonZeroU8>,
|
||||
|
||||
clock_frequency: u32,
|
||||
event_clock_period: ClockElapsed,
|
||||
envelope_clock_period: ClockElapsed,
|
||||
channels: Vec<Channel>,
|
||||
channel_frequencies: [(u8, u16); CHANNELS],
|
||||
dac: Dac,
|
||||
@ -318,8 +468,8 @@ impl Ym2612 {
|
||||
selected_reg_1: None,
|
||||
|
||||
clock_frequency,
|
||||
event_clock_period: 3 * 144 * 1_000_000_000 / clock_frequency as ClockElapsed,
|
||||
channels: vec![Channel::new(sample_rate); 8],
|
||||
envelope_clock_period: 351 * 1_000_000_000 / clock_frequency as ClockElapsed, //3 * 144 * 1_000_000_000 / clock_frequency as ClockElapsed,
|
||||
channels: (0..6).map(|i| Channel::new(format!("ch {}", i), sample_rate)).collect(),
|
||||
channel_frequencies: [(0, 0); CHANNELS],
|
||||
|
||||
dac: Dac::default(),
|
||||
@ -337,7 +487,41 @@ impl Ym2612 {
|
||||
registers: vec![0; 512],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Steppable for Ym2612 {
|
||||
fn step(&mut self, system: &System) -> Result<ClockElapsed, Error> {
|
||||
let rate = self.source.samples_per_second();
|
||||
let available = self.source.space_available();
|
||||
let samples = if available < rate / 1000 { available } else { rate / 1000 };
|
||||
let nanos_per_sample = 1_000_000_000 / rate;
|
||||
|
||||
//if self.source.space_available() >= samples {
|
||||
let mut buffer = vec![0.0; samples];
|
||||
for (i, buffered_sample) in buffer.iter_mut().enumerate().take(samples) {
|
||||
let envelope_clock = (system.clock + (i * nanos_per_sample) as Clock) / self.envelope_clock_period;
|
||||
let mut sample = 0.0;
|
||||
|
||||
for ch in 0..5 {
|
||||
sample += self.channels[ch].get_sample(envelope_clock);
|
||||
}
|
||||
|
||||
if self.dac.enabled {
|
||||
sample += self.dac.get_sample();
|
||||
} else {
|
||||
sample += self.channels[5].get_sample(envelope_clock);
|
||||
}
|
||||
|
||||
*buffered_sample = sample.clamp(-1.0, 1.0);
|
||||
}
|
||||
self.source.write_samples(system.clock, &buffer);
|
||||
//}
|
||||
|
||||
Ok(1_000_000) // Every 1ms of simulated time
|
||||
}
|
||||
}
|
||||
|
||||
impl Ym2612 {
|
||||
pub fn set_register(&mut self, bank: u8, reg: u8, data: u8) {
|
||||
// Keep a copy for debugging purposes, and if the original values are needed
|
||||
self.registers[bank as usize * 256 + reg as usize] = data;
|
||||
@ -359,7 +543,15 @@ impl Ym2612 {
|
||||
},
|
||||
|
||||
0x28 => {
|
||||
let ch = (data as usize) & 0x07;
|
||||
let num = (data as usize) & 0x07;
|
||||
let ch = match num {
|
||||
0 | 1 | 2 => num,
|
||||
4 | 5 | 6 => num - 1,
|
||||
_ => {
|
||||
warn!("{}: attempted key on/off to invalid channel {}", DEV_NAME, num);
|
||||
return;
|
||||
},
|
||||
};
|
||||
self.channels[ch].next_on_state = data >> 4;
|
||||
self.channels[ch].reset();
|
||||
},
|
||||
@ -442,15 +634,16 @@ impl Ym2612 {
|
||||
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 first_decay_level = (self.registers[0x80 + index] & 0x0F) >> 4;
|
||||
let second_decay_rate = self.registers[0x70 + index] & 0x1F;
|
||||
let release_rate = self.registers[0x80 + index] & 0x0F;
|
||||
|
||||
self.channels[ch].operators[op].set_attack_rate(calculate_rate(attack_rate, rate_scaling, keycode));
|
||||
self.channels[ch].operators[op].set_first_decay_rate(calculate_rate(first_decay_rate, rate_scaling, keycode));
|
||||
self.channels[ch].operators[op].set_first_decay_level(calculate_rate(first_decay_level, rate_scaling, keycode));
|
||||
self.channels[ch].operators[op].set_second_decay_rate(calculate_rate(second_decay_rate, rate_scaling, keycode));
|
||||
self.channels[ch].operators[op].set_release_rate(calculate_rate(release_rate, rate_scaling, keycode));
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -469,7 +662,7 @@ fn calculate_rate(rate: u8, rate_scaling: u8, keycode: u8) -> usize {
|
||||
_ => 8, // this shouldn't be possible
|
||||
};
|
||||
|
||||
(2 * rate as usize + (keycode as usize / scale)).min(64)
|
||||
(2 * rate as usize + (keycode as usize / scale)).min(63)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@ -508,39 +701,6 @@ fn get_ch_index(ch: usize) -> usize {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Steppable for Ym2612 {
|
||||
fn step(&mut self, system: &System) -> Result<ClockElapsed, Error> {
|
||||
let rate = self.source.samples_per_second();
|
||||
let available = self.source.space_available();
|
||||
let samples = if available < rate / 1000 { available } else { rate / 1000 };
|
||||
let nanos_per_sample = 1_000_000_000 / rate;
|
||||
|
||||
//if self.source.space_available() >= samples {
|
||||
let mut buffer = vec![0.0; samples];
|
||||
for (i, buffered_sample) in buffer.iter_mut().enumerate().take(samples) {
|
||||
let event_clock = (system.clock + (i * nanos_per_sample) as Clock) / self.event_clock_period;
|
||||
let mut sample = 0.0;
|
||||
|
||||
for ch in 0..6 {
|
||||
sample += self.channels[ch].get_sample(event_clock);
|
||||
}
|
||||
|
||||
if self.dac.enabled {
|
||||
sample += self.dac.get_sample();
|
||||
} else {
|
||||
sample += self.channels[6].get_sample(event_clock);
|
||||
}
|
||||
|
||||
*buffered_sample = sample.clamp(-1.0, 1.0);
|
||||
}
|
||||
self.source.write_samples(system.clock, &buffer);
|
||||
//}
|
||||
|
||||
Ok(1_000_000) // Every 1ms of simulated time
|
||||
}
|
||||
}
|
||||
|
||||
impl Addressable for Ym2612 {
|
||||
fn len(&self) -> usize {
|
||||
0x04
|
||||
|
6
todo.txt
6
todo.txt
@ -1,4 +1,10 @@
|
||||
|
||||
* add a Duration-like clock type that records femto seconds
|
||||
* move parser to utils/ of some kind, or lib/
|
||||
|
||||
* add doc strings everywhere
|
||||
* get rustfmt, rustdoc, and clippy working in some kind of semi-automatic fashion
|
||||
|
||||
* the interrupt controller stuff is really not good. It should be more like busport, and connected to a device at startup (eg. create
|
||||
interrupt controller, then create objects that use that controller and pass in values, maybe an option so that the controller doesn't
|
||||
have to be hooked up, meaning hardware interrupts would not be used.
|
||||
|
Loading…
Reference in New Issue
Block a user