use super::tables::{EXP_ROM, KSL, KSL_SHIFT, LOGSIN_ROM, MUL};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum EgState {
Off,
Attack,
Decay,
Sustain,
Release,
}
const MAX_ATT_INDEX: i32 = 511;
const fn spread8(count: u32, phase: u32) -> u8 {
(((count * (phase + 1)) >> 3) - ((count * phase) >> 3)) as u8
}
const EG_INC: [u8; 15 * 8] = {
let mut t = [0u8; 15 * 8];
let mut row = 0usize;
while row < 15 {
let mut p = 0u32;
while p < 8 {
let v = if row < 4 {
spread8(4 + row as u32, p) } else if row < 8 {
spread8(2 * (4 + (row as u32 - 4)), p) } else if row < 12 {
2 * spread8(2 * (4 + (row as u32 - 8)), p) } else if row == 12 {
4 } else if row == 13 {
8 } else {
0 };
t[row * 8 + p as usize] = v;
p += 1;
}
row += 1;
}
t
};
pub(crate) struct Operator {
phase_inc: u32,
mul: u8,
waveform: u8,
tl_eg: u16,
ksl_reg: u8,
ksr: bool,
am: bool,
vib: bool,
sustaining: bool,
attack_rate: u8,
decay_rate: u8,
sustain_level_eg: u16, release_rate: u8,
fnum: u16,
block: u8,
phase: u32,
eg_state: EgState,
volume: i32, prev_out: i32,
out: i32,
}
impl Operator {
pub(crate) fn new() -> Self {
Self {
phase_inc: 0,
mul: 0,
waveform: 0,
tl_eg: 0,
ksl_reg: 0,
ksr: false,
am: false,
vib: false,
sustaining: false,
attack_rate: 0,
decay_rate: 0,
sustain_level_eg: 0,
release_rate: 0,
fnum: 0,
block: 0,
phase: 0,
eg_state: EgState::Off,
volume: MAX_ATT_INDEX,
prev_out: 0,
out: 0,
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn set_patch(
&mut self,
mul: u8,
waveform: u8,
tl_reg: u8,
ksl_reg: u8,
ksr: bool,
sustaining: bool,
attack_rate: u8,
decay_rate: u8,
sustain_reg: u8,
release_rate: u8,
am: bool,
vib: bool,
) {
self.mul = mul & 0x0F;
self.waveform = waveform & 0x07;
self.tl_eg = ((tl_reg & 0x3F) as u16) << 2;
self.ksl_reg = ksl_reg & 0x03;
self.ksr = ksr;
self.am = am;
self.vib = vib;
self.sustaining = sustaining;
self.attack_rate = attack_rate & 0x0F;
self.decay_rate = decay_rate & 0x0F;
self.sustain_level_eg = if sustain_reg >= 0x0F {
0x1F0
} else {
((sustain_reg & 0x0F) as u16) << 4
};
self.release_rate = release_rate & 0x0F;
}
pub(crate) fn set_frequency(&mut self, fnum: u16, block: u8, rate_scale_q16: u32) {
self.fnum = fnum & 0x3FF;
self.block = block & 0x07;
let base = ((self.fnum as u32) << self.block) >> 1;
let native = (base * MUL[self.mul as usize]) >> 1;
self.phase_inc = ((native as u64 * rate_scale_q16 as u64) >> 16) as u32;
}
pub(crate) fn set_total_level(&mut self, tl_reg: u8) {
self.tl_eg = ((tl_reg & 0x3F) as u16) << 2;
}
pub(crate) fn write_reg20(&mut self, val: u8) {
self.am = val & 0x80 != 0;
self.vib = val & 0x40 != 0;
self.sustaining = val & 0x20 != 0;
self.ksr = val & 0x10 != 0;
self.mul = val & 0x0F;
self.set_frequency(self.fnum, self.block, 1 << 16);
}
pub(crate) fn write_reg40(&mut self, val: u8) {
self.ksl_reg = val >> 6;
self.tl_eg = ((val & 0x3F) as u16) << 2;
}
pub(crate) fn write_reg60(&mut self, val: u8) {
self.attack_rate = val >> 4;
self.decay_rate = val & 0x0F;
}
pub(crate) fn write_reg80(&mut self, val: u8) {
let sustain_reg = val >> 4;
self.sustain_level_eg = if sustain_reg >= 0x0F {
0x1F0
} else {
(sustain_reg as u16) << 4
};
self.release_rate = val & 0x0F;
}
pub(crate) fn write_reg_e0(&mut self, val: u8) {
self.waveform = val & 0x07;
}
pub(crate) fn key_on(&mut self) {
self.eg_state = EgState::Attack;
self.phase = 0;
}
pub(crate) fn key_off(&mut self) {
if self.eg_state != EgState::Off {
self.eg_state = EgState::Release;
}
}
pub(crate) fn is_silent(&self) -> bool {
self.eg_state == EgState::Off
|| (self.eg_state == EgState::Release && self.volume >= MAX_ATT_INDEX)
}
fn ksr_offset(&self) -> u8 {
let ksn = (self.block << 1) | ((self.fnum >> 9) & 1) as u8; if self.ksr {
ksn
} else {
ksn >> 2
}
}
fn eg_params(reg: u8, ksr: u8, attack: bool) -> (u32, usize) {
if reg == 0 {
return (0, 14 * 8); }
let idx = 16 + ((reg as usize) << 2) + ksr as usize;
if attack && idx >= 16 + 60 {
return (0, 13 * 8); }
let r = idx - 16;
if r < 52 {
((12 - (r >> 2)) as u32, (r & 3) * 8) } else if r < 56 {
(0, (4 + (r & 3)) * 8) } else if r < 60 {
(0, (8 + (r & 3)) * 8) } else {
(0, 12 * 8) }
}
fn tick_envelope(&mut self, eg_cnt: u32) {
let ksr = self.ksr_offset();
match self.eg_state {
EgState::Off | EgState::Sustain => {}
EgState::Attack => {
let (sh, sel) = Self::eg_params(self.attack_rate, ksr, true);
if eg_cnt & ((1 << sh) - 1) == 0 {
let inc = EG_INC[sel + ((eg_cnt >> sh) & 7) as usize] as i32;
self.volume += (!self.volume * inc) >> 3;
if self.volume <= 0 {
self.volume = 0;
self.eg_state = EgState::Decay;
}
}
}
EgState::Decay => {
let (sh, sel) = Self::eg_params(self.decay_rate, ksr, false);
if eg_cnt & ((1 << sh) - 1) == 0 {
self.volume += EG_INC[sel + ((eg_cnt >> sh) & 7) as usize] as i32;
if self.volume >= self.sustain_level_eg as i32 {
self.eg_state = if self.sustaining {
EgState::Sustain
} else {
EgState::Release
};
}
}
}
EgState::Release => {
let (sh, sel) = Self::eg_params(self.release_rate, ksr, false);
if eg_cnt & ((1 << sh) - 1) == 0 {
self.volume += EG_INC[sel + ((eg_cnt >> sh) & 7) as usize] as i32;
if self.volume >= MAX_ATT_INDEX {
self.volume = MAX_ATT_INDEX;
self.eg_state = EgState::Off;
}
}
}
}
}
fn ksl_attenuation(&self) -> u16 {
if self.ksl_reg == 0 {
return 0;
}
let base = (KSL[(self.fnum >> 6) as usize] as i32) << 2;
let level = base - ((8 - self.block as i32) << 5);
if level <= 0 {
0
} else {
(level >> KSL_SHIFT[self.ksl_reg as usize]) as u16
}
}
fn attenuation_eg(&self, tremolo: u16) -> u32 {
let env = self.volume.max(0) as u32;
let trem = if self.am { tremolo as u32 } else { 0 };
env + self.tl_eg as u32 + self.ksl_attenuation() as u32 + trem
}
fn vib_delta(&self, vibpos: u8) -> i32 {
if !self.vib {
return 0;
}
let mut range = ((self.fnum >> 7) & 7) as i32;
if vibpos & 3 == 0 {
range = 0;
} else if vibpos & 1 == 1 {
range >>= 1;
}
if vibpos & 4 != 0 {
range = -range;
}
(((range << self.block) >> 1) * MUL[self.mul as usize] as i32) >> 1
}
pub(crate) fn next(&mut self, mod_input: i32, tremolo: u16, vibpos: u8, eg_cnt: u32) -> i32 {
self.advance(vibpos, eg_cnt);
let phase10 = ((self.phase >> 9) as i32).wrapping_add(mod_input) as u32 & 0x3FF;
self.output_at(phase10, tremolo)
}
pub(crate) fn advance(&mut self, vibpos: u8, eg_cnt: u32) {
self.tick_envelope(eg_cnt);
let inc = (self.phase_inc as i32).wrapping_add(self.vib_delta(vibpos));
self.phase = self.phase.wrapping_add(inc as u32);
}
pub(crate) fn output_at(&mut self, phase10: u32, tremolo: u16) -> i32 {
let att = self.attenuation_eg(tremolo);
let out = waveform_output(self.waveform, phase10 & 0x3FF, att);
self.prev_out = self.out;
self.out = out;
out
}
pub(crate) fn phase_out(&self) -> u32 {
self.phase >> 9
}
pub(crate) fn feedback_phase(&self, fb_reg: u8) -> i32 {
if fb_reg == 0 {
0
} else {
(self.prev_out + self.out) >> (9 - fb_reg as i32).max(0)
}
}
}
fn waveform_output(waveform: u8, phase10: u32, att_eg: u32) -> i32 {
let p = phase10 & 0x3FF;
let (idx, neg, mute) = match waveform & 0x07 {
0 => {
let quad = (p >> 8) & 3;
let idx = if quad & 1 != 0 {
255 - (p & 0xFF)
} else {
p & 0xFF
};
(idx, quad & 2 != 0, false)
}
1 => {
if p & 0x200 != 0 {
(0, false, true)
} else {
let idx = if p & 0x100 != 0 {
255 - (p & 0xFF)
} else {
p & 0xFF
};
(idx, false, false)
}
}
2 | 6 => {
let pp = p & 0x1FF;
let idx = if pp & 0x100 != 0 {
255 - (pp & 0xFF)
} else {
pp & 0xFF
};
(idx, false, false)
}
3 | 7 => {
let pp = p & 0x1FF;
if pp & 0x100 != 0 {
(0, false, true)
} else {
(pp & 0xFF, false, false)
}
}
_ => {
let quad = (p >> 8) & 3;
let idx = if quad & 1 != 0 {
255 - (p & 0xFF)
} else {
p & 0xFF
};
(idx, quad & 2 != 0, false)
}
};
if mute {
return 0;
}
let total = LOGSIN_ROM[idx as usize] as u32 + (att_eg << 3);
let shift = total >> 8;
let mag = if shift >= 13 {
0
} else {
(((EXP_ROM[(total & 0xFF) as usize] as u32) << 2) >> shift) as i32
};
if neg {
-mag
} else {
mag
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn eg_inc_counts_match_rate_law() {
let row_sum = |r: usize| -> u32 { (0..8).map(|p| EG_INC[r * 8 + p] as u32).sum() };
for lo in 0..4 {
assert_eq!(row_sum(lo), 4 + lo as u32, "row {lo}");
}
for lo in 0..4 {
assert_eq!(row_sum(4 + lo), 2 * (4 + lo as u32), "row {}", 4 + lo);
}
for lo in 0..4 {
assert_eq!(row_sum(8 + lo), 4 * (4 + lo as u32), "row {}", 8 + lo);
}
assert_eq!(row_sum(12), 32); assert_eq!(row_sum(13), 64); assert_eq!(row_sum(14), 0); for &v in EG_INC.iter() {
assert!(matches!(v, 0 | 1 | 2 | 4 | 8), "illegal step {v}");
}
}
}