1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
//! NavState methods: params.
use super::*;
impl NavState {
/// Adjust the currently selected synth parameter by delta.
/// Returns the (mixer_id, param_index, new_value) if changed, for sending to audio.
pub(crate) fn adjust_synth_param(&mut self, delta: f32) -> Option<(usize, usize, f32)> {
let idx = self.clip_view.synth_param_cursor;
if let Some(track) = self.tracks.get_mut(self.track_cursor) {
if idx < track.synth_params.len() {
// Index 0 is always a discrete selector (waveform for synth, kit for drums)
// Synth: 4 options → step 0.25. Drums: 5 kits → step 0.20
let is_jupiter = track.instrument_type == Some(InstrumentType::Jupiter8);
let is_odyssey = track.instrument_type == Some(InstrumentType::Odyssey);
let is_juno = track.instrument_type == Some(InstrumentType::Juno60);
let is_discrete = if is_jupiter {
phosphor_dsp::jupiter::is_discrete(idx)
} else if is_odyssey {
phosphor_dsp::odyssey::is_discrete(idx)
} else if is_juno {
phosphor_dsp::juno::is_discrete(idx)
} else {
idx == 0
};
let actual_delta = if is_discrete {
let step = if is_jupiter {
match idx {
0 => 1.0 / (phosphor_dsp::jupiter::PATCH_COUNT as f32 - 0.01),
_ => 0.25,
}
} else if is_odyssey {
match idx {
0 => 1.0 / (phosphor_dsp::odyssey::PATCH_COUNT as f32 - 0.01),
6 => 0.34, // 3 filter types
_ => 0.5,
}
} else if is_juno {
match idx {
0 => 1.0 / (phosphor_dsp::juno::PATCH_COUNT as f32 - 0.01),
12 => 0.25, // 4 chorus modes
_ => 0.5, // on/off switches
}
} else {
match track.instrument_type {
Some(InstrumentType::DrumRack) => 0.1, // 10 kits
Some(InstrumentType::DX7) => 1.0 / (phosphor_dsp::dx7::PATCH_COUNT as f32 - 0.01),
_ => 0.25,
}
};
if delta > 0.0 { step } else { -step }
} else {
delta
};
let new_val = (track.synth_params[idx] + actual_delta).clamp(0.0, 1.0);
track.synth_params[idx] = new_val;
// When patch selector changes, sync all params from preset
if idx == 0 {
let new_params = match track.instrument_type {
Some(InstrumentType::Jupiter8) => {
Some(phosphor_dsp::jupiter::Jupiter8Synth::params_for_patch(new_val))
}
Some(InstrumentType::Odyssey) => {
Some(phosphor_dsp::odyssey::OdysseySynth::params_for_patch(new_val))
}
Some(InstrumentType::Juno60) => {
Some(phosphor_dsp::juno::Juno60Synth::params_for_patch(new_val))
}
_ => None,
};
if let Some(preset_params) = new_params {
for (i, &v) in preset_params.iter().enumerate() {
track.synth_params[i] = v;
}
}
}
if let Some(mixer_id) = track.mixer_id {
return Some((mixer_id, idx, new_val));
}
}
}
None
}
/// Show controls for the currently selected track and route MIDI to it.
/// For instrument tracks: opens clip view with Synth tab, activates MIDI input.
/// For bus tracks: no clip view, deactivates MIDI.
pub(crate) fn show_current_track_controls(&mut self) {
// Deactivate MIDI on ALL tracks first
for track in &self.tracks {
if let Some(ref h) = track.handle {
h.config.midi_active.store(false, std::sync::atomic::Ordering::Relaxed);
}
}
if let Some(track) = self.tracks.get(self.track_cursor) {
if track.is_live() {
if let Some(ref h) = track.handle {
h.config.midi_active.store(true, std::sync::atomic::Ordering::Relaxed);
}
self.clip_view_visible = true;
// Use the currently selected clip element, or default to clip 0
let clip_idx = match self.track_element {
super::TrackElement::Clip(i) if i < track.clips.len() => i,
_ => 0,
};
self.clip_view_target = Some((self.track_cursor, clip_idx));
// If track has recorded clips, show piano roll. Otherwise show synth.
if !track.clips.is_empty() {
self.clip_view.clip_tab = ClipTab::PianoRoll;
self.clip_view.focus = ClipViewFocus::PianoRoll;
self.clip_view.piano_roll.focus = PianoRollFocus::Navigation;
self.clip_view.piano_roll.column = 0;
} else {
self.clip_view.fx_panel_tab = FxPanelTab::Synth;
self.clip_view.focus = ClipViewFocus::FxPanel;
self.clip_view.synth_param_cursor = 0;
}
} else {
// Bus track — hide clip view
self.clip_view_visible = false;
self.clip_view_target = None;
}
}
}
}