mod matrix
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
use params::ParamId;
|
use params::{ParamId, ParamStore};
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
use crate::voice::ModSources;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum ModSource {
|
pub enum ModSource {
|
||||||
Env(u8),
|
Env(u8),
|
||||||
Lfo(u8),
|
Lfo(u8),
|
||||||
@@ -11,7 +13,30 @@ pub enum ModSource {
|
|||||||
Macro(u8),
|
Macro(u8),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, Default)]
|
||||||
|
pub struct ControlSources {
|
||||||
|
pub modwheel: f32,
|
||||||
|
pub aftertouch: f32,
|
||||||
|
pub macros: [f32; 8],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ControlSources {
|
||||||
|
#[inline]
|
||||||
|
pub fn sync_macros(&mut self, p: &ParamStore) {
|
||||||
|
self.macros = [
|
||||||
|
p.get(ParamId::Macro1),
|
||||||
|
p.get(ParamId::Macro2),
|
||||||
|
p.get(ParamId::Macro3),
|
||||||
|
p.get(ParamId::Macro4),
|
||||||
|
p.get(ParamId::Macro5),
|
||||||
|
p.get(ParamId::Macro6),
|
||||||
|
p.get(ParamId::Macro7),
|
||||||
|
p.get(ParamId::Macro8),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct ModSlot {
|
pub struct ModSlot {
|
||||||
pub active: bool,
|
pub active: bool,
|
||||||
pub source: ModSource,
|
pub source: ModSource,
|
||||||
@@ -32,15 +57,112 @@ impl Default for ModSlot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct ModValues {
|
||||||
|
data: [f32; ParamId::COUNT],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModValues {
|
||||||
|
#[inline]
|
||||||
|
pub fn zero() -> Self {
|
||||||
|
Self {
|
||||||
|
data: [0.0; ParamId::COUNT],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn get(&self, id: ParamId) -> f32 {
|
||||||
|
// SAFETY: ParamId repr(usize), COUNT is the discriminant of the sentinel
|
||||||
|
unsafe { *self.data.get_unchecked(id as usize) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn add(&mut self, id: ParamId, v: f32) {
|
||||||
|
unsafe {
|
||||||
|
*self.data.get_unchecked_mut(id as usize) += v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ModMatrix {
|
pub struct ModMatrix {
|
||||||
pub slots: [ModSlot; 64],
|
pub slots: [ModSlot; 64],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for ModMatrix {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ModMatrix {
|
impl ModMatrix {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
slots: [ModSlot::default(); 64],
|
slots: [ModSlot::default(); 64],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn apply(&self, _params: ¶ms::ParamStore) {}
|
|
||||||
|
#[inline]
|
||||||
|
pub fn compute(&self, ms: &ModSources, cs: &ControlSources) -> ModValues {
|
||||||
|
let mut out = ModValues::zero();
|
||||||
|
|
||||||
|
for slot in &self.slots {
|
||||||
|
if !slot.active {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let depth = slot.depth;
|
||||||
|
if depth == 0.0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let src: f32 = match slot.source {
|
||||||
|
ModSource::Env(i) => ms.env.get(i as usize).copied().unwrap_or(0.0),
|
||||||
|
ModSource::Lfo(i) => ms.lfo.get(i as usize).copied().unwrap_or(0.0),
|
||||||
|
ModSource::Velocity => ms.vel,
|
||||||
|
ModSource::Note => ms.note,
|
||||||
|
ModSource::Modwheel => cs.modwheel,
|
||||||
|
ModSource::Aftertouch => cs.aftertouch,
|
||||||
|
ModSource::Macro(i) => cs.macros.get(i as usize).copied().unwrap_or(0.0),
|
||||||
|
};
|
||||||
|
|
||||||
|
out.add(slot.dest, src * depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn free_slot(&self) -> Option<usize> {
|
||||||
|
self.slots.iter().position(|s| !s.active)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn connect(
|
||||||
|
&mut self,
|
||||||
|
source: ModSource,
|
||||||
|
dest: ParamId,
|
||||||
|
depth: f32,
|
||||||
|
per_voice: bool,
|
||||||
|
) -> Option<usize> {
|
||||||
|
let idx = self.free_slot()?;
|
||||||
|
self.slots[idx] = ModSlot {
|
||||||
|
active: true,
|
||||||
|
source,
|
||||||
|
dest,
|
||||||
|
depth,
|
||||||
|
per_voice,
|
||||||
|
};
|
||||||
|
Some(idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disconnect(&mut self, idx: usize) {
|
||||||
|
if idx < 64 {
|
||||||
|
self.slots[idx] = ModSlot::default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_dest(&self, dest: ParamId) -> bool {
|
||||||
|
self.slots.iter().any(|s| s.active && s.dest == dest)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn active_slots(&self) -> impl Iterator<Item = (usize, &ModSlot)> {
|
||||||
|
self.slots.iter().enumerate().filter(|(_, s)| s.active)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use params::{ParamId, ParamStore};
|
|||||||
use crate::{
|
use crate::{
|
||||||
effects::{DistType, EffectsChain},
|
effects::{DistType, EffectsChain},
|
||||||
filter::FilterKind,
|
filter::FilterKind,
|
||||||
|
mod_matrix::{ControlSources, ModMatrix},
|
||||||
oscillator::WavetableBank,
|
oscillator::WavetableBank,
|
||||||
voice::{PlayMode, PortamentoMode, Voice},
|
voice::{PlayMode, PortamentoMode, Voice},
|
||||||
};
|
};
|
||||||
@@ -18,6 +19,8 @@ pub struct Synth {
|
|||||||
sample_rate: f32,
|
sample_rate: f32,
|
||||||
round_robin: usize,
|
round_robin: usize,
|
||||||
play_mode: PlayMode,
|
play_mode: PlayMode,
|
||||||
|
pub mod_matrix: ModMatrix,
|
||||||
|
control_sources: ControlSources,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Synth {
|
impl Synth {
|
||||||
@@ -29,6 +32,8 @@ impl Synth {
|
|||||||
sample_rate: sr,
|
sample_rate: sr,
|
||||||
round_robin: 0,
|
round_robin: 0,
|
||||||
play_mode: PlayMode::Poly,
|
play_mode: PlayMode::Poly,
|
||||||
|
mod_matrix: ModMatrix::new(),
|
||||||
|
control_sources: ControlSources::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,15 +64,30 @@ impl Synth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_modwheel(&mut self, v: f32) {
|
||||||
|
self.control_sources.modwheel = v.clamp(0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_aftertouch(&mut self, v: f32) {
|
||||||
|
self.control_sources.aftertouch = v.clamp(0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mod_matrix_mut(&mut self) -> &mut ModMatrix {
|
||||||
|
&mut self.mod_matrix
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn process(&mut self, host_bpm: f32) -> (f32, f32) {
|
pub fn process(&mut self, host_bpm: f32) -> (f32, f32) {
|
||||||
self.sync_params();
|
self.sync_params();
|
||||||
|
|
||||||
|
self.control_sources.sync_macros(&self.params);
|
||||||
|
|
||||||
let mut l = 0.0f32;
|
let mut l = 0.0f32;
|
||||||
let mut r = 0.0f32;
|
let mut r = 0.0f32;
|
||||||
|
|
||||||
for v in &mut self.voices {
|
for v in &mut self.voices {
|
||||||
if v.active {
|
if v.active {
|
||||||
let (vl, vr) = v.process(host_bpm);
|
let (vl, vr) = v.process(host_bpm, &self.mod_matrix, &self.control_sources);
|
||||||
l += vl;
|
l += vl;
|
||||||
r += vr;
|
r += vr;
|
||||||
}
|
}
|
||||||
@@ -105,6 +125,8 @@ impl Synth {
|
|||||||
PortamentoMode::Exponential
|
PortamentoMode::Exponential
|
||||||
};
|
};
|
||||||
|
|
||||||
|
self.play_mode = play_mode_from(p.get(ParamId::Polyphony));
|
||||||
|
|
||||||
for v in &mut self.voices {
|
for v in &mut self.voices {
|
||||||
v.filter1.cutoff = f1_cutoff;
|
v.filter1.cutoff = f1_cutoff;
|
||||||
v.filter1.resonance = f1_res;
|
v.filter1.resonance = f1_res;
|
||||||
@@ -120,6 +142,21 @@ impl Synth {
|
|||||||
|
|
||||||
v.portamento_time = port_time;
|
v.portamento_time = port_time;
|
||||||
v.portamento_mode = port_mode;
|
v.portamento_mode = port_mode;
|
||||||
|
|
||||||
|
sync_env(&mut v.envs[0], p, 0);
|
||||||
|
sync_env(&mut v.envs[1], p, 1);
|
||||||
|
sync_env(&mut v.envs[2], p, 2);
|
||||||
|
|
||||||
|
sync_lfo(&mut v.lfos[0], p, 0);
|
||||||
|
sync_lfo(&mut v.lfos[1], p, 1);
|
||||||
|
sync_lfo(&mut v.lfos[2], p, 2);
|
||||||
|
sync_lfo(&mut v.lfos[3], p, 3);
|
||||||
|
|
||||||
|
sync_osc_base(&mut v.oscs[0], p, 0);
|
||||||
|
sync_osc_base(&mut v.oscs[1], p, 1);
|
||||||
|
sync_osc_base(&mut v.oscs[2], p, 2);
|
||||||
|
|
||||||
|
v.filter_serial = (p.get(ParamId::FilterRouting) as u32) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let fx = &mut self.fx;
|
let fx = &mut self.fx;
|
||||||
@@ -144,7 +181,7 @@ impl Synth {
|
|||||||
fx.delay.mix = p.get(ParamId::DelayMix);
|
fx.delay.mix = p.get(ParamId::DelayMix);
|
||||||
|
|
||||||
let eq = &mut fx.eq;
|
let eq = &mut fx.eq;
|
||||||
let new_low = p.get(ParamId::EqLowGain) * 24.0 - 12.0; // ±12 dB
|
let new_low = p.get(ParamId::EqLowGain) * 24.0 - 12.0;
|
||||||
let new_mid_f = p.get(ParamId::EqMidFreq) * 7900.0 + 100.0;
|
let new_mid_f = p.get(ParamId::EqMidFreq) * 7900.0 + 100.0;
|
||||||
let new_mid_g = p.get(ParamId::EqMidGain) * 24.0 - 12.0;
|
let new_mid_g = p.get(ParamId::EqMidGain) * 24.0 - 12.0;
|
||||||
let new_high = p.get(ParamId::EqHighGain) * 24.0 - 12.0;
|
let new_high = p.get(ParamId::EqHighGain) * 24.0 - 12.0;
|
||||||
@@ -173,6 +210,69 @@ impl Synth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ENV_BASE: [ParamId; 3] = [ParamId::Env1Delay, ParamId::Env2Delay, ParamId::Env3Delay];
|
||||||
|
|
||||||
|
fn sync_env(env: &mut crate::envelope::Dahdsr, p: &ParamStore, idx: usize) {
|
||||||
|
let base = ENV_BASE[idx] as usize;
|
||||||
|
fn load(p: &ParamStore, base: usize, off: usize) -> f32 {
|
||||||
|
let id: ParamId = unsafe { std::mem::transmute(base + off) };
|
||||||
|
p.get(id)
|
||||||
|
}
|
||||||
|
env.delay = load(p, base, 0) * 2.0;
|
||||||
|
env.attack = load(p, base, 1) * 10.0 + 0.001;
|
||||||
|
env.hold = load(p, base, 2) * 2.0;
|
||||||
|
env.decay = load(p, base, 3) * 10.0 + 0.001;
|
||||||
|
env.sustain = load(p, base, 4);
|
||||||
|
env.release = load(p, base, 5) * 10.0 + 0.001;
|
||||||
|
env.attack_curve = load(p, base, 6) * 2.0 - 1.0;
|
||||||
|
env.decay_curve = load(p, base, 7) * 2.0 - 1.0;
|
||||||
|
env.release_curve = load(p, base, 8) * 2.0 - 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LFO_BASE: [ParamId; 4] = [
|
||||||
|
ParamId::Lfo1Rate,
|
||||||
|
ParamId::Lfo2Rate,
|
||||||
|
ParamId::Lfo3Rate,
|
||||||
|
ParamId::Lfo4Rate,
|
||||||
|
];
|
||||||
|
|
||||||
|
fn sync_lfo(lfo: &mut crate::lfo::Lfo, p: &ParamStore, idx: usize) {
|
||||||
|
let base = LFO_BASE[idx] as usize;
|
||||||
|
fn load(p: &ParamStore, base: usize, off: usize) -> f32 {
|
||||||
|
let id: ParamId = unsafe { std::mem::transmute(base + off) };
|
||||||
|
p.get(id)
|
||||||
|
}
|
||||||
|
lfo.rate = load(p, base, 0) * 20.0 + 0.01;
|
||||||
|
lfo.initial_phase = load(p, base, 1);
|
||||||
|
lfo.depth = load(p, base, 2);
|
||||||
|
lfo.wave_pos = load(p, base, 3);
|
||||||
|
lfo.mode = if load(p, base, 4) > 0.5 {
|
||||||
|
crate::lfo::LfoMode::BpmSync
|
||||||
|
} else {
|
||||||
|
crate::lfo::LfoMode::FreeRun
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const OSC_BASE: [ParamId; 3] = [ParamId::Osc1Gain, ParamId::Osc2Gain, ParamId::Osc3Gain];
|
||||||
|
|
||||||
|
fn sync_osc_base(osc: &mut crate::oscillator::WavetableOsc, p: &ParamStore, idx: usize) {
|
||||||
|
let base = OSC_BASE[idx] as usize;
|
||||||
|
fn load(p: &ParamStore, base: usize, off: usize) -> f32 {
|
||||||
|
let id: ParamId = unsafe { std::mem::transmute(base + off) };
|
||||||
|
p.get(id)
|
||||||
|
}
|
||||||
|
osc.gain = load(p, base, 0);
|
||||||
|
osc.pan = load(p, base, 1) * 2.0 - 1.0;
|
||||||
|
osc.wave_pos = load(p, base, 7);
|
||||||
|
|
||||||
|
osc.semitone = load(p, base, 2) * 96.0 - 48.0;
|
||||||
|
osc.fine = (load(p, base, 3) * 2.0 - 1.0) * 100.0;
|
||||||
|
|
||||||
|
osc.unison_count = (load(p, base, 4) * 15.0 + 1.0) as u8;
|
||||||
|
osc.unison_detune = load(p, base, 5);
|
||||||
|
osc.unison_spread = load(p, base, 6);
|
||||||
|
}
|
||||||
|
|
||||||
fn filter_kind_from(v: u32) -> FilterKind {
|
fn filter_kind_from(v: u32) -> FilterKind {
|
||||||
match v {
|
match v {
|
||||||
0 => FilterKind::Ladder,
|
0 => FilterKind::Ladder,
|
||||||
@@ -191,3 +291,13 @@ fn dist_type_from(v: u32) -> DistType {
|
|||||||
_ => DistType::SoftClip,
|
_ => DistType::SoftClip,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn play_mode_from(v: f32) -> PlayMode {
|
||||||
|
if v < 0.33 {
|
||||||
|
PlayMode::Poly
|
||||||
|
} else if v < 0.66 {
|
||||||
|
PlayMode::Mono
|
||||||
|
} else {
|
||||||
|
PlayMode::Legato
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
|
use params::ParamId;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
envelope::{Dahdsr, Stage},
|
envelope::{Dahdsr, Stage},
|
||||||
filter::Filter,
|
filter::Filter,
|
||||||
lfo::Lfo,
|
lfo::Lfo,
|
||||||
|
mod_matrix::{ControlSources, ModMatrix, ModValues},
|
||||||
oscillator::{WavetableBank, WavetableOsc, midi_to_freq},
|
oscillator::{WavetableBank, WavetableOsc, midi_to_freq},
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -144,7 +147,17 @@ impl Voice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn process(&mut self, host_bpm: f32) -> (f32, f32) {
|
fn semitones_to_ratio(semis: f32) -> f32 {
|
||||||
|
(semis * (2.0_f32.ln() / 12.0)).exp()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn process(
|
||||||
|
&mut self,
|
||||||
|
host_bpm: f32,
|
||||||
|
matrix: &ModMatrix,
|
||||||
|
cs: &ControlSources,
|
||||||
|
) -> (f32, f32) {
|
||||||
let amp_env = self.tick_mod_sources(host_bpm);
|
let amp_env = self.tick_mod_sources(host_bpm);
|
||||||
|
|
||||||
if self.envs[0].stage == Stage::Idle {
|
if self.envs[0].stage == Stage::Idle {
|
||||||
@@ -152,16 +165,64 @@ impl Voice {
|
|||||||
return (0.0, 0.0);
|
return (0.0, 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let freq = self.advance_portamento();
|
let mv: ModValues = matrix.compute(&self.mod_sources, cs);
|
||||||
|
|
||||||
|
let pitch_mod_semis = mv.get(ParamId::Osc1Semitone)
|
||||||
|
+ mv.get(ParamId::Osc2Semitone)
|
||||||
|
+ mv.get(ParamId::Osc3Semitone);
|
||||||
|
let pitch_ratio = Self::semitones_to_ratio(pitch_mod_semis * 24.0);
|
||||||
|
|
||||||
|
let base_freq = self.advance_portamento();
|
||||||
|
let freq = base_freq * pitch_ratio;
|
||||||
|
|
||||||
let (mut l, mut r) = (0.0f32, 0.0f32);
|
let (mut l, mut r) = (0.0f32, 0.0f32);
|
||||||
for osc in &mut self.oscs {
|
|
||||||
|
let wave_mods = [
|
||||||
|
mv.get(ParamId::Osc1WavePos),
|
||||||
|
mv.get(ParamId::Osc2WavePos),
|
||||||
|
mv.get(ParamId::Osc3WavePos),
|
||||||
|
];
|
||||||
|
let gain_mods = [
|
||||||
|
mv.get(ParamId::Osc1Gain),
|
||||||
|
mv.get(ParamId::Osc2Gain),
|
||||||
|
mv.get(ParamId::Osc3Gain),
|
||||||
|
];
|
||||||
|
let pan_mods = [
|
||||||
|
mv.get(ParamId::Osc1Pan),
|
||||||
|
mv.get(ParamId::Osc2Pan),
|
||||||
|
mv.get(ParamId::Osc3Pan),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (i, osc) in self.oscs.iter_mut().enumerate() {
|
||||||
|
let eff_wave = (osc.wave_pos + wave_mods[i]).clamp(0.0, 1.0);
|
||||||
|
let eff_gain = (osc.gain + gain_mods[i]).max(0.0);
|
||||||
|
let eff_pan = (osc.pan + pan_mods[i]).clamp(-1.0, 1.0);
|
||||||
|
|
||||||
|
let saved_wave = osc.wave_pos;
|
||||||
|
let saved_gain = osc.gain;
|
||||||
|
let saved_pan = osc.pan;
|
||||||
|
osc.wave_pos = eff_wave;
|
||||||
|
osc.gain = eff_gain;
|
||||||
|
osc.pan = eff_pan;
|
||||||
|
|
||||||
let (ol, or_) = osc.tick(freq, self.sample_rate, 0.0);
|
let (ol, or_) = osc.tick(freq, self.sample_rate, 0.0);
|
||||||
l += ol;
|
l += ol;
|
||||||
r += or_;
|
r += or_;
|
||||||
}
|
|
||||||
|
|
||||||
|
osc.wave_pos = saved_wave;
|
||||||
|
osc.gain = saved_gain;
|
||||||
|
osc.pan = saved_pan;
|
||||||
|
}
|
||||||
let ms = self.mod_sources;
|
let ms = self.mod_sources;
|
||||||
|
|
||||||
|
let eff_c1 = (self.filter1.cutoff + mv.get(ParamId::Filter1Cutoff)).clamp(0.0, 1.0);
|
||||||
|
let eff_c2 = (self.filter2.cutoff + mv.get(ParamId::Filter2Cutoff)).clamp(0.0, 1.0);
|
||||||
|
|
||||||
|
let saved_c1 = self.filter1.cutoff;
|
||||||
|
let saved_c2 = self.filter2.cutoff;
|
||||||
|
self.filter1.cutoff = eff_c1;
|
||||||
|
self.filter2.cutoff = eff_c2;
|
||||||
|
|
||||||
let fm1 = Self::filter_fm(&ms, self.filter1.fm_amount);
|
let fm1 = Self::filter_fm(&ms, self.filter1.fm_amount);
|
||||||
let fm2 = Self::filter_fm(&ms, self.filter2.fm_amount);
|
let fm2 = Self::filter_fm(&ms, self.filter2.fm_amount);
|
||||||
|
|
||||||
@@ -179,7 +240,12 @@ impl Voice {
|
|||||||
((l1 + l2) * 0.5, (r1 + r2) * 0.5)
|
((l1 + l2) * 0.5, (r1 + r2) * 0.5)
|
||||||
};
|
};
|
||||||
|
|
||||||
let amp = amp_env * self.velocity;
|
self.filter1.cutoff = saved_c1;
|
||||||
|
self.filter2.cutoff = saved_c2;
|
||||||
|
|
||||||
|
let amp_mod = mv.get(ParamId::MasterVolume);
|
||||||
|
let amp = amp_env * self.velocity * (1.0 + amp_mod).max(0.0);
|
||||||
|
|
||||||
(l * amp, r * amp)
|
(l * amp, r * amp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user