Skip to content

Commit

Permalink
Merge pull request #41 from ardura/stereo-algorithm-update
Browse files Browse the repository at this point in the history
v1.3.0 update - stereo algorithms and chorus
  • Loading branch information
ardura committed Jun 13, 2024
2 parents 0daeac8 + 2ba4b40 commit c5f469d
Show file tree
Hide file tree
Showing 10 changed files with 2,025 additions and 399 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "Actuate"
version = "1.2.8"
version = "1.3.0"
edition = "2021"
authors = ["Ardura <[email protected]>"]
license = "GPL-3.0-or-later"
Expand Down
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Actuate
# Actuate (Latest is v1.3.0)
A Synthesizer, Sampler, and Granulizer built in Rust + Nih-Plug
Written by Ardura

Expand Down Expand Up @@ -75,11 +75,15 @@ Since Actuate 1.2.8 the new file browser and UI use native text editing. This cr
- Unfortunately I don't know where this would be on Linux or Mac so I'm open to help from Ableton users!

## Roadmap
- [ ] Create an additive module
- [ ] Create filter release bypass toggle
- [ ] Find more things to add here

(old items pre 1.3.0)
- [x] Create a Preset Browser
- [x] Add more reverb styles
- [ ] ~~Add more decay styles~~ Not doing
- [ ] Fix some bandpass glitching on certain filter types (need to find these scenarios still)
- [ ] Create different stereo spreading algorithms
- [x] Fix some bandpass glitching on certain filter types
- [x] Create different stereo spreading algorithms
- [x] Make the GUI nicer - see Discussion https://github.com/ardura/Actuate/discussions/26
- [x] Look into making the preset loading more reliable
- [x] Fix text input not working (right now it's a OS safe workaround)
Expand Down
7 changes: 7 additions & 0 deletions src/actuate_enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,13 @@ pub enum PitchRouting {
Osc2_Osc3,
}

#[derive(Enum, PartialEq, Clone, Copy, Serialize, Deserialize)]
pub enum StereoAlgorithm {
Original,
CubeSpread,
ExpSpread,
}


// These let us output ToString for the ComboBox stuff + Nih-Plug or string usage
impl fmt::Display for PresetType {
Expand Down
56 changes: 44 additions & 12 deletions src/actuate_gui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use nih_plug_egui::{create_egui_editor, egui::{self, Align2, Color32, Pos2, Rect
use crate::{
actuate_enums::{
FilterAlgorithms, LFOSelect, ModulationDestination, ModulationSource, PresetType, UIBottomSelection},
actuate_structs::ActuatePresetV126, audio_module::{AudioModule, AudioModuleType},
actuate_structs::ActuatePresetV130, audio_module::{AudioModule, AudioModuleType},
Actuate, ActuateParams,
CustomWidgets::{
slim_checkbox, toggle_switch, ui_knob::{self, KnobLayout},
Expand All @@ -34,7 +34,7 @@ use crate::{
pub(crate) fn make_actuate_gui(instance: &mut Actuate, _async_executor: AsyncExecutor<Actuate>) -> Option<Box<dyn Editor>> {
let params: Arc<ActuateParams> = instance.params.clone();
let arc_preset_lib_name: Arc<Mutex<String>> = Arc::clone(&instance.preset_lib_name);
let arc_preset: Arc<Mutex<Vec<ActuatePresetV126>>> = Arc::clone(&instance.preset_lib);
let arc_preset: Arc<Mutex<Vec<ActuatePresetV130>>> = Arc::clone(&instance.preset_lib);
let arc_preset_name: Arc<Mutex<String>> = Arc::clone(&instance.preset_name);
let arc_preset_info: Arc<Mutex<String>> = Arc::clone(&instance.preset_info);
let arc_preset_category: Arc<Mutex<PresetType>> = Arc::clone(&instance.preset_category);
Expand Down Expand Up @@ -762,7 +762,7 @@ pub(crate) fn make_actuate_gui(instance: &mut Actuate, _async_executor: AsyncExe
}
} else {
// Filter results
let results: Vec<ActuatePresetV126> = arc_preset.lock().unwrap().clone();
let results: Vec<ActuatePresetV130> = arc_preset.lock().unwrap().clone();
let mut filtered_results: Vec<usize> = Vec::new();
for (index, preset) in results.iter().enumerate() {
if (filter_acid.load(Ordering::SeqCst) && preset.tag_acid == true) ||
Expand Down Expand Up @@ -2219,13 +2219,22 @@ VCF: Voltage Controlled Filter model".to_string());
});
},
LFOSelect::Misc => {
ui.horizontal(|ui|{
ui.label(RichText::new("Link Cutoff 2 to Cutoff 1")
.font(FONT)
)
.on_hover_text("Filter 1 will control both filter cutoff values");
let filter_cutoff_link = toggle_switch::ToggleSwitch::for_param(&params.filter_cutoff_link, setter);
ui.add(filter_cutoff_link);
ui.vertical(|ui|{
ui.horizontal(|ui|{
ui.label(RichText::new("Link Cutoff 2 to Cutoff 1")
.font(FONT)
)
.on_hover_text("Filter 1 will control both filter cutoff values");
let filter_cutoff_link = toggle_switch::ToggleSwitch::for_param(&params.filter_cutoff_link, setter);
ui.add(filter_cutoff_link);
});
ui.horizontal(|ui|{
ui.label(RichText::new("Stereo Behavior")
.font(FONT)
)
.on_hover_text("The stereo algorithm to use for voice spreads");
ui.add(ParamSlider::for_param(&params.stereo_algorithm, setter).with_width(180.0));
});
});
},
LFOSelect::FM => {
Expand Down Expand Up @@ -2776,7 +2785,7 @@ For constant FM, turn Sustain to 100% and A,D,R to 0%".to_string());
if dialog.show(egui_ctx).selected() {
if let Some(file) = dialog.path() {
let opened_file = Some(file.to_path_buf());
let unserialized: Option<ActuatePresetV126>;
let unserialized: Option<ActuatePresetV130>;
(_, unserialized) = Actuate::import_preset(opened_file);

if unserialized.is_some() {
Expand Down Expand Up @@ -2897,7 +2906,7 @@ For constant FM, turn Sustain to 100% and A,D,R to 0%".to_string());
ui.painter().text(popup_pos, Align2::CENTER_CENTER, "Loading...", LOADING_FONT, Color32::BLACK);

let opened_file = Some(file.to_path_buf());
let unserialized: Vec<ActuatePresetV126>;
let unserialized: Vec<ActuatePresetV130>;
(default_name, unserialized) = Actuate::load_preset_bank(opened_file);
let temppath = default_name.clone();
let path = Path::new(&temppath);
Expand Down Expand Up @@ -3140,6 +3149,29 @@ For constant FM, turn Sustain to 100% and A,D,R to 0%".to_string());
.with_width(268.0));
});
ui.separator();
// Chorus
ui.horizontal(|ui|{
ui.label(RichText::new("Chorus")
.font(FONT));
let use_chorus_toggle = toggle_switch::ToggleSwitch::for_param(&params.use_chorus, setter);
ui.add(use_chorus_toggle);
});
ui.vertical(|ui|{
ui.add(CustomParamSlider::ParamSlider::for_param(&params.chorus_amount, setter)
.set_left_sided_label(true)
.set_label_width(84.0)
.with_width(268.0));
ui.add(CustomParamSlider::ParamSlider::for_param(&params.chorus_range, setter)
.slimmer(0.7)
.set_left_sided_label(true)
.set_label_width(84.0)
.with_width(268.0));
ui.add(CustomParamSlider::ParamSlider::for_param(&params.chorus_speed, setter)
.set_left_sided_label(true)
.set_label_width(84.0)
.with_width(268.0));
});
ui.separator();
// Phaser
ui.horizontal(|ui|{
ui.label(RichText::new("Phaser")
Expand Down
12 changes: 10 additions & 2 deletions src/actuate_structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use serde::{Deserialize, Serialize};

use crate::{actuate_enums::{AMFilterRouting, FilterAlgorithms, FilterRouting, ModulationDestination, ModulationSource, PitchRouting, PresetType, ReverbModel}, audio_module::{AudioModuleType, Oscillator::{self, RetriggerStyle, SmoothStyle, VoiceType}}, fx::{delay::{DelaySnapValues, DelayType}, saturation::SaturationType, ArduraFilter, StateVariableFilter::ResonanceType}, LFOController};
use crate::{actuate_enums::{AMFilterRouting, FilterAlgorithms, FilterRouting, ModulationDestination, ModulationSource, PitchRouting, PresetType, ReverbModel, StereoAlgorithm}, audio_module::{AudioModuleType, Oscillator::{self, RetriggerStyle, SmoothStyle, VoiceType}}, fx::{delay::{DelaySnapValues, DelayType}, saturation::SaturationType, ArduraFilter, StateVariableFilter::ResonanceType}, LFOController};

/// Modulation struct for passing mods to audio modules
#[derive(Serialize, Deserialize, Clone)]
Expand All @@ -22,7 +22,7 @@ pub struct ModulationStruct {

/// This is the structure that represents a storable preset value
#[derive(Serialize, Deserialize, Clone)]
pub struct ActuatePresetV126 {
pub struct ActuatePresetV130 {
// Information
pub preset_name: String,
pub preset_info: String,
Expand Down Expand Up @@ -269,6 +269,9 @@ pub struct ActuatePresetV126 {
pub fm_decay_curve: Oscillator::SmoothStyle,
pub fm_release_curve: Oscillator::SmoothStyle,

// Stereo
pub stereo_algorithm: StereoAlgorithm,

// EQ
pub pre_use_eq: bool,
pub pre_low_freq: f32,
Expand Down Expand Up @@ -312,6 +315,11 @@ pub struct ActuatePresetV126 {
pub phaser_rate: f32,
pub phaser_feedback: f32,

pub use_chorus: bool,
pub chorus_amount: f32,
pub chorus_range: f32,
pub chorus_speed: f32,

pub use_buffermod: bool,
pub buffermod_amount: f32,
pub buffermod_depth: f32,
Expand Down
63 changes: 49 additions & 14 deletions src/audio_module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,7 @@ pub(crate) mod Oscillator;
pub(crate) mod frequency_modulation;
use self::Oscillator::{DeterministicWhiteNoiseGenerator, OscState, RetriggerStyle, SmoothStyle};
use crate::{
ActuateParams,
CustomWidgets::{ui_knob::{self, KnobLayout}, CustomVerticalSlider},
PitchRouting, DARK_GREY_UI_COLOR, FONT_COLOR, LIGHTER_GREY_UI_COLOR, MEDIUM_GREY_UI_COLOR, SMALLER_FONT, YELLOW_MUSTARD
actuate_enums::StereoAlgorithm, ActuateParams, CustomWidgets::{ui_knob::{self, KnobLayout}, CustomVerticalSlider}, PitchRouting, DARK_GREY_UI_COLOR, FONT_COLOR, LIGHTER_GREY_UI_COLOR, MEDIUM_GREY_UI_COLOR, SMALLER_FONT, YELLOW_MUSTARD
};
use crate::{CustomWidgets::{BeizerButton::{self, ButtonLayout}, BoolButton}, DARKER_GREY_UI_COLOR};
use CustomVerticalSlider::ParamSlider as VerticalParamSlider;
Expand Down Expand Up @@ -1478,6 +1476,7 @@ Random: Sample uses a new random position every note".to_string());
uni_velocity_mod: f32,
vel_gain_mod: f32,
vel_lfo_gain_mod: f32,
stereo_algorithm: StereoAlgorithm,
) -> (f32, f32, bool, bool) {
// If the process is in here the file dialog is not open per lib.rs

Expand Down Expand Up @@ -1989,7 +1988,7 @@ Random: Sample uses a new random position every note".to_string());
};
let mut unison_angles = vec![0.0; unison_even_voices as usize];
for i in 1..(unison_even_voices + 1) {
let voice_angle = self.calculate_panning(i - 1, self.osc_unison);
let voice_angle = self.calculate_panning(i - 1, self.osc_unison, stereo_algorithm);
unison_angles[(i - 1) as usize] = voice_angle;
}

Expand Down Expand Up @@ -3050,7 +3049,17 @@ Random: Sample uses a new random position every note".to_string());
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;
Expand Down Expand Up @@ -3484,7 +3493,7 @@ Random: Sample uses a new random position every note".to_string());
}
}

fn calculate_panning(&mut self, voice_index: i32, num_voices: i32) -> f32 {
fn calculate_panning(&mut self, voice_index: i32, num_voices: i32, stereo_algorithm: StereoAlgorithm) -> f32 {
// Ensure the voice index is within bounds.
let voice_index = voice_index.min(num_voices - 1);

Expand Down Expand Up @@ -3521,16 +3530,42 @@ Random: Sample uses a new random position every note".to_string());
// Calculate the pan angle for voices with index 0 and 1.
let base_angle = ((voice_index / 2) as f32) / ((num_voices / 2) as f32 - 1.0) - 0.5;

// Determine the final angle based on even or odd index.
let angle = if voice_index % 2 == 0 {
-base_angle
} else {
base_angle
};
let angle: f32;
match stereo_algorithm {
StereoAlgorithm::CubeSpread => {
let poly_base_angle = base_angle.powf(3.0)/0.3;
// Determine the final angle based on even or odd index.
angle = if voice_index % 2 == 0 {
-poly_base_angle
} else {
poly_base_angle
};
},
StereoAlgorithm::ExpSpread => {
let exp_base_angle = base_angle.exp() - 1.0; // Exponential transformation
let max_exp_angle = (1.0f32).exp() - 1.0;
let normalized_exp_angle = exp_base_angle / max_exp_angle;

// Determine the final angle based on even or odd index.
angle = if voice_index % 2 == 0 {
-normalized_exp_angle
} else {
normalized_exp_angle
};
},
StereoAlgorithm::Original => {
// Determine the final angle based on even or odd index.
angle = if voice_index % 2 == 0 {
-base_angle
} else {
base_angle
};
},
}

if voice_index == 0 {
self.two_voice_stereo_flipper = !self.two_voice_stereo_flipper;
}

angle * std::f32::consts::PI * sign // Use full scale for other cases
angle * std::f32::consts::PI * sign
}
}
1 change: 1 addition & 0 deletions src/fx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ pub(crate) mod reverb;
pub(crate) mod aw_galactic_reverb;
pub(crate) mod simple_space_reverb;
pub(crate) mod saturation;
pub(crate) mod chorus;
Loading

0 comments on commit c5f469d

Please sign in to comment.