1use 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 instrument = track.instrument_type?;
24 let new_val = if crate::discrete::is_discrete(instrument, idx) {
25 crate::discrete::step(instrument, idx, track.synth_params[idx], delta > 0.0)
26 } else {
27 (track.synth_params[idx] + delta).clamp(0.0, 1.0)
28 };
29 track.synth_params[idx] = new_val;
30
31 let is_program_selector = idx == 0
36 || (instrument == InstrumentType::Prophet6
37 && idx == phosphor_dsp::prophet6::P_BANK);
38 if is_program_selector {
44 let new_params: Option<Vec<f32>> = match track.instrument_type {
45 Some(InstrumentType::Synth | InstrumentType::Sampler) => {
46 Some(phosphor_dsp::synth::PhosphorSynth::params_for_patch(new_val).to_vec())
47 }
48 Some(InstrumentType::Jupiter8) => {
49 Some(phosphor_dsp::jupiter::Jupiter8Synth::params_for_patch(new_val).to_vec())
50 }
51 Some(InstrumentType::Odyssey) => {
52 Some(phosphor_dsp::odyssey::OdysseySynth::params_for_patch(new_val).to_vec())
53 }
54 Some(InstrumentType::Juno60) => {
55 Some(phosphor_dsp::juno::Juno60Synth::params_for_patch(new_val).to_vec())
56 }
57 Some(InstrumentType::Rhodes) => {
58 Some(phosphor_dsp::rhodes::RhodesPiano::params_for_patch(new_val).to_vec())
59 }
60 Some(InstrumentType::LittlePhatty) => {
61 Some(phosphor_dsp::phatty::LittlePhatty::params_for_patch(new_val).to_vec())
62 }
63 Some(InstrumentType::Prophet6) => Some(
64 phosphor_dsp::prophet6::params_for_program(
65 track.synth_params[phosphor_dsp::prophet6::P_BANK],
66 track.synth_params[phosphor_dsp::prophet6::P_PROGRAM],
67 )
68 .to_vec(),
69 ),
70 _ => None,
71 };
72 if let Some(preset_params) = new_params {
73 for (slot, v) in track.synth_params.iter_mut().zip(preset_params) {
74 *slot = v;
75 }
76 }
77 }
78
79 if let Some(mixer_id) = track.mixer_id {
80 return Some((mixer_id, idx, new_val));
81 }
82 }
83 }
84 None
85 }
86
87
88 pub fn show_current_track_controls(&mut self) {
92 for track in &self.tracks {
94 if let Some(ref h) = track.handle {
95 h.config.midi_active.store(false, std::sync::atomic::Ordering::Relaxed);
96 }
97 }
98
99 if let Some(track) = self.tracks.get(self.track_cursor) {
100 if track.is_live() {
101 if let Some(ref h) = track.handle {
102 h.config.midi_active.store(true, std::sync::atomic::Ordering::Relaxed);
103 }
104 self.clip_view_visible = true;
105
106 let clip_idx = match self.track_element {
108 super::TrackElement::Clip(i) if i < track.clips.len() => i,
109 _ => 0,
110 };
111 self.clip_view_target = Some((self.track_cursor, clip_idx));
112
113 if track.sequencer.is_some() {
117 self.clip_view.clip_tab = ClipTab::Sequencer;
118 self.clip_view.focus = ClipViewFocus::PianoRoll;
119 self.clip_view.sequencer.focus_band(SeqBand::Grid);
120 self.focused_pane = Pane::ClipView;
125 } else if !track.clips.is_empty() {
126 self.clip_view.clip_tab = ClipTab::PianoRoll;
127 self.clip_view.focus = ClipViewFocus::PianoRoll;
128 self.clip_view.piano_roll.focus = PianoRollFocus::Navigation;
129 self.clip_view.piano_roll.column = 0;
130 } else {
131 self.clip_view.fx_panel_tab = FxPanelTab::Synth;
132 self.clip_view.focus = ClipViewFocus::FxPanel;
133 self.clip_view.synth_param_cursor = 0;
134 }
135 } else {
136 self.clip_view_visible = false;
138 self.clip_view_target = None;
139 }
140 }
141 }
142
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148 use phosphor_dsp::{drum_rack, dx7, juno, jupiter, rhodes};
149
150 fn dx7_track() -> NavState {
152 let mut nav = NavState::new(super::super::initial_tracks());
153 let mut track = TrackState::new("dx7", 0, true, TrackKind::Instrument, vec![]);
154 track.instrument_type = Some(InstrumentType::DX7);
155 track.synth_params = dx7::PARAM_DEFAULTS.to_vec();
156 nav.tracks.insert(0, track);
157 nav.track_cursor = 0;
158 nav
159 }
160
161 fn selected(nav: &NavState) -> (usize, usize) {
162 let p = &nav.tracks[0].synth_params;
163 (dx7::bank_index(p[dx7::P_BANK]), dx7::patch_index(p[dx7::P_PATCH]))
164 }
165
166 #[test]
167 fn dx7_selectors_move_one_step_per_keypress() {
168 let mut nav = dx7_track();
172 nav.clip_view.synth_param_cursor = dx7::P_PATCH;
173 let (bank, patch) = selected(&nav);
174 for step in 1..=5 {
175 nav.adjust_synth_param(0.05);
176 assert_eq!(selected(&nav), (bank, patch + step), "patch knob step {step}");
177 }
178 for step in (0..5).rev() {
179 nav.adjust_synth_param(-0.05);
180 assert_eq!(selected(&nav), (bank, patch + step), "patch knob back to {step}");
181 }
182
183 nav.clip_view.synth_param_cursor = dx7::P_BANK;
184 for step in 1..dx7::BANK_COUNT {
185 nav.adjust_synth_param(0.05);
186 assert_eq!(selected(&nav), (step, patch), "bank knob step {step}");
187 }
188 for _ in 0..4 {
190 nav.adjust_synth_param(0.05);
191 }
192 assert_eq!(selected(&nav), (dx7::BANK_COUNT - 1, patch));
193 }
194
195 fn juno_track() -> NavState {
197 let mut nav = NavState::new(super::super::initial_tracks());
198 let mut track = TrackState::new("juno", 0, true, TrackKind::Instrument, vec![]);
199 track.instrument_type = Some(InstrumentType::Juno60);
200 track.synth_params = juno::PARAM_DEFAULTS.to_vec();
201 nav.tracks.insert(0, track);
202 nav.track_cursor = 0;
203 nav
204 }
205
206 fn juno_patch(nav: &NavState) -> usize {
207 juno::patch_index(nav.tracks[0].synth_params[juno::P_PATCH])
208 }
209
210 #[test]
211 fn juno_selectors_move_one_step_per_keypress() {
212 let mut nav = juno_track();
218 nav.clip_view.synth_param_cursor = juno::P_PATCH;
219 for step in 1..juno::PATCH_COUNT {
220 nav.adjust_synth_param(0.05);
221 assert_eq!(juno_patch(&nav), step, "patch knob step {step}");
222 }
223 nav.adjust_synth_param(0.05);
224 assert_eq!(juno_patch(&nav), juno::PATCH_COUNT - 1, "patch knob ran off the top");
225 for step in (0..juno::PATCH_COUNT - 1).rev() {
226 nav.adjust_synth_param(-0.05);
227 assert_eq!(juno_patch(&nav), step, "patch knob back to {step}");
228 }
229
230 for _ in 0..juno::PATCH_COUNT {
233 nav.adjust_synth_param(0.05);
234 }
235 let panel = &nav.tracks[0].synth_params;
236 assert_eq!(juno_patch(&nav), juno::PATCH_COUNT - 1);
237 assert!((panel[juno::P_RESO] - 1.0).abs() < 1e-6, "res {}", panel[juno::P_RESO]);
238
239 let mut nav = juno_track();
242 nav.clip_view.synth_param_cursor = juno::P_PWM_MODE;
243 let label = |nav: &NavState| {
244 juno::discrete_label(juno::P_PWM_MODE, nav.tracks[0].synth_params[juno::P_PWM_MODE])
245 };
246 assert_eq!(label(&nav), Some("LFO"));
247 let mut seen = Vec::new();
248 for _ in 0..3 {
249 nav.adjust_synth_param(0.05);
250 seen.push(label(&nav));
251 }
252 assert_eq!(seen, [Some("MAN"), Some("ENV"), Some("ENV")]);
253 }
254
255 fn rhodes_track() -> NavState {
257 let mut nav = NavState::new(super::super::initial_tracks());
258 let mut track = TrackState::new("rhode", 0, true, TrackKind::Instrument, vec![]);
259 track.instrument_type = Some(InstrumentType::Rhodes);
260 track.synth_params = rhodes::PARAM_DEFAULTS.to_vec();
261 nav.tracks.insert(0, track);
262 nav.track_cursor = 0;
263 nav
264 }
265
266 #[test]
267 fn the_rhodes_patch_knob_moves_one_piano_per_keypress() {
268 let mut nav = rhodes_track();
272 nav.clip_view.synth_param_cursor = rhodes::P_PATCH;
273 let patch = |nav: &NavState| {
274 rhodes::patch_index(nav.tracks[0].synth_params[rhodes::P_PATCH])
275 };
276 assert_eq!(rhodes::PATCH_NAMES[patch(&nav)], "MK1 Stage");
277 for step in 1..rhodes::PATCH_COUNT {
278 nav.adjust_synth_param(0.05);
279 assert_eq!(patch(&nav), step, "patch knob step {step}");
280 }
281 nav.adjust_synth_param(0.05);
282 assert_eq!(patch(&nav), rhodes::PATCH_COUNT - 1, "patch knob ran off the top");
283 let panel = &nav.tracks[0].synth_params;
285 let want = rhodes::RhodesPiano::params_for_patch(panel[rhodes::P_PATCH]);
286 for i in 1..rhodes::PARAM_COUNT {
287 assert!(
288 (panel[i] - want[i]).abs() < 1e-6,
289 "{} came back as {} where the patch says {}",
290 rhodes::PARAM_NAMES[i], panel[i], want[i]
291 );
292 }
293 for step in (0..rhodes::PATCH_COUNT - 1).rev() {
294 nav.adjust_synth_param(-0.05);
295 assert_eq!(patch(&nav), step, "patch knob back to {step}");
296 }
297
298 let mut nav = rhodes_track();
300 nav.clip_view.synth_param_cursor = rhodes::P_VOICING;
301 let before = nav.tracks[0].synth_params.clone();
302 nav.adjust_synth_param(0.05);
303 let after = &nav.tracks[0].synth_params;
304 assert!((after[rhodes::P_VOICING] - (before[rhodes::P_VOICING] + 0.05)).abs() < 1e-6);
305 for i in 0..after.len() {
306 if i != rhodes::P_VOICING {
307 assert_eq!(before[i], after[i], "{} moved with the voicing", rhodes::PARAM_NAMES[i]);
308 }
309 }
310 }
311
312 fn jupiter_track() -> NavState {
314 let mut nav = NavState::new(super::super::initial_tracks());
315 let mut track = TrackState::new("jupiter", 0, true, TrackKind::Instrument, vec![]);
316 track.instrument_type = Some(InstrumentType::Jupiter8);
317 track.synth_params = jupiter::PARAM_DEFAULTS.to_vec();
318 nav.tracks.insert(0, track);
319 nav.track_cursor = 0;
320 nav
321 }
322
323 #[test]
324 fn jupiter_selectors_move_one_step_per_keypress() {
325 let mut nav = jupiter_track();
330 nav.clip_view.synth_param_cursor = jupiter::P_PATCH;
331 let patch = |nav: &NavState| {
332 jupiter::patch_index(nav.tracks[0].synth_params[jupiter::P_PATCH])
333 };
334 for step in 1..jupiter::PATCH_COUNT {
335 nav.adjust_synth_param(0.05);
336 assert_eq!(patch(&nav), step, "patch knob step {step}");
337 }
338 nav.adjust_synth_param(0.05);
339 assert_eq!(patch(&nav), jupiter::PATCH_COUNT - 1, "patch knob ran off the top");
340 for step in (0..jupiter::PATCH_COUNT - 1).rev() {
341 nav.adjust_synth_param(-0.05);
342 assert_eq!(patch(&nav), step, "patch knob back to {step}");
343 }
344
345 let mut nav = jupiter_track();
348 nav.clip_view.synth_param_cursor = jupiter::P_VCO2_WAVE;
349 let label = |nav: &NavState| {
350 jupiter::discrete_label(
351 jupiter::P_VCO2_WAVE,
352 nav.tracks[0].synth_params[jupiter::P_VCO2_WAVE],
353 )
354 };
355 assert_eq!(label(&nav), Some("SAW"));
356 let mut seen = Vec::new();
357 for _ in 0..3 {
358 nav.adjust_synth_param(0.05);
359 seen.push(label(&nav));
360 }
361 assert_eq!(seen, [Some("PLS"), Some("NOISE"), Some("NOISE")]);
362 }
363
364 fn prophet6_track() -> NavState {
366 let mut nav = NavState::new(super::super::initial_tracks());
367 let mut track = TrackState::new("p6", 0, true, TrackKind::Instrument, vec![]);
368 track.instrument_type = Some(InstrumentType::Prophet6);
369 track.synth_params = phosphor_dsp::prophet6::param_defaults().to_vec();
370 nav.tracks.insert(0, track);
371 nav.track_cursor = 0;
372 nav
373 }
374
375 #[test]
383 fn both_prophet_six_selectors_load_the_program() {
384 use phosphor_dsp::prophet6;
385
386 let mut nav = prophet6_track();
387 let panel = |nav: &NavState| nav.tracks[0].synth_params.clone();
388 assert_eq!(panel(&nav), prophet6::params_for_program(0.0, 0.0).to_vec());
389
390 nav.clip_view.synth_param_cursor = prophet6::P_PROGRAM;
392 for step in 1..8 {
393 nav.adjust_synth_param(0.05);
394 let expected = prophet6::params_for_program(
395 nav.tracks[0].synth_params[prophet6::P_BANK],
396 nav.tracks[0].synth_params[prophet6::P_PROGRAM],
397 );
398 assert_eq!(panel(&nav), expected.to_vec(), "program knob step {step}");
399 assert_eq!(
400 prophet6::program_index(
401 nav.tracks[0].synth_params[prophet6::P_BANK],
402 nav.tracks[0].synth_params[prophet6::P_PROGRAM],
403 ),
404 step
405 );
406 }
407
408 nav.clip_view.synth_param_cursor = prophet6::P_BANK;
410 for bank in 1..prophet6::BANK_COUNT {
411 nav.adjust_synth_param(0.05);
412 let expected = prophet6::params_for_program(
413 nav.tracks[0].synth_params[prophet6::P_BANK],
414 nav.tracks[0].synth_params[prophet6::P_PROGRAM],
415 );
416 assert_eq!(panel(&nav), expected.to_vec(), "bank knob step {bank}");
417 assert_eq!(
418 prophet6::program_index(
419 nav.tracks[0].synth_params[prophet6::P_BANK],
420 nav.tracks[0].synth_params[prophet6::P_PROGRAM],
421 ),
422 bank * prophet6::PROGRAMS_PER_BANK + 7,
423 "the bank knob lost the program knob's position"
424 );
425 }
426
427 nav.clip_view.synth_param_cursor = prophet6::P_LP_CUTOFF;
429 let before = panel(&nav);
430 nav.adjust_synth_param(0.05);
431 let after = panel(&nav);
432 for (index, (a, b)) in before.iter().zip(&after).enumerate() {
433 if index != prophet6::P_LP_CUTOFF {
434 assert_eq!(a, b, "{} moved with the cutoff", prophet6::PARAM_NAMES[index]);
435 }
436 }
437 assert!(after[prophet6::P_LP_CUTOFF] > before[prophet6::P_LP_CUTOFF]);
438 }
439
440 fn drum_track() -> NavState {
442 let mut nav = NavState::new(super::super::initial_tracks());
443 let mut track = TrackState::new("drums", 0, true, TrackKind::Instrument, vec![]);
444 track.instrument_type = Some(InstrumentType::DrumRack);
445 track.synth_params = drum_rack::PARAM_DEFAULTS.to_vec();
446 nav.tracks.insert(0, track);
447 nav.track_cursor = 0;
448 nav
449 }
450
451 #[test]
452 fn the_drum_kit_selector_moves_one_kit_per_keypress() {
453 let mut nav = drum_track();
459 nav.clip_view.synth_param_cursor = drum_rack::P_KIT;
460 let kit = |nav: &NavState| {
461 drum_rack::discrete_label(drum_rack::P_KIT, nav.tracks[0].synth_params[drum_rack::P_KIT])
462 };
463 let last = *drum_rack::KIT_LABELS.last().unwrap();
464 assert_eq!(kit(&nav), Some("808"));
465 for label in drum_rack::KIT_LABELS.iter().skip(1) {
466 nav.adjust_synth_param(0.05);
467 assert_eq!(kit(&nav), Some(*label));
468 }
469 nav.adjust_synth_param(0.05);
470 assert_eq!(kit(&nav), Some(last), "the kit knob ran off the top");
471 for label in drum_rack::KIT_LABELS.iter().rev().skip(1) {
472 nav.adjust_synth_param(-0.05);
473 assert_eq!(kit(&nav), Some(*label));
474 }
475
476 nav.clip_view.synth_param_cursor = drum_rack::P_BD_DECAY;
479 let before = nav.tracks[0].synth_params.clone();
480 nav.adjust_synth_param(-0.05);
481 let after = &nav.tracks[0].synth_params;
482 assert!((after[drum_rack::P_BD_DECAY] - 0.45).abs() < 1e-6);
483 for i in 0..after.len() {
484 if i != drum_rack::P_BD_DECAY {
485 assert_eq!(before[i], after[i], "{} moved with the kick's decay", drum_rack::PARAM_NAMES[i]);
486 }
487 }
488 }
489
490 #[test]
491 fn the_dx7_bank_knob_is_the_last_parameter() {
492 let nav = dx7_track();
496 assert_eq!(nav.tracks[0].synth_params.len(), dx7::PARAM_COUNT);
497 assert_eq!(dx7::P_BANK, dx7::PARAM_COUNT - 1);
498 assert_eq!(dx7::PARAM_NAMES[dx7::P_GAIN], "gain", "index 0-7 must not move");
499 }
500}