use crate::state::InstrumentType;
const MAX_POSITIONS: usize = 1_024;
#[must_use]
pub fn is_discrete(instrument: InstrumentType, param: usize) -> bool {
match instrument {
InstrumentType::Synth | InstrumentType::Sampler => phosphor_dsp::synth::is_discrete(param),
InstrumentType::DrumRack => phosphor_dsp::drum_rack::is_discrete(param),
InstrumentType::DX7 => phosphor_dsp::dx7::is_discrete(param),
InstrumentType::Jupiter8 => phosphor_dsp::jupiter::is_discrete(param),
InstrumentType::Odyssey => phosphor_dsp::odyssey::is_discrete(param),
InstrumentType::Juno60 => phosphor_dsp::juno::is_discrete(param),
}
}
#[must_use]
pub fn step(instrument: InstrumentType, param: usize, value: f32, up: bool) -> f32 {
match instrument {
InstrumentType::Synth | InstrumentType::Sampler => {
phosphor_dsp::synth::step_discrete(param, value, up)
}
InstrumentType::DrumRack => phosphor_dsp::drum_rack::step_discrete(param, value, up),
InstrumentType::DX7 => phosphor_dsp::dx7::step_discrete(param, value, up),
InstrumentType::Jupiter8 => phosphor_dsp::jupiter::step_discrete(param, value, up),
InstrumentType::Odyssey => phosphor_dsp::odyssey::step_discrete(param, value, up),
InstrumentType::Juno60 => phosphor_dsp::juno::step_discrete(param, value, up),
}
}
#[must_use]
pub fn positions(instrument: InstrumentType, param: usize) -> Option<Vec<f32>> {
if !is_discrete(instrument, param) {
return None;
}
let mut knob = 0.0f32;
for _ in 0..MAX_POSITIONS {
let down = step(instrument, param, knob, false);
if down == knob {
break;
}
knob = down;
}
let mut found = vec![knob];
for _ in 0..MAX_POSITIONS {
let up = step(instrument, param, knob, true);
if up == knob {
break;
}
knob = up;
found.push(knob);
}
Some(found)
}
#[must_use]
pub fn index_of(instrument: InstrumentType, param: usize, value: f32) -> Option<usize> {
let found = positions(instrument, param)?;
let mut best = 0;
let mut best_distance = f32::INFINITY;
for (i, knob) in found.iter().enumerate() {
let distance = (knob - value).abs();
if distance < best_distance {
best = i;
best_distance = distance;
}
}
Some(best)
}
#[must_use]
pub fn knob_at(instrument: InstrumentType, param: usize, index: usize) -> Option<f32> {
let found = positions(instrument, param)?;
found.get(index).or_else(|| found.last()).copied()
}
#[cfg(test)]
mod tests {
use super::*;
use phosphor_dsp::{drum_rack, dx7, jupiter, juno, odyssey, synth};
#[test]
fn the_walk_finds_every_position() {
assert_eq!(
positions(InstrumentType::DrumRack, drum_rack::P_KIT).unwrap().len(),
drum_rack::KIT_COUNT
);
assert_eq!(
positions(InstrumentType::Jupiter8, jupiter::P_PATCH).unwrap().len(),
jupiter::PATCH_COUNT
);
assert_eq!(
positions(InstrumentType::Juno60, juno::P_PATCH).unwrap().len(),
juno::PATCH_COUNT
);
assert_eq!(
positions(InstrumentType::Odyssey, odyssey::P_PATCH).unwrap().len(),
odyssey::PATCH_COUNT
);
assert_eq!(
positions(InstrumentType::DX7, dx7::P_BANK).unwrap().len(),
dx7::BANK_COUNT
);
let patches = positions(InstrumentType::DX7, dx7::P_PATCH).unwrap().len();
assert_eq!(patches * dx7::BANK_COUNT, dx7::VOICE_COUNT);
assert_eq!(
positions(InstrumentType::Synth, synth::P_PATCH).unwrap().len(),
synth::PATCH_COUNT
);
assert_eq!(
positions(InstrumentType::Sampler, synth::P_PATCH).unwrap().len(),
synth::PATCH_COUNT
);
assert_eq!(positions(InstrumentType::Synth, synth::P_A_TUNE).unwrap().len(), 49);
assert!(positions(InstrumentType::Synth, synth::P_CUTOFF).is_none());
}
#[test]
fn a_position_round_trips_through_its_index() {
for instrument in InstrumentType::ALL {
let count = crate::preset::param_count(*instrument);
for param in 0..count {
if !is_discrete(*instrument, param) {
assert!(index_of(*instrument, param, 0.5).is_none());
continue;
}
let found = positions(*instrument, param).unwrap();
for (i, knob) in found.iter().enumerate() {
assert_eq!(
index_of(*instrument, param, *knob),
Some(i),
"{instrument:?} param {param} position {i} ({knob})"
);
assert_eq!(knob_at(*instrument, param, i), Some(*knob));
}
assert_eq!(
knob_at(*instrument, param, found.len() + 100),
found.last().copied()
);
}
}
}
#[test]
fn an_index_survives_the_bank_growing() {
let ten_kit_knob = 1.5 / 10.0;
let fifteen_kit_knob =
knob_at(InstrumentType::DrumRack, drum_rack::P_KIT, 1).unwrap();
assert_eq!(drum_rack::discrete_label(drum_rack::P_KIT, ten_kit_knob), Some("707"));
assert_eq!(
drum_rack::discrete_label(drum_rack::P_KIT, fifteen_kit_knob),
Some("909")
);
}
#[test]
fn nonsense_knob_values_land_on_a_real_position() {
for value in [-1.0f32, 0.0, 1.0, 2.0, f32::NAN, f32::INFINITY, f32::NEG_INFINITY] {
let index = index_of(InstrumentType::DrumRack, drum_rack::P_KIT, value).unwrap();
assert!(index < drum_rack::KIT_COUNT, "{value} landed on position {index}");
}
}
}