1#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
6#[repr(u8)]
7pub enum Module {
8 OdFx = 0,
9 Amp = 1,
10 Cab = 2,
11 Mod = 3,
12 Delay = 4,
13 Reverb = 5,
14 Gate = 6,
15 Eq = 7,
16 Loop = 8,
17 Drum = 9,
18}
19
20impl Module {
21 pub const ALL: [Module; 10] = [
22 Module::OdFx,
23 Module::Amp,
24 Module::Cab,
25 Module::Mod,
26 Module::Delay,
27 Module::Reverb,
28 Module::Gate,
29 Module::Eq,
30 Module::Loop,
31 Module::Drum,
32 ];
33
34 pub const EFFECTS: [Module; 8] = [
36 Module::OdFx,
37 Module::Amp,
38 Module::Cab,
39 Module::Mod,
40 Module::Delay,
41 Module::Reverb,
42 Module::Gate,
43 Module::Eq,
44 ];
45
46 pub fn from_index(i: u8) -> Option<Module> {
47 Module::ALL.get(i as usize).copied()
48 }
49
50 pub fn index(self) -> usize {
51 self as usize
52 }
53
54 pub fn is_effect(self) -> bool {
55 (self as u8) < 8
56 }
57
58 pub fn name(self) -> &'static str {
59 match self {
60 Module::OdFx => "OD/FX",
61 Module::Amp => "AMP",
62 Module::Cab => "CAB",
63 Module::Mod => "MOD",
64 Module::Delay => "DELAY",
65 Module::Reverb => "REVERB",
66 Module::Gate => "GATE",
67 Module::Eq => "EQ",
68 Module::Loop => "LOOP",
69 Module::Drum => "DRUM",
70 }
71 }
72
73 pub fn head_cc(self) -> Option<u8> {
74 self.is_effect().then_some(self as u8)
75 }
76
77 pub fn param_ccs(self) -> Option<(u8, u8)> {
79 Some(match self {
80 Module::Gate => (0x08, 2),
81 Module::OdFx => (0x0C, 4),
82 Module::Amp => (0x12, 6),
83 Module::Cab => (0x1A, 5),
84 Module::Eq => (0x20, 12),
85 Module::Mod => (0x2C, 4),
86 Module::Delay => (0x30, 4),
87 Module::Reverb => (0x36, 4),
88 Module::Loop | Module::Drum => return None,
89 })
90 }
91
92 pub fn from_param_cc(cc: u8) -> Option<(Module, usize)> {
94 Module::EFFECTS.into_iter().find_map(|m| {
95 let (first, n) = m.param_ccs()?;
96 (first..first + n)
97 .contains(&cc)
98 .then(|| (m, (cc - first) as usize))
99 })
100 }
101
102 pub fn models(self) -> &'static [&'static str] {
103 match self {
104 Module::OdFx => OD_MODELS,
105 Module::Amp => AMP_MODELS,
106 Module::Cab => CAB_MODELS,
107 Module::Mod => MOD_MODELS,
108 Module::Delay => DELAY_MODELS,
109 Module::Reverb => REVERB_MODELS,
110 Module::Gate => &["Noise Gate"],
111 Module::Eq => &["6-band EQ", "10-band EQ"],
112 Module::Loop | Module::Drum => &[],
113 }
114 }
115
116 pub fn model_name(self, model: u8) -> String {
117 match self.models().get(model as usize) {
118 Some(n) => (*n).to_owned(),
119 None => format!("Model {}", model + 1),
120 }
121 }
122
123 pub fn params(self, model: u8) -> Vec<ParamDef> {
125 let m = model as usize;
126 match self {
127 Module::OdFx => {
128 let mut v = knobs(OD_PARAMS.get(m).copied().unwrap_or(&[]));
129 if m == 13 {
130 v[0] = ParamDef::enumerated(0, "GAIN BOOST", &["OFF", "ON"]); }
132 v
133 }
134 Module::Amp => knobs(&["GAIN", "BASS", "MIDDLE", "TREBLE", "PRESENCE", "MASTER"]),
135 Module::Cab => {
136 let mut v = vec![
137 ParamDef::enumerated(0, "MIC", MICS),
138 ParamDef::enumerated(1, "POSITION", POSITIONS),
139 ParamDef::knob(2, "LOW CUT"),
140 ParamDef::knob(3, "HIGH CUT"),
141 ParamDef::knob(4, "VOLUME"),
142 ];
143 if model >= 30 {
144 v.drain(..2); }
146 v
147 }
148 Module::Mod => MOD_PARAMS.get(m).map(|p| p.to_vec()).unwrap_or_default(),
149 Module::Delay => knobs(if m == 2 {
150 &["TIME", "FEEDBACK", "MOD", "LEVEL"]
151 } else {
152 &["TIME", "FEEDBACK", "LEVEL"]
153 }),
154 Module::Reverb => knobs(REVERB_PARAMS.get(m).copied().unwrap_or(&[])),
155 Module::Gate => knobs(&["THRESHOLD", "DECAY"]),
156 Module::Eq => {
157 let names: &[&'static str] = if model == 1 { EQ10 } else { EQ6 };
158 names
159 .iter()
160 .enumerate()
161 .map(|(i, n)| ParamDef {
162 slot: i as u8,
163 name: n,
164 kind: if model == 1 && (i == 0 || i == 11) {
165 ParamKind::Knob
166 } else {
167 ParamKind::Bipolar
168 },
169 })
170 .collect()
171 }
172 Module::Loop | Module::Drum => vec![],
173 }
174 }
175
176 pub fn param(self, model: u8, slot: usize) -> Option<ParamDef> {
178 self.params(model).into_iter().find(|p| p.slot as usize == slot)
179 }
180}
181
182#[derive(Clone, Copy, Debug, PartialEq, Eq)]
183pub enum ParamKind {
184 Knob,
186 Bipolar,
188 Enum(&'static [&'static str]),
190}
191
192#[derive(Clone, Copy, Debug, PartialEq, Eq)]
193pub struct ParamDef {
194 pub slot: u8,
196 pub name: &'static str,
197 pub kind: ParamKind,
198}
199
200impl ParamDef {
201 const fn knob(slot: u8, name: &'static str) -> Self {
202 ParamDef { slot, name, kind: ParamKind::Knob }
203 }
204
205 const fn enumerated(slot: u8, name: &'static str, opts: &'static [&'static str]) -> Self {
206 ParamDef { slot, name, kind: ParamKind::Enum(opts) }
207 }
208
209 pub fn format(&self, v: u8) -> String {
211 match self.kind {
212 ParamKind::Knob => v.to_string(),
213 ParamKind::Bipolar => format!("{:+}", v as i16 - 50),
214 ParamKind::Enum(opts) => opts
215 .get(v as usize)
216 .map(|s| (*s).to_owned())
217 .unwrap_or_else(|| v.to_string()),
218 }
219 }
220}
221
222fn knobs(names: &[&'static str]) -> Vec<ParamDef> {
223 names
224 .iter()
225 .enumerate()
226 .map(|(i, n)| ParamDef::knob(i as u8, n))
227 .collect()
228}
229
230pub const OD_MODELS: &[&str] = &[
231 "Red Comp", "RS Comp", "Yellow Dist", "Boost RC", "Boost AC", "DS1", "TS9", "BD2", "Klone", "Ratt2",
232 "Red Drive", "Cruncher", "Muff", "Boost", "Face Fuzz", "ST Drive",
233];
234
235const OD_PARAMS: &[&[&str]] = &[
236 &["OUTPUT", "SENSITIVITY"],
237 &["SUSTAIN", "LEVEL"],
238 &["OUTPUT", "DISTORTION"],
239 &["GAIN", "VOLUME", "BASS", "TREBLE"],
240 &["GAIN", "VOLUME", "BASS", "TREBLE"],
241 &["GAIN", "TONE", "VOLUME"],
242 &["DRIVE", "TONE", "LEVEL"],
243 &["LEVEL", "TONE", "GAIN"],
244 &["VOLUME", "DRIVE", "TONE"],
245 &["DIST", "FILTER", "VOLUME"],
246 &["DRIVE", "TONE", "LEVEL"],
247 &["VOLUME", "TONE", "GAIN"],
248 &["VOLUME", "TONE", "SUSTAIN"],
249 &["GAIN BOOST", "VOLUME"],
250 &["GAIN", "TONE", "OUTPUT"],
251 &["VOLUME", "GAIN", "FILTER"],
252];
253
254pub const AMP_MODELS: &[&str] = &[
255 "Black Twin", "Tweed 5F1", "Bassguy", "Prince Rev", "Tweed 5F8", "Deluxe Rev", "Bdotone CL", "Voxy 30HW",
256 "Mar J800", "Mar J45", "Mar Plexi100", "Jazz CL", "Mes Rector", "Mes MKIV Lead", "Mes MKV Red",
257 "Mes LStar CL", "Cornfed M50", "ODS-100", "Spawn Qrod", "Soloist 100", "Fryman HB", "Fryman DS", "PVH 61",
258 "Eddie 51", "2Rock J35", "Ampug SV Bass", "Ampug Port Bass", "FR Bass", "Mes 400 Bass", "Agula DP751",
259 "USER AMP 01", "USER AMP 02", "USER AMP 03", "USER AMP 04", "USER AMP 05", "USER AMP 06", "USER AMP 07",
260 "USER AMP 08", "USER AMP 09", "USER AMP 10",
261];
262
263pub const CAB_MODELS: &[&str] = &[
264 "Twin 212", "Tweed 110", "Bassguy 410", "Prince 112", "Tweed 210", "Deluxe 112", "Bdotone 212", "Voxy 212",
265 "Mar G12M 412", "Mar Silver 212", "Mar V30 412", "Jazz 212", "Mes OS 412", "Mes Trad 412", "Mes V30 212",
266 "Mes EV 112", "Cornfed 212", "ODS 212", "Spawn 412", "SLO 412", "Fryman 412", "Fryman 212", "PVH 412",
267 "Eddie 412", "2Rock 212", "Ampug 810", "Ampug 410", "FR 210", "Mes 410", "Agula 810",
268 "USER IR 01", "USER IR 02", "USER IR 03", "USER IR 04", "USER IR 05", "USER IR 06", "USER IR 07",
269 "USER IR 08", "USER IR 09", "USER IR 10",
270];
271
272pub const MICS: &[&str] = &["E906", "M160", "MD421", "R121", "SM57", "SM58", "SM7B", "U47"];
273pub const POSITIONS: &[&str] = &["EDGE", "MIDDLE", "CENTER"];
274
275pub const MOD_MODELS: &[&str] = &[
276 "Chorus-1", "Chorus-2", "Stereo Chorus", "Vibrato", "Detune", "Flanger", "Phaser 90", "Phaser 100", "Chorus+",
277 "Uni Vibe", "Tremolo", "Chorus-3", "Harmony",
278];
279
280const KEYS: &[&str] = &["C", "C#", "D", "Eb", "E", "F", "F#", "G", "Ab", "A", "Bb", "B"];
281const HARM: &[&str] = &["-8TH", "-6TH", "-5TH", "-4TH", "-3TH", "3TH", "4TH", "5TH", "6TH", "8TH"];
282
283const MOD_PARAMS: &[&[ParamDef]] = &[
284 &[ParamDef::knob(0, "INTENSITY"), ParamDef::knob(1, "DEPTH"), ParamDef::knob(2, "RATE")],
285 &[ParamDef::knob(0, "RATE"), ParamDef::knob(1, "DEPTH")],
286 &[ParamDef::knob(0, "INTENSITY"), ParamDef::knob(1, "WIDTH"), ParamDef::knob(2, "RATE")],
287 &[ParamDef::knob(0, "RATE"), ParamDef::knob(1, "DEPTH")],
288 &[ParamDef::knob(0, "L"), ParamDef::knob(1, "MIX"), ParamDef::knob(2, "R")],
289 &[
290 ParamDef::knob(0, "LEVEL"),
291 ParamDef::knob(1, "RATE"),
292 ParamDef::knob(2, "WIDTH"),
293 ParamDef::knob(3, "FEEDBACK"),
294 ],
295 &[ParamDef::knob(0, "SPEED")],
296 &[ParamDef::knob(0, "INTENSITY"), ParamDef::knob(1, "SPEED")],
297 &[
298 ParamDef::knob(0, "SPEED"),
299 ParamDef::knob(1, "WIDTH"),
300 ParamDef::enumerated(2, "MODE", &["CHORUS", "FLANGER"]),
301 ParamDef::knob(3, "INTENSITY"),
302 ],
303 &[
304 ParamDef::knob(0, "SPEED"),
305 ParamDef::knob(1, "VOLUME"),
306 ParamDef::knob(2, "INTENSITY"),
307 ParamDef::enumerated(3, "MODE", &["CHORUS", "VIBE"]),
308 ],
309 &[ParamDef::knob(0, "RATE"), ParamDef::knob(1, "DEPTH")],
310 &[ParamDef::knob(0, "RATE"), ParamDef::knob(1, "DEPTH"), ParamDef::knob(2, "TONE")],
311 &[
312 ParamDef::enumerated(0, "KEY", KEYS),
313 ParamDef::enumerated(1, "MODE", &["MINOR", "MAJOR"]),
314 ParamDef::enumerated(2, "HARM", HARM),
315 ParamDef::knob(3, "MIX"),
316 ],
317];
318
319pub const DELAY_MODELS: &[&str] = &[
320 "Analog Delay", "Digital Delay", "Mod Delay", "Tape Delay", "Reverse Delay", "Pan Delay", "Physical Delay",
321];
322
323pub const REVERB_MODELS: &[&str] = &["Room", "Hall", "Plate", "Spring", "Shimmer", "Damp"];
324
325const REVERB_PARAMS: &[&[&str]] = &[
326 &["DECAY", "TONE", "LEVEL"],
327 &["DECAY", "TONE", "PRE DELAY", "LEVEL"],
328 &["DECAY", "LEVEL"],
329 &["DECAY", "LEVEL"],
330 &["LEVEL", "DECAY", "SHIMMER"],
331 &["LEVEL", "DEPTH"],
332];
333
334const EQ6: &[&str] = &["100Hz", "220Hz", "500Hz", "1.2kHz", "2.5kHz", "6.4kHz"];
335const EQ10: &[&str] = &[
336 "GAIN", "31Hz", "62.5Hz", "125Hz", "250Hz", "500Hz", "1kHz", "2kHz", "4kHz", "8kHz", "16kHz", "VOLUME",
337];
338
339pub const NOTES: &[&str] = &["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
340
341pub const DRUM_STYLES: &[&str] = &[
342 "Pop", "Rock", "Metal", "Disco", "Funk", "Blues", "Hip Hop", "Jazz", "Latin", "Metro",
343];
344const METRO: &[&str] = &["4/4", "2/4", "3/4", "5/4", "6/8", "7/8", "9/8", "10/8", "11/8", "12/8"];
345
346pub fn drum_pattern_name(v: u8) -> String {
348 let (style, pat) = ((v / 10) as usize, (v % 10) as usize);
349 match DRUM_STYLES.get(style) {
350 Some(&"Metro") => format!("Metro {}", METRO[pat]),
351 Some(s) => format!("{s} {:02}", pat + 1),
352 None => format!("Pattern {v}"),
353 }
354}
355
356#[cfg(test)]
357mod tests {
358 use super::*;
359
360 #[test]
361 fn param_cc_lookup() {
362 assert_eq!(Module::from_param_cc(0x12), Some((Module::Amp, 0)));
363 assert_eq!(Module::from_param_cc(0x09), Some((Module::Gate, 1)));
364 assert_eq!(Module::from_param_cc(0x2B), Some((Module::Eq, 11)));
365 assert_eq!(Module::from_param_cc(0x3A), None);
366 }
367
368 #[test]
369 fn model_counts() {
370 assert_eq!(OD_MODELS.len(), 16);
371 assert_eq!(AMP_MODELS.len(), 40);
372 assert_eq!(CAB_MODELS.len(), 40);
373 assert_eq!(MOD_MODELS.len(), 13);
374 assert_eq!(MOD_PARAMS.len(), 13);
375 assert_eq!(OD_PARAMS.len(), 16);
376 }
377
378 #[test]
379 fn drum_names() {
380 assert_eq!(drum_pattern_name(13), "Rock 04");
381 assert_eq!(drum_pattern_name(92), "Metro 3/4");
382 }
383}