use alloc::boxed::Box;
use alloc::vec;
use alloc::vec::Vec;
use crate::core::instr_robsid::{
ArpMode, ArpPhase, CutoffSweep, ReleaseMode, RobEffects, SkydiveMode, VibratoMode, VoiceProgram,
};
use crate::core::instr_sid::SidVoice;
use super::{SidFx, SidModel, SidRegion, SidSynth, SidVoicePatch};
pub struct SidBank {
synths: Vec<Option<SidSynth>>,
channel_instr: Vec<Option<usize>>,
frame_counter: u32,
speed: u8,
model: SidModel,
region: SidRegion,
output_rate: u32,
}
impl SidBank {
pub fn new(
output_rate: u32,
num_instruments: usize,
num_channels: usize,
speed: u8,
model: SidModel,
region: SidRegion,
) -> Self {
Self {
synths: (0..num_instruments).map(|_| None).collect(),
channel_instr: vec![None; num_channels],
speed: speed.max(1),
frame_counter: u32::MAX,
model,
region,
output_rate: output_rate.max(1),
}
}
pub fn begin_frame(&mut self) {
self.frame_counter = self.frame_counter.wrapping_add(1);
for s in self.synths.iter_mut().flatten() {
s.advance_pw();
}
}
fn synth_mut(&mut self, instr: usize) -> Option<&mut SidSynth> {
let slot = self.synths.get_mut(instr)?;
if slot.is_none() {
*slot = Some(SidSynth::new(self.output_rate, self.model, self.region));
}
slot.as_mut()
}
fn synth_at(&mut self, instr: usize) -> Option<&mut SidSynth> {
self.synths.get_mut(instr).and_then(|o| o.as_mut())
}
#[allow(clippy::too_many_arguments)]
pub fn note_on(
&mut self,
channel: usize,
instr: usize,
voice: &SidVoice,
fx: &RobEffects,
program: Option<&VoiceProgram>,
release_after: u16,
milli_hz: u32,
) {
if let Some(prev) = self.channel_instr.get(channel).copied().flatten() {
if prev != instr {
if let Some(s) = self.synth_at(prev) {
s.note_cut(channel);
}
}
}
let patch = patch_from_voice(voice);
let mut sfx = fx_from_robeffects(fx, program);
sfx.release_after = release_after;
if let Some(s) = self.synth_mut(instr) {
s.note_on(channel, &patch, &sfx, milli_hz);
}
if let Some(slot) = self.channel_instr.get_mut(channel) {
*slot = Some(instr);
}
}
pub fn advance_fx(&mut self, channel: usize) {
let frame = self.frame_counter;
let speed = self.speed;
if let Some(instr) = self.channel_instr.get(channel).copied().flatten() {
if let Some(s) = self.synth_at(instr) {
s.advance_fx(channel, frame, speed);
}
}
}
pub fn set_frequency(&mut self, channel: usize, milli_hz: u32) {
if let Some(instr) = self.channel_instr.get(channel).copied().flatten() {
if let Some(s) = self.synth_at(instr) {
s.set_frequency(channel, milli_hz);
}
}
}
pub fn note_off(&mut self, channel: usize, is_fetch: bool) {
if let Some(instr) = self.channel_instr.get(channel).copied().flatten() {
if let Some(s) = self.synth_at(instr) {
s.note_off(channel, is_fetch);
}
}
}
pub fn note_cut(&mut self, channel: usize) {
if let Some(instr) = self.channel_instr.get(channel).copied().flatten() {
if let Some(s) = self.synth_at(instr) {
s.note_cut(channel);
}
}
if let Some(slot) = self.channel_instr.get_mut(channel) {
*slot = None;
}
}
pub fn any_active(&self) -> bool {
self.synths.iter().flatten().any(|s| s.any_active())
}
pub fn mix_frame(&mut self) -> i32 {
let mut acc = 0i32;
for s in self.synths.iter_mut().flatten() {
if s.any_active() {
let (mix, _voices) = s.clock();
acc += mix as i32;
}
}
acc
}
}
impl SidBank {
#[cfg(feature = "std")]
#[doc(hidden)]
pub fn snapshot(&self) -> [u8; 0x19] {
let mut regs = [0u8; 0x19];
regs[0x18] = 0x0f;
for ch in 0..3 {
if let Some(instr) = self.channel_instr.get(ch).copied().flatten() {
if let Some(s) = self.synths.get(instr).and_then(|o| o.as_ref()) {
if let Some(vr) = s.owner_voice_regs(ch) {
regs[ch * 7..ch * 7 + 7].copy_from_slice(&vr);
}
}
}
}
regs
}
#[cfg(feature = "std")]
#[doc(hidden)]
pub fn capture_frame(&self) {
if CAPTURE_ON.with(|f| f.get()) {
push_capture(self.snapshot());
}
}
}
#[cfg(feature = "std")]
fn push_capture(snap: [u8; 0x19]) {
CAPTURE.with(|c| c.borrow_mut().push(snap));
}
#[cfg(feature = "std")]
thread_local! {
static CAPTURE_ON: core::cell::Cell<bool> = const { core::cell::Cell::new(false) };
static CAPTURE: core::cell::RefCell<Vec<[u8; 0x19]>> = const { core::cell::RefCell::new(Vec::new()) };
}
#[cfg(feature = "std")]
#[doc(hidden)]
pub fn capture_begin() {
CAPTURE.with(|c| c.borrow_mut().clear());
STATE_CAPTURE.with(|c| c.borrow_mut().clear());
CAPTURE_ON.with(|f| f.set(true));
}
#[cfg(feature = "std")]
#[doc(hidden)]
pub fn capture_take() -> Vec<[u8; 0x19]> {
CAPTURE_ON.with(|f| f.set(false));
CAPTURE.with(|c| core::mem::take(&mut *c.borrow_mut()))
}
#[cfg(feature = "std")]
thread_local! {
static STATE_CAPTURE: core::cell::RefCell<Vec<[u8; 21]>> =
const { core::cell::RefCell::new(Vec::new()) };
}
#[cfg(feature = "std")]
#[doc(hidden)]
pub fn state_push(state: [u8; 21]) {
if CAPTURE_ON.with(|f| f.get()) {
STATE_CAPTURE.with(|c| c.borrow_mut().push(state));
}
}
#[cfg(feature = "std")]
#[doc(hidden)]
pub fn state_take() -> Vec<[u8; 21]> {
STATE_CAPTURE.with(|c| core::mem::take(&mut *c.borrow_mut()))
}
pub(crate) fn patch_from_voice(v: &SidVoice) -> SidVoicePatch {
let waveform = (v.ctrl_triangle as u8)
| ((v.ctrl_sawtooth as u8) << 1)
| ((v.ctrl_pulse as u8) << 2)
| ((v.ctrl_noise as u8) << 3);
SidVoicePatch {
waveform,
pulse_width: v.pw & 0x0FFF,
attack_decay: v.ad,
sustain_release: v.sr,
ring: v.ctrl_rm,
sync: v.ctrl_sync,
test: v.ctrl_test,
gate: v.ctrl_gate,
}
}
pub(crate) fn fx_from_robeffects(re: &RobEffects, program: Option<&VoiceProgram>) -> SidFx {
SidFx {
program: program
.map(|p| super::wave_program::ProgramSteps::from_model(p).0)
.unwrap_or_default(),
pw_sweep: re.pulse_sweep.enable || re.pulse_sweep.speed != 0,
pw_speed: re.pulse_sweep.speed,
pw_delay: re.pulse_sweep.delay,
pw_low_byte_inc: re.pulse_sweep.low_byte_mode,
pw_hi_bound: re.pulse_sweep.bounce.map_or(0, |b| b.hi),
pw_lo_bound: re.pulse_sweep.bounce.map_or(0, |b| b.lo),
pw_bounds_set: re.pulse_sweep.bounce.is_some(),
pw_reseed_on_note: re.pulse_sweep.reseed_on_note,
vib_len_gate: matches!(re.vibrato, VibratoMode::Bipolar { .. }),
vibrato: matches!(
re.vibrato,
VibratoMode::Upward { .. } | VibratoMode::Bipolar { .. }
),
vib_depth: match re.vibrato {
VibratoMode::Upward { depth, .. } | VibratoMode::Bipolar { depth, .. } => depth,
_ => 0,
},
vib_div: if let VibratoMode::Bipolar { div, .. } = re.vibrato {
div
} else {
0
},
vib_tempvdif_reg: if let VibratoMode::Upward { reg_step, .. } = re.vibrato {
reg_step
} else {
0
},
arp: matches!(re.arpeggio, ArpMode::Octave),
interp_vib: matches!(re.vibrato, VibratoMode::Semitone { .. }),
interp_half_depth: if let VibratoMode::Semitone { half_depth, .. } = re.vibrato {
half_depth
} else {
0
},
interp_shift: if let VibratoMode::Semitone { shift, .. } = re.vibrato {
shift
} else {
0
},
interp_delay: if let VibratoMode::Semitone { delay, .. } = re.vibrato {
delay
} else {
0
},
interp_flat: if let VibratoMode::Semitone { flat, .. } = re.vibrato {
flat
} else {
0
},
wave_attack_ctrl: re.two_phase.attack_shape.to_ctrl(),
wave_attack_frames: re.two_phase.attack_frames,
wave_attack_note: re.two_phase.attack_note.unwrap_or(0),
wave_attack_note_set: re.two_phase.attack_note.is_some(),
wave_alt_ctrl: re.wave_alt.alt_shape.to_ctrl(),
arp2_reg: if let ArpMode::TwoNoteFixed(r) = re.arpeggio {
r
} else {
0
},
arp_steps: if let ArpMode::Cycle { steps, .. } = re.arpeggio {
steps
} else {
[0; 3]
},
arp_len: if let ArpMode::Cycle { len, .. } = re.arpeggio {
len
} else {
0
},
arp_per_note: matches!(
re.arpeggio,
ArpMode::Cycle {
phase: ArpPhase::PerNote,
..
}
),
arp_follow_climb: matches!(
re.arpeggio,
ArpMode::Cycle {
follow_climb: true,
..
}
),
skydive_climb: re.skydive.enable && matches!(re.skydive.mode, SkydiveMode::Climb),
skydive_add: if let (true, SkydiveMode::Add(a)) = (re.skydive.enable, re.skydive.mode) {
a
} else {
0
},
skydive_when: re.skydive.length_gate,
drum: re.drum.enable,
filter: re.filter.enable,
filter_resfilt: (re.filter.resonance << 4) | re.filter.routing.to_bits(),
filter_step: if let CutoffSweep::Wrap { step } = re.filter.sweep {
step
} else {
0
},
filter_seed: re.filter.cutoff_seed,
filter_bounce: matches!(re.filter.sweep, CutoffSweep::Bounce { .. }),
filter_bounce_step: if let CutoffSweep::Bounce { step, .. } = re.filter.sweep {
step
} else {
0
},
filter_bounce_min: if let CutoffSweep::Bounce { min, .. } = re.filter.sweep {
min
} else {
0
},
filter_bounce_max: if let CutoffSweep::Bounce { max, .. } = re.filter.sweep {
max
} else {
0
},
filter_bounce_up: matches!(re.filter.sweep, CutoffSweep::Bounce { up: true, .. }),
filter_mode: re.filter.mode.to_bits(),
filter_reseed_each_note: re.filter.reseed_each_note,
drum_no_freq_slide: re.drum.no_freq_slide,
zero_adsr_on_note_off: matches!(re.release, ReleaseMode::HardCut),
release_ramp: matches!(re.release, ReleaseMode::Ramp { .. }),
release_ad: if let ReleaseMode::Ramp { ad, .. } = re.release {
ad
} else {
0
},
release_sr: if let ReleaseMode::Ramp { sr, .. } = re.release {
sr
} else {
0
},
release_after: 0,
}
}
pub enum SidDriver {
PerInstrument(SidBank),
Coupled(Box<super::coupled::CoupledSid>),
}
impl SidDriver {
pub fn begin_frame(&mut self) {
match self {
Self::PerInstrument(b) => b.begin_frame(),
Self::Coupled(c) => c.begin_frame(),
}
}
#[allow(clippy::too_many_arguments)]
pub fn note_on(
&mut self,
channel: usize,
instr: usize,
voice: &SidVoice,
fx: &RobEffects,
program: Option<&VoiceProgram>,
release_after: u16,
milli_hz: u32,
) {
match self {
Self::PerInstrument(b) => {
b.note_on(channel, instr, voice, fx, program, release_after, milli_hz)
}
Self::Coupled(c) => {
let patch = patch_from_voice(voice);
let mut sfx = fx_from_robeffects(fx, program);
sfx.release_after = release_after;
c.note_on(channel, instr, &patch, &sfx, milli_hz);
}
}
}
pub fn retie(
&mut self,
channel: usize,
instr: usize,
voice: &SidVoice,
fx: &RobEffects,
program: Option<&VoiceProgram>,
) {
if let Self::Coupled(c) = self {
let patch = patch_from_voice(voice);
let sfx = fx_from_robeffects(fx, program);
c.retie(channel, instr, &patch, &sfx);
}
}
pub fn advance_fx(&mut self, channel: usize) {
match self {
Self::PerInstrument(b) => b.advance_fx(channel),
Self::Coupled(c) => c.advance_fx(channel),
}
}
pub fn set_frequency(&mut self, channel: usize, milli_hz: u32) {
match self {
Self::PerInstrument(b) => b.set_frequency(channel, milli_hz),
Self::Coupled(c) => c.set_frequency(channel, milli_hz),
}
}
pub fn note_off(&mut self, channel: usize, is_fetch: bool) {
match self {
Self::PerInstrument(b) => b.note_off(channel, is_fetch),
Self::Coupled(c) => c.note_off(channel, is_fetch),
}
}
pub fn note_cut(&mut self, channel: usize) {
match self {
Self::PerInstrument(b) => b.note_cut(channel),
Self::Coupled(c) => c.note_cut(channel),
}
}
pub fn any_active(&self) -> bool {
match self {
Self::PerInstrument(b) => b.any_active(),
Self::Coupled(c) => c.any_active(),
}
}
pub fn mix_frame(&mut self) -> i32 {
match self {
Self::PerInstrument(b) => b.mix_frame(),
Self::Coupled(c) => c.mix_frame(),
}
}
pub fn capture_frame(&self) {
#[cfg(feature = "std")]
match self {
Self::PerInstrument(b) => b.capture_frame(),
Self::Coupled(c) => {
if CAPTURE_ON.with(|f| f.get()) {
push_capture(c.snapshot());
}
}
}
}
}