use crate::element::{
AmpContext, PairRole, PartialArena, Wavegen, lookup_timbre,
};
use crate::midi::{Event, Message};
use crate::param::{PartialParam, TimbreParam};
use crate::reverb::{self, Reverb};
use crate::{CM32L, ControlCommand, ControlError, Frame, Synth};
use alloc::boxed::Box;
fn distort_sample(sample: i16) -> i32 {
if (sample & 0x2000) == 0 {
(sample & 0x1fff) as i32
} else {
(sample | !0x1fff) as i32
}
}
fn get_pan_factor(pan: i32) -> i32 {
if pan == 0 {
0
} else {
((pan as i64 * 8192 + 7) / 14) as i32
}
}
impl Synth for CM32L {
fn current_time(&self) -> u32 {
self.time
}
fn play_msg_at(&mut self, msg: u32, mut time: u32) -> bool {
match Message::try_from(msg) {
Ok(parsed) => {
let msg_len: u32 = if (msg & 0xE0) == 0xC0 { 2 } else { 3 };
let transfer_time = (msg_len * 32000 * 8) / 31250;
let last = self.last_midi_timestamp as i32;
if (time as i32).wrapping_sub(last) < 0 {
time = self.last_midi_timestamp;
}
time = time.wrapping_add(transfer_time);
self.last_midi_timestamp = time;
if self.midi_queue.push(Event { msg: parsed, time }) {
true
} else {
warn!("Unable to enqueue message");
false
}
}
Err(_e) => {
warn!("Ignoring message: {}", _e);
true
}
}
}
fn play_sysex_at(&mut self, sysex: &[u8], time: u32) -> bool {
let msg = Message::sysex(Box::from(sysex));
let event = Event { msg, time };
self.midi_queue.push(event)
}
fn render(&mut self, out: &mut [Frame]) {
let total = out.len() as u32;
let mut produced = 0u32;
while produced < total {
if self.free_partials.is_aborting_poly() {
trace!("render waiting for abort");
let start = produced as usize;
let slice = &mut out[start..start + 1];
self.fill_frames(slice);
self.time = self.time.wrapping_add(1);
produced = produced.wrapping_add(1);
continue;
}
if let Some(ev) = self.midi_queue.peek() {
if ev.time <= self.time {
let msg = ev.msg.clone();
self.process_midi(msg);
if !self.free_partials.is_aborting_poly() {
self.midi_queue.pop();
}
let start = produced as usize;
let slice = &mut out[start..start + 1];
self.fill_frames(slice);
self.time = self.time.wrapping_add(1);
produced = produced.wrapping_add(1);
continue;
}
}
let chunk = match self.midi_queue.peek() {
Some(ev) => {
let until_next = ev.time.saturating_sub(self.time);
until_next.min(total - produced)
}
None => total - produced,
};
if chunk > 0 {
let start = produced as usize;
let end = (produced + chunk) as usize;
let slice = &mut out[start..end];
self.fill_frames(slice);
self.time = self.time.wrapping_add(chunk);
produced = produced.wrapping_add(chunk);
continue;
}
}
}
fn apply_command(
&mut self,
command: ControlCommand,
) -> Result<(), ControlError> {
self.apply_command_inner(command)
}
}
impl CM32L {
fn fill_frames(&mut self, out: &mut [Frame]) {
let out_len = out.len();
let _span = debug_span!("fill_frames", frame_count = out_len);
let mut pos = 0;
while pos < out_len {
let chunk_len = (out_len - pos).min(reverb::MAX_BUFFER_LEN);
let chunk = &mut out[pos..pos + chunk_len];
self.reverb.prepare(chunk_len);
for i in 0..32 {
let partial = match &self.free_partials.partials[i] {
Some(p) => p,
None => continue,
};
let part = &self.parts.parts[partial.config.part_idx];
let (amp_ctx, modulation, pitch_bend) =
(&part.amp_ctx, part.modulation, part.pitch_bend);
let timbre = lookup_timbre(&partial.config, &self.mem);
Self::render_partial(
&mut self.free_partials,
i,
amp_ctx,
&mut self.reverb,
self.rom.pcm(),
modulation,
pitch_bend,
self.master_tune_pitch_delta,
timbre,
);
}
self.reverb.apply_la32_output();
self.reverb.process();
self.reverb.mix_output(chunk);
for frame in chunk.iter_mut() {
frame.0 = self.lpf_left.process(frame.0);
frame.1 = self.lpf_right.process(frame.1);
}
pos += chunk_len;
}
self.free_partials
.cleanup_finished_polys(&mut self.free_polys, &mut self.parts);
self.free_partials
.check_aborting_poly_done(&mut self.free_polys, &mut self.parts);
}
fn process_partial_sample(
partials: &mut PartialArena,
idx: usize,
amp_ctx: &AmpContext,
pcm_rom: &[i16],
modulation: u8,
pitch_bend: i32,
master_tune: i32,
use_interpolated: bool,
pp: &PartialParam,
) -> Option<i16> {
{
let partial = partials.partials[idx].as_ref()?;
let dead = partial.tva.is_dead();
let pcm_ex = matches!(
&partial.wavegen,
Wavegen::Pcm(pcm) if pcm.is_exhausted()
);
if dead || pcm_ex {
let _part = partial.config.part_idx;
debug!(idx, _part, dead, pcm_ex, "partial deactivated");
partials.deactivate(idx);
return None;
}
}
let Some(partial) = partials.partials[idx].as_mut() else {
return None;
};
let (pitch, tvp_timer_fired) =
partial.tvp.next_pitch(modulation, pitch_bend, master_tune);
if tvp_timer_fired {
partial.tva.recalc_sustain(amp_ctx, &pp.tva);
}
let amp = partial.tva.next_amplitude(amp_ctx, &pp.tva);
let cutoff = match &partial.wavegen {
Wavegen::Pcm(_) => 0,
Wavegen::Oscillator(_) => partial.tvf.next_cutoff(&pp.tvf),
};
let amp_val = 67117056u32.saturating_sub(amp);
let sample = match &mut partial.wavegen {
Wavegen::Pcm(pcm) => {
let sample = if use_interpolated {
pcm.get_interpolated_sample(pcm_rom, amp_val)
} else {
pcm.get_first_sample(pcm_rom, amp_val)
};
pcm.advance(pitch);
if pcm.is_exhausted() { 0 } else { sample }
}
Wavegen::Oscillator(synth) => {
synth.generate_samples(amp_val, pitch, cutoff);
synth.get_output_sample()
}
};
trace!(sample, amp_val, amp, pitch, "partial sample");
Some(sample)
}
fn render_partial(
partials: &mut PartialArena,
partial_idx: usize,
amp_ctx: &AmpContext,
reverb: &mut Reverb,
pcm_rom: &[i16],
modulation: u8,
pitch_bend: i32,
master_tune: i32,
timbre: &TimbreParam,
) {
let partial = match &partials.partials[partial_idx] {
Some(p) => p,
None => return,
};
let pair_role = partial.config.pair_role;
let _span = debug_span!("partial", partial_idx, ?pair_role);
if matches!(pair_role, PairRole::RingSlave { .. }) {
return;
}
let use_reverb = partial.config.reverb;
let panpot = partial.config.panpot;
let owner_poly_idx = partial.config.owner_poly_idx;
let master_slot = partial.config.timbre_slot;
let ring_mod_slave = match pair_role {
PairRole::RingMaster {
slave_idx,
ring_only,
} if partials.partials[slave_idx]
.as_ref()
.is_some_and(|s| s.config.owner_poly_idx == owner_poly_idx) =>
{
partials.partials[slave_idx].as_ref().map(|slave| {
(slave_idx, ring_only, slave.config.timbre_slot)
})
}
_ => None,
};
let (buf_left, buf_right) = if use_reverb {
reverb.dry_bufs()
} else {
reverb.clean_bufs()
};
let left_pan_factor = get_pan_factor(panpot);
let right_pan_factor = get_pan_factor(14 - panpot);
let master_pp = &timbre.partials[master_slot];
for i in 0..buf_left.len() {
let master_sample = match Self::process_partial_sample(
partials,
partial_idx,
amp_ctx,
pcm_rom,
modulation,
pitch_bend,
master_tune,
true,
master_pp,
) {
Some(s) => s,
None => {
if let Some((slave_idx, _, _)) = ring_mod_slave {
debug!(
slave_idx,
"deactivating ring mod slave with master"
);
partials.deactivate(slave_idx);
}
break;
}
};
let slave_sample = if let Some((slave_idx, ring_only, slave_slot)) =
ring_mod_slave
{
let slave_pp = &timbre.partials[slave_slot];
match Self::process_partial_sample(
partials,
slave_idx,
amp_ctx,
pcm_rom,
modulation,
pitch_bend,
master_tune,
false,
slave_pp,
) {
Some(s) => {
let slave_died = partials.partials[slave_idx]
.as_ref()
.map_or(false, |p| p.tva.is_dead());
if slave_died {
debug!(
slave_idx,
ring_only, "ring mod slave TVA died mid-sample"
);
partials.deactivate(slave_idx);
if ring_only {
partials.deactivate(partial_idx);
break;
}
None
} else {
Some((s, ring_only))
}
}
None => {
if ring_only {
partials.deactivate(partial_idx);
break;
}
None
}
}
} else {
None
};
let sample = if let Some((slave_sample, ring_only)) = slave_sample {
let mod_master = distort_sample(master_sample);
let mod_slave = distort_sample(slave_sample);
let ring_mod_sample = ((mod_master * mod_slave) >> 13) as i16;
if ring_only {
ring_mod_sample
} else {
master_sample.saturating_add(ring_mod_sample)
}
} else {
master_sample
};
let scaled = sample as i32;
let left_sample = (scaled * left_pan_factor) >> 13;
let right_sample = (scaled * right_pan_factor) >> 13;
buf_left[i] = buf_left[i].saturating_add(left_sample as i16);
buf_right[i] = buf_right[i].saturating_add(right_sample as i16);
}
}
}
#[cfg(test)]
mod tests {
use crate::rom::Rom;
use crate::{CM32L, Frame, Synth};
use alloc::vec;
#[cfg(feature = "bundle-rom")]
fn new_synth() -> CM32L {
CM32L::new(Rom::bundled())
}
#[cfg(not(feature = "bundle-rom"))]
fn new_synth() -> CM32L {
use crate::rom::{ControlArray, PcmArray};
const CTRL: &ControlArray = include_bytes!("../rom/CM32L_CONTROL.ROM");
const PCM_RAW: &PcmArray = include_bytes!("../rom/CM32L_PCM.ROM");
CM32L::new(Rom::new(CTRL, PCM_RAW).unwrap())
}
#[test]
fn render_across_u32_overflow_no_events() {
let mut s = new_synth();
s.time = u32::MAX - 500;
s.last_midi_timestamp = s.time;
let mut buf = vec![Frame(0, 0); 1024];
s.render(&mut buf);
assert_eq!(s.current_time(), 523);
s.render(&mut buf);
assert_eq!(s.current_time(), 1547);
}
#[test]
fn render_across_u32_overflow_with_event() {
let mut s = new_synth();
s.time = u32::MAX - 500;
s.last_midi_timestamp = s.time;
let note_on = 0x7f3899; s.play_msg_at(note_on, u32::MAX - 200);
let mut buf = vec![Frame(0, 0); 1024];
s.render(&mut buf);
assert_eq!(s.current_time(), 523);
let has_audio = buf.iter().any(|&Frame(l, r)| l != 0 || r != 0);
assert!(has_audio);
}
}