A port attempt of WaveSabre (by logicoma) to Rust.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

472 lines
16 KiB

use crate::parameters::*;
use crate::helpers::SignalIOParams;
use crate::helpers;
const MAX_DEV_PARAMS : usize = 9;
#[derive(Debug, PartialEq, Copy, Clone)]
enum EventType {
None,
NoteOn,
NoteOff,
}
#[derive(Debug, PartialEq, Copy, Clone)]
struct Event {
typ: EventType,
delta_samples: i32,
note: i32,
velocity: i32,
}
impl Event {
fn new() -> Event {
Event {
typ: EventType::None,
delta_samples: 0,
note: 0,
velocity: 0,
}
}
fn clear(&mut self) {
self.typ = EventType::None;
}
}
#[derive(Debug, PartialEq, Copy, Clone)]
pub struct VoiceData {
pub sample_rate: f64,
pub is_on: bool,
pub note: i32,
pub detune: f32,
pub pan: f32,
pub vibrato_phase: f64,
slide_active: bool,
slide_delta: f64,
slide_samples: i32,
destination_note: i32,
current_note: f64,
}
impl VoiceData {
fn new(sample_rate: f64) -> VoiceData {
VoiceData {
sample_rate,
is_on: false,
note: 0,
detune: 0.0,
pan: 0.5,
vibrato_phase: 0.0,
slide_active: false,
slide_delta: 0.0,
slide_samples: 0,
destination_note: 0,
current_note: 0.0,
}
}
pub fn note_on(&mut self, note: i32, _velocity: i32, detune: f32, pan: f32) {
self.is_on = true;
self.note = note;
self.detune = detune;
self.pan = pan;
self.current_note = note as f64;
self.slide_active = false;
}
pub fn note_off(&mut self) {
}
pub fn note_slide(&mut self, slide: f32, note: i32) {
self.slide_active = true;
self.destination_note = note;
let slide_time = 10.0 * helpers::pow(slide as f64, 4.0);
self.slide_delta = (note as f64 - self.current_note)
/ self.sample_rate * slide_time;
self.slide_samples = (self.sample_rate * slide_time) as i32;
}
pub fn get_note(&mut self) -> f64 {
if self.slide_active {
self.current_note += self.slide_delta;
self.slide_samples -= 1;
if self.slide_samples < 0 {
self.note = self.destination_note;
self.slide_active = false;
self.current_note = self.destination_note as f64;
}
}
self.current_note
}
}
pub trait Voice<P>: Copy + Clone {
fn new(sample_rate: f64) -> Self;
fn note_on(&mut self, data: &mut VoiceData, params: &mut P, note: i32, velocity: i32, detune: f32, pan: f32);
fn note_off(&mut self, data: &mut VoiceData, params: &mut P);
fn note_slide(&mut self, data: &mut VoiceData, params: &mut P, slide: f32, note: i32);
fn get_note(&mut self, data: &mut VoiceData, params: &mut P) -> f64;
fn run(&mut self,
data: &mut VoiceData,
params: &mut P,
song_pos: f64,
sample_num: usize,
out_offs: usize,
outputs: &mut [f32]);
}
pub struct SynthDeviceParams {
master_level: f32,
voices_unisono: i32,
voices_detune: f32,
voices_pan: f32,
vibrato_freq: f64,
vibrato_amount: f32,
rise: f32,
slide: f32,
voice_mode: VoiceMode,
}
impl SynthDeviceParams {
pub fn new() -> SynthDeviceParams {
SynthDeviceParams {
master_level: 0.0,
voices_unisono: 1,
voices_detune: 0.0,
voices_pan: 0.5,
vibrato_freq: 0.0,
vibrato_amount: 0.0,
rise: 0.0,
slide: 0.0,
voice_mode: VoiceMode::Polyphonic,
}
}
pub fn new_with_params(p: &mut SignalIOParams) -> SynthDeviceParams {
let mut dev_params = SynthDeviceParams::new();
dev_params.init_params(&mut p);
dev_params
}
fn init_params(&mut self, p: &mut SignalIOParams) {
p.input("m_vol", 0.0, 1.0, 1.0);
p.input("v_uniso", 0.0, 1.0, 0.0);
p.input("v_detune", 0.0, 1.0, 0.0);
p.input("v_pan", 0.0, 1.0, 0.0);
p.input("vi_f", 0.0, 1.0, 0.0);
p.input("vi_amt", 0.0, 1.0, 0.0);
p.input("rise", 0.0, 1.0, 0.0);
p.input("slide_t", 0.0, 1.0, 0.0);
p.input("v_mode", 0.0, 1.0, 0.0);
self.master_level = p.v(0);
self.voices_unisono = helpers::param_to_unisono(p.v(1));
self.voices_detune = p.v(2);
self.voices_pan = p.v(3);
self.vibrato_freq = helpers::param_to_vibrato_freq(p.v(4));
self.vibrato_amount = p.v(5);
self.rise = p.v(6);
self.slide = p.v(7);
self.set_voice_mode(p.v(8).into());
}
fn exec(&mut self, t: f32, regs: &mut [f32]) {
self.params.master_level = self.params.params.inputs[4].calc(regs);
self.params.vibrato_freq =
helpers::param_to_vibrato_freq(self.params.params.inputs[34].calc(regs));
self.params.vibrato_amount = self.params.params.inputs[35].calc(regs);
self.params.rise = self.params.params.inputs[36].calc(regs);
// TODO calc the other params?!
// let a = self.values[0].calc(regs);
// let p = self.values[1].calc(regs);
// let v = self.values[2].calc(regs);
// let f = self.values[3].calc(regs);
// regs[self.out] = a * (((f * t) + p).sin() + v);
//d// println!("OUT: {}, {}", regs[self.out], self.out);
}
}
// HOW DO I GET THIS SORTED OUT?
// SynthDevice needs access to these params, and the voices too.
// The only way to provide both access is an Rc/RefCell.
// Or is it?
// I could copy the params to the SynthDevice on trait/exec(),
// then the access would be fast(er).
pub struct SynthDevice<V, P>
where V: Voice<P> {
sample_rate: f64,
mono_active: bool,
note_log: [i32; 128],
note_count: i32,
active_notes: [bool; 128],
voice_data: [VoiceData; 256],
voices: [V; 256],
events: [Event; 256],
dev_params: SynthDeviceParams,
pub params: P,
}
fn clear_outputs(outputs: &mut [f32]) {
for out in outputs.iter_mut() {
*out = 0.0;
}
}
macro_rules! voice_data_zip {
($self: ident) => { $self.voices.iter_mut().zip($self.voice_data.iter_mut()) }
}
macro_rules! detuned_notes_on {
($self: ident, $e: ident, $j: ident) => {
for (v, vd) in voice_data_zip!($self) {
if $j <= 0 { break; }
if !vd.is_on {
$j -= 1;
let f = if $self.voices_unisono > 1 {
$j as f32 / ($self.voices_unisono as f32 - 1.0)
} else {
$j as f32
};
v.note_on(
vd, &mut $self.params, $e.note, $e.velocity,
f * $self.voices_detune,
(f - 0.5) * ($self.voices_pan * 2.0 - 1.0) + 0.5);
}
}
}
}
impl<P, V: Voice<P>> SynthDevice<V, P> {
pub fn new(sample_rate: f64, params: P) -> Self {
SynthDevice {
sample_rate,
mono_active: false,
note_count: 0,
active_notes: [false; 128],
note_log: [0; 128],
voice_data: [VoiceData::new(sample_rate); 256],
voices: [V::new(sample_rate); 256],
events: [Event::new(); 256],
dev_params: SynthDeviceParams::new(),
params,
}
}
pub fn run(&mut self, mut song_pos: f64,
mut num_samples: usize,
_inputs: &mut [f32],
outputs: &mut [f32]) {
let orig_num_samples = num_samples;
clear_outputs(outputs);
let mut out_offs = 0;
while num_samples > 0 {
let mut samples_to_next_event = num_samples as i32;
for e in self.events.iter_mut() {
if e.typ == EventType::None {
continue;
}
if e.delta_samples == 0 {
match e.typ {
EventType::NoteOn => {
let mut j = self.voices_unisono;
match self.voice_mode {
VoiceMode::Polyphonic => {
detuned_notes_on!(self, e, j);
},
VoiceMode::MonoLegatoTrill => {
self.active_notes[e.note as usize] = true;
self.note_log[self.note_count as usize] = e.note;
if !self.mono_active { // no current note active, start new one
self.mono_active = true;
detuned_notes_on!(self, e, j);
} else { // mono note active, slide to new note
for (v, vd) in voice_data_zip!(self) {
if vd.is_on {
v.note_slide(vd, &mut self.params, self.slide, e.note);
}
}
}
},
}
},
EventType::NoteOff => {
match self.voice_mode {
VoiceMode::Polyphonic => {
for (v, vd) in voice_data_zip!(self) {
if vd.is_on && vd.note == e.note {
v.note_off(vd, &mut self.params);
}
}
},
VoiceMode::MonoLegatoTrill => {
self.active_notes[e.note as usize] = false;
let log_note =
self.note_log[(self.note_count - 1) as usize];
if e.note == log_note {
while self.note_count > 0 {
if self.active_notes[
self.note_log[
(self.note_count - 1)
as usize]
as usize] {
for (v, vd) in voice_data_zip!(self) {
if vd.is_on {
v.note_slide(
vd,
&mut self.params,
self.slide,
self.note_log[
(self.note_count - 1)
as usize]);
}
}
break;
}
self.note_count -= 1;
}
if self.note_count == 0 {
self.mono_active = false;
for an in self.active_notes.iter_mut() {
*an = false;
}
for (v, vd) in voice_data_zip!(self) {
if vd.is_on {
v.note_off(vd, &mut self.params);
}
}
}
}
},
}
},
_ => (),
}
e.typ = EventType::None;
} else if e.delta_samples < samples_to_next_event {
samples_to_next_event = e.delta_samples;
}
}
let mut cnt = 0;
for (v, vd) in self.voices.iter_mut().zip(self.voice_data.iter_mut()) {
if vd.is_on {
cnt += 1;
v.run(vd, &mut self.params, song_pos, num_samples, out_offs, outputs);
}
}
//d// println!("VOICES ON: {}", cnt);
for e in self.events.iter_mut() {
if e.typ != EventType::None {
e.delta_samples -= samples_to_next_event;
}
}
song_pos += samples_to_next_event as f64 / self.sample_rate;
out_offs += samples_to_next_event as usize;
num_samples -= samples_to_next_event as usize;
}
}
fn all_notes_off(&mut self)
{
for (vd, v) in self.voice_data.iter_mut().zip(self.voices.iter_mut()) {
if vd.is_on { v.note_off(vd, &mut self.params); }
}
self.mono_active = false;
self.note_count = 0;
for an in self.active_notes.iter_mut() {
*an = false;
}
self.clear_events();
}
// XXX: Invariant: note_on must only be called with increasing
// delta_samples. Otherwise the algorithm in run() will
// not behave well. The invariant is, that the self.events
// array is sorted by ascending delta_samples.
pub fn note_on(&mut self, note: i32, velocity: i32, delta_samples: i32) {
for ev in self.events.iter_mut() {
if ev.typ == EventType::None {
ev.typ = EventType::NoteOn;
ev.delta_samples = delta_samples;
ev.note = note;
ev.velocity = velocity;
break;
}
}
}
pub fn note_off(&mut self, note: i32, delta_samples: i32) {
for ev in self.events.iter_mut() {
if ev.typ == EventType::None {
ev.typ = EventType::NoteOff;
ev.delta_samples = delta_samples;
ev.note = note;
break;
}
}
}
fn set_voice_mode(&mut self, vm: VoiceMode) {
if self.voice_mode == vm {
return;
}
self.all_notes_off();
for vd in self.voice_data.iter_mut() {
vd.is_on = false;
}
self.voice_mode = vm;
}
fn get_voice_mode(&self) -> VoiceMode { self.voice_mode }
fn clear_events(&mut self) {
for e in self.events.iter_mut() { e.clear(); }
}
}
//struct SynthDevice<V>
// where V: Voice {
// voice_data: [VoiceData; 256],
// voices: [V; 256],
//}
// SynthDevice -> Manages voices and note on/off events
// Slaughter -> Holds parameters
// Voice -> Generates the sounds, has a poitner to Slaugher/ParameterHolder
// Voice becomes SlaugherVoice with a common set of (runtime changing) parameters.
// Problem: Parameters are maybe changing while the sound is playing!
// We can't have mutable and non mutable references at the same time.
// We have multiple references to a single set of parameters.
// We don't have access at the same time => RefCell?!
// => Rc/RefCell means there is an indirect access each time a voice
// does something. But copying the parameter data into
// the voices on each change is too wasteful too.