Skip to content

Commit cbcf4dc

Browse files
authored
fix: shared oversampling control with race condition fixes (#227)
1 parent e75ab07 commit cbcf4dc

File tree

13 files changed

+290
-122
lines changed

13 files changed

+290
-122
lines changed

presets/High_Gain.json

Lines changed: 96 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,63 +3,127 @@
33
"description": null,
44
"author": null,
55
"stages": [
6+
{
7+
"NoiseGate": {
8+
"threshold_db": -40.0,
9+
"ratio": 10.0,
10+
"attack_ms": 0.5,
11+
"hold_ms": 80.0,
12+
"release_ms": 150.0,
13+
"bypassed": false
14+
}
15+
},
616
{
717
"Preamp": {
8-
"gain": 6.7000003,
9-
"bias": 0.0,
10-
"clipper_type": "ClassA"
18+
"gain": 3.5,
19+
"bias": 0.05,
20+
"clipper_type": "Triode",
21+
"bypassed": false
1122
}
1223
},
1324
{
14-
"ToneStack": {
15-
"model": "British",
16-
"bass": 0.75,
17-
"mid": 1.0,
18-
"treble": 0.95,
19-
"presence": 0.65000004
25+
"Preamp": {
26+
"gain": 5.2000003,
27+
"bias": 0.40000004,
28+
"clipper_type": "Triode",
29+
"bypassed": false
2030
}
2131
},
2232
{
23-
"Compressor": {
24-
"attack_ms": 1.0,
25-
"release_ms": 80.0,
26-
"threshold_db": -16.0,
27-
"ratio": 4.0,
28-
"makeup_db": 16.7
33+
"Preamp": {
34+
"gain": 4.2,
35+
"bias": -0.1,
36+
"clipper_type": "Triode",
37+
"bypassed": false
2938
}
3039
},
3140
{
3241
"Preamp": {
33-
"gain": 6.8,
34-
"bias": 1.4901161e-08,
35-
"clipper_type": "Hard"
42+
"gain": 5.0,
43+
"bias": 0.15,
44+
"clipper_type": "Triode",
45+
"bypassed": false
3646
}
3747
},
3848
{
39-
"Level": {
40-
"gain": 0.5
49+
"Preamp": {
50+
"gain": 5.8,
51+
"bias": -0.05,
52+
"clipper_type": "Triode",
53+
"bypassed": false
4154
}
4255
},
4356
{
44-
"NoiseGate": {
45-
"threshold_db": -13.0,
46-
"ratio": 10.0,
47-
"attack_ms": 3.7,
48-
"hold_ms": 10.0,
49-
"release_ms": 100.0
57+
"ToneStack": {
58+
"model": "American",
59+
"bass": 0.90000004,
60+
"mid": 1.0,
61+
"treble": 1.0,
62+
"presence": 0.65000004,
63+
"bypassed": false
64+
}
65+
},
66+
{
67+
"PowerAmp": {
68+
"drive": 0.15,
69+
"amp_type": "ClassAB",
70+
"sag": 0.3,
71+
"sag_release": 120.0,
72+
"bypassed": false
73+
}
74+
},
75+
{
76+
"MultibandSaturator": {
77+
"low_drive": 0.06,
78+
"mid_drive": 0.16,
79+
"high_drive": 0.099999994,
80+
"low_level": 0.72999996,
81+
"mid_level": 0.87,
82+
"high_level": 0.84,
83+
"low_freq": 347.0,
84+
"high_freq": 2810.0,
85+
"bypassed": false
86+
}
87+
},
88+
{
89+
"Eq": {
90+
"gains": [
91+
-7.7999997,
92+
-5.9,
93+
-3.1999998,
94+
-3.4999998,
95+
-1.9999999,
96+
-1.0999998,
97+
-0.09999982,
98+
0.5000002,
99+
0.0,
100+
-1.5999999,
101+
1.7881393e-7,
102+
-0.79999983,
103+
0.0,
104+
-1.5999999,
105+
-2.6999998,
106+
-3.4999998
107+
],
108+
"bypassed": false
50109
}
51110
},
52111
{
53-
"Level": {
54-
"gain": 1.0
112+
"Reverb": {
113+
"room_size": 0.22,
114+
"damping": 0.72999996,
115+
"mix": 0.2,
116+
"bypassed": false
55117
}
56118
}
57119
],
58-
"ir_name": "Jesterdyne/Engl/sc450-left-01.wav",
120+
"ir_name": "Science Amplification/4x12/G12H-150/SM57 Darker.wav",
121+
"ir_gain": 0.21,
122+
"pitch_shift_semitones": 0,
59123
"input_filters": {
60124
"hp_enabled": true,
61-
"hp_cutoff": 187.0,
125+
"hp_cutoff": 80.0,
62126
"lp_enabled": true,
63-
"lp_cutoff": 9913.0
127+
"lp_cutoff": 9000.0
64128
}
65-
}
129+
}

rustortion-plugin/src/backend.rs

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ pub struct PluginBackend {
2020
shared_state: Arc<SharedState>,
2121
capabilities: Capabilities,
2222
sample_rate: f32,
23-
oversampling_factor: u32,
2423
}
2524

2625
impl PluginBackend {
@@ -31,7 +30,6 @@ impl PluginBackend {
3130
ir_loader: Option<Arc<IrLoader>>,
3231
shared_state: Arc<SharedState>,
3332
sample_rate: f32,
34-
oversampling_factor: u32,
3533
) -> Self {
3634
Self {
3735
engine_handle,
@@ -41,7 +39,6 @@ impl PluginBackend {
4139
shared_state,
4240
capabilities: Capabilities::plugin(),
4341
sample_rate,
44-
oversampling_factor,
4542
}
4643
}
4744

@@ -50,9 +47,16 @@ impl PluginBackend {
5047
self.params.chain_state.lock().ok()?.clone()
5148
}
5249

50+
/// Effective sample rate using the *active* (applied) oversampling factor,
51+
/// not the requested one. This ensures chain rebuilds match the current
52+
/// sampler state.
5353
#[allow(clippy::cast_precision_loss)]
5454
fn effective_sample_rate(&self) -> f32 {
55-
self.sample_rate * self.oversampling_factor as f32
55+
let active = self
56+
.shared_state
57+
.active_oversampling
58+
.load(std::sync::atomic::Ordering::Relaxed);
59+
self.sample_rate * active as f32
5660
}
5761

5862
/// Notify the host that a parameter value changed from the GUI.
@@ -187,10 +191,23 @@ impl ParamBackend for PluginBackend {
187191
self.notify_host_param_changed(param.as_ptr(), param.preview_normalized(idx));
188192
}
189193

190-
fn set_oversampling(&self, _factor: u32) {
191-
// Oversampling changes in plugin mode are handled through the
192-
// nih-plug parameter system and the process() loop. This trait
193-
// method is a no-op for the plugin backend.
194+
fn set_oversampling(&self, factor: u32) {
195+
debug_assert!(
196+
factor.is_power_of_two() && factor > 0 && factor <= 16,
197+
"oversampling factor must be a power of two in [1, 16], got {factor}"
198+
);
199+
self.shared_state
200+
.requested_oversampling
201+
.store(factor, std::sync::atomic::Ordering::Relaxed);
202+
// Sync to params for DAW project persistence
203+
self.params
204+
.oversampling_factor
205+
.store(factor, std::sync::atomic::Ordering::Relaxed);
206+
// Mark DAW session dirty so the new value is saved with the project.
207+
// #[persist] fields are serialized passively and don't trigger a save.
208+
let param = &self.params.preset_idx;
209+
let current = param.modulated_normalized_value();
210+
self.notify_host_param_changed(param.as_ptr(), current);
194211
}
195212

196213
fn sample_rate(&self) -> u32 {
@@ -200,7 +217,9 @@ impl ParamBackend for PluginBackend {
200217
}
201218

202219
fn oversampling_factor(&self) -> u32 {
203-
self.oversampling_factor
220+
self.shared_state
221+
.requested_oversampling
222+
.load(std::sync::atomic::Ordering::Relaxed)
204223
}
205224

206225
fn capabilities(&self) -> &Capabilities {

rustortion-plugin/src/editor.rs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,6 @@ impl Editor for PluginEditor {
7979
.sample_rate
8080
.load(std::sync::atomic::Ordering::Relaxed),
8181
);
82-
let os_idx = self
83-
.shared_state
84-
.oversampling_idx
85-
.load(std::sync::atomic::Ordering::Relaxed);
86-
let oversampling_factor = 2_u32.pow(u32::from(os_idx));
87-
8882
let restored_preset_idx = self.params.preset_idx.value();
8983

9084
let flags = PluginAppFlags {
@@ -94,7 +88,6 @@ impl Editor for PluginEditor {
9488
engine_handle,
9589
ir_loader,
9690
sample_rate,
97-
oversampling_factor,
9891
restored_preset_idx,
9992
};
10093

@@ -145,7 +138,6 @@ struct PluginAppFlags {
145138
engine_handle: Option<rustortion_core::audio::engine::EngineHandle>,
146139
ir_loader: Option<Arc<rustortion_core::ir::loader::IrLoader>>,
147140
sample_rate: f32,
148-
oversampling_factor: u32,
149141
restored_preset_idx: i32,
150142
}
151143

@@ -172,7 +164,6 @@ impl iced_baseview::Application for PluginApp {
172164
flags.ir_loader,
173165
flags.shared_state.clone(),
174166
flags.sample_rate,
175-
flags.oversampling_factor,
176167
);
177168

178169
let available_irs = backend.get_available_irs();
@@ -209,6 +200,7 @@ impl iced_baseview::Application for PluginApp {
209200
preset_handler.load_preset_by_name(name);
210201
}
211202

203+
let oversampling_factor = backend.oversampling_factor();
212204
let shared = SharedApp {
213205
backend,
214206
stages: Vec::new(),
@@ -222,6 +214,7 @@ impl iced_baseview::Application for PluginApp {
222214
peak_meter_display: PeakMeterDisplay::default(),
223215
hotkey_handler: HotkeyHandler::new(HotkeySettings::default()),
224216
input_filter_config: rustortion_core::preset::InputFilterConfig::default(),
217+
oversampling_factor,
225218
is_recording: false,
226219
};
227220

0 commit comments

Comments
 (0)