use xmrs::prelude::*;
use super::Channel;
#[derive(Clone, Copy, Default, PartialEq, Eq)]
pub(crate) enum SidEdge {
#[default]
None,
Trigger,
KeyOff,
KeyOffFetch,
Cut,
}
#[derive(Clone, Default)]
pub(crate) struct SidChannelState {
pub instr: Option<usize>,
pub pitch: Option<Pitch>,
pub edge: SidEdge,
pub freq_slide_reg: i32,
}
pub(crate) struct SidChannelUpdate {
pub edge: SidEdge,
pub held: Option<SidHeld>,
}
pub(crate) struct SidHeld {
pub instr: usize,
pub milli_hz: u32,
}
#[cfg(feature = "import_sid")]
const PAL_CLOCK_HZ: u64 = 985_248;
impl<'a> Channel<'a> {
fn sid_row_has_note_event(&self) -> bool {
matches!(
self.current.event,
CellEvent::NoteOn { .. }
| CellEvent::NoteOnGhost { .. }
| CellEvent::NoteOff { .. }
| CellEvent::NoteCut
| CellEvent::NoteFade
| CellEvent::InstrReset
)
}
pub(super) fn row_targets_sid(&self) -> Option<usize> {
if !self.sid_row_has_note_event() {
return None;
}
if self.current.has_instrument_column() {
match self.current_track_instrument {
Some(i) => match self.module.instrument.get(i).map(|x| &x.instr_type) {
Some(InstrumentType::RobSid(_)) => Some(i),
_ => None,
},
None => self.sid.instr,
}
} else {
self.sid.instr
}
}
pub(super) fn tick0_sid(&mut self, sid_instr: usize) {
match self.current.event {
CellEvent::NoteOn { pitch, velocity } | CellEvent::NoteOnGhost { pitch, velocity } => {
self.sid.instr = Some(sid_instr);
self.sid.pitch = Some(pitch);
self.sid.edge = SidEdge::Trigger;
self.volume = velocity;
self.struck_this_tick = true;
self.note = super::compose_played_pitch(pitch, PitchDelta::from_q8_8_i16(0));
self.period = self.period_helper.note_to_period(self.note);
self.effective_period = self.period;
}
CellEvent::InstrReset => {
self.sid.instr = Some(sid_instr);
self.sid.edge = SidEdge::Trigger;
self.struck_this_tick = true;
}
CellEvent::NoteOff { .. } | CellEvent::NoteFade => {
self.sid.edge = if self.current.has_instrument_column() {
SidEdge::KeyOffFetch
} else {
SidEdge::KeyOff
};
}
CellEvent::NoteCut => {
self.sid.edge = SidEdge::Cut;
self.sid.instr = None;
}
CellEvent::None => {}
}
}
pub(super) fn sid_clear_for_sample(&mut self) {
if self.sid_row_has_note_event() && self.sid.instr.is_some() {
self.sid.edge = SidEdge::Cut;
self.sid.instr = None;
self.sid.pitch = None;
}
}
pub(crate) fn take_sid_update(&mut self) -> Option<SidChannelUpdate> {
let edge = core::mem::take(&mut self.sid.edge);
if edge == SidEdge::Trigger {
self.sid.freq_slide_reg = 0;
} else {
self.sid_advance_freq_slide();
}
let held = match (self.sid.instr, self.sid.pitch) {
(Some(i), Some(_)) => match self.module.instrument.get(i).map(|x| &x.instr_type) {
Some(InstrumentType::RobSid(_)) => Some(SidHeld {
instr: i,
milli_hz: self.sid_current_milli_hz(),
}),
_ => None,
},
_ => None,
};
if edge == SidEdge::None && held.is_none() {
None
} else {
Some(SidChannelUpdate { edge, held })
}
}
fn sid_current_milli_hz(&self) -> u32 {
let mut period = self.period;
if matches!(
self.period_helper.freq_type,
FrequencyType::AmigaFrequencies
) {
period = period.saturating_add_signed(self.vibrato_value_raw());
}
let freq = self.period_helper.period_to_frequency(period);
let freq_hz_q8 = freq.raw_q24_8() as u64; let base = ((freq_hz_q8 * 261_625) / (256 * 8363)) as u32;
if self.sid.freq_slide_reg == 0 {
return base;
}
let base_reg = (((base as u64) << 24) / (PAL_CLOCK_HZ * 1000)) as i64;
let swept = (base_reg + self.sid.freq_slide_reg as i64).rem_euclid(0x1_0000);
(((swept as u64) * PAL_CLOCK_HZ * 1000) >> 24) as u32
}
#[cfg(feature = "import_sid")]
fn sid_advance_freq_slide(&mut self) {
use xmrs::core::daw::automation::AutomationTarget;
let abs_tick = self.current_abs_tick;
let Some(track_idx) = self.lookup_track_idx(abs_tick, self.current_song_idx) else {
return;
};
let tgt = AutomationTarget::TrackPitch(track_idx);
let Some(state) = self
.module
.lanes_for(tgt, self.current_song_idx)
.find_map(|l| l.slide_state_at(abs_tick))
else {
return;
};
if !state.armed {
return;
}
let rate = state.rate.raw(); if rate == 0 {
return;
}
let reg_delta = ((rate.unsigned_abs() as i64) << 4) * if rate < 0 { 1 } else { -1 };
self.sid.freq_slide_reg = (self.sid.freq_slide_reg as i64 + reg_delta)
.clamp(i32::MIN as i64, i32::MAX as i64) as i32;
}
}