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 is_jupiter = track.instrument_type == Some(InstrumentType::Jupiter8);
17 let is_odyssey = track.instrument_type == Some(InstrumentType::Odyssey);
18 let is_juno = track.instrument_type == Some(InstrumentType::Juno60);
19 let is_dx7 = track.instrument_type == Some(InstrumentType::DX7);
20 let is_drum = track.instrument_type == Some(InstrumentType::DrumRack);
21 let is_discrete = if is_jupiter {
22 phosphor_dsp::jupiter::is_discrete(idx)
23 } else if is_odyssey {
24 phosphor_dsp::odyssey::is_discrete(idx)
25 } else if is_juno {
26 phosphor_dsp::juno::is_discrete(idx)
27 } else if is_dx7 {
28 phosphor_dsp::dx7::is_discrete(idx)
32 } else if is_drum {
33 phosphor_dsp::drum_rack::is_discrete(idx)
34 } else {
35 idx == 0
36 };
37 let actual_delta = if is_discrete {
38 let step = if is_odyssey {
39 match idx {
40 0 => 1.0 / (phosphor_dsp::odyssey::PATCH_COUNT as f32 - 0.01),
41 6 => 0.34, _ => 0.5,
43 }
44 } else {
45 0.25
46 };
47 if delta > 0.0 { step } else { -step }
48 } else {
49 delta
50 };
51 let new_val = if is_discrete && is_dx7 {
59 phosphor_dsp::dx7::step_discrete(idx, track.synth_params[idx], delta > 0.0)
60 } else if is_discrete && is_drum {
61 phosphor_dsp::drum_rack::step_discrete(idx, track.synth_params[idx], delta > 0.0)
62 } else if is_discrete && is_juno {
63 phosphor_dsp::juno::step_discrete(idx, track.synth_params[idx], delta > 0.0)
64 } else if is_discrete && is_jupiter {
65 phosphor_dsp::jupiter::step_discrete(idx, track.synth_params[idx], delta > 0.0)
66 } else {
67 (track.synth_params[idx] + actual_delta).clamp(0.0, 1.0)
68 };
69 track.synth_params[idx] = new_val;
70
71 if idx == 0 {
78 let new_params: Option<Vec<f32>> = match track.instrument_type {
79 Some(InstrumentType::Jupiter8) => {
80 Some(phosphor_dsp::jupiter::Jupiter8Synth::params_for_patch(new_val).to_vec())
81 }
82 Some(InstrumentType::Odyssey) => {
83 Some(phosphor_dsp::odyssey::OdysseySynth::params_for_patch(new_val).to_vec())
84 }
85 Some(InstrumentType::Juno60) => {
86 Some(phosphor_dsp::juno::Juno60Synth::params_for_patch(new_val).to_vec())
87 }
88 _ => None,
89 };
90 if let Some(preset_params) = new_params {
91 for (slot, v) in track.synth_params.iter_mut().zip(preset_params) {
92 *slot = v;
93 }
94 }
95 }
96
97 if let Some(mixer_id) = track.mixer_id {
98 return Some((mixer_id, idx, new_val));
99 }
100 }
101 }
102 None
103 }
104
105
106 pub fn show_current_track_controls(&mut self) {
110 for track in &self.tracks {
112 if let Some(ref h) = track.handle {
113 h.config.midi_active.store(false, std::sync::atomic::Ordering::Relaxed);
114 }
115 }
116
117 if let Some(track) = self.tracks.get(self.track_cursor) {
118 if track.is_live() {
119 if let Some(ref h) = track.handle {
120 h.config.midi_active.store(true, std::sync::atomic::Ordering::Relaxed);
121 }
122 self.clip_view_visible = true;
123
124 let clip_idx = match self.track_element {
126 super::TrackElement::Clip(i) if i < track.clips.len() => i,
127 _ => 0,
128 };
129 self.clip_view_target = Some((self.track_cursor, clip_idx));
130
131 if !track.clips.is_empty() {
133 self.clip_view.clip_tab = ClipTab::PianoRoll;
134 self.clip_view.focus = ClipViewFocus::PianoRoll;
135 self.clip_view.piano_roll.focus = PianoRollFocus::Navigation;
136 self.clip_view.piano_roll.column = 0;
137 } else {
138 self.clip_view.fx_panel_tab = FxPanelTab::Synth;
139 self.clip_view.focus = ClipViewFocus::FxPanel;
140 self.clip_view.synth_param_cursor = 0;
141 }
142 } else {
143 self.clip_view_visible = false;
145 self.clip_view_target = None;
146 }
147 }
148 }
149
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155 use phosphor_dsp::{drum_rack, dx7, juno, jupiter};
156
157 fn dx7_track() -> NavState {
159 let mut nav = NavState::new(super::super::initial_tracks());
160 let mut track = TrackState::new("dx7", 0, true, TrackKind::Instrument, vec![]);
161 track.instrument_type = Some(InstrumentType::DX7);
162 track.synth_params = dx7::PARAM_DEFAULTS.to_vec();
163 nav.tracks.insert(0, track);
164 nav.track_cursor = 0;
165 nav
166 }
167
168 fn selected(nav: &NavState) -> (usize, usize) {
169 let p = &nav.tracks[0].synth_params;
170 (dx7::bank_index(p[dx7::P_BANK]), dx7::patch_index(p[dx7::P_PATCH]))
171 }
172
173 #[test]
174 fn dx7_selectors_move_one_step_per_keypress() {
175 let mut nav = dx7_track();
179 nav.clip_view.synth_param_cursor = dx7::P_PATCH;
180 let (bank, patch) = selected(&nav);
181 for step in 1..=5 {
182 nav.adjust_synth_param(0.05);
183 assert_eq!(selected(&nav), (bank, patch + step), "patch knob step {step}");
184 }
185 for step in (0..5).rev() {
186 nav.adjust_synth_param(-0.05);
187 assert_eq!(selected(&nav), (bank, patch + step), "patch knob back to {step}");
188 }
189
190 nav.clip_view.synth_param_cursor = dx7::P_BANK;
191 for step in 1..dx7::BANK_COUNT {
192 nav.adjust_synth_param(0.05);
193 assert_eq!(selected(&nav), (step, patch), "bank knob step {step}");
194 }
195 for _ in 0..4 {
197 nav.adjust_synth_param(0.05);
198 }
199 assert_eq!(selected(&nav), (dx7::BANK_COUNT - 1, patch));
200 }
201
202 fn juno_track() -> NavState {
204 let mut nav = NavState::new(super::super::initial_tracks());
205 let mut track = TrackState::new("juno", 0, true, TrackKind::Instrument, vec![]);
206 track.instrument_type = Some(InstrumentType::Juno60);
207 track.synth_params = juno::PARAM_DEFAULTS.to_vec();
208 nav.tracks.insert(0, track);
209 nav.track_cursor = 0;
210 nav
211 }
212
213 fn juno_patch(nav: &NavState) -> usize {
214 juno::patch_index(nav.tracks[0].synth_params[juno::P_PATCH])
215 }
216
217 #[test]
218 fn juno_selectors_move_one_step_per_keypress() {
219 let mut nav = juno_track();
225 nav.clip_view.synth_param_cursor = juno::P_PATCH;
226 for step in 1..juno::PATCH_COUNT {
227 nav.adjust_synth_param(0.05);
228 assert_eq!(juno_patch(&nav), step, "patch knob step {step}");
229 }
230 nav.adjust_synth_param(0.05);
231 assert_eq!(juno_patch(&nav), juno::PATCH_COUNT - 1, "patch knob ran off the top");
232 for step in (0..juno::PATCH_COUNT - 1).rev() {
233 nav.adjust_synth_param(-0.05);
234 assert_eq!(juno_patch(&nav), step, "patch knob back to {step}");
235 }
236
237 for _ in 0..juno::PATCH_COUNT {
240 nav.adjust_synth_param(0.05);
241 }
242 let panel = &nav.tracks[0].synth_params;
243 assert_eq!(juno_patch(&nav), juno::PATCH_COUNT - 1);
244 assert!((panel[juno::P_RESO] - 1.0).abs() < 1e-6, "res {}", panel[juno::P_RESO]);
245
246 let mut nav = juno_track();
249 nav.clip_view.synth_param_cursor = juno::P_PWM_MODE;
250 let label = |nav: &NavState| {
251 juno::discrete_label(juno::P_PWM_MODE, nav.tracks[0].synth_params[juno::P_PWM_MODE])
252 };
253 assert_eq!(label(&nav), Some("LFO"));
254 let mut seen = Vec::new();
255 for _ in 0..3 {
256 nav.adjust_synth_param(0.05);
257 seen.push(label(&nav));
258 }
259 assert_eq!(seen, [Some("MAN"), Some("ENV"), Some("ENV")]);
260 }
261
262 fn jupiter_track() -> NavState {
264 let mut nav = NavState::new(super::super::initial_tracks());
265 let mut track = TrackState::new("jupiter", 0, true, TrackKind::Instrument, vec![]);
266 track.instrument_type = Some(InstrumentType::Jupiter8);
267 track.synth_params = jupiter::PARAM_DEFAULTS.to_vec();
268 nav.tracks.insert(0, track);
269 nav.track_cursor = 0;
270 nav
271 }
272
273 #[test]
274 fn jupiter_selectors_move_one_step_per_keypress() {
275 let mut nav = jupiter_track();
280 nav.clip_view.synth_param_cursor = jupiter::P_PATCH;
281 let patch = |nav: &NavState| {
282 jupiter::patch_index(nav.tracks[0].synth_params[jupiter::P_PATCH])
283 };
284 for step in 1..jupiter::PATCH_COUNT {
285 nav.adjust_synth_param(0.05);
286 assert_eq!(patch(&nav), step, "patch knob step {step}");
287 }
288 nav.adjust_synth_param(0.05);
289 assert_eq!(patch(&nav), jupiter::PATCH_COUNT - 1, "patch knob ran off the top");
290 for step in (0..jupiter::PATCH_COUNT - 1).rev() {
291 nav.adjust_synth_param(-0.05);
292 assert_eq!(patch(&nav), step, "patch knob back to {step}");
293 }
294
295 let mut nav = jupiter_track();
298 nav.clip_view.synth_param_cursor = jupiter::P_VCO2_WAVE;
299 let label = |nav: &NavState| {
300 jupiter::discrete_label(
301 jupiter::P_VCO2_WAVE,
302 nav.tracks[0].synth_params[jupiter::P_VCO2_WAVE],
303 )
304 };
305 assert_eq!(label(&nav), Some("SAW"));
306 let mut seen = Vec::new();
307 for _ in 0..3 {
308 nav.adjust_synth_param(0.05);
309 seen.push(label(&nav));
310 }
311 assert_eq!(seen, [Some("PLS"), Some("NOISE"), Some("NOISE")]);
312 }
313
314 fn drum_track() -> NavState {
316 let mut nav = NavState::new(super::super::initial_tracks());
317 let mut track = TrackState::new("drums", 0, true, TrackKind::Instrument, vec![]);
318 track.instrument_type = Some(InstrumentType::DrumRack);
319 track.synth_params = drum_rack::PARAM_DEFAULTS.to_vec();
320 nav.tracks.insert(0, track);
321 nav.track_cursor = 0;
322 nav
323 }
324
325 #[test]
326 fn the_drum_kit_selector_moves_one_kit_per_keypress() {
327 let mut nav = drum_track();
333 nav.clip_view.synth_param_cursor = drum_rack::P_KIT;
334 let kit = |nav: &NavState| {
335 drum_rack::discrete_label(drum_rack::P_KIT, nav.tracks[0].synth_params[drum_rack::P_KIT])
336 };
337 let last = *drum_rack::KIT_LABELS.last().unwrap();
338 assert_eq!(kit(&nav), Some("808"));
339 for label in drum_rack::KIT_LABELS.iter().skip(1) {
340 nav.adjust_synth_param(0.05);
341 assert_eq!(kit(&nav), Some(*label));
342 }
343 nav.adjust_synth_param(0.05);
344 assert_eq!(kit(&nav), Some(last), "the kit knob ran off the top");
345 for label in drum_rack::KIT_LABELS.iter().rev().skip(1) {
346 nav.adjust_synth_param(-0.05);
347 assert_eq!(kit(&nav), Some(*label));
348 }
349
350 nav.clip_view.synth_param_cursor = drum_rack::P_BD_DECAY;
353 let before = nav.tracks[0].synth_params.clone();
354 nav.adjust_synth_param(-0.05);
355 let after = &nav.tracks[0].synth_params;
356 assert!((after[drum_rack::P_BD_DECAY] - 0.45).abs() < 1e-6);
357 for i in 0..after.len() {
358 if i != drum_rack::P_BD_DECAY {
359 assert_eq!(before[i], after[i], "{} moved with the kick's decay", drum_rack::PARAM_NAMES[i]);
360 }
361 }
362 }
363
364 #[test]
365 fn the_dx7_bank_knob_is_the_last_parameter() {
366 let nav = dx7_track();
370 assert_eq!(nav.tracks[0].synth_params.len(), dx7::PARAM_COUNT);
371 assert_eq!(dx7::P_BANK, dx7::PARAM_COUNT - 1);
372 assert_eq!(dx7::PARAM_NAMES[dx7::P_GAIN], "gain", "index 0-7 must not move");
373 }
374}