gled_model/asset/midi_controller/
target.rs1use serde::{Deserialize, Serialize};
5use strum::{Display, EnumDiscriminants, EnumIter};
6
7#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy, Default, EnumDiscriminants)]
13#[serde(from = "MidiEffectTargetRepr", into = "MidiEffectTargetRepr")]
14#[strum_discriminants(name(MidiEffectTargetKind), derive(Display, EnumIter))]
15pub enum MidiEffectTarget {
16 #[default]
17 #[strum_discriminants(strum(serialize = "Selected Effect"))]
18 Selected,
19 #[strum_discriminants(strum(serialize = "Effect Index"))]
20 Index { index: u8 },
21}
22
23impl MidiEffectTarget {
24 pub fn resolve(&self, selected_effect: usize) -> usize {
26 match self {
27 Self::Selected => selected_effect,
28 Self::Index { index } => usize::from(*index),
29 }
30 }
31}
32
33#[derive(Serialize, Deserialize)]
35#[serde(untagged)]
36enum MidiEffectTargetRepr {
37 Index(u8),
38 Named(MidiEffectTargetName),
39}
40
41#[derive(Serialize, Deserialize)]
42enum MidiEffectTargetName {
43 Selected,
44}
45
46impl From<MidiEffectTargetRepr> for MidiEffectTarget {
47 fn from(repr: MidiEffectTargetRepr) -> Self {
48 match repr {
49 MidiEffectTargetRepr::Index(index) => Self::Index { index },
50 MidiEffectTargetRepr::Named(MidiEffectTargetName::Selected) => Self::Selected,
51 }
52 }
53}
54
55impl From<MidiEffectTarget> for MidiEffectTargetRepr {
56 fn from(target: MidiEffectTarget) -> Self {
57 match target {
58 MidiEffectTarget::Selected => Self::Named(MidiEffectTargetName::Selected),
59 MidiEffectTarget::Index { index } => Self::Index(index),
60 }
61 }
62}
63
64#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
68pub struct MidiEncoder {
69 pub encoding: MidiRelativeEncoding,
70 pub steps_per_unit: u8,
71}
72
73impl Default for MidiEncoder {
74 fn default() -> Self {
75 Self {
76 encoding: MidiRelativeEncoding::default(),
77 steps_per_unit: 1,
78 }
79 }
80}
81
82#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy, Default, Display, EnumIter)]
86pub enum MidiRelativeEncoding {
87 #[default]
90 #[strum(serialize = "Two's Complement")]
91 TwosComplement,
92 #[strum(serialize = "Sign Magnitude")]
95 SignMagnitude,
96}
97
98impl MidiRelativeEncoding {
99 pub fn steps(self, value: u8) -> i32 {
101 let value = i32::from(value);
102 if value < 64 {
103 return value;
104 }
105 match self {
106 Self::TwosComplement => value - 128,
107 Self::SignMagnitude => 64 - value,
108 }
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn a_fixed_index_is_saved_as_the_plain_number_it_was() {
118 let target = MidiEffectTarget::Index { index: 3 };
119 assert_eq!(serde_json::to_string(&target).expect("serializes"), "3");
120 assert_eq!(
121 serde_json::from_str::<MidiEffectTarget>("3").expect("loads"),
122 target
123 );
124 }
125
126 #[test]
127 fn the_selected_effect_is_saved_by_name() {
128 assert_eq!(
129 serde_json::to_string(&MidiEffectTarget::Selected).expect("serializes"),
130 "\"Selected\""
131 );
132 assert_eq!(
133 serde_json::from_str::<MidiEffectTarget>("\"Selected\"").expect("loads"),
134 MidiEffectTarget::Selected
135 );
136 }
137
138 #[test]
139 fn encodings_agree_on_clockwise_turns() {
140 for encoding in [
141 MidiRelativeEncoding::TwosComplement,
142 MidiRelativeEncoding::SignMagnitude,
143 ] {
144 assert_eq!(encoding.steps(0), 0, "{encoding}");
145 assert_eq!(encoding.steps(1), 1, "{encoding}");
146 assert_eq!(encoding.steps(7), 7, "{encoding}");
147 }
148 }
149
150 #[test]
151 fn twos_complement_counts_down_from_127() {
152 assert_eq!(MidiRelativeEncoding::TwosComplement.steps(127), -1);
153 assert_eq!(MidiRelativeEncoding::TwosComplement.steps(125), -3);
154 }
155
156 #[test]
157 fn sign_magnitude_counts_up_from_65() {
158 assert_eq!(MidiRelativeEncoding::SignMagnitude.steps(65), -1);
159 assert_eq!(MidiRelativeEncoding::SignMagnitude.steps(67), -3);
160 }
161}