From f40dd01542f353f73cfcc2b8de412b8bf60be2e2 Mon Sep 17 00:00:00 2001 From: ardura Date: Wed, 16 Oct 2024 09:10:24 -0700 Subject: [PATCH] 1.3.5 Update --- src/audio_module.rs | 1352 +++++++++++++++++++++++++++----- src/audio_module/Oscillator.rs | 2 +- src/fx/VCFilter.rs | 13 +- src/fx/aw_galactic_reverb.rs | 5 +- 4 files changed, 1162 insertions(+), 210 deletions(-) diff --git a/src/audio_module.rs b/src/audio_module.rs index 593c4fa..5b9c8e4 100644 --- a/src/audio_module.rs +++ b/src/audio_module.rs @@ -868,7 +868,7 @@ impl AudioModule { Free: constantly running phase based off previous note Retrigger: wave form restarts at every new note Random: Wave and all unisons use a new random phase every note -UniRandom: Every voice uses its own unique random phase every note".to_string()); +MRandom: Every voice uses its own unique random phase every note".to_string()); ui.add(osc_1_retrigger_knob); }); @@ -1795,7 +1795,7 @@ Random: Sample uses a new random position every note".to_string()); Free: constantly running phase based off previous note Retrigger: wave form restarts at every new note Random: Wave and all unisons use a new random phase every note -UniRandom: Every voice uses its own unique random phase every note".to_string()); +MRandom: Every voice uses its own unique random phase every note".to_string()); ui.add(osc_1_retrigger_knob); }); @@ -2711,7 +2711,7 @@ UniRandom: Every voice uses its own unique random phase every note".to_string()) // Start our phase back at 0 new_phase = 0.0; } - RetriggerStyle::Random | RetriggerStyle::UniRandom => { + RetriggerStyle::Random | RetriggerStyle::MRandom => { match self.audio_module_type { AudioModuleType::Sampler => { let mut rng = rand::thread_rng(); @@ -2868,7 +2868,7 @@ UniRandom: Every voice uses its own unique random phase every note".to_string()) // Create our granulizer/sampler starting position from our knob scale scaled_sample_pos = if self.start_position > 0.0 && self.osc_retrigger != RetriggerStyle::Random - && self.osc_retrigger != RetriggerStyle::UniRandom + && self.osc_retrigger != RetriggerStyle::MRandom { (self.sample_lib[note as usize][0].len() as f32 * self.start_position) @@ -2877,7 +2877,7 @@ UniRandom: Every voice uses its own unique random phase every note".to_string()) } // Retrigger and use 0 else if self.osc_retrigger != RetriggerStyle::Random - && self.osc_retrigger != RetriggerStyle::UniRandom + && self.osc_retrigger != RetriggerStyle::MRandom { 0_usize } @@ -3178,7 +3178,7 @@ UniRandom: Every voice uses its own unique random phase every note".to_string()) for unison_voice in 0..(self.osc_unison as usize - 1) { let uni_phase = match self.osc_retrigger { - RetriggerStyle::UniRandom => { + RetriggerStyle::MRandom => { match self.audio_module_type { AudioModuleType::Additive | AudioModuleType::Sine | @@ -5320,200 +5320,362 @@ UniRandom: Every voice uses its own unique random phase every note".to_string()) stereo_voices_l += left_amp; stereo_voices_r += right_amp; } - } - // Stereo applies to unison voices - // Sum our voices for output - summed_voices_l += center_voices; - summed_voices_r += center_voices; - // Scaling of output based on stereo voices and unison - summed_voices_l += stereo_voices_l / (self.osc_unison - 1).clamp(1, 9) as f32; - summed_voices_r += stereo_voices_r / (self.osc_unison - 1).clamp(1, 9) as f32; - - // Blending - if self.osc_unison > 1 { - summed_voices_l = (summed_voices_l + summed_voices_r * 0.8)/2.0; - summed_voices_r = (summed_voices_r + summed_voices_l * 0.8)/2.0; - } - - // Stereo Spreading code - let width_coeff = match stereo_algorithm { - StereoAlgorithm::Original => { - self.osc_stereo * 0.5 - } - StereoAlgorithm::CubeSpread => { - self.osc_stereo - }, - StereoAlgorithm::ExpSpread => { - self.osc_stereo * 1.8 - }, - }; - let mid = (summed_voices_l + summed_voices_r) * 0.5; - let stereo = (summed_voices_r - summed_voices_l) * width_coeff; - summed_voices_l = mid - stereo; - summed_voices_r = mid + stereo; + //} - // Return output - (summed_voices_l, summed_voices_r) - }, - AudioModuleType::Sampler => { - let mut summed_voices_l: f32 = 0.0; - let mut summed_voices_r: f32 = 0.0; - let mut center_voices_l: f32 = 0.0; - let mut center_voices_r: f32 = 0.0; - let mut stereo_voices_l: f32 = 0.0; - let mut stereo_voices_r: f32 = 0.0; - for voice in self.playing_voices.voices.iter_mut() { - // Get our current gain amount for use in match below - let temp_osc_gain_multiplier: f32 = match voice.state { - OscState::Attacking => voice.osc_attack.next(), - OscState::Decaying => voice.osc_decay.next(), - OscState::Sustaining => self.osc_sustain / 1999.9, - OscState::Releasing => voice.osc_release.next(), - OscState::Off => 0.0, - }; - voice.amp_current = temp_osc_gain_multiplier; - let usize_note = voice.note as usize; + ////////////////////////////////////////////////////////////////////////// + // POLYFILTER UPDATE + ////////////////////////////////////////////////////////////////////////// + + // Filter 1 Processing + /////////////////////////////////////////////////////////////// + let mut next_filter_step: f32 = 0.0; + let mut next_filter_step_2: f32 = 0.0; + if self.filter_wet > 0.0 { + // Filter state movement code + ////////////////////////////////////////// + // If a note is ending and we should enter releasing + if note_off { + let old_filter_state = voice.filter_state_1; + voice.filter_state_1 = OscState::Releasing; + // Reset our filter release to be at sustain level to start + voice.filter_rel_smoother_1.reset( + match old_filter_state { + OscState::Attacking => voice.filter_atk_smoother_1.next(), + OscState::Decaying | OscState::Releasing => voice.filter_dec_smoother_1.next(), + OscState::Sustaining => voice.filter_dec_smoother_1.next(), + OscState::Off => self.filter_cutoff, + }, + ); + // Move release to the cutoff to end + voice.filter_rel_smoother_1.set_target(self.sample_rate, self.filter_cutoff); + } - // If we even have valid samples loaded - if self.sample_lib[0][0].len() > 1 - && self.loaded_sample[0].len() > 1 - && self.sample_lib.len() > 1 - { - // Use our Vec>> - // If our note is valid 0-127 - if usize_note < self.sample_lib.len() { - // If our sample position is valid for our note - if voice.sample_pos < self.sample_lib[usize_note][0].len() { - // Get our channels of sample vectors - let NoteVector = &self.sample_lib[usize_note]; - // We don't need to worry about mono/stereo here because it's been setup in load_new_sample() - center_voices_l += - NoteVector[0][voice.sample_pos] * temp_osc_gain_multiplier; - center_voices_r += - NoteVector[1][voice.sample_pos] * temp_osc_gain_multiplier; - } + // If our attack has finished + if voice.filter_atk_smoother_1.steps_left() == 0 && voice.filter_state_1 == OscState::Attacking + { + voice.filter_state_1 = OscState::Decaying; + // This makes our filter decay start at env peak point + voice.filter_dec_smoother_1.reset( + voice.filter_atk_smoother_1.next().clamp(20.0, 20000.0), + ); + // Set up the smoother for our filter movement to go from our decay point to our sustain point + voice.filter_dec_smoother_1.set_target( + self.sample_rate, + ( + self.filter_cutoff + + // This scales the peak env to be much gentler for the TILT filter + match self.filter_alg_type { + FilterAlgorithms::SVF | FilterAlgorithms::VCF | FilterAlgorithms::V4 | FilterAlgorithms::A4I => self.filter_env_peak, + FilterAlgorithms::TILT => adv_scale_value( + self.filter_env_peak, + -19980.0, + 19980.0, + -5000.0, + 5000.0, + ), + } + ).clamp(20.0, 20000.0) + * (self.filter_env_sustain / 1999.9), + ); } - let scaled_start_position = (self.sample_lib[usize_note][0].len() as f32 - * self.start_position) - .floor() as usize; - let scaled_end_position = (self.sample_lib[usize_note][0].len() as f32 - * self._end_position) - .floor() as usize; - // Sampler moves position - voice.sample_pos += 1; - if voice.loop_it - && (voice.sample_pos > self.sample_lib[usize_note][0].len() - || voice.sample_pos > scaled_end_position) + // If our decay has finished move to sustain state + if voice.filter_dec_smoother_1.steps_left() == 0 + && voice.filter_state_1 == OscState::Decaying { - voice.sample_pos = scaled_start_position; - } else if voice.sample_pos > scaled_end_position { - voice.sample_pos = self.sample_lib[usize_note][0].len(); - voice.state = OscState::Off; + voice.filter_state_1 = OscState::Sustaining; } + // use proper variable now that there are four filters and multiple states + // This double addition of voice.cutoff_modulation + cutoff_mod will stack the mod at the time of the voice movement with the current + next_filter_step = match voice.filter_state_1 { + OscState::Attacking => { + (voice.filter_atk_smoother_1.next() + voice.cutoff_modulation + cutoff_mod).clamp(20.0, 20000.0) + } + OscState::Decaying => { + (voice.filter_dec_smoother_1.next() + voice.cutoff_modulation + cutoff_mod).clamp(20.0, 20000.0) + } + OscState::Sustaining => { + (voice.filter_dec_smoother_1.next() + voice.cutoff_modulation + cutoff_mod).clamp(20.0, 20000.0) + } + OscState::Releasing => { + if self.filter_env_release <= 0.0001 { + (voice.filter_dec_smoother_1.next() + voice.cutoff_modulation + cutoff_mod).clamp(20.0, 20000.0) + } else { + (voice.filter_rel_smoother_1.next() + voice.cutoff_modulation + cutoff_mod).clamp(20.0, 20000.0) + } + } + // I don't expect this to be used + _ => (self.filter_cutoff + voice.cutoff_modulation + cutoff_mod).clamp(20.0, 20000.0), + }; } - } - - let mut temp_unison_voice_l = 0.0; - let mut temp_unison_voice_r = 0.0; - // Stereo applies to unison voices - for unison_voice in self.unison_voices.voices.iter_mut() { - // Get our current gain amount for use in match below - let temp_osc_gain_multiplier: f32 = match unison_voice.state { - OscState::Attacking => unison_voice.osc_attack.next(), - OscState::Decaying => unison_voice.osc_decay.next(), - OscState::Sustaining => self.osc_sustain / 1999.9, - OscState::Releasing => unison_voice.osc_release.next(), - OscState::Off => 0.0, - }; - unison_voice.amp_current = temp_osc_gain_multiplier; - let usize_note = unison_voice.note as usize; - - // If we even have valid samples loaded - if self.sample_lib[0][0].len() > 1 - && self.loaded_sample[0].len() > 1 - && self.sample_lib.len() > 1 - { - // Use our Vec>> - // If our note is valid 0-127 - if usize_note < self.sample_lib.len() { - // If our sample position is valid for our note - if unison_voice.sample_pos < self.sample_lib[usize_note][0].len() { - // Get our channels of sample vectors - let NoteVector = &self.sample_lib[usize_note]; - // We don't need to worry about mono/stereo here because it's been setup in load_new_sample() - temp_unison_voice_l += - NoteVector[0][unison_voice.sample_pos] * temp_osc_gain_multiplier; - temp_unison_voice_r += - NoteVector[1][unison_voice.sample_pos] * temp_osc_gain_multiplier; - } + if self.filter_wet_2 > 0.0 { + // Filter state movement code + ////////////////////////////////////////// + // If a note is ending and we should enter releasing + if note_off { + let old_filter_state = voice.filter_state_2; + voice.filter_state_2 = OscState::Releasing; + // Reset our filter release to be at sustain level to start + voice.filter_rel_smoother_2.reset( + match old_filter_state { + OscState::Attacking => voice.filter_atk_smoother_2.next(), + OscState::Decaying | OscState::Releasing => voice.filter_dec_smoother_2.next(), + OscState::Sustaining => voice.filter_dec_smoother_2.next(), + OscState::Off => self.filter_cutoff_2, + }, + ); + // Move release to the cutoff to end + voice.filter_rel_smoother_2.set_target(self.sample_rate, self.filter_cutoff_2); } - let scaled_start_position = (self.sample_lib[usize_note][0].len() as f32 - * self.start_position) - .floor() as usize; - let scaled_end_position = (self.sample_lib[usize_note][0].len() as f32 - * self._end_position) - .floor() as usize; - // Sampler moves position - unison_voice.sample_pos += 1; - if unison_voice.loop_it - && (unison_voice.sample_pos > self.sample_lib[usize_note][0].len() - || unison_voice.sample_pos > scaled_end_position) + // If our attack has finished + if voice.filter_atk_smoother_2.steps_left() == 0 && voice.filter_state_2 == OscState::Attacking { - unison_voice.sample_pos = scaled_start_position; - } else if unison_voice.sample_pos > scaled_end_position { - unison_voice.sample_pos = self.sample_lib[usize_note][0].len(); - unison_voice.state = OscState::Off; + voice.filter_state_2 = OscState::Decaying; + // This makes our filter decay start at env peak point + voice.filter_dec_smoother_2.reset( + voice.filter_atk_smoother_2.next().clamp(20.0, 20000.0), + ); + // Set up the smoother for our filter movement to go from our decay point to our sustain point + voice.filter_dec_smoother_2.set_target( + self.sample_rate, + ( + self.filter_cutoff_2 + + // This scales the peak env to be much gentler for the TILT filter + match self.filter_alg_type_2 { + FilterAlgorithms::SVF | FilterAlgorithms::VCF | FilterAlgorithms::V4 | FilterAlgorithms::A4I => self.filter_env_peak_2, + FilterAlgorithms::TILT => adv_scale_value( + self.filter_env_peak_2, + -19980.0, + 19980.0, + -5000.0, + 5000.0, + ), + } + ).clamp(20.0, 20000.0) + * (self.filter_env_sustain_2 / 1999.9), + ); } - } - // Create our stereo pan for unison - // Our angle comes back as radians - let pan = unison_voice._angle; - - // Precompute sine and cosine of the angle - let cos_pan = pan.cos(); - let sin_pan = pan.sin(); - - // Calculate the amplitudes for the panned voice using vector operations - let scale = SQRT_2 / 2.0; - let temp_unison_voice_scaled_l = scale * temp_unison_voice_l; - let temp_unison_voice_scaled_r = scale * temp_unison_voice_r; - - let left_amp = temp_unison_voice_scaled_l * (cos_pan + sin_pan); - let right_amp = temp_unison_voice_scaled_r * (cos_pan - sin_pan); + // If our decay has finished move to sustain state + if voice.filter_dec_smoother_2.steps_left() == 0 + && voice.filter_state_2 == OscState::Decaying + { + voice.filter_state_2 = OscState::Sustaining; + } + // use proper variable now that there are four filters and multiple states + next_filter_step_2 = match voice.filter_state_2 { + OscState::Attacking => { + (voice.filter_atk_smoother_2.next() + voice.cutoff_modulation_2 + cutoff_mod_2).clamp(20.0, 20000.0) + } + OscState::Decaying => { + (voice.filter_dec_smoother_2.next() + voice.cutoff_modulation_2 + cutoff_mod_2).clamp(20.0, 20000.0) + } + OscState::Sustaining => { + (voice.filter_dec_smoother_2.next() + voice.cutoff_modulation_2 + cutoff_mod_2).clamp(20.0, 20000.0) + } + OscState::Releasing => { + if self.filter_env_release_2 <= 0.0001 { + (voice.filter_dec_smoother_2.next() + voice.cutoff_modulation_2 + cutoff_mod_2).clamp(20.0, 20000.0) + } else { + (voice.filter_rel_smoother_2.next() + voice.cutoff_modulation_2 + cutoff_mod_2).clamp(20.0, 20000.0) + } + } + // I don't expect this to be used + _ => (self.filter_cutoff_2 + voice.cutoff_modulation_2 + cutoff_mod_2).clamp(20.0, 20000.0), + }; + } - // Add the voice to the sum of stereo voices - stereo_voices_l += left_amp; - stereo_voices_r += right_amp; + ////////////////////////////////////////////////////////////////////////// + // POLYFILTER UPDATE + ////////////////////////////////////////////////////////////////////////// + match self.audio_module_routing { + AMFilterRouting::Bypass | AMFilterRouting::UNSETROUTING => { + left_output += center_voices + stereo_voices_l; + right_output += center_voices + stereo_voices_r; + }, + AMFilterRouting::Filter1 => { + left_output_filter1 = center_voices + stereo_voices_l; + right_output_filter1 = center_voices + stereo_voices_r; + }, + AMFilterRouting::Filter2 => { + left_output_filter2 = center_voices + stereo_voices_l; + right_output_filter2 = center_voices + stereo_voices_r; + }, + AMFilterRouting::Both => { + left_output_filter1 = center_voices + stereo_voices_l; + right_output_filter1 = center_voices + stereo_voices_r; + left_output_filter2 = center_voices + stereo_voices_l; + right_output_filter2 = center_voices + stereo_voices_r; + }, + } + + if self.audio_module_routing != AMFilterRouting::Bypass { + match self.filter_routing { + FilterRouting::Parallel => { + let (filter1_processed_l,filter1_processed_r) = filter_process_1( + self.filter_alg_type.clone(), + self.filter_resonance, + self.sample_rate, + self.filter_res_type.clone(), + self.lp_amount, + self.bp_amount, + self.hp_amount, + self.filter_wet, + self.tilt_filter_type.clone(), + self.vcf_filter_type.clone(), + voice, + next_filter_step, + resonance_mod, + left_output_filter1, + right_output_filter1, + ); + let (filter2_processed_l,filter2_processed_r) = filter_process_2( + self.filter_alg_type_2.clone(), + self.filter_resonance_2, + self.sample_rate, + self.filter_res_type_2.clone(), + self.lp_amount_2, + self.bp_amount_2, + self.hp_amount_2, + self.filter_wet_2, + self.tilt_filter_type_2.clone(), + self.vcf_filter_type_2.clone(), + voice, + next_filter_step_2, + resonance_mod_2, + left_output_filter2, + right_output_filter2, + ); + left_output += filter1_processed_l + filter2_processed_l; + right_output += filter1_processed_r + filter2_processed_r; + } + FilterRouting::Series12 => { + let (filter1_processed_l,filter1_processed_r) = filter_process_1( + self.filter_alg_type.clone(), + self.filter_resonance, + self.sample_rate, + self.filter_res_type.clone(), + self.lp_amount, + self.bp_amount, + self.hp_amount, + self.filter_wet, + self.tilt_filter_type.clone(), + self.vcf_filter_type.clone(), + voice, + next_filter_step, + resonance_mod, + left_output_filter1, + right_output_filter1, + ); + let (filter2_processed_l,filter2_processed_r) = filter_process_2( + self.filter_alg_type_2.clone(), + self.filter_resonance_2, + self.sample_rate, + self.filter_res_type_2.clone(), + self.lp_amount_2, + self.bp_amount_2, + self.hp_amount_2, + self.filter_wet_2, + self.tilt_filter_type_2.clone(), + self.vcf_filter_type_2.clone(), + voice, + next_filter_step_2, + resonance_mod_2, + left_output_filter2 + filter1_processed_l, + right_output_filter2 + filter1_processed_r, + ); + left_output += filter2_processed_l; + right_output += filter2_processed_r; + } + FilterRouting::Series21 => { + let (filter2_processed_l,filter2_processed_r) = filter_process_2( + self.filter_alg_type_2.clone(), + self.filter_resonance_2, + self.sample_rate, + self.filter_res_type_2.clone(), + self.lp_amount_2, + self.bp_amount_2, + self.hp_amount_2, + self.filter_wet_2, + self.tilt_filter_type_2.clone(), + self.vcf_filter_type_2.clone(), + voice, + next_filter_step_2, + resonance_mod_2, + left_output_filter2, + right_output_filter2, + ); + let (filter1_processed_l,filter1_processed_r) = filter_process_1( + self.filter_alg_type.clone(), + self.filter_resonance, + self.sample_rate, + self.filter_res_type.clone(), + self.lp_amount, + self.bp_amount, + self.hp_amount, + self.filter_wet, + self.tilt_filter_type.clone(), + self.vcf_filter_type.clone(), + voice, + next_filter_step, + resonance_mod, + left_output_filter1 + filter2_processed_l, + right_output_filter1 + filter2_processed_r, + ); + left_output += filter1_processed_l; + right_output += filter1_processed_r; + } + } + } } + + + + // Stereo applies to unison voices // Sum our voices for output - summed_voices_l += center_voices_l; - summed_voices_r += center_voices_r; + summed_voices_l += left_output; + summed_voices_r += right_output; // Scaling of output based on stereo voices and unison - summed_voices_l += stereo_voices_l / (self.osc_unison - 1).clamp(1, 9) as f32; - summed_voices_r += stereo_voices_r / (self.osc_unison - 1).clamp(1, 9) as f32; + //summed_voices_l += stereo_voices_l / (self.osc_unison - 1).clamp(1, 9) as f32; + //summed_voices_r += stereo_voices_r / (self.osc_unison - 1).clamp(1, 9) as f32; + + // Blending + if self.osc_unison > 1 { + summed_voices_l = (summed_voices_l + summed_voices_r * 0.8)/2.0; + summed_voices_r = (summed_voices_r + summed_voices_l * 0.8)/2.0; + } // Stereo Spreading code - let width_coeff = self.osc_stereo * 0.5; + let width_coeff = match stereo_algorithm { + StereoAlgorithm::Original => { + self.osc_stereo * 0.5 + } + StereoAlgorithm::CubeSpread => { + self.osc_stereo + }, + StereoAlgorithm::ExpSpread => { + self.osc_stereo * 1.8 + }, + }; let mid = (summed_voices_l + summed_voices_r) * 0.5; let stereo = (summed_voices_r - summed_voices_l) * width_coeff; summed_voices_l = mid - stereo; summed_voices_r = mid + stereo; + // Return output (summed_voices_l, summed_voices_r) }, - AudioModuleType::Off | AudioModuleType::UnsetAm => { - // Do nothing, return 0.0 - (0.0, 0.0) - }, - AudioModuleType::Granulizer => { + AudioModuleType::Sampler => { let mut summed_voices_l: f32 = 0.0; let mut summed_voices_r: f32 = 0.0; + let mut center_voices_l: f32 = 0.0; + let mut center_voices_r: f32 = 0.0; + let mut stereo_voices_l: f32 = 0.0; + let mut stereo_voices_r: f32 = 0.0; for voice in self.playing_voices.voices.iter_mut() { // Get our current gain amount for use in match below let temp_osc_gain_multiplier: f32 = match voice.state { @@ -5539,46 +5701,24 @@ UniRandom: Every voice uses its own unique random phase every note".to_string()) if voice.sample_pos < self.sample_lib[usize_note][0].len() { // Get our channels of sample vectors let NoteVector = &self.sample_lib[usize_note]; - // If we are in crossfade or in middle of grain after atttack ends - if voice.grain_state == GrainState::Attacking { - // Add our current grain - if voice.grain_attack.steps_left() != 0 { - // This format is: Output = CurrentSample * Voice ADSR * GrainRelease - summed_voices_l += NoteVector[0][voice.sample_pos] - * temp_osc_gain_multiplier - * voice.grain_attack.next(); - summed_voices_r += NoteVector[1][voice.sample_pos] - * temp_osc_gain_multiplier - * voice.grain_attack.next(); - } else { - // This format is: Output = CurrentSample * Voice ADSR * GrainRelease - summed_voices_l += NoteVector[0][voice.sample_pos] - * temp_osc_gain_multiplier; - summed_voices_r += NoteVector[1][voice.sample_pos] - * temp_osc_gain_multiplier; - } - } - // If we are in crossfade - else if voice.grain_state == GrainState::Releasing { - summed_voices_l += NoteVector[0][voice.sample_pos] - * temp_osc_gain_multiplier - * voice.grain_release.next(); - summed_voices_r += NoteVector[1][voice.sample_pos] - * temp_osc_gain_multiplier - * voice.grain_release.next(); - } + // We don't need to worry about mono/stereo here because it's been setup in load_new_sample() + center_voices_l += + NoteVector[0][voice.sample_pos] * temp_osc_gain_multiplier; + center_voices_r += + NoteVector[1][voice.sample_pos] * temp_osc_gain_multiplier; } } - let scaled_start_position = (self.loaded_sample[0].len() as f32 + + let scaled_start_position = (self.sample_lib[usize_note][0].len() as f32 * self.start_position) .floor() as usize; - let scaled_end_position = (self.loaded_sample[0].len() as f32 + let scaled_end_position = (self.sample_lib[usize_note][0].len() as f32 * self._end_position) .floor() as usize; - // Granulizer moves position + // Sampler moves position voice.sample_pos += 1; if voice.loop_it - && (voice.sample_pos > self.loaded_sample[0].len() + && (voice.sample_pos > self.sample_lib[usize_note][0].len() || voice.sample_pos > scaled_end_position) { voice.sample_pos = scaled_start_position; @@ -5588,7 +5728,813 @@ UniRandom: Every voice uses its own unique random phase every note".to_string()) } } } - (summed_voices_l, summed_voices_r) + + let mut temp_unison_voice_l = 0.0; + let mut temp_unison_voice_r = 0.0; + // Stereo applies to unison voices + for unison_voice in self.unison_voices.voices.iter_mut() { + // Get our current gain amount for use in match below + let temp_osc_gain_multiplier: f32 = match unison_voice.state { + OscState::Attacking => unison_voice.osc_attack.next(), + OscState::Decaying => unison_voice.osc_decay.next(), + OscState::Sustaining => self.osc_sustain / 1999.9, + OscState::Releasing => unison_voice.osc_release.next(), + OscState::Off => 0.0, + }; + unison_voice.amp_current = temp_osc_gain_multiplier; + + let usize_note = unison_voice.note as usize; + + // If we even have valid samples loaded + if self.sample_lib[0][0].len() > 1 + && self.loaded_sample[0].len() > 1 + && self.sample_lib.len() > 1 + { + // Use our Vec>> + // If our note is valid 0-127 + if usize_note < self.sample_lib.len() { + // If our sample position is valid for our note + if unison_voice.sample_pos < self.sample_lib[usize_note][0].len() { + // Get our channels of sample vectors + let NoteVector = &self.sample_lib[usize_note]; + // We don't need to worry about mono/stereo here because it's been setup in load_new_sample() + temp_unison_voice_l += + NoteVector[0][unison_voice.sample_pos] * temp_osc_gain_multiplier; + temp_unison_voice_r += + NoteVector[1][unison_voice.sample_pos] * temp_osc_gain_multiplier; + } + } + + let scaled_start_position = (self.sample_lib[usize_note][0].len() as f32 + * self.start_position) + .floor() as usize; + let scaled_end_position = (self.sample_lib[usize_note][0].len() as f32 + * self._end_position) + .floor() as usize; + // Sampler moves position + unison_voice.sample_pos += 1; + if unison_voice.loop_it + && (unison_voice.sample_pos > self.sample_lib[usize_note][0].len() + || unison_voice.sample_pos > scaled_end_position) + { + unison_voice.sample_pos = scaled_start_position; + } else if unison_voice.sample_pos > scaled_end_position { + unison_voice.sample_pos = self.sample_lib[usize_note][0].len(); + unison_voice.state = OscState::Off; + } + } + // Create our stereo pan for unison + + // Our angle comes back as radians + let pan = unison_voice._angle; + + // Precompute sine and cosine of the angle + let cos_pan = pan.cos(); + let sin_pan = pan.sin(); + + // Calculate the amplitudes for the panned voice using vector operations + let scale = SQRT_2 / 2.0; + let temp_unison_voice_scaled_l = scale * temp_unison_voice_l; + let temp_unison_voice_scaled_r = scale * temp_unison_voice_r; + + let left_amp = temp_unison_voice_scaled_l * (cos_pan + sin_pan); + let right_amp = temp_unison_voice_scaled_r * (cos_pan - sin_pan); + + // Add the voice to the sum of stereo voices + stereo_voices_l += left_amp; + stereo_voices_r += right_amp; + } + + + for voice in self.playing_voices.voices.iter_mut() { + ////////////////////////////////////////////////////////////////////////// + // POLYFILTER UPDATE + ////////////////////////////////////////////////////////////////////////// + + // Filter 1 Processing + /////////////////////////////////////////////////////////////// + let mut next_filter_step: f32 = 0.0; + let mut next_filter_step_2: f32 = 0.0; + if self.filter_wet > 0.0 { + // Filter state movement code + ////////////////////////////////////////// + // If a note is ending and we should enter releasing + if note_off { + let old_filter_state = voice.filter_state_1; + voice.filter_state_1 = OscState::Releasing; + // Reset our filter release to be at sustain level to start + voice.filter_rel_smoother_1.reset( + match old_filter_state { + OscState::Attacking => voice.filter_atk_smoother_1.next(), + OscState::Decaying | OscState::Releasing => voice.filter_dec_smoother_1.next(), + OscState::Sustaining => voice.filter_dec_smoother_1.next(), + OscState::Off => self.filter_cutoff, + }, + ); + // Move release to the cutoff to end + voice.filter_rel_smoother_1.set_target(self.sample_rate, self.filter_cutoff); + } + + // If our attack has finished + if voice.filter_atk_smoother_1.steps_left() == 0 && voice.filter_state_1 == OscState::Attacking + { + voice.filter_state_1 = OscState::Decaying; + // This makes our filter decay start at env peak point + voice.filter_dec_smoother_1.reset( + voice.filter_atk_smoother_1.next().clamp(20.0, 20000.0), + ); + // Set up the smoother for our filter movement to go from our decay point to our sustain point + voice.filter_dec_smoother_1.set_target( + self.sample_rate, + ( + self.filter_cutoff + + // This scales the peak env to be much gentler for the TILT filter + match self.filter_alg_type { + FilterAlgorithms::SVF | FilterAlgorithms::VCF | FilterAlgorithms::V4 | FilterAlgorithms::A4I => self.filter_env_peak, + FilterAlgorithms::TILT => adv_scale_value( + self.filter_env_peak, + -19980.0, + 19980.0, + -5000.0, + 5000.0, + ), + } + ).clamp(20.0, 20000.0) + * (self.filter_env_sustain / 1999.9), + ); + } + + // If our decay has finished move to sustain state + if voice.filter_dec_smoother_1.steps_left() == 0 + && voice.filter_state_1 == OscState::Decaying + { + voice.filter_state_1 = OscState::Sustaining; + } + // use proper variable now that there are four filters and multiple states + // This double addition of voice.cutoff_modulation + cutoff_mod will stack the mod at the time of the voice movement with the current + next_filter_step = match voice.filter_state_1 { + OscState::Attacking => { + (voice.filter_atk_smoother_1.next() + voice.cutoff_modulation + cutoff_mod).clamp(20.0, 20000.0) + } + OscState::Decaying => { + (voice.filter_dec_smoother_1.next() + voice.cutoff_modulation + cutoff_mod).clamp(20.0, 20000.0) + } + OscState::Sustaining => { + (voice.filter_dec_smoother_1.next() + voice.cutoff_modulation + cutoff_mod).clamp(20.0, 20000.0) + } + OscState::Releasing => { + if self.filter_env_release <= 0.0001 { + (voice.filter_dec_smoother_1.next() + voice.cutoff_modulation + cutoff_mod).clamp(20.0, 20000.0) + } else { + (voice.filter_rel_smoother_1.next() + voice.cutoff_modulation + cutoff_mod).clamp(20.0, 20000.0) + } + } + // I don't expect this to be used + _ => (self.filter_cutoff + voice.cutoff_modulation + cutoff_mod).clamp(20.0, 20000.0), + }; + } + + if self.filter_wet_2 > 0.0 { + // Filter state movement code + ////////////////////////////////////////// + // If a note is ending and we should enter releasing + if note_off { + let old_filter_state = voice.filter_state_2; + voice.filter_state_2 = OscState::Releasing; + // Reset our filter release to be at sustain level to start + voice.filter_rel_smoother_2.reset( + match old_filter_state { + OscState::Attacking => voice.filter_atk_smoother_2.next(), + OscState::Decaying | OscState::Releasing => voice.filter_dec_smoother_2.next(), + OscState::Sustaining => voice.filter_dec_smoother_2.next(), + OscState::Off => self.filter_cutoff_2, + }, + ); + // Move release to the cutoff to end + voice.filter_rel_smoother_2.set_target(self.sample_rate, self.filter_cutoff_2); + } + + // If our attack has finished + if voice.filter_atk_smoother_2.steps_left() == 0 && voice.filter_state_2 == OscState::Attacking + { + voice.filter_state_2 = OscState::Decaying; + // This makes our filter decay start at env peak point + voice.filter_dec_smoother_2.reset( + voice.filter_atk_smoother_2.next().clamp(20.0, 20000.0), + ); + // Set up the smoother for our filter movement to go from our decay point to our sustain point + voice.filter_dec_smoother_2.set_target( + self.sample_rate, + ( + self.filter_cutoff_2 + + // This scales the peak env to be much gentler for the TILT filter + match self.filter_alg_type_2 { + FilterAlgorithms::SVF | FilterAlgorithms::VCF | FilterAlgorithms::V4 | FilterAlgorithms::A4I => self.filter_env_peak_2, + FilterAlgorithms::TILT => adv_scale_value( + self.filter_env_peak_2, + -19980.0, + 19980.0, + -5000.0, + 5000.0, + ), + } + ).clamp(20.0, 20000.0) + * (self.filter_env_sustain_2 / 1999.9), + ); + } + + // If our decay has finished move to sustain state + if voice.filter_dec_smoother_2.steps_left() == 0 + && voice.filter_state_2 == OscState::Decaying + { + voice.filter_state_2 = OscState::Sustaining; + } + // use proper variable now that there are four filters and multiple states + next_filter_step_2 = match voice.filter_state_2 { + OscState::Attacking => { + (voice.filter_atk_smoother_2.next() + voice.cutoff_modulation_2 + cutoff_mod_2).clamp(20.0, 20000.0) + } + OscState::Decaying => { + (voice.filter_dec_smoother_2.next() + voice.cutoff_modulation_2 + cutoff_mod_2).clamp(20.0, 20000.0) + } + OscState::Sustaining => { + (voice.filter_dec_smoother_2.next() + voice.cutoff_modulation_2 + cutoff_mod_2).clamp(20.0, 20000.0) + } + OscState::Releasing => { + if self.filter_env_release_2 <= 0.0001 { + (voice.filter_dec_smoother_2.next() + voice.cutoff_modulation_2 + cutoff_mod_2).clamp(20.0, 20000.0) + } else { + (voice.filter_rel_smoother_2.next() + voice.cutoff_modulation_2 + cutoff_mod_2).clamp(20.0, 20000.0) + } + } + // I don't expect this to be used + _ => (self.filter_cutoff_2 + voice.cutoff_modulation_2 + cutoff_mod_2).clamp(20.0, 20000.0), + }; + } + + ////////////////////////////////////////////////////////////////////////// + // POLYFILTER UPDATE + ////////////////////////////////////////////////////////////////////////// + match self.audio_module_routing { + AMFilterRouting::Bypass | AMFilterRouting::UNSETROUTING => { + left_output += center_voices_l + stereo_voices_l; + right_output += center_voices_r + stereo_voices_r; + }, + AMFilterRouting::Filter1 => { + left_output_filter1 = center_voices_l + stereo_voices_l; + right_output_filter1 = center_voices_r + stereo_voices_r; + }, + AMFilterRouting::Filter2 => { + left_output_filter2 = center_voices_l + stereo_voices_l; + right_output_filter2 = center_voices_r + stereo_voices_r; + }, + AMFilterRouting::Both => { + left_output_filter1 = center_voices_l + stereo_voices_l; + right_output_filter1 = center_voices_r + stereo_voices_r; + left_output_filter2 = center_voices_l + stereo_voices_l; + right_output_filter2 = center_voices_r + stereo_voices_r; + }, + } + + if self.audio_module_routing != AMFilterRouting::Bypass { + match self.filter_routing { + FilterRouting::Parallel => { + let (filter1_processed_l,filter1_processed_r) = filter_process_1( + self.filter_alg_type.clone(), + self.filter_resonance, + self.sample_rate, + self.filter_res_type.clone(), + self.lp_amount, + self.bp_amount, + self.hp_amount, + self.filter_wet, + self.tilt_filter_type.clone(), + self.vcf_filter_type.clone(), + voice, + next_filter_step, + resonance_mod, + left_output_filter1, + right_output_filter1, + ); + let (filter2_processed_l,filter2_processed_r) = filter_process_2( + self.filter_alg_type_2.clone(), + self.filter_resonance_2, + self.sample_rate, + self.filter_res_type_2.clone(), + self.lp_amount_2, + self.bp_amount_2, + self.hp_amount_2, + self.filter_wet_2, + self.tilt_filter_type_2.clone(), + self.vcf_filter_type_2.clone(), + voice, + next_filter_step_2, + resonance_mod_2, + left_output_filter2, + right_output_filter2, + ); + left_output += filter1_processed_l + filter2_processed_l; + right_output += filter1_processed_r + filter2_processed_r; + } + FilterRouting::Series12 => { + let (filter1_processed_l,filter1_processed_r) = filter_process_1( + self.filter_alg_type.clone(), + self.filter_resonance, + self.sample_rate, + self.filter_res_type.clone(), + self.lp_amount, + self.bp_amount, + self.hp_amount, + self.filter_wet, + self.tilt_filter_type.clone(), + self.vcf_filter_type.clone(), + voice, + next_filter_step, + resonance_mod, + left_output_filter1, + right_output_filter1, + ); + let (filter2_processed_l,filter2_processed_r) = filter_process_2( + self.filter_alg_type_2.clone(), + self.filter_resonance_2, + self.sample_rate, + self.filter_res_type_2.clone(), + self.lp_amount_2, + self.bp_amount_2, + self.hp_amount_2, + self.filter_wet_2, + self.tilt_filter_type_2.clone(), + self.vcf_filter_type_2.clone(), + voice, + next_filter_step_2, + resonance_mod_2, + left_output_filter2 + filter1_processed_l, + right_output_filter2 + filter1_processed_r, + ); + left_output += filter2_processed_l; + right_output += filter2_processed_r; + } + FilterRouting::Series21 => { + let (filter2_processed_l,filter2_processed_r) = filter_process_2( + self.filter_alg_type_2.clone(), + self.filter_resonance_2, + self.sample_rate, + self.filter_res_type_2.clone(), + self.lp_amount_2, + self.bp_amount_2, + self.hp_amount_2, + self.filter_wet_2, + self.tilt_filter_type_2.clone(), + self.vcf_filter_type_2.clone(), + voice, + next_filter_step_2, + resonance_mod_2, + left_output_filter2, + right_output_filter2, + ); + let (filter1_processed_l,filter1_processed_r) = filter_process_1( + self.filter_alg_type.clone(), + self.filter_resonance, + self.sample_rate, + self.filter_res_type.clone(), + self.lp_amount, + self.bp_amount, + self.hp_amount, + self.filter_wet, + self.tilt_filter_type.clone(), + self.vcf_filter_type.clone(), + voice, + next_filter_step, + resonance_mod, + left_output_filter1 + filter2_processed_l, + right_output_filter1 + filter2_processed_r, + ); + left_output += filter1_processed_l; + right_output += filter1_processed_r; + } + } + } + } + + + + + + + + + + // Sum our voices for output + summed_voices_l += left_output; + summed_voices_r += right_output; + // Scaling of output based on stereo voices and unison + //summed_voices_l += stereo_voices_l / (self.osc_unison - 1).clamp(1, 9) as f32; + //summed_voices_r += stereo_voices_r / (self.osc_unison - 1).clamp(1, 9) as f32; + + // Stereo Spreading code + let width_coeff = self.osc_stereo * 0.5; + let mid = (summed_voices_l + summed_voices_r) * 0.5; + let stereo = (summed_voices_r - summed_voices_l) * width_coeff; + summed_voices_l = mid - stereo; + summed_voices_r = mid + stereo; + + (summed_voices_l, summed_voices_r) + }, + AudioModuleType::Off | AudioModuleType::UnsetAm => { + // Do nothing, return 0.0 + (0.0, 0.0) + }, + AudioModuleType::Granulizer => { + let mut summed_voices_l: f32 = 0.0; + let mut summed_voices_r: f32 = 0.0; + for voice in self.playing_voices.voices.iter_mut() { + // Get our current gain amount for use in match below + let temp_osc_gain_multiplier: f32 = match voice.state { + OscState::Attacking => voice.osc_attack.next(), + OscState::Decaying => voice.osc_decay.next(), + OscState::Sustaining => self.osc_sustain / 1999.9, + OscState::Releasing => voice.osc_release.next(), + OscState::Off => 0.0, + }; + voice.amp_current = temp_osc_gain_multiplier; + + let usize_note = voice.note as usize; + + // If we even have valid samples loaded + if self.sample_lib[0][0].len() > 1 + && self.loaded_sample[0].len() > 1 + && self.sample_lib.len() > 1 + { + // Use our Vec>> + // If our note is valid 0-127 + if usize_note < self.sample_lib.len() { + // If our sample position is valid for our note + if voice.sample_pos < self.sample_lib[usize_note][0].len() { + // Get our channels of sample vectors + let NoteVector = &self.sample_lib[usize_note]; + // If we are in crossfade or in middle of grain after atttack ends + if voice.grain_state == GrainState::Attacking { + // Add our current grain + if voice.grain_attack.steps_left() != 0 { + // This format is: Output = CurrentSample * Voice ADSR * GrainRelease + summed_voices_l += NoteVector[0][voice.sample_pos] + * temp_osc_gain_multiplier + * voice.grain_attack.next(); + summed_voices_r += NoteVector[1][voice.sample_pos] + * temp_osc_gain_multiplier + * voice.grain_attack.next(); + } else { + // This format is: Output = CurrentSample * Voice ADSR * GrainRelease + summed_voices_l += NoteVector[0][voice.sample_pos] + * temp_osc_gain_multiplier; + summed_voices_r += NoteVector[1][voice.sample_pos] + * temp_osc_gain_multiplier; + } + } + // If we are in crossfade + else if voice.grain_state == GrainState::Releasing { + summed_voices_l += NoteVector[0][voice.sample_pos] + * temp_osc_gain_multiplier + * voice.grain_release.next(); + summed_voices_r += NoteVector[1][voice.sample_pos] + * temp_osc_gain_multiplier + * voice.grain_release.next(); + } + } + } + let scaled_start_position = (self.loaded_sample[0].len() as f32 + * self.start_position) + .floor() as usize; + let scaled_end_position = (self.loaded_sample[0].len() as f32 + * self._end_position) + .floor() as usize; + // Granulizer moves position + voice.sample_pos += 1; + if voice.loop_it + && (voice.sample_pos > self.loaded_sample[0].len() + || voice.sample_pos > scaled_end_position) + { + voice.sample_pos = scaled_start_position; + } else if voice.sample_pos > scaled_end_position { + voice.sample_pos = self.sample_lib[usize_note][0].len(); + voice.state = OscState::Off; + } + } + } + + + for voice in self.playing_voices.voices.iter_mut() { + ////////////////////////////////////////////////////////////////////////// + // POLYFILTER UPDATE + ////////////////////////////////////////////////////////////////////////// + + // Filter 1 Processing + /////////////////////////////////////////////////////////////// + let mut next_filter_step: f32 = 0.0; + let mut next_filter_step_2: f32 = 0.0; + if self.filter_wet > 0.0 { + // Filter state movement code + ////////////////////////////////////////// + // If a note is ending and we should enter releasing + if note_off { + let old_filter_state = voice.filter_state_1; + voice.filter_state_1 = OscState::Releasing; + // Reset our filter release to be at sustain level to start + voice.filter_rel_smoother_1.reset( + match old_filter_state { + OscState::Attacking => voice.filter_atk_smoother_1.next(), + OscState::Decaying | OscState::Releasing => voice.filter_dec_smoother_1.next(), + OscState::Sustaining => voice.filter_dec_smoother_1.next(), + OscState::Off => self.filter_cutoff, + }, + ); + // Move release to the cutoff to end + voice.filter_rel_smoother_1.set_target(self.sample_rate, self.filter_cutoff); + } + + // If our attack has finished + if voice.filter_atk_smoother_1.steps_left() == 0 && voice.filter_state_1 == OscState::Attacking + { + voice.filter_state_1 = OscState::Decaying; + // This makes our filter decay start at env peak point + voice.filter_dec_smoother_1.reset( + voice.filter_atk_smoother_1.next().clamp(20.0, 20000.0), + ); + // Set up the smoother for our filter movement to go from our decay point to our sustain point + voice.filter_dec_smoother_1.set_target( + self.sample_rate, + ( + self.filter_cutoff + + // This scales the peak env to be much gentler for the TILT filter + match self.filter_alg_type { + FilterAlgorithms::SVF | FilterAlgorithms::VCF | FilterAlgorithms::V4 | FilterAlgorithms::A4I => self.filter_env_peak, + FilterAlgorithms::TILT => adv_scale_value( + self.filter_env_peak, + -19980.0, + 19980.0, + -5000.0, + 5000.0, + ), + } + ).clamp(20.0, 20000.0) + * (self.filter_env_sustain / 1999.9), + ); + } + + // If our decay has finished move to sustain state + if voice.filter_dec_smoother_1.steps_left() == 0 + && voice.filter_state_1 == OscState::Decaying + { + voice.filter_state_1 = OscState::Sustaining; + } + // use proper variable now that there are four filters and multiple states + // This double addition of voice.cutoff_modulation + cutoff_mod will stack the mod at the time of the voice movement with the current + next_filter_step = match voice.filter_state_1 { + OscState::Attacking => { + (voice.filter_atk_smoother_1.next() + voice.cutoff_modulation + cutoff_mod).clamp(20.0, 20000.0) + } + OscState::Decaying => { + (voice.filter_dec_smoother_1.next() + voice.cutoff_modulation + cutoff_mod).clamp(20.0, 20000.0) + } + OscState::Sustaining => { + (voice.filter_dec_smoother_1.next() + voice.cutoff_modulation + cutoff_mod).clamp(20.0, 20000.0) + } + OscState::Releasing => { + if self.filter_env_release <= 0.0001 { + (voice.filter_dec_smoother_1.next() + voice.cutoff_modulation + cutoff_mod).clamp(20.0, 20000.0) + } else { + (voice.filter_rel_smoother_1.next() + voice.cutoff_modulation + cutoff_mod).clamp(20.0, 20000.0) + } + } + // I don't expect this to be used + _ => (self.filter_cutoff + voice.cutoff_modulation + cutoff_mod).clamp(20.0, 20000.0), + }; + } + + if self.filter_wet_2 > 0.0 { + // Filter state movement code + ////////////////////////////////////////// + // If a note is ending and we should enter releasing + if note_off { + let old_filter_state = voice.filter_state_2; + voice.filter_state_2 = OscState::Releasing; + // Reset our filter release to be at sustain level to start + voice.filter_rel_smoother_2.reset( + match old_filter_state { + OscState::Attacking => voice.filter_atk_smoother_2.next(), + OscState::Decaying | OscState::Releasing => voice.filter_dec_smoother_2.next(), + OscState::Sustaining => voice.filter_dec_smoother_2.next(), + OscState::Off => self.filter_cutoff_2, + }, + ); + // Move release to the cutoff to end + voice.filter_rel_smoother_2.set_target(self.sample_rate, self.filter_cutoff_2); + } + + // If our attack has finished + if voice.filter_atk_smoother_2.steps_left() == 0 && voice.filter_state_2 == OscState::Attacking + { + voice.filter_state_2 = OscState::Decaying; + // This makes our filter decay start at env peak point + voice.filter_dec_smoother_2.reset( + voice.filter_atk_smoother_2.next().clamp(20.0, 20000.0), + ); + // Set up the smoother for our filter movement to go from our decay point to our sustain point + voice.filter_dec_smoother_2.set_target( + self.sample_rate, + ( + self.filter_cutoff_2 + + // This scales the peak env to be much gentler for the TILT filter + match self.filter_alg_type_2 { + FilterAlgorithms::SVF | FilterAlgorithms::VCF | FilterAlgorithms::V4 | FilterAlgorithms::A4I => self.filter_env_peak_2, + FilterAlgorithms::TILT => adv_scale_value( + self.filter_env_peak_2, + -19980.0, + 19980.0, + -5000.0, + 5000.0, + ), + } + ).clamp(20.0, 20000.0) + * (self.filter_env_sustain_2 / 1999.9), + ); + } + + // If our decay has finished move to sustain state + if voice.filter_dec_smoother_2.steps_left() == 0 + && voice.filter_state_2 == OscState::Decaying + { + voice.filter_state_2 = OscState::Sustaining; + } + // use proper variable now that there are four filters and multiple states + next_filter_step_2 = match voice.filter_state_2 { + OscState::Attacking => { + (voice.filter_atk_smoother_2.next() + voice.cutoff_modulation_2 + cutoff_mod_2).clamp(20.0, 20000.0) + } + OscState::Decaying => { + (voice.filter_dec_smoother_2.next() + voice.cutoff_modulation_2 + cutoff_mod_2).clamp(20.0, 20000.0) + } + OscState::Sustaining => { + (voice.filter_dec_smoother_2.next() + voice.cutoff_modulation_2 + cutoff_mod_2).clamp(20.0, 20000.0) + } + OscState::Releasing => { + if self.filter_env_release_2 <= 0.0001 { + (voice.filter_dec_smoother_2.next() + voice.cutoff_modulation_2 + cutoff_mod_2).clamp(20.0, 20000.0) + } else { + (voice.filter_rel_smoother_2.next() + voice.cutoff_modulation_2 + cutoff_mod_2).clamp(20.0, 20000.0) + } + } + // I don't expect this to be used + _ => (self.filter_cutoff_2 + voice.cutoff_modulation_2 + cutoff_mod_2).clamp(20.0, 20000.0), + }; + } + + ////////////////////////////////////////////////////////////////////////// + // POLYFILTER UPDATE + ////////////////////////////////////////////////////////////////////////// + match self.audio_module_routing { + AMFilterRouting::Bypass | AMFilterRouting::UNSETROUTING => { + left_output += summed_voices_l; + right_output += summed_voices_r; + }, + AMFilterRouting::Filter1 => { + left_output_filter1 = summed_voices_l; + right_output_filter1 = summed_voices_r; + }, + AMFilterRouting::Filter2 => { + left_output_filter2 = summed_voices_l; + right_output_filter2 = summed_voices_r; + }, + AMFilterRouting::Both => { + left_output_filter1 = summed_voices_l; + right_output_filter1 = summed_voices_r; + left_output_filter2 = summed_voices_l; + right_output_filter2 = summed_voices_r; + }, + } + + if self.audio_module_routing != AMFilterRouting::Bypass { + match self.filter_routing { + FilterRouting::Parallel => { + let (filter1_processed_l,filter1_processed_r) = filter_process_1( + self.filter_alg_type.clone(), + self.filter_resonance, + self.sample_rate, + self.filter_res_type.clone(), + self.lp_amount, + self.bp_amount, + self.hp_amount, + self.filter_wet, + self.tilt_filter_type.clone(), + self.vcf_filter_type.clone(), + voice, + next_filter_step, + resonance_mod, + left_output_filter1, + right_output_filter1, + ); + let (filter2_processed_l,filter2_processed_r) = filter_process_2( + self.filter_alg_type_2.clone(), + self.filter_resonance_2, + self.sample_rate, + self.filter_res_type_2.clone(), + self.lp_amount_2, + self.bp_amount_2, + self.hp_amount_2, + self.filter_wet_2, + self.tilt_filter_type_2.clone(), + self.vcf_filter_type_2.clone(), + voice, + next_filter_step_2, + resonance_mod_2, + left_output_filter2, + right_output_filter2, + ); + left_output += filter1_processed_l + filter2_processed_l; + right_output += filter1_processed_r + filter2_processed_r; + } + FilterRouting::Series12 => { + let (filter1_processed_l,filter1_processed_r) = filter_process_1( + self.filter_alg_type.clone(), + self.filter_resonance, + self.sample_rate, + self.filter_res_type.clone(), + self.lp_amount, + self.bp_amount, + self.hp_amount, + self.filter_wet, + self.tilt_filter_type.clone(), + self.vcf_filter_type.clone(), + voice, + next_filter_step, + resonance_mod, + left_output_filter1, + right_output_filter1, + ); + let (filter2_processed_l,filter2_processed_r) = filter_process_2( + self.filter_alg_type_2.clone(), + self.filter_resonance_2, + self.sample_rate, + self.filter_res_type_2.clone(), + self.lp_amount_2, + self.bp_amount_2, + self.hp_amount_2, + self.filter_wet_2, + self.tilt_filter_type_2.clone(), + self.vcf_filter_type_2.clone(), + voice, + next_filter_step_2, + resonance_mod_2, + left_output_filter2 + filter1_processed_l, + right_output_filter2 + filter1_processed_r, + ); + left_output += filter2_processed_l; + right_output += filter2_processed_r; + } + FilterRouting::Series21 => { + let (filter2_processed_l,filter2_processed_r) = filter_process_2( + self.filter_alg_type_2.clone(), + self.filter_resonance_2, + self.sample_rate, + self.filter_res_type_2.clone(), + self.lp_amount_2, + self.bp_amount_2, + self.hp_amount_2, + self.filter_wet_2, + self.tilt_filter_type_2.clone(), + self.vcf_filter_type_2.clone(), + voice, + next_filter_step_2, + resonance_mod_2, + left_output_filter2, + right_output_filter2, + ); + let (filter1_processed_l,filter1_processed_r) = filter_process_1( + self.filter_alg_type.clone(), + self.filter_resonance, + self.sample_rate, + self.filter_res_type.clone(), + self.lp_amount, + self.bp_amount, + self.hp_amount, + self.filter_wet, + self.tilt_filter_type.clone(), + self.vcf_filter_type.clone(), + voice, + next_filter_step, + resonance_mod, + left_output_filter1 + filter2_processed_l, + right_output_filter1 + filter2_processed_r, + ); + left_output += filter1_processed_l; + right_output += filter1_processed_r; + } + } + } + } + + //(summed_voices_l, summed_voices_r) + (left_output, right_output) }, }; diff --git a/src/audio_module/Oscillator.rs b/src/audio_module/Oscillator.rs index ab5c643..555c01e 100644 --- a/src/audio_module/Oscillator.rs +++ b/src/audio_module/Oscillator.rs @@ -515,7 +515,7 @@ pub enum RetriggerStyle { Free, Retrigger, Random, - UniRandom, + MRandom, } // Super useful function to scale an input 0-1 into other ranges diff --git a/src/fx/VCFilter.rs b/src/fx/VCFilter.rs index e987b1f..3bc7115 100644 --- a/src/fx/VCFilter.rs +++ b/src/fx/VCFilter.rs @@ -1,3 +1,4 @@ + use nih_plug::params::enums::Enum; use serde::{Deserialize, Serialize}; @@ -51,7 +52,7 @@ impl VCFilter { sample_rate: f32, ) { let mut recalculate = false; - if self.center_freq.clamp(20.0, 17000.0) != center_freq.clamp(20.0, 17000.0) { + if (self.center_freq.clamp(20.0, 17000.0) != center_freq.clamp(20.0, 17000.0) ) || ( center_freq > 17000.0 && self.center_freq != center_freq ) { self.center_freq = center_freq.clamp(20.0, 17000.0); recalculate = true; } @@ -67,7 +68,7 @@ impl VCFilter { recalculate = true; } if recalculate { - self.f = 2.0 * self.center_freq / self.sample_rate; + self.f = (2.0 * self.center_freq / self.sample_rate).min(0.99); self.k = 3.6 * self.f - 1.6 * self.f * self.f - 1.0; self.p = (self.k + 1.0) * 0.5; let scale = (1.0 - self.p).exp() * 0.9; @@ -76,12 +77,16 @@ impl VCFilter { } pub fn process(&mut self, input: f32) -> f32 { + // Catch coming from another filter + if self.center_freq > 17000.0 { + self.update(self.center_freq, self.resonance, self.shape.clone(), self.sample_rate); + } let x = input - self.r * self.y[3]; - self.y[0] = x * self.p + self.olds[0] * self.p - self.k * self.y[0]; + self.y[0] = x * self.p + self.olds[0] * self.p - self.k * self.y[0]; self.y[1] = self.y[0] * self.p + self.olds[1] * self.p - self.k * self.y[1]; self.y[2] = self.y[1] * self.p + self.olds[2] * self.p - self.k * self.y[2]; self.y[3] = self.y[2] * self.p + self.olds[3] * self.p - self.k * self.y[3]; - self.y[3] = self.y[3] - (self.y[3].powf(3.0)) / 6.0; + self.y[3] = (self.y[3] - (self.y[3].powf(3.0)) / 6.0).clamp(-1.0, 1.0); self.olds[0] = x; self.olds[1] = self.y[0]; self.olds[2] = self.y[1]; diff --git a/src/fx/aw_galactic_reverb.rs b/src/fx/aw_galactic_reverb.rs index 3634bfa..39ccc07 100644 --- a/src/fx/aw_galactic_reverb.rs +++ b/src/fx/aw_galactic_reverb.rs @@ -387,8 +387,9 @@ impl GalacticReverb { self.iir_b_r = (self.iir_b_r * (1.0 - self.lowpass)) + (output_r * self.lowpass); output_r = self.iir_b_r; - output_l = input_l * (1.0 - self.wet) + output_l * self.wet; - output_r = input_r * (1.0 - self.wet) + output_r * self.wet; + // Changed this wet summing to match my other reverb + output_l = input_l + output_l * self.wet; + output_r = input_r + output_r * self.wet; (output_l, output_r) } } \ No newline at end of file