use serde::{Deserialize, Serialize};
use strum::{Display, EnumDiscriminants, EnumIter};
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy, Default, EnumDiscriminants)]
#[serde(from = "MidiEffectTargetRepr", into = "MidiEffectTargetRepr")]
#[strum_discriminants(name(MidiEffectTargetKind), derive(Display, EnumIter))]
pub enum MidiEffectTarget {
#[default]
#[strum_discriminants(strum(serialize = "Selected Effect"))]
Selected,
#[strum_discriminants(strum(serialize = "Effect Index"))]
Index { index: u8 },
}
impl MidiEffectTarget {
pub fn resolve(&self, selected_effect: usize) -> usize {
match self {
Self::Selected => selected_effect,
Self::Index { index } => usize::from(*index),
}
}
}
#[derive(Serialize, Deserialize)]
#[serde(untagged)]
enum MidiEffectTargetRepr {
Index(u8),
Named(MidiEffectTargetName),
}
#[derive(Serialize, Deserialize)]
enum MidiEffectTargetName {
Selected,
}
impl From<MidiEffectTargetRepr> for MidiEffectTarget {
fn from(repr: MidiEffectTargetRepr) -> Self {
match repr {
MidiEffectTargetRepr::Index(index) => Self::Index { index },
MidiEffectTargetRepr::Named(MidiEffectTargetName::Selected) => Self::Selected,
}
}
}
impl From<MidiEffectTarget> for MidiEffectTargetRepr {
fn from(target: MidiEffectTarget) -> Self {
match target {
MidiEffectTarget::Selected => Self::Named(MidiEffectTargetName::Selected),
MidiEffectTarget::Index { index } => Self::Index(index),
}
}
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
pub struct MidiEncoder {
pub encoding: MidiRelativeEncoding,
pub steps_per_unit: u8,
}
impl Default for MidiEncoder {
fn default() -> Self {
Self {
encoding: MidiRelativeEncoding::default(),
steps_per_unit: 1,
}
}
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy, Default, Display, EnumIter)]
pub enum MidiRelativeEncoding {
#[default]
#[strum(serialize = "Two's Complement")]
TwosComplement,
#[strum(serialize = "Sign Magnitude")]
SignMagnitude,
}
impl MidiRelativeEncoding {
pub fn steps(self, value: u8) -> i32 {
let value = i32::from(value);
if value < 64 {
return value;
}
match self {
Self::TwosComplement => value - 128,
Self::SignMagnitude => 64 - value,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_fixed_index_is_saved_as_the_plain_number_it_was() {
let target = MidiEffectTarget::Index { index: 3 };
assert_eq!(serde_json::to_string(&target).expect("serializes"), "3");
assert_eq!(
serde_json::from_str::<MidiEffectTarget>("3").expect("loads"),
target
);
}
#[test]
fn the_selected_effect_is_saved_by_name() {
assert_eq!(
serde_json::to_string(&MidiEffectTarget::Selected).expect("serializes"),
"\"Selected\""
);
assert_eq!(
serde_json::from_str::<MidiEffectTarget>("\"Selected\"").expect("loads"),
MidiEffectTarget::Selected
);
}
#[test]
fn encodings_agree_on_clockwise_turns() {
for encoding in [
MidiRelativeEncoding::TwosComplement,
MidiRelativeEncoding::SignMagnitude,
] {
assert_eq!(encoding.steps(0), 0, "{encoding}");
assert_eq!(encoding.steps(1), 1, "{encoding}");
assert_eq!(encoding.steps(7), 7, "{encoding}");
}
}
#[test]
fn twos_complement_counts_down_from_127() {
assert_eq!(MidiRelativeEncoding::TwosComplement.steps(127), -1);
assert_eq!(MidiRelativeEncoding::TwosComplement.steps(125), -3);
}
#[test]
fn sign_magnitude_counts_up_from_65() {
assert_eq!(MidiRelativeEncoding::SignMagnitude.steps(65), -1);
assert_eq!(MidiRelativeEncoding::SignMagnitude.steps(67), -3);
}
}