use super::*;
impl NavState {
pub 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() {
let instrument = track.instrument_type?;
let new_val = if crate::discrete::is_discrete(instrument, idx) {
crate::discrete::step(instrument, idx, track.synth_params[idx], delta > 0.0)
} else {
(track.synth_params[idx] + delta).clamp(0.0, 1.0)
};
track.synth_params[idx] = new_val;
if idx == 0 {
let new_params: Option<Vec<f32>> = match track.instrument_type {
Some(InstrumentType::Jupiter8) => {
Some(phosphor_dsp::jupiter::Jupiter8Synth::params_for_patch(new_val).to_vec())
}
Some(InstrumentType::Odyssey) => {
Some(phosphor_dsp::odyssey::OdysseySynth::params_for_patch(new_val).to_vec())
}
Some(InstrumentType::Juno60) => {
Some(phosphor_dsp::juno::Juno60Synth::params_for_patch(new_val).to_vec())
}
_ => None,
};
if let Some(preset_params) = new_params {
for (slot, v) in track.synth_params.iter_mut().zip(preset_params) {
*slot = v;
}
}
}
if let Some(mixer_id) = track.mixer_id {
return Some((mixer_id, idx, new_val));
}
}
}
None
}
pub fn show_current_track_controls(&mut self) {
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;
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.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 {
self.clip_view_visible = false;
self.clip_view_target = None;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use phosphor_dsp::{drum_rack, dx7, juno, jupiter};
fn dx7_track() -> NavState {
let mut nav = NavState::new(super::super::initial_tracks());
let mut track = TrackState::new("dx7", 0, true, TrackKind::Instrument, vec![]);
track.instrument_type = Some(InstrumentType::DX7);
track.synth_params = dx7::PARAM_DEFAULTS.to_vec();
nav.tracks.insert(0, track);
nav.track_cursor = 0;
nav
}
fn selected(nav: &NavState) -> (usize, usize) {
let p = &nav.tracks[0].synth_params;
(dx7::bank_index(p[dx7::P_BANK]), dx7::patch_index(p[dx7::P_PATCH]))
}
#[test]
fn dx7_selectors_move_one_step_per_keypress() {
let mut nav = dx7_track();
nav.clip_view.synth_param_cursor = dx7::P_PATCH;
let (bank, patch) = selected(&nav);
for step in 1..=5 {
nav.adjust_synth_param(0.05);
assert_eq!(selected(&nav), (bank, patch + step), "patch knob step {step}");
}
for step in (0..5).rev() {
nav.adjust_synth_param(-0.05);
assert_eq!(selected(&nav), (bank, patch + step), "patch knob back to {step}");
}
nav.clip_view.synth_param_cursor = dx7::P_BANK;
for step in 1..dx7::BANK_COUNT {
nav.adjust_synth_param(0.05);
assert_eq!(selected(&nav), (step, patch), "bank knob step {step}");
}
for _ in 0..4 {
nav.adjust_synth_param(0.05);
}
assert_eq!(selected(&nav), (dx7::BANK_COUNT - 1, patch));
}
fn juno_track() -> NavState {
let mut nav = NavState::new(super::super::initial_tracks());
let mut track = TrackState::new("juno", 0, true, TrackKind::Instrument, vec![]);
track.instrument_type = Some(InstrumentType::Juno60);
track.synth_params = juno::PARAM_DEFAULTS.to_vec();
nav.tracks.insert(0, track);
nav.track_cursor = 0;
nav
}
fn juno_patch(nav: &NavState) -> usize {
juno::patch_index(nav.tracks[0].synth_params[juno::P_PATCH])
}
#[test]
fn juno_selectors_move_one_step_per_keypress() {
let mut nav = juno_track();
nav.clip_view.synth_param_cursor = juno::P_PATCH;
for step in 1..juno::PATCH_COUNT {
nav.adjust_synth_param(0.05);
assert_eq!(juno_patch(&nav), step, "patch knob step {step}");
}
nav.adjust_synth_param(0.05);
assert_eq!(juno_patch(&nav), juno::PATCH_COUNT - 1, "patch knob ran off the top");
for step in (0..juno::PATCH_COUNT - 1).rev() {
nav.adjust_synth_param(-0.05);
assert_eq!(juno_patch(&nav), step, "patch knob back to {step}");
}
for _ in 0..juno::PATCH_COUNT {
nav.adjust_synth_param(0.05);
}
let panel = &nav.tracks[0].synth_params;
assert_eq!(juno_patch(&nav), juno::PATCH_COUNT - 1);
assert!((panel[juno::P_RESO] - 1.0).abs() < 1e-6, "res {}", panel[juno::P_RESO]);
let mut nav = juno_track();
nav.clip_view.synth_param_cursor = juno::P_PWM_MODE;
let label = |nav: &NavState| {
juno::discrete_label(juno::P_PWM_MODE, nav.tracks[0].synth_params[juno::P_PWM_MODE])
};
assert_eq!(label(&nav), Some("LFO"));
let mut seen = Vec::new();
for _ in 0..3 {
nav.adjust_synth_param(0.05);
seen.push(label(&nav));
}
assert_eq!(seen, [Some("MAN"), Some("ENV"), Some("ENV")]);
}
fn jupiter_track() -> NavState {
let mut nav = NavState::new(super::super::initial_tracks());
let mut track = TrackState::new("jupiter", 0, true, TrackKind::Instrument, vec![]);
track.instrument_type = Some(InstrumentType::Jupiter8);
track.synth_params = jupiter::PARAM_DEFAULTS.to_vec();
nav.tracks.insert(0, track);
nav.track_cursor = 0;
nav
}
#[test]
fn jupiter_selectors_move_one_step_per_keypress() {
let mut nav = jupiter_track();
nav.clip_view.synth_param_cursor = jupiter::P_PATCH;
let patch = |nav: &NavState| {
jupiter::patch_index(nav.tracks[0].synth_params[jupiter::P_PATCH])
};
for step in 1..jupiter::PATCH_COUNT {
nav.adjust_synth_param(0.05);
assert_eq!(patch(&nav), step, "patch knob step {step}");
}
nav.adjust_synth_param(0.05);
assert_eq!(patch(&nav), jupiter::PATCH_COUNT - 1, "patch knob ran off the top");
for step in (0..jupiter::PATCH_COUNT - 1).rev() {
nav.adjust_synth_param(-0.05);
assert_eq!(patch(&nav), step, "patch knob back to {step}");
}
let mut nav = jupiter_track();
nav.clip_view.synth_param_cursor = jupiter::P_VCO2_WAVE;
let label = |nav: &NavState| {
jupiter::discrete_label(
jupiter::P_VCO2_WAVE,
nav.tracks[0].synth_params[jupiter::P_VCO2_WAVE],
)
};
assert_eq!(label(&nav), Some("SAW"));
let mut seen = Vec::new();
for _ in 0..3 {
nav.adjust_synth_param(0.05);
seen.push(label(&nav));
}
assert_eq!(seen, [Some("PLS"), Some("NOISE"), Some("NOISE")]);
}
fn drum_track() -> NavState {
let mut nav = NavState::new(super::super::initial_tracks());
let mut track = TrackState::new("drums", 0, true, TrackKind::Instrument, vec![]);
track.instrument_type = Some(InstrumentType::DrumRack);
track.synth_params = drum_rack::PARAM_DEFAULTS.to_vec();
nav.tracks.insert(0, track);
nav.track_cursor = 0;
nav
}
#[test]
fn the_drum_kit_selector_moves_one_kit_per_keypress() {
let mut nav = drum_track();
nav.clip_view.synth_param_cursor = drum_rack::P_KIT;
let kit = |nav: &NavState| {
drum_rack::discrete_label(drum_rack::P_KIT, nav.tracks[0].synth_params[drum_rack::P_KIT])
};
let last = *drum_rack::KIT_LABELS.last().unwrap();
assert_eq!(kit(&nav), Some("808"));
for label in drum_rack::KIT_LABELS.iter().skip(1) {
nav.adjust_synth_param(0.05);
assert_eq!(kit(&nav), Some(*label));
}
nav.adjust_synth_param(0.05);
assert_eq!(kit(&nav), Some(last), "the kit knob ran off the top");
for label in drum_rack::KIT_LABELS.iter().rev().skip(1) {
nav.adjust_synth_param(-0.05);
assert_eq!(kit(&nav), Some(*label));
}
nav.clip_view.synth_param_cursor = drum_rack::P_BD_DECAY;
let before = nav.tracks[0].synth_params.clone();
nav.adjust_synth_param(-0.05);
let after = &nav.tracks[0].synth_params;
assert!((after[drum_rack::P_BD_DECAY] - 0.45).abs() < 1e-6);
for i in 0..after.len() {
if i != drum_rack::P_BD_DECAY {
assert_eq!(before[i], after[i], "{} moved with the kick's decay", drum_rack::PARAM_NAMES[i]);
}
}
}
#[test]
fn the_dx7_bank_knob_is_the_last_parameter() {
let nav = dx7_track();
assert_eq!(nav.tracks[0].synth_params.len(), dx7::PARAM_COUNT);
assert_eq!(dx7::P_BANK, dx7::PARAM_COUNT - 1);
assert_eq!(dx7::PARAM_NAMES[dx7::P_GAIN], "gain", "index 0-7 must not move");
}
}