mirror of
https://github.com/transistorfet/moa.git
synced 2024-12-01 09:49:39 +00:00
Added detune to ym2612, and channel enable, even though it's still only mono
This commit is contained in:
parent
b243aa9910
commit
a6e236a762
@ -120,6 +120,41 @@ const RATE_TABLE: &[u16] = &[
|
|||||||
8, 8, 8, 8, 8, 8, 8, 8,
|
8, 8, 8, 8, 8, 8, 8, 8,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const DETUNE_TABLE: &[u8] = &[
|
||||||
|
0, 0, 1, 2,
|
||||||
|
0, 0, 1, 2,
|
||||||
|
0, 0, 1, 2,
|
||||||
|
0, 0, 1, 2,
|
||||||
|
0, 1, 2, 2,
|
||||||
|
0, 1, 2, 3,
|
||||||
|
0, 1, 2, 3,
|
||||||
|
0, 1, 2, 3,
|
||||||
|
0, 1, 2, 4,
|
||||||
|
0, 1, 3, 4,
|
||||||
|
0, 1, 3, 4,
|
||||||
|
0, 1, 3, 5,
|
||||||
|
0, 2, 4, 5,
|
||||||
|
0, 2, 4, 6,
|
||||||
|
0, 2, 4, 6,
|
||||||
|
0, 2, 5, 7,
|
||||||
|
0, 2, 5, 8,
|
||||||
|
0, 3, 6, 8,
|
||||||
|
0, 3, 6, 9,
|
||||||
|
0, 3, 7, 10,
|
||||||
|
0, 4, 8, 11,
|
||||||
|
0, 4, 8, 12,
|
||||||
|
0, 4, 9, 13,
|
||||||
|
0, 5, 10, 14,
|
||||||
|
0, 5, 11, 16,
|
||||||
|
0, 6, 12, 17,
|
||||||
|
0, 6, 13, 19,
|
||||||
|
0, 7, 14, 20,
|
||||||
|
0, 8, 16, 22,
|
||||||
|
0, 8, 16, 22,
|
||||||
|
0, 8, 16, 22,
|
||||||
|
0, 8, 16, 22,
|
||||||
|
];
|
||||||
|
|
||||||
const SIN_TABLE_SIZE: usize = 512;
|
const SIN_TABLE_SIZE: usize = 512;
|
||||||
const POW_TABLE_SIZE: usize = 1 << 13;
|
const POW_TABLE_SIZE: usize = 1 << 13;
|
||||||
|
|
||||||
@ -302,23 +337,35 @@ impl PhaseGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn calculate_phase_increment(&mut self) {
|
fn calculate_phase_increment(&mut self) {
|
||||||
|
// Start with the Fnumber
|
||||||
let increment = self.fnumber as u32;
|
let increment = self.fnumber as u32;
|
||||||
|
|
||||||
|
// Shift according to the block (octave)
|
||||||
let increment = if self.block == 0 {
|
let increment = if self.block == 0 {
|
||||||
increment >> 1
|
increment >> 1
|
||||||
} else {
|
} else {
|
||||||
increment << self.block - 1
|
increment << self.block - 1
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO detune
|
// Apply detune
|
||||||
//let inc =
|
let keycode = get_keycode(self.block, self.fnumber);
|
||||||
|
let sign = self.detune >> 2;
|
||||||
|
let detune_index = (self.detune & 0x03) as usize;
|
||||||
|
let detune = DETUNE_TABLE[keycode * 4 + detune_index] as u32;
|
||||||
|
let increment = if sign == 0 {
|
||||||
|
increment + detune
|
||||||
|
} else {
|
||||||
|
increment - detune
|
||||||
|
}.min(0x1FFFF);
|
||||||
|
|
||||||
|
// Apply multiple
|
||||||
let increment = if self.multiple == 0 {
|
let increment = if self.multiple == 0 {
|
||||||
increment >> 1
|
increment >> 1
|
||||||
} else {
|
} else {
|
||||||
(increment * self.multiple).min(MAX_PHASE)
|
(increment * self.multiple).min(MAX_PHASE)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Cache the value for use later, since it only changes when the input registers are set
|
||||||
self.increment = increment;
|
self.increment = increment;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -329,6 +376,26 @@ impl PhaseGenerator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Map the upper 5 bits of the fnumber to the lower 2 bits of the keycode
|
||||||
|
///
|
||||||
|
/// The upper of the two bits is bit 11 of the fnumber, and the lower bit is follows
|
||||||
|
/// the formula F11 & (F10 | F9 | F8) | !F11 & (F10 & F9 & F8), where the bit numbers
|
||||||
|
/// of the fnumber value start from 1 instead of 0. It's easier to map this with an
|
||||||
|
/// lookup table than to calculate this.
|
||||||
|
///
|
||||||
|
/// K1 = F11
|
||||||
|
/// K0 = F11 & (F10 | F9 | F8) | !F11 & (F10 & F9 & F8)
|
||||||
|
const FNUMBER_TO_KEYCODE: &[u8] = &[
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 1,
|
||||||
|
2, 3, 3, 3, 3, 3, 3, 3,
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Generate the keycode required for detune calculations using the block and fnumber
|
||||||
|
fn get_keycode(block: u8, fnumber: u16) -> usize {
|
||||||
|
((block as usize) << 2) | FNUMBER_TO_KEYCODE[(fnumber as usize) >> 7] as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
enum OperatorAlgorithm {
|
enum OperatorAlgorithm {
|
||||||
A0,
|
A0,
|
||||||
@ -341,7 +408,6 @@ enum OperatorAlgorithm {
|
|||||||
A7,
|
A7,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct Operator {
|
struct Operator {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@ -391,11 +457,11 @@ impl Operator {
|
|||||||
let phase = self.phase.update_phase(fm_clock);
|
let phase = self.phase.update_phase(fm_clock);
|
||||||
|
|
||||||
let mod_phase = phase + modulator;
|
let mod_phase = phase + modulator;
|
||||||
|
//if self.debug_name == "ch 2, op 0" {
|
||||||
let mut output = POW_TABLE[(SIN_TABLE[(mod_phase & 0x1FF) as usize] + envelope) as usize];
|
//println!("{:4x} {:4x} {:4x} {:4x}", phase, self.phase.increment, modulator, envelope);
|
||||||
//if self.debug_name == "ch 3, op 1" {
|
|
||||||
//print!("{:4x}", output);
|
|
||||||
//}
|
//}
|
||||||
|
let mut output = POW_TABLE[(SIN_TABLE[(mod_phase & 0x1FF) as usize] + envelope) as usize];
|
||||||
|
|
||||||
if mod_phase & 0x200 != 0 {
|
if mod_phase & 0x200 != 0 {
|
||||||
output = (output as i16 * -1) as u16
|
output = (output as i16 * -1) as u16
|
||||||
}
|
}
|
||||||
@ -404,36 +470,38 @@ impl Operator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn fnumber_to_frequency(block: u8, fnumber: u16) -> f32 {
|
|
||||||
(fnumber as f32 * 0.0264) * 2_u32.pow(block as u32) as f32
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct Channel {
|
struct Channel {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
debug_name: String,
|
debug_name: String,
|
||||||
|
enabled: (bool, bool),
|
||||||
operators: Vec<Operator>,
|
operators: Vec<Operator>,
|
||||||
|
algorithm: OperatorAlgorithm,
|
||||||
|
|
||||||
key_state: u8,
|
key_state: u8,
|
||||||
next_key_clock: FmClock,
|
next_key_clock: FmClock,
|
||||||
next_key_state: u8,
|
next_key_state: u8,
|
||||||
base_frequency: f32,
|
|
||||||
algorithm: OperatorAlgorithm,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Channel {
|
impl Channel {
|
||||||
fn new(debug_name: String) -> Self {
|
fn new(debug_name: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
debug_name: debug_name.clone(),
|
debug_name: debug_name.clone(),
|
||||||
|
enabled: (true, true),
|
||||||
operators: (0..OPERATORS).map(|i| Operator::new(format!("{}, op {}", debug_name, i))).collect(),
|
operators: (0..OPERATORS).map(|i| Operator::new(format!("{}, op {}", debug_name, i))).collect(),
|
||||||
|
algorithm: OperatorAlgorithm::A0,
|
||||||
|
|
||||||
key_state: 0,
|
key_state: 0,
|
||||||
next_key_clock: 0,
|
next_key_clock: 0,
|
||||||
next_key_state: 0,
|
next_key_state: 0,
|
||||||
base_frequency: 0.0,
|
|
||||||
algorithm: OperatorAlgorithm::A0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_enabled(&mut self, left: bool, right: bool) {
|
||||||
|
self.enabled = (left, right);
|
||||||
|
}
|
||||||
|
|
||||||
fn change_key_state(&mut self, fm_clock: FmClock, key: u8) {
|
fn change_key_state(&mut self, fm_clock: FmClock, key: u8) {
|
||||||
self.next_key_clock = fm_clock;
|
self.next_key_clock = fm_clock;
|
||||||
self.next_key_state = key;
|
self.next_key_state = key;
|
||||||
@ -449,20 +517,25 @@ impl Channel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_sample(&mut self, clocks: (FmClock, EnvelopeClock)) -> f32 {
|
fn get_sample(&mut self, clocks: (FmClock, EnvelopeClock)) -> (f32, f32) {
|
||||||
self.check_key_change(clocks);
|
self.check_key_change(clocks);
|
||||||
let mut output = self.get_algorithm_output(clocks);
|
let output = self.get_algorithm_output(clocks);
|
||||||
|
|
||||||
let output = if output & 0x2000 == 0 {
|
let output = if output & 0x2000 == 0 {
|
||||||
output as i16
|
output as i16
|
||||||
} else {
|
} else {
|
||||||
(output | 0xC000) as i16
|
(output | 0xC000) as i16
|
||||||
};
|
};
|
||||||
//if self.debug_name == "ch 0" {
|
|
||||||
//println!("{}", output);
|
//let output = output * 2 / 3;
|
||||||
|
//if self.debug_name == "ch 2" {
|
||||||
|
//println!("{:6x}", output);
|
||||||
//}
|
//}
|
||||||
let output = output as f32 / (1 << 14) as f32;
|
let output = output as f32 / (1 << 14) as f32;
|
||||||
output
|
|
||||||
|
let left = if self.enabled.0 { output } else { 0.0 };
|
||||||
|
let right = if self.enabled.1 { output } else { 0.0 };
|
||||||
|
(left, right)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_algorithm_output(&mut self, clocks: (FmClock, EnvelopeClock)) -> u16 {
|
fn get_algorithm_output(&mut self, clocks: (FmClock, EnvelopeClock)) -> u16 {
|
||||||
@ -649,11 +722,11 @@ impl Ym2612 {
|
|||||||
let mut sample = 0.0;
|
let mut sample = 0.0;
|
||||||
|
|
||||||
for ch in 0..(CHANNELS - 1) {
|
for ch in 0..(CHANNELS - 1) {
|
||||||
sample += self.channels[ch].get_sample(clocks);
|
sample += self.channels[ch].get_sample(clocks).0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.dac.enabled {
|
if !self.dac.enabled {
|
||||||
sample += self.channels[CHANNELS - 1].get_sample(clocks);
|
sample += self.channels[CHANNELS - 1].get_sample(clocks).0;
|
||||||
}
|
}
|
||||||
|
|
||||||
sample
|
sample
|
||||||
@ -744,6 +817,7 @@ impl Ym2612 {
|
|||||||
|
|
||||||
reg if (0xB0..=0xB2).contains(®) => {
|
reg if (0xB0..=0xB2).contains(®) => {
|
||||||
let ch = get_ch(bank, reg);
|
let ch = get_ch(bank, reg);
|
||||||
|
// TODO add feedback values
|
||||||
self.channels[ch].algorithm = match data & 0x07 {
|
self.channels[ch].algorithm = match data & 0x07 {
|
||||||
0 => OperatorAlgorithm::A0,
|
0 => OperatorAlgorithm::A0,
|
||||||
1 => OperatorAlgorithm::A1,
|
1 => OperatorAlgorithm::A1,
|
||||||
@ -757,6 +831,12 @@ impl Ym2612 {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
reg if (0xB4..=0xB6).contains(®) => {
|
||||||
|
let ch = get_ch(bank, reg - 4);
|
||||||
|
// TODO add AMS and FMS (which only apply to the LFO)
|
||||||
|
self.channels[ch].set_enabled(data & 0x80 != 0, data & 0x40 != 0);
|
||||||
|
},
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
warn!("{}: !!! unhandled write to register {:0x} with {:0x}", DEV_NAME, reg, data);
|
warn!("{}: !!! unhandled write to register {:0x} with {:0x}", DEV_NAME, reg, data);
|
||||||
},
|
},
|
||||||
|
3
todo.txt
3
todo.txt
@ -2,6 +2,9 @@
|
|||||||
* the first 512 entries are 0 for some reason, in the log table, but otherwise seems ok
|
* 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
|
* you need to scale the output sample to be +/- 1.0 instead of 0-1.0
|
||||||
|
|
||||||
|
* AudioFrame (and possibly the mixer and source) should be moved to the core, it should probably have the sample rate
|
||||||
|
* need to be able to support stereo output eventually
|
||||||
|
|
||||||
* can you make the frontend more adaptive to the input that the devices are using
|
* 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
|
* 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
|
* need to re-add a mechanism for audio frame dialation, either based on speed, or somehow automatic, use clocks and make them aligned
|
||||||
|
Loading…
Reference in New Issue
Block a user