use super::{
shift_semitones_down, shift_semitones_up, SidFx, CTRL_GATE, SEMI_DEN, SEMI_NUM, VIB_GATE_FRAMES,
};
#[inline]
fn shift_semitones_signed(base: u32, semis: i8) -> u32 {
if semis >= 0 {
shift_semitones_up(base, semis as u8)
} else {
shift_semitones_down(base, (-(semis as i16)) as u8)
}
}
pub(crate) struct FxState {
pub base_milli_hz: u32,
pub gated: bool,
pub note_frame: u32,
pub drum_hi: u8,
pub gate_off_ctrl: u8,
pub skydive_semis: u8,
pub interp_ctr: u8,
pub interp_up: bool,
}
#[inline]
pub(crate) fn freq_reg(milli_hz: u32, clock_hz: u32) -> u16 {
(((milli_hz as u64) << 24) / (clock_hz as u64 * 1000)).min(65535) as u16
}
pub(crate) fn modulate(
s: &mut FxState,
fx: &SidFx,
frame: u32,
speed: u8,
clock_hz: u32,
) -> (u16, Option<u8>) {
let base = s.base_milli_hz;
let gated = s.gated;
let note_frame = s.note_frame;
let drum_hi0 = s.drum_hi;
let gate_off_ctrl = s.gate_off_ctrl;
let skydive_semis = s.skydive_semis;
let mut reg = freq_reg(base, clock_hz);
let vib_ok = !fx.vib_len_gate || (gated && note_frame >= VIB_GATE_FRAMES);
if fx.vibrato && fx.vib_depth != 0 && vib_ok && fx.vib_len_gate {
let depth = fx.vib_depth as i64;
let period = (2 * depth) as u32;
let p = (frame % period) as i64;
let phase = if p <= depth { p } else { period as i64 - p };
let semitone = (base as i64) * SEMI_NUM / SEMI_DEN;
let step = semitone >> (fx.vib_div as u32);
let delta = (phase - depth / 2) * step;
reg = freq_reg((base as i64 + delta).max(0) as u32, clock_hz);
} else if fx.vibrato && fx.vib_depth != 0 && vib_ok {
let mut osc = (frame & 7) as i64;
if osc > 3 {
osc ^= 7;
}
if fx.vib_tempvdif_reg != 0 {
let base_reg = freq_reg(base, clock_hz) as i64;
reg = (base_reg + fx.vib_tempvdif_reg as i64 * osc).clamp(0, 0xFFFF) as u16;
} else {
let shift = ((fx.vib_depth as u32) >> 2).min(8) + 1;
let semitone = (base as i64) * SEMI_NUM / SEMI_DEN;
let delta = (semitone >> shift) * osc;
reg = freq_reg((base as i64 + delta).max(0) as u32, clock_hz);
}
}
let mut ctrl_override = None;
let mut new_drum_hi = drum_hi0;
if fx.drum && gated && drum_hi0 != 0 {
if note_frame < speed as u32 {
if !fx.drum_no_freq_slide {
reg = (reg & 0x00FF) | ((drum_hi0 as u16) << 8);
}
ctrl_override = Some(0x80);
} else {
if !fx.drum_no_freq_slide {
new_drum_hi = drum_hi0 - 1;
reg = (reg & 0x00FF) | ((drum_hi0 as u16) << 8);
}
ctrl_override = Some(if gate_off_ctrl == 0 {
0x80
} else {
gate_off_ctrl
});
}
}
if fx.skydive_add != 0
&& (gated || fx.skydive_when == 0)
&& (frame & 1 != 0)
&& new_drum_hi != 0
{
reg = (reg & 0x00FF) | ((new_drum_hi as u16) << 8);
let delta = (fx.skydive_add as i32) >> 8;
new_drum_hi = (new_drum_hi as i32 + delta).clamp(0, 255) as u8;
}
if fx.skydive_climb && skydive_semis != 0 {
reg = freq_reg(shift_semitones_up(base, skydive_semis), clock_hz);
}
if fx.interp_vib && fx.interp_half_depth > 0 {
let base_reg = freq_reg(base, clock_hz) as i32;
let down1 = freq_reg(shift_semitones_down(base, 1), clock_hz) as i32;
let step = (base_reg - down1) >> fx.interp_shift;
let half = (fx.interp_half_depth >> 1) as i32;
let ctr = s.interp_ctr as i32;
reg = (base_reg - half * step + ctr * step).clamp(0, 0xFFFF) as u16;
let depth = fx.interp_half_depth;
if s.interp_up {
if s.interp_ctr >= depth {
s.interp_up = false;
s.interp_ctr = depth.saturating_sub(1);
} else {
s.interp_ctr += 1;
}
} else if s.interp_ctr == 0 {
s.interp_up = true;
s.interp_ctr = 1;
} else {
s.interp_ctr -= 1;
}
}
if fx.arp {
let n = if frame & 1 == 0 {
base
} else {
base.saturating_mul(2)
};
reg = freq_reg(n, clock_hz);
}
if fx.arp3 {
let off = [0i8, fx.arp3_off_a, fx.arp3_off_b][(frame % 3) as usize];
reg = freq_reg(shift_semitones_signed(base, off), clock_hz);
}
if fx.arp2_reg != 0 && note_frame & 1 == 0 {
reg = fx.arp2_reg;
}
if fx.alt_arp_semitones != 0 {
let climbed = if fx.skydive_climb && skydive_semis != 0 {
shift_semitones_up(base, skydive_semis)
} else {
base
};
reg = if frame & 1 == 0 {
freq_reg(
shift_semitones_down(climbed, fx.alt_arp_semitones),
clock_hz,
)
} else {
freq_reg(climbed, clock_hz)
};
}
if fx.wave_alt_ctrl != 0 && gated {
ctrl_override = Some(if note_frame & 1 == 1 {
gate_off_ctrl | CTRL_GATE
} else {
fx.wave_alt_ctrl
});
}
if new_drum_hi != drum_hi0 {
s.drum_hi = new_drum_hi;
}
(reg, ctrl_override)
}