phosphor_app/state/
params.rs1use super::*;
4
5impl NavState {
6
7 pub fn adjust_synth_param(&mut self, delta: f32) -> Option<(usize, usize, f32)> {
10 let idx = self.clip_view.synth_param_cursor;
11 if let Some(track) = self.tracks.get_mut(self.track_cursor) {
12 if idx < track.synth_params.len() {
13 let is_jupiter = track.instrument_type == Some(InstrumentType::Jupiter8);
16 let is_odyssey = track.instrument_type == Some(InstrumentType::Odyssey);
17 let is_juno = track.instrument_type == Some(InstrumentType::Juno60);
18 let is_dx7 = track.instrument_type == Some(InstrumentType::DX7);
19 let is_discrete = if is_jupiter {
20 phosphor_dsp::jupiter::is_discrete(idx)
21 } else if is_odyssey {
22 phosphor_dsp::odyssey::is_discrete(idx)
23 } else if is_juno {
24 phosphor_dsp::juno::is_discrete(idx)
25 } else if is_dx7 {
26 phosphor_dsp::dx7::is_discrete(idx)
30 } else {
31 idx == 0
32 };
33 let actual_delta = if is_discrete {
34 let step = if is_odyssey {
35 match idx {
36 0 => 1.0 / (phosphor_dsp::odyssey::PATCH_COUNT as f32 - 0.01),
37 6 => 0.34, _ => 0.5,
39 }
40 } else {
41 match track.instrument_type {
42 Some(InstrumentType::DrumRack) => 0.1, _ => 0.25,
44 }
45 };
46 if delta > 0.0 { step } else { -step }
47 } else {
48 delta
49 };
50 let new_val = if is_discrete && is_dx7 {
57 phosphor_dsp::dx7::step_discrete(idx, track.synth_params[idx], delta > 0.0)
58 } else if is_discrete && is_juno {
59 phosphor_dsp::juno::step_discrete(idx, track.synth_params[idx], delta > 0.0)
60 } else if is_discrete && is_jupiter {
61 phosphor_dsp::jupiter::step_discrete(idx, track.synth_params[idx], delta > 0.0)
62 } else {
63 (track.synth_params[idx] + actual_delta).clamp(0.0, 1.0)
64 };
65 track.synth_params[idx] = new_val;
66
67 if idx == 0 {
74 let new_params: Option<Vec<f32>> = match track.instrument_type {
75 Some(InstrumentType::Jupiter8) => {
76 Some(phosphor_dsp::jupiter::Jupiter8Synth::params_for_patch(new_val).to_vec())
77 }
78 Some(InstrumentType::Odyssey) => {
79 Some(phosphor_dsp::odyssey::OdysseySynth::params_for_patch(new_val).to_vec())
80 }
81 Some(InstrumentType::Juno60) => {
82 Some(phosphor_dsp::juno::Juno60Synth::params_for_patch(new_val).to_vec())
83 }
84 _ => None,
85 };
86 if let Some(preset_params) = new_params {
87 for (slot, v) in track.synth_params.iter_mut().zip(preset_params) {
88 *slot = v;
89 }
90 }
91 }
92
93 if let Some(mixer_id) = track.mixer_id {
94 return Some((mixer_id, idx, new_val));
95 }
96 }
97 }
98 None
99 }
100
101
102 pub fn show_current_track_controls(&mut self) {
106 for track in &self.tracks {
108 if let Some(ref h) = track.handle {
109 h.config.midi_active.store(false, std::sync::atomic::Ordering::Relaxed);
110 }
111 }
112
113 if let Some(track) = self.tracks.get(self.track_cursor) {
114 if track.is_live() {
115 if let Some(ref h) = track.handle {
116 h.config.midi_active.store(true, std::sync::atomic::Ordering::Relaxed);
117 }
118 self.clip_view_visible = true;
119
120 let clip_idx = match self.track_element {
122 super::TrackElement::Clip(i) if i < track.clips.len() => i,
123 _ => 0,
124 };
125 self.clip_view_target = Some((self.track_cursor, clip_idx));
126
127 if !track.clips.is_empty() {
129 self.clip_view.clip_tab = ClipTab::PianoRoll;
130 self.clip_view.focus = ClipViewFocus::PianoRoll;
131 self.clip_view.piano_roll.focus = PianoRollFocus::Navigation;
132 self.clip_view.piano_roll.column = 0;
133 } else {
134 self.clip_view.fx_panel_tab = FxPanelTab::Synth;
135 self.clip_view.focus = ClipViewFocus::FxPanel;
136 self.clip_view.synth_param_cursor = 0;
137 }
138 } else {
139 self.clip_view_visible = false;
141 self.clip_view_target = None;
142 }
143 }
144 }
145
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151 use phosphor_dsp::{dx7, juno, jupiter};
152
153 fn dx7_track() -> NavState {
155 let mut nav = NavState::new(super::super::initial_tracks());
156 let mut track = TrackState::new("dx7", 0, true, TrackKind::Instrument, vec![]);
157 track.instrument_type = Some(InstrumentType::DX7);
158 track.synth_params = dx7::PARAM_DEFAULTS.to_vec();
159 nav.tracks.insert(0, track);
160 nav.track_cursor = 0;
161 nav
162 }
163
164 fn selected(nav: &NavState) -> (usize, usize) {
165 let p = &nav.tracks[0].synth_params;
166 (dx7::bank_index(p[dx7::P_BANK]), dx7::patch_index(p[dx7::P_PATCH]))
167 }
168
169 #[test]
170 fn dx7_selectors_move_one_step_per_keypress() {
171 let mut nav = dx7_track();
175 nav.clip_view.synth_param_cursor = dx7::P_PATCH;
176 let (bank, patch) = selected(&nav);
177 for step in 1..=5 {
178 nav.adjust_synth_param(0.05);
179 assert_eq!(selected(&nav), (bank, patch + step), "patch knob step {step}");
180 }
181 for step in (0..5).rev() {
182 nav.adjust_synth_param(-0.05);
183 assert_eq!(selected(&nav), (bank, patch + step), "patch knob back to {step}");
184 }
185
186 nav.clip_view.synth_param_cursor = dx7::P_BANK;
187 for step in 1..dx7::BANK_COUNT {
188 nav.adjust_synth_param(0.05);
189 assert_eq!(selected(&nav), (step, patch), "bank knob step {step}");
190 }
191 for _ in 0..4 {
193 nav.adjust_synth_param(0.05);
194 }
195 assert_eq!(selected(&nav), (dx7::BANK_COUNT - 1, patch));
196 }
197
198 fn juno_track() -> NavState {
200 let mut nav = NavState::new(super::super::initial_tracks());
201 let mut track = TrackState::new("juno", 0, true, TrackKind::Instrument, vec![]);
202 track.instrument_type = Some(InstrumentType::Juno60);
203 track.synth_params = juno::PARAM_DEFAULTS.to_vec();
204 nav.tracks.insert(0, track);
205 nav.track_cursor = 0;
206 nav
207 }
208
209 fn juno_patch(nav: &NavState) -> usize {
210 juno::patch_index(nav.tracks[0].synth_params[juno::P_PATCH])
211 }
212
213 #[test]
214 fn juno_selectors_move_one_step_per_keypress() {
215 let mut nav = juno_track();
221 nav.clip_view.synth_param_cursor = juno::P_PATCH;
222 for step in 1..juno::PATCH_COUNT {
223 nav.adjust_synth_param(0.05);
224 assert_eq!(juno_patch(&nav), step, "patch knob step {step}");
225 }
226 nav.adjust_synth_param(0.05);
227 assert_eq!(juno_patch(&nav), juno::PATCH_COUNT - 1, "patch knob ran off the top");
228 for step in (0..juno::PATCH_COUNT - 1).rev() {
229 nav.adjust_synth_param(-0.05);
230 assert_eq!(juno_patch(&nav), step, "patch knob back to {step}");
231 }
232
233 for _ in 0..juno::PATCH_COUNT {
236 nav.adjust_synth_param(0.05);
237 }
238 let panel = &nav.tracks[0].synth_params;
239 assert_eq!(juno_patch(&nav), juno::PATCH_COUNT - 1);
240 assert!((panel[juno::P_RESO] - 1.0).abs() < 1e-6, "res {}", panel[juno::P_RESO]);
241
242 let mut nav = juno_track();
245 nav.clip_view.synth_param_cursor = juno::P_PWM_MODE;
246 let label = |nav: &NavState| {
247 juno::discrete_label(juno::P_PWM_MODE, nav.tracks[0].synth_params[juno::P_PWM_MODE])
248 };
249 assert_eq!(label(&nav), Some("LFO"));
250 let mut seen = Vec::new();
251 for _ in 0..3 {
252 nav.adjust_synth_param(0.05);
253 seen.push(label(&nav));
254 }
255 assert_eq!(seen, [Some("MAN"), Some("ENV"), Some("ENV")]);
256 }
257
258 fn jupiter_track() -> NavState {
260 let mut nav = NavState::new(super::super::initial_tracks());
261 let mut track = TrackState::new("jupiter", 0, true, TrackKind::Instrument, vec![]);
262 track.instrument_type = Some(InstrumentType::Jupiter8);
263 track.synth_params = jupiter::PARAM_DEFAULTS.to_vec();
264 nav.tracks.insert(0, track);
265 nav.track_cursor = 0;
266 nav
267 }
268
269 #[test]
270 fn jupiter_selectors_move_one_step_per_keypress() {
271 let mut nav = jupiter_track();
276 nav.clip_view.synth_param_cursor = jupiter::P_PATCH;
277 let patch = |nav: &NavState| {
278 jupiter::patch_index(nav.tracks[0].synth_params[jupiter::P_PATCH])
279 };
280 for step in 1..jupiter::PATCH_COUNT {
281 nav.adjust_synth_param(0.05);
282 assert_eq!(patch(&nav), step, "patch knob step {step}");
283 }
284 nav.adjust_synth_param(0.05);
285 assert_eq!(patch(&nav), jupiter::PATCH_COUNT - 1, "patch knob ran off the top");
286 for step in (0..jupiter::PATCH_COUNT - 1).rev() {
287 nav.adjust_synth_param(-0.05);
288 assert_eq!(patch(&nav), step, "patch knob back to {step}");
289 }
290
291 let mut nav = jupiter_track();
294 nav.clip_view.synth_param_cursor = jupiter::P_VCO2_WAVE;
295 let label = |nav: &NavState| {
296 jupiter::discrete_label(
297 jupiter::P_VCO2_WAVE,
298 nav.tracks[0].synth_params[jupiter::P_VCO2_WAVE],
299 )
300 };
301 assert_eq!(label(&nav), Some("SAW"));
302 let mut seen = Vec::new();
303 for _ in 0..3 {
304 nav.adjust_synth_param(0.05);
305 seen.push(label(&nav));
306 }
307 assert_eq!(seen, [Some("PLS"), Some("NOISE"), Some("NOISE")]);
308 }
309
310 #[test]
311 fn the_dx7_bank_knob_is_the_last_parameter() {
312 let nav = dx7_track();
316 assert_eq!(nav.tracks[0].synth_params.len(), dx7::PARAM_COUNT);
317 assert_eq!(dx7::P_BANK, dx7::PARAM_COUNT - 1);
318 assert_eq!(dx7::PARAM_NAMES[dx7::P_GAIN], "gain", "index 0-7 must not move");
319 }
320}