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 if idx == 0 {
38 let new_params: Option<Vec<f32>> = match track.instrument_type {
39 Some(InstrumentType::Synth | InstrumentType::Sampler) => {
40 Some(phosphor_dsp::synth::PhosphorSynth::params_for_patch(new_val).to_vec())
41 }
42 Some(InstrumentType::Jupiter8) => {
43 Some(phosphor_dsp::jupiter::Jupiter8Synth::params_for_patch(new_val).to_vec())
44 }
45 Some(InstrumentType::Odyssey) => {
46 Some(phosphor_dsp::odyssey::OdysseySynth::params_for_patch(new_val).to_vec())
47 }
48 Some(InstrumentType::Juno60) => {
49 Some(phosphor_dsp::juno::Juno60Synth::params_for_patch(new_val).to_vec())
50 }
51 Some(InstrumentType::Rhodes) => {
52 Some(phosphor_dsp::rhodes::RhodesPiano::params_for_patch(new_val).to_vec())
53 }
54 _ => None,
55 };
56 if let Some(preset_params) = new_params {
57 for (slot, v) in track.synth_params.iter_mut().zip(preset_params) {
58 *slot = v;
59 }
60 }
61 }
62
63 if let Some(mixer_id) = track.mixer_id {
64 return Some((mixer_id, idx, new_val));
65 }
66 }
67 }
68 None
69 }
70
71
72 pub fn show_current_track_controls(&mut self) {
76 for track in &self.tracks {
78 if let Some(ref h) = track.handle {
79 h.config.midi_active.store(false, std::sync::atomic::Ordering::Relaxed);
80 }
81 }
82
83 if let Some(track) = self.tracks.get(self.track_cursor) {
84 if track.is_live() {
85 if let Some(ref h) = track.handle {
86 h.config.midi_active.store(true, std::sync::atomic::Ordering::Relaxed);
87 }
88 self.clip_view_visible = true;
89
90 let clip_idx = match self.track_element {
92 super::TrackElement::Clip(i) if i < track.clips.len() => i,
93 _ => 0,
94 };
95 self.clip_view_target = Some((self.track_cursor, clip_idx));
96
97 if !track.clips.is_empty() {
99 self.clip_view.clip_tab = ClipTab::PianoRoll;
100 self.clip_view.focus = ClipViewFocus::PianoRoll;
101 self.clip_view.piano_roll.focus = PianoRollFocus::Navigation;
102 self.clip_view.piano_roll.column = 0;
103 } else {
104 self.clip_view.fx_panel_tab = FxPanelTab::Synth;
105 self.clip_view.focus = ClipViewFocus::FxPanel;
106 self.clip_view.synth_param_cursor = 0;
107 }
108 } else {
109 self.clip_view_visible = false;
111 self.clip_view_target = None;
112 }
113 }
114 }
115
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use phosphor_dsp::{drum_rack, dx7, juno, jupiter, rhodes};
122
123 fn dx7_track() -> NavState {
125 let mut nav = NavState::new(super::super::initial_tracks());
126 let mut track = TrackState::new("dx7", 0, true, TrackKind::Instrument, vec![]);
127 track.instrument_type = Some(InstrumentType::DX7);
128 track.synth_params = dx7::PARAM_DEFAULTS.to_vec();
129 nav.tracks.insert(0, track);
130 nav.track_cursor = 0;
131 nav
132 }
133
134 fn selected(nav: &NavState) -> (usize, usize) {
135 let p = &nav.tracks[0].synth_params;
136 (dx7::bank_index(p[dx7::P_BANK]), dx7::patch_index(p[dx7::P_PATCH]))
137 }
138
139 #[test]
140 fn dx7_selectors_move_one_step_per_keypress() {
141 let mut nav = dx7_track();
145 nav.clip_view.synth_param_cursor = dx7::P_PATCH;
146 let (bank, patch) = selected(&nav);
147 for step in 1..=5 {
148 nav.adjust_synth_param(0.05);
149 assert_eq!(selected(&nav), (bank, patch + step), "patch knob step {step}");
150 }
151 for step in (0..5).rev() {
152 nav.adjust_synth_param(-0.05);
153 assert_eq!(selected(&nav), (bank, patch + step), "patch knob back to {step}");
154 }
155
156 nav.clip_view.synth_param_cursor = dx7::P_BANK;
157 for step in 1..dx7::BANK_COUNT {
158 nav.adjust_synth_param(0.05);
159 assert_eq!(selected(&nav), (step, patch), "bank knob step {step}");
160 }
161 for _ in 0..4 {
163 nav.adjust_synth_param(0.05);
164 }
165 assert_eq!(selected(&nav), (dx7::BANK_COUNT - 1, patch));
166 }
167
168 fn juno_track() -> NavState {
170 let mut nav = NavState::new(super::super::initial_tracks());
171 let mut track = TrackState::new("juno", 0, true, TrackKind::Instrument, vec![]);
172 track.instrument_type = Some(InstrumentType::Juno60);
173 track.synth_params = juno::PARAM_DEFAULTS.to_vec();
174 nav.tracks.insert(0, track);
175 nav.track_cursor = 0;
176 nav
177 }
178
179 fn juno_patch(nav: &NavState) -> usize {
180 juno::patch_index(nav.tracks[0].synth_params[juno::P_PATCH])
181 }
182
183 #[test]
184 fn juno_selectors_move_one_step_per_keypress() {
185 let mut nav = juno_track();
191 nav.clip_view.synth_param_cursor = juno::P_PATCH;
192 for step in 1..juno::PATCH_COUNT {
193 nav.adjust_synth_param(0.05);
194 assert_eq!(juno_patch(&nav), step, "patch knob step {step}");
195 }
196 nav.adjust_synth_param(0.05);
197 assert_eq!(juno_patch(&nav), juno::PATCH_COUNT - 1, "patch knob ran off the top");
198 for step in (0..juno::PATCH_COUNT - 1).rev() {
199 nav.adjust_synth_param(-0.05);
200 assert_eq!(juno_patch(&nav), step, "patch knob back to {step}");
201 }
202
203 for _ in 0..juno::PATCH_COUNT {
206 nav.adjust_synth_param(0.05);
207 }
208 let panel = &nav.tracks[0].synth_params;
209 assert_eq!(juno_patch(&nav), juno::PATCH_COUNT - 1);
210 assert!((panel[juno::P_RESO] - 1.0).abs() < 1e-6, "res {}", panel[juno::P_RESO]);
211
212 let mut nav = juno_track();
215 nav.clip_view.synth_param_cursor = juno::P_PWM_MODE;
216 let label = |nav: &NavState| {
217 juno::discrete_label(juno::P_PWM_MODE, nav.tracks[0].synth_params[juno::P_PWM_MODE])
218 };
219 assert_eq!(label(&nav), Some("LFO"));
220 let mut seen = Vec::new();
221 for _ in 0..3 {
222 nav.adjust_synth_param(0.05);
223 seen.push(label(&nav));
224 }
225 assert_eq!(seen, [Some("MAN"), Some("ENV"), Some("ENV")]);
226 }
227
228 fn rhodes_track() -> NavState {
230 let mut nav = NavState::new(super::super::initial_tracks());
231 let mut track = TrackState::new("rhode", 0, true, TrackKind::Instrument, vec![]);
232 track.instrument_type = Some(InstrumentType::Rhodes);
233 track.synth_params = rhodes::PARAM_DEFAULTS.to_vec();
234 nav.tracks.insert(0, track);
235 nav.track_cursor = 0;
236 nav
237 }
238
239 #[test]
240 fn the_rhodes_patch_knob_moves_one_piano_per_keypress() {
241 let mut nav = rhodes_track();
245 nav.clip_view.synth_param_cursor = rhodes::P_PATCH;
246 let patch = |nav: &NavState| {
247 rhodes::patch_index(nav.tracks[0].synth_params[rhodes::P_PATCH])
248 };
249 assert_eq!(rhodes::PATCH_NAMES[patch(&nav)], "MK1 Stage");
250 for step in 1..rhodes::PATCH_COUNT {
251 nav.adjust_synth_param(0.05);
252 assert_eq!(patch(&nav), step, "patch knob step {step}");
253 }
254 nav.adjust_synth_param(0.05);
255 assert_eq!(patch(&nav), rhodes::PATCH_COUNT - 1, "patch knob ran off the top");
256 let panel = &nav.tracks[0].synth_params;
258 let want = rhodes::RhodesPiano::params_for_patch(panel[rhodes::P_PATCH]);
259 for i in 1..rhodes::PARAM_COUNT {
260 assert!(
261 (panel[i] - want[i]).abs() < 1e-6,
262 "{} came back as {} where the patch says {}",
263 rhodes::PARAM_NAMES[i], panel[i], want[i]
264 );
265 }
266 for step in (0..rhodes::PATCH_COUNT - 1).rev() {
267 nav.adjust_synth_param(-0.05);
268 assert_eq!(patch(&nav), step, "patch knob back to {step}");
269 }
270
271 let mut nav = rhodes_track();
273 nav.clip_view.synth_param_cursor = rhodes::P_VOICING;
274 let before = nav.tracks[0].synth_params.clone();
275 nav.adjust_synth_param(0.05);
276 let after = &nav.tracks[0].synth_params;
277 assert!((after[rhodes::P_VOICING] - (before[rhodes::P_VOICING] + 0.05)).abs() < 1e-6);
278 for i in 0..after.len() {
279 if i != rhodes::P_VOICING {
280 assert_eq!(before[i], after[i], "{} moved with the voicing", rhodes::PARAM_NAMES[i]);
281 }
282 }
283 }
284
285 fn jupiter_track() -> NavState {
287 let mut nav = NavState::new(super::super::initial_tracks());
288 let mut track = TrackState::new("jupiter", 0, true, TrackKind::Instrument, vec![]);
289 track.instrument_type = Some(InstrumentType::Jupiter8);
290 track.synth_params = jupiter::PARAM_DEFAULTS.to_vec();
291 nav.tracks.insert(0, track);
292 nav.track_cursor = 0;
293 nav
294 }
295
296 #[test]
297 fn jupiter_selectors_move_one_step_per_keypress() {
298 let mut nav = jupiter_track();
303 nav.clip_view.synth_param_cursor = jupiter::P_PATCH;
304 let patch = |nav: &NavState| {
305 jupiter::patch_index(nav.tracks[0].synth_params[jupiter::P_PATCH])
306 };
307 for step in 1..jupiter::PATCH_COUNT {
308 nav.adjust_synth_param(0.05);
309 assert_eq!(patch(&nav), step, "patch knob step {step}");
310 }
311 nav.adjust_synth_param(0.05);
312 assert_eq!(patch(&nav), jupiter::PATCH_COUNT - 1, "patch knob ran off the top");
313 for step in (0..jupiter::PATCH_COUNT - 1).rev() {
314 nav.adjust_synth_param(-0.05);
315 assert_eq!(patch(&nav), step, "patch knob back to {step}");
316 }
317
318 let mut nav = jupiter_track();
321 nav.clip_view.synth_param_cursor = jupiter::P_VCO2_WAVE;
322 let label = |nav: &NavState| {
323 jupiter::discrete_label(
324 jupiter::P_VCO2_WAVE,
325 nav.tracks[0].synth_params[jupiter::P_VCO2_WAVE],
326 )
327 };
328 assert_eq!(label(&nav), Some("SAW"));
329 let mut seen = Vec::new();
330 for _ in 0..3 {
331 nav.adjust_synth_param(0.05);
332 seen.push(label(&nav));
333 }
334 assert_eq!(seen, [Some("PLS"), Some("NOISE"), Some("NOISE")]);
335 }
336
337 fn drum_track() -> NavState {
339 let mut nav = NavState::new(super::super::initial_tracks());
340 let mut track = TrackState::new("drums", 0, true, TrackKind::Instrument, vec![]);
341 track.instrument_type = Some(InstrumentType::DrumRack);
342 track.synth_params = drum_rack::PARAM_DEFAULTS.to_vec();
343 nav.tracks.insert(0, track);
344 nav.track_cursor = 0;
345 nav
346 }
347
348 #[test]
349 fn the_drum_kit_selector_moves_one_kit_per_keypress() {
350 let mut nav = drum_track();
356 nav.clip_view.synth_param_cursor = drum_rack::P_KIT;
357 let kit = |nav: &NavState| {
358 drum_rack::discrete_label(drum_rack::P_KIT, nav.tracks[0].synth_params[drum_rack::P_KIT])
359 };
360 let last = *drum_rack::KIT_LABELS.last().unwrap();
361 assert_eq!(kit(&nav), Some("808"));
362 for label in drum_rack::KIT_LABELS.iter().skip(1) {
363 nav.adjust_synth_param(0.05);
364 assert_eq!(kit(&nav), Some(*label));
365 }
366 nav.adjust_synth_param(0.05);
367 assert_eq!(kit(&nav), Some(last), "the kit knob ran off the top");
368 for label in drum_rack::KIT_LABELS.iter().rev().skip(1) {
369 nav.adjust_synth_param(-0.05);
370 assert_eq!(kit(&nav), Some(*label));
371 }
372
373 nav.clip_view.synth_param_cursor = drum_rack::P_BD_DECAY;
376 let before = nav.tracks[0].synth_params.clone();
377 nav.adjust_synth_param(-0.05);
378 let after = &nav.tracks[0].synth_params;
379 assert!((after[drum_rack::P_BD_DECAY] - 0.45).abs() < 1e-6);
380 for i in 0..after.len() {
381 if i != drum_rack::P_BD_DECAY {
382 assert_eq!(before[i], after[i], "{} moved with the kick's decay", drum_rack::PARAM_NAMES[i]);
383 }
384 }
385 }
386
387 #[test]
388 fn the_dx7_bank_knob_is_the_last_parameter() {
389 let nav = dx7_track();
393 assert_eq!(nav.tracks[0].synth_params.len(), dx7::PARAM_COUNT);
394 assert_eq!(dx7::P_BANK, dx7::PARAM_COUNT - 1);
395 assert_eq!(dx7::PARAM_NAMES[dx7::P_GAIN], "gain", "index 0-7 must not move");
396 }
397}