Skip to content

Commit 1e5b521

Browse files
authored
Add Saving and Loading Presets (#40)
* Add Saving and Loading Presets * Feedback
1 parent 2a775a1 commit 1e5b521

18 files changed

Lines changed: 746 additions & 13 deletions

Cargo.lock

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ chrono = "0.4"
1616
clap = {version = "4.5", features = ["derive", "env"] }
1717
rubato = "0.16"
1818
serde = { version = "1.0", features = ["derive"] }
19+
serde_json = "1.0"
1920
crossbeam = "0.8"
2021
iced = "0.13"
2122
log = "0.4"

presets/Clean.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "Clean",
3+
"description": "Clean and pristine tone with subtle warmth",
4+
"author": "Rustortion",
5+
"stages": [
6+
{
7+
"Filter": {
8+
"filter_type": "Highpass",
9+
"cutoff_hz": 80.0,
10+
"resonance": 0.0
11+
}
12+
},
13+
{
14+
"Preamp": {
15+
"gain": 2.0,
16+
"bias": 0.0,
17+
"clipper_type": "Soft"
18+
}
19+
},
20+
{
21+
"ToneStack": {
22+
"model": "American",
23+
"bass": 0.6,
24+
"mid": 0.5,
25+
"treble": 0.7,
26+
"presence": 0.4
27+
}
28+
},
29+
{
30+
"Level": {
31+
"gain": 0.8
32+
}
33+
}
34+
]
35+
}

presets/Crunch.json

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"name": "Crunch",
3+
"description": "Classic rock crunch with mid-forward character",
4+
"author": "Rustortion",
5+
"stages": [
6+
{
7+
"Filter": {
8+
"filter_type": "Highpass",
9+
"cutoff_hz": 100.0,
10+
"resonance": 0.1
11+
}
12+
},
13+
{
14+
"Preamp": {
15+
"gain": 6.0,
16+
"bias": 0.1,
17+
"clipper_type": "Medium"
18+
}
19+
},
20+
{
21+
"ToneStack": {
22+
"model": "British",
23+
"bass": 0.5,
24+
"mid": 0.7,
25+
"treble": 0.6,
26+
"presence": 0.5
27+
}
28+
},
29+
{
30+
"Compressor": {
31+
"attack_ms": 2.0,
32+
"release_ms": 50.0,
33+
"threshold_db": -15.0,
34+
"ratio": 3.0,
35+
"makeup_db": 3.0
36+
}
37+
},
38+
{
39+
"Level": {
40+
"gain": 0.9
41+
}
42+
}
43+
]
44+
}

presets/Lead.json

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"name": "Lead",
3+
"description": "High-gain lead tone with sustain and clarity",
4+
"author": "Rustortion",
5+
"stages": [
6+
{
7+
"Filter": {
8+
"filter_type": "Highpass",
9+
"cutoff_hz": 120.0,
10+
"resonance": 0.2
11+
}
12+
},
13+
{
14+
"Preamp": {
15+
"gain": 8.5,
16+
"bias": 0.0,
17+
"clipper_type": "Asymmetric"
18+
}
19+
},
20+
{
21+
"ToneStack": {
22+
"model": "Modern",
23+
"bass": 0.4,
24+
"mid": 0.8,
25+
"treble": 0.7,
26+
"presence": 0.6
27+
}
28+
},
29+
{
30+
"Compressor": {
31+
"attack_ms": 1.0,
32+
"release_ms": 80.0,
33+
"threshold_db": -12.0,
34+
"ratio": 4.0,
35+
"makeup_db": 6.0
36+
}
37+
},
38+
{
39+
"Level": {
40+
"gain": 1.1
41+
}
42+
}
43+
]
44+
}

src/gui/app.rs

Lines changed: 159 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,64 @@
11
use iced::{Element, Length, Task, Theme};
22
use log::{error, info};
33

4-
use crate::gui::components::{control::Control, stage_list::StageList};
4+
use crate::gui::components::{control::Control, preset_bar::PresetBar, stage_list::StageList};
55
use crate::gui::config::{StageConfig, StageType};
66
use crate::gui::messages::{Message, StageMessage};
7+
use crate::gui::preset::{Preset, PresetManager};
78
use crate::io::manager::ProcessorManager;
89
use crate::sim::chain::AmplifierChain;
910

10-
#[derive(Debug)]
1111
pub struct AmplifierApp {
1212
processor_manager: ProcessorManager,
1313
stages: Vec<StageConfig>,
1414
selected_stage_type: StageType,
1515
is_recording: bool,
16+
preset_manager: PresetManager,
17+
selected_preset: Option<String>,
18+
preset_bar: PresetBar,
19+
new_preset_name: String,
20+
show_save_input: bool,
1621
}
1722

1823
impl AmplifierApp {
1924
pub fn new(processor_manager: ProcessorManager) -> Self {
20-
Self {
25+
let preset_manager = PresetManager::new("./presets").unwrap_or_else(|e| {
26+
error!("Failed to create preset manager: {e}");
27+
// Create an empty preset manager as fallback
28+
PresetManager::new(std::env::temp_dir().join("rustortion_presets_fallback"))
29+
.expect("Failed to create fallback preset manager")
30+
});
31+
32+
let presets = preset_manager.get_presets();
33+
let selected_preset = presets.first().map(|p| p.name.clone());
34+
let preset_bar = PresetBar::new(presets, selected_preset.clone());
35+
36+
// Load the first preset if available
37+
let stages = if let Some(preset_name) = &selected_preset {
38+
preset_manager
39+
.get_preset_by_name(preset_name)
40+
.map(|p| p.stages.clone())
41+
.unwrap_or_default()
42+
} else {
43+
Vec::new()
44+
};
45+
46+
let app = Self {
2147
processor_manager,
22-
stages: Vec::new(),
48+
stages,
2349
selected_stage_type: StageType::default(),
2450
is_recording: false,
25-
}
51+
preset_manager,
52+
selected_preset,
53+
preset_bar,
54+
new_preset_name: String::new(),
55+
show_save_input: false,
56+
};
57+
58+
// Update the processor chain with the loaded preset
59+
app.update_processor_chain();
60+
61+
app
2662
}
2763

2864
pub fn update(&mut self, message: Message) -> Task<Message> {
@@ -55,6 +91,103 @@ impl AmplifierApp {
5591
Message::StageTypeSelected(stage_type) => {
5692
self.selected_stage_type = stage_type;
5793
}
94+
Message::PresetSelected(preset_name) => {
95+
if let Some(preset) = self.preset_manager.get_preset_by_name(&preset_name) {
96+
self.stages = preset.stages.clone();
97+
self.selected_preset = Some(preset_name.clone());
98+
self.preset_bar
99+
.set_selected_preset(Some(preset_name.clone()));
100+
should_update_chain = true;
101+
info!("Loaded preset: {preset_name}");
102+
}
103+
}
104+
Message::ShowSavePreset => {
105+
self.show_save_input = true;
106+
self.preset_bar.show_save_input(true);
107+
}
108+
Message::CancelSavePreset => {
109+
self.show_save_input = false;
110+
self.new_preset_name.clear();
111+
self.preset_bar.show_save_input(false);
112+
}
113+
Message::PresetNameChanged(name) => {
114+
self.new_preset_name = name.clone();
115+
self.preset_bar.set_new_preset_name(name);
116+
}
117+
Message::SavePreset => {
118+
if !self.new_preset_name.trim().is_empty() {
119+
let preset = Preset::new(self.new_preset_name.clone(), self.stages.clone());
120+
121+
match self.preset_manager.save_preset(&preset) {
122+
Ok(()) => {
123+
info!("Saved preset: {}", self.new_preset_name);
124+
self.selected_preset = Some(self.new_preset_name.clone());
125+
self.preset_bar
126+
.update_presets(self.preset_manager.get_presets());
127+
self.preset_bar
128+
.set_selected_preset(Some(self.new_preset_name.clone()));
129+
self.show_save_input = false;
130+
self.preset_bar.show_save_input(false);
131+
self.new_preset_name.clear();
132+
}
133+
Err(e) => {
134+
error!("Failed to save preset: {e}");
135+
}
136+
}
137+
}
138+
}
139+
Message::UpdateCurrentPreset => {
140+
if let Some(ref preset_name) = self.selected_preset {
141+
let preset = Preset::new(preset_name.clone(), self.stages.clone());
142+
143+
match self.preset_manager.save_preset(&preset) {
144+
Ok(()) => {
145+
info!("Updated preset: {preset_name}");
146+
self.preset_bar
147+
.update_presets(self.preset_manager.get_presets());
148+
}
149+
Err(e) => {
150+
error!("Failed to update preset: {e}");
151+
}
152+
}
153+
}
154+
}
155+
Message::DeletePreset(preset_name) => {
156+
match self.preset_manager.delete_preset(&preset_name) {
157+
Ok(()) => {
158+
info!("Deleted preset: {preset_name}");
159+
self.preset_bar
160+
.update_presets(self.preset_manager.get_presets());
161+
162+
// Clear selection if we deleted the current preset
163+
if self.selected_preset.as_ref() == Some(&preset_name) {
164+
self.selected_preset = None;
165+
self.preset_bar.set_selected_preset(None);
166+
// Optionally load the first available preset or clear stages
167+
if let Some(first_preset) = self.preset_manager.get_presets().first() {
168+
self.stages = first_preset.stages.clone();
169+
self.selected_preset = Some(first_preset.name.clone());
170+
self.preset_bar
171+
.set_selected_preset(Some(first_preset.name.clone()));
172+
} else {
173+
self.stages.clear();
174+
}
175+
176+
should_update_chain = true;
177+
}
178+
}
179+
Err(e) => {
180+
error!("Failed to delete preset: {e}");
181+
}
182+
}
183+
}
184+
Message::ConfirmOverwritePreset => {
185+
self.save_current_preset();
186+
self.preset_bar.hide_overwrite_confirmation();
187+
}
188+
Message::CancelOverwritePreset => {
189+
self.preset_bar.hide_overwrite_confirmation();
190+
}
58191
Message::StartRecording => match self.processor_manager.enable_recording() {
59192
Ok(()) => {
60193
self.is_recording = true;
@@ -149,6 +282,26 @@ impl AmplifierApp {
149282
}
150283
}
151284

285+
fn save_current_preset(&mut self) {
286+
let preset = Preset::new(self.new_preset_name.clone(), self.stages.clone());
287+
288+
match self.preset_manager.save_preset(&preset) {
289+
Ok(()) => {
290+
info!("Saved preset: {}", self.new_preset_name);
291+
self.selected_preset = Some(self.new_preset_name.clone());
292+
self.preset_bar
293+
.update_presets(self.preset_manager.get_presets());
294+
self.preset_bar
295+
.set_selected_preset(Some(self.new_preset_name.clone()));
296+
self.show_save_input = false;
297+
self.preset_bar.show_save_input(false);
298+
self.new_preset_name.clear();
299+
}
300+
Err(e) => {
301+
error!("Failed to save preset: {e}");
302+
}
303+
}
304+
}
152305
fn update_processor_chain(&self) {
153306
let sample_rate = self.processor_manager.sample_rate();
154307
let chain = build_amplifier_chain(&self.stages, sample_rate);
@@ -160,6 +313,7 @@ impl AmplifierApp {
160313

161314
container(
162315
column![
316+
self.preset_bar.view(),
163317
StageList::new(&self.stages).view(),
164318
Control::new(self.selected_stage_type, self.is_recording).view()
165319
]

0 commit comments

Comments
 (0)