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 _ => None,
52 };
53 if let Some(preset_params) = new_params {
54 for (slot, v) in track.synth_params.iter_mut().zip(preset_params) {
55 *slot = v;
56 }
57 }
58 }
59
60 if let Some(mixer_id) = track.mixer_id {
61 return Some((mixer_id, idx, new_val));
62 }
63 }
64 }
65 None
66 }
67
68
69 pub fn show_current_track_controls(&mut self) {
73 for track in &self.tracks {
75 if let Some(ref h) = track.handle {
76 h.config.midi_active.store(false, std::sync::atomic::Ordering::Relaxed);
77 }
78 }
79
80 if let Some(track) = self.tracks.get(self.track_cursor) {
81 if track.is_live() {
82 if let Some(ref h) = track.handle {
83 h.config.midi_active.store(true, std::sync::atomic::Ordering::Relaxed);
84 }
85 self.clip_view_visible = true;
86
87 let clip_idx = match self.track_element {
89 super::TrackElement::Clip(i) if i < track.clips.len() => i,
90 _ => 0,
91 };
92 self.clip_view_target = Some((self.track_cursor, clip_idx));
93
94 if !track.clips.is_empty() {
96 self.clip_view.clip_tab = ClipTab::PianoRoll;
97 self.clip_view.focus = ClipViewFocus::PianoRoll;
98 self.clip_view.piano_roll.focus = PianoRollFocus::Navigation;
99 self.clip_view.piano_roll.column = 0;
100 } else {
101 self.clip_view.fx_panel_tab = FxPanelTab::Synth;
102 self.clip_view.focus = ClipViewFocus::FxPanel;
103 self.clip_view.synth_param_cursor = 0;
104 }
105 } else {
106 self.clip_view_visible = false;
108 self.clip_view_target = None;
109 }
110 }
111 }
112
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118 use phosphor_dsp::{drum_rack, dx7, juno, jupiter};
119
120 fn dx7_track() -> NavState {
122 let mut nav = NavState::new(super::super::initial_tracks());
123 let mut track = TrackState::new("dx7", 0, true, TrackKind::Instrument, vec![]);
124 track.instrument_type = Some(InstrumentType::DX7);
125 track.synth_params = dx7::PARAM_DEFAULTS.to_vec();
126 nav.tracks.insert(0, track);
127 nav.track_cursor = 0;
128 nav
129 }
130
131 fn selected(nav: &NavState) -> (usize, usize) {
132 let p = &nav.tracks[0].synth_params;
133 (dx7::bank_index(p[dx7::P_BANK]), dx7::patch_index(p[dx7::P_PATCH]))
134 }
135
136 #[test]
137 fn dx7_selectors_move_one_step_per_keypress() {
138 let mut nav = dx7_track();
142 nav.clip_view.synth_param_cursor = dx7::P_PATCH;
143 let (bank, patch) = selected(&nav);
144 for step in 1..=5 {
145 nav.adjust_synth_param(0.05);
146 assert_eq!(selected(&nav), (bank, patch + step), "patch knob step {step}");
147 }
148 for step in (0..5).rev() {
149 nav.adjust_synth_param(-0.05);
150 assert_eq!(selected(&nav), (bank, patch + step), "patch knob back to {step}");
151 }
152
153 nav.clip_view.synth_param_cursor = dx7::P_BANK;
154 for step in 1..dx7::BANK_COUNT {
155 nav.adjust_synth_param(0.05);
156 assert_eq!(selected(&nav), (step, patch), "bank knob step {step}");
157 }
158 for _ in 0..4 {
160 nav.adjust_synth_param(0.05);
161 }
162 assert_eq!(selected(&nav), (dx7::BANK_COUNT - 1, patch));
163 }
164
165 fn juno_track() -> NavState {
167 let mut nav = NavState::new(super::super::initial_tracks());
168 let mut track = TrackState::new("juno", 0, true, TrackKind::Instrument, vec![]);
169 track.instrument_type = Some(InstrumentType::Juno60);
170 track.synth_params = juno::PARAM_DEFAULTS.to_vec();
171 nav.tracks.insert(0, track);
172 nav.track_cursor = 0;
173 nav
174 }
175
176 fn juno_patch(nav: &NavState) -> usize {
177 juno::patch_index(nav.tracks[0].synth_params[juno::P_PATCH])
178 }
179
180 #[test]
181 fn juno_selectors_move_one_step_per_keypress() {
182 let mut nav = juno_track();
188 nav.clip_view.synth_param_cursor = juno::P_PATCH;
189 for step in 1..juno::PATCH_COUNT {
190 nav.adjust_synth_param(0.05);
191 assert_eq!(juno_patch(&nav), step, "patch knob step {step}");
192 }
193 nav.adjust_synth_param(0.05);
194 assert_eq!(juno_patch(&nav), juno::PATCH_COUNT - 1, "patch knob ran off the top");
195 for step in (0..juno::PATCH_COUNT - 1).rev() {
196 nav.adjust_synth_param(-0.05);
197 assert_eq!(juno_patch(&nav), step, "patch knob back to {step}");
198 }
199
200 for _ in 0..juno::PATCH_COUNT {
203 nav.adjust_synth_param(0.05);
204 }
205 let panel = &nav.tracks[0].synth_params;
206 assert_eq!(juno_patch(&nav), juno::PATCH_COUNT - 1);
207 assert!((panel[juno::P_RESO] - 1.0).abs() < 1e-6, "res {}", panel[juno::P_RESO]);
208
209 let mut nav = juno_track();
212 nav.clip_view.synth_param_cursor = juno::P_PWM_MODE;
213 let label = |nav: &NavState| {
214 juno::discrete_label(juno::P_PWM_MODE, nav.tracks[0].synth_params[juno::P_PWM_MODE])
215 };
216 assert_eq!(label(&nav), Some("LFO"));
217 let mut seen = Vec::new();
218 for _ in 0..3 {
219 nav.adjust_synth_param(0.05);
220 seen.push(label(&nav));
221 }
222 assert_eq!(seen, [Some("MAN"), Some("ENV"), Some("ENV")]);
223 }
224
225 fn jupiter_track() -> NavState {
227 let mut nav = NavState::new(super::super::initial_tracks());
228 let mut track = TrackState::new("jupiter", 0, true, TrackKind::Instrument, vec![]);
229 track.instrument_type = Some(InstrumentType::Jupiter8);
230 track.synth_params = jupiter::PARAM_DEFAULTS.to_vec();
231 nav.tracks.insert(0, track);
232 nav.track_cursor = 0;
233 nav
234 }
235
236 #[test]
237 fn jupiter_selectors_move_one_step_per_keypress() {
238 let mut nav = jupiter_track();
243 nav.clip_view.synth_param_cursor = jupiter::P_PATCH;
244 let patch = |nav: &NavState| {
245 jupiter::patch_index(nav.tracks[0].synth_params[jupiter::P_PATCH])
246 };
247 for step in 1..jupiter::PATCH_COUNT {
248 nav.adjust_synth_param(0.05);
249 assert_eq!(patch(&nav), step, "patch knob step {step}");
250 }
251 nav.adjust_synth_param(0.05);
252 assert_eq!(patch(&nav), jupiter::PATCH_COUNT - 1, "patch knob ran off the top");
253 for step in (0..jupiter::PATCH_COUNT - 1).rev() {
254 nav.adjust_synth_param(-0.05);
255 assert_eq!(patch(&nav), step, "patch knob back to {step}");
256 }
257
258 let mut nav = jupiter_track();
261 nav.clip_view.synth_param_cursor = jupiter::P_VCO2_WAVE;
262 let label = |nav: &NavState| {
263 jupiter::discrete_label(
264 jupiter::P_VCO2_WAVE,
265 nav.tracks[0].synth_params[jupiter::P_VCO2_WAVE],
266 )
267 };
268 assert_eq!(label(&nav), Some("SAW"));
269 let mut seen = Vec::new();
270 for _ in 0..3 {
271 nav.adjust_synth_param(0.05);
272 seen.push(label(&nav));
273 }
274 assert_eq!(seen, [Some("PLS"), Some("NOISE"), Some("NOISE")]);
275 }
276
277 fn drum_track() -> NavState {
279 let mut nav = NavState::new(super::super::initial_tracks());
280 let mut track = TrackState::new("drums", 0, true, TrackKind::Instrument, vec![]);
281 track.instrument_type = Some(InstrumentType::DrumRack);
282 track.synth_params = drum_rack::PARAM_DEFAULTS.to_vec();
283 nav.tracks.insert(0, track);
284 nav.track_cursor = 0;
285 nav
286 }
287
288 #[test]
289 fn the_drum_kit_selector_moves_one_kit_per_keypress() {
290 let mut nav = drum_track();
296 nav.clip_view.synth_param_cursor = drum_rack::P_KIT;
297 let kit = |nav: &NavState| {
298 drum_rack::discrete_label(drum_rack::P_KIT, nav.tracks[0].synth_params[drum_rack::P_KIT])
299 };
300 let last = *drum_rack::KIT_LABELS.last().unwrap();
301 assert_eq!(kit(&nav), Some("808"));
302 for label in drum_rack::KIT_LABELS.iter().skip(1) {
303 nav.adjust_synth_param(0.05);
304 assert_eq!(kit(&nav), Some(*label));
305 }
306 nav.adjust_synth_param(0.05);
307 assert_eq!(kit(&nav), Some(last), "the kit knob ran off the top");
308 for label in drum_rack::KIT_LABELS.iter().rev().skip(1) {
309 nav.adjust_synth_param(-0.05);
310 assert_eq!(kit(&nav), Some(*label));
311 }
312
313 nav.clip_view.synth_param_cursor = drum_rack::P_BD_DECAY;
316 let before = nav.tracks[0].synth_params.clone();
317 nav.adjust_synth_param(-0.05);
318 let after = &nav.tracks[0].synth_params;
319 assert!((after[drum_rack::P_BD_DECAY] - 0.45).abs() < 1e-6);
320 for i in 0..after.len() {
321 if i != drum_rack::P_BD_DECAY {
322 assert_eq!(before[i], after[i], "{} moved with the kick's decay", drum_rack::PARAM_NAMES[i]);
323 }
324 }
325 }
326
327 #[test]
328 fn the_dx7_bank_knob_is_the_last_parameter() {
329 let nav = dx7_track();
333 assert_eq!(nav.tracks[0].synth_params.len(), dx7::PARAM_COUNT);
334 assert_eq!(dx7::P_BANK, dx7::PARAM_COUNT - 1);
335 assert_eq!(dx7::PARAM_NAMES[dx7::P_GAIN], "gain", "index 0-7 must not move");
336 }
337}