1use crate::gui_channel::WAVEFORM_DOWNSAMPLE;
2use crate::music::{SavedMetricStructure};
3use derive_more::{Add, Div, Mul, Sub};
4use serde::{Deserialize, Serialize};
5use std::ops::{Index, IndexMut};
6use std::path::PathBuf;
7use std::str::FromStr;
8use std::sync::atomic::{AtomicUsize, Ordering};
9use std::sync::Arc;
10
11#[cfg(test)]
12mod tests {
13 use super::*;
14
15 #[test]
16 fn test_from_str() {
17 assert_eq!(
18 Command::Start,
19 Command::from_str("Start", &[][..]).unwrap()(CommandData { data: 0 })
20 );
21
22 assert_eq!(
23 Command::SetTime(FrameTime(100)),
24 Command::from_str("SetTime", &["100"][..]).unwrap()(CommandData { data: 0 })
25 );
26
27 assert_eq!(
28 Command::Looper(LooperCommand::Record, LooperTarget::All),
29 Command::from_str("Record", &["All"][..]).unwrap()(CommandData { data: 0 })
30 );
31
32 assert_eq!(
33 Command::Looper(LooperCommand::Overdub, LooperTarget::Selected),
34 Command::from_str("Overdub", &["Selected"][..]).unwrap()(CommandData { data: 0 })
35 );
36
37 assert_eq!(
38 Command::Looper(LooperCommand::Mute, LooperTarget::Index(13)),
39 Command::from_str("Mute", &["13"][..]).unwrap()(CommandData { data: 0 })
40 );
41 }
42}
43
44static SAMPLE_RATE: AtomicUsize = AtomicUsize::new(44100);
45
46pub fn set_sample_rate(sample_rate: usize) {
47 SAMPLE_RATE.store(sample_rate, Ordering::SeqCst);
48}
49
50pub fn get_sample_rate() -> usize {
51 SAMPLE_RATE.load(Ordering::SeqCst)
52}
53
54pub fn get_sample_rate_ms() -> f64 {
55 get_sample_rate() as f64 / 1000.0
56}
57
58#[derive(
59 Serialize,
60 Deserialize,
61 Clone,
62 Copy,
63 Debug,
64 Eq,
65 PartialEq,
66 Hash,
67 Ord,
68 PartialOrd,
69 Add,
70 Mul,
71 Sub,
72 Div,
73)]
74pub struct FrameTime(pub i64);
75
76impl FrameTime {
77 pub fn from_ms(ms: f64) -> FrameTime {
78 FrameTime((ms * get_sample_rate_ms()) as i64)
79 }
80
81 pub fn to_ms(&self) -> f64 {
82 (self.0 as f64) / get_sample_rate_ms()
83 }
84
85 pub fn to_waveform(&self) -> i64 {
86 self.0 / WAVEFORM_DOWNSAMPLE as i64
87 }
88}
89
90#[derive(Debug, PartialEq)]
91pub struct CommandData {
92 pub data: u8,
93}
94
95#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq, Hash)]
96pub enum LooperTarget {
97 Id(u32),
98 Index(u8),
99 All,
100 Selected,
101}
102
103#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
104pub enum LooperCommand {
105 Record,
107 Overdub,
108 Play,
109 Mute,
110 Solo,
111 Clear,
112
113 SetSpeed(LooperSpeed),
114
115 SetPan(f32),
117
118 SetLevel(f32),
120
121 RecordOverdubPlay,
123
124 AddToPart(Part),
126 RemoveFromPart(Part),
127
128 Delete,
130
131 Undo,
132 Redo,
133}
134
135impl LooperCommand {
136 pub fn from_str(
137 command: &str,
138 args: &[&str],
139 ) -> Result<Box<dyn Fn(CommandData) -> Command + Send>, String> {
140 use Command::Looper;
141 use LooperCommand::*;
142
143 let target_type = args.get(0).ok_or(format!("{} expects a target", command))?;
144
145 let target = match *target_type {
146 "All" => LooperTarget::All,
147 "Selected" => LooperTarget::Selected,
148 i => LooperTarget::Index(u8::from_str(i).map_err(|_| {
149 format!(
150 "{} expects a target (All, Selected, or a looper index)",
151 command
152 )
153 })?),
154 };
155
156 Ok(match command {
157 "Record" => Box::new(move |_| Looper(Record, target)),
158 "Overdub" => Box::new(move |_| Looper(Overdub, target)),
159 "Play" => Box::new(move |_| Looper(Play, target)),
160 "Mute" => Box::new(move |_| Looper(Mute, target)),
161 "Solo" => Box::new(move |_| Looper(Solo, target)),
162 "RecordOverdubPlay" => Box::new(move |_| Looper(RecordOverdubPlay, target)),
163 "Delete" => Box::new(move |_| Looper(Delete, target)),
164 "Clear" => Box::new(move |_| Looper(Clear, target)),
165
166 "SetPan" => {
167 let v = args.get(1).ok_or(
168 "SetPan expects a target and a pan value between -1 and 1".to_string(),
169 )?;
170
171 let arg = if *v == "$data" {
172 None
173 } else {
174 let f = f32::from_str(v)
175 .map_err(|_| format!("Invalid value for SetPan: '{}'", v))?;
176 if f < -1.0 || f > 1.0 {
177 return Err("Value for SetPan must be between -1 and 1".to_string());
178 }
179 Some(f)
180 };
181
182 Box::new(move |d| {
183 Looper(
184 SetPan(arg.unwrap_or(d.data as f32 / 127.0 * 2.0 - 1.0)),
185 target,
186 )
187 })
188 },
189
190 "SetLevel" => {
191 let v = args.get(1).ok_or(
192 "SetLevel expects a target and a level value between 0 and 1".to_string(),
193 )?;
194
195 let arg = if *v == "$data" {
196 None
197 } else {
198 let f = f32::from_str(v)
199 .map_err(|_| format!("Invalid value for SetLevel: '{}'", v))?;
200 if f < 0.0 || f > 1.0 {
201 return Err("Value for SetLevel must be between 0 and 1".to_string());
202 }
203 Some(f)
204 };
205
206 Box::new(move |d| {
207 Looper(
208 SetLevel(arg.unwrap_or(d.data as f32 / 127.0)),
209 target,
210 )
211 })
212 }
213
214
215 "1/2x" => Box::new(move |_| Looper(SetSpeed(LooperSpeed::Half), target)),
216 "1x" => Box::new(move |_| Looper(SetSpeed(LooperSpeed::One), target)),
217 "2x" => Box::new(move |_| Looper(SetSpeed(LooperSpeed::Double), target)),
218
219 "Undo" => Box::new(move |_| Looper(Undo, target)),
220 "Redo" => Box::new(move |_| Looper(Redo, target)),
221
222 _ => return Err(format!("{} is not a valid command", command)),
223 })
224 }
225}
226
227#[derive(Debug, Clone, PartialEq)]
228pub enum Command {
229 Looper(LooperCommand, LooperTarget),
230
231 Start,
232 Stop,
233 Pause,
234
235 StartStop,
236 PlayPause,
237
238 Reset,
239 SetTime(FrameTime),
240
241 AddLooper,
242 SelectLooperById(u32),
243 SelectLooperByIndex(u8),
244
245 SelectPreviousLooper,
246 SelectNextLooper,
247
248 PreviousPart,
249 NextPart,
250 GoToPart(Part),
251
252 SetQuantizationMode(QuantizationMode),
253
254 SaveSession(Arc<PathBuf>),
255 LoadSession(Arc<PathBuf>),
256
257 SetMetronomeLevel(u8),
258
259 SetTempoBPM(f32),
260 SetTimeSignature(u8, u8),
261}
262
263impl Command {
264 pub fn from_str(
265 command: &str,
266 args: &[&str],
267 ) -> Result<Box<dyn Fn(CommandData) -> Command + Send>, String> {
268 Ok(match command {
269 "Start" => Box::new(|_| Command::Start),
270 "Stop" => Box::new(|_| Command::Stop),
271 "Pause" => Box::new(|_| Command::Pause),
272 "StartStop" => Box::new(|_| Command::StartStop),
273 "PlayPause" => Box::new(|_| Command::PlayPause),
274 "Reset" => Box::new(|_| Command::Reset),
275
276 "SetTime" => {
277 let arg = args
278 .get(0)
279 .and_then(|s| i64::from_str(s).ok())
280 .map(|t| FrameTime(t))
281 .ok_or("SetTime expects a single numeric argument, time".to_string())?;
282 Box::new(move |_| Command::SetTime(arg))
283 }
284
285 "AddLooper" => Box::new(|_| Command::AddLooper),
286 "SelectLooperById" => {
287 let arg = args
288 .get(0)
289 .and_then(|s| u32::from_str(s).ok())
290 .ok_or(
291 "SelectLooperById expects a single numeric argument, the looper id"
292 .to_string(),
293 )?
294 .to_owned();
295
296 Box::new(move |_| Command::SelectLooperById(arg))
297 }
298
299 "SelectLooperByIndex" => {
300 let arg = args.get(0).and_then(|s| u8::from_str(s).ok()).ok_or(
301 "SelectLooperByIndex expects a single numeric argument, the looper index"
302 .to_string(),
303 )?;
304 Box::new(move |_| Command::SelectLooperByIndex(arg))
305 }
306
307 "SelectPreviousLooper" => Box::new(|_| Command::SelectPreviousLooper),
308 "SelectNextLooper" => Box::new(|_| Command::SelectNextLooper),
309
310 "PreviousPart" => Box::new(|_| Command::PreviousPart),
311 "NextPart" => Box::new(|_| Command::NextPart),
312 "GoToPart" => {
313 let arg = args
314 .get(0)
315 .and_then(|s| match s.as_ref() {
316 "A" => Some(Part::A),
317 "B" => Some(Part::B),
318 "C" => Some(Part::C),
319 "D" => Some(Part::D),
320 _ => None,
321 })
322 .ok_or("GoToPart expects a part name (one of A, B, C, or D)".to_string())?;
323
324 Box::new(move |_| Command::GoToPart(arg))
325 }
326
327 "SetQuantizationMode" => {
328 let arg = args
329 .get(0)
330 .and_then(|s| match s.as_ref() {
331 "Free" => Some(QuantizationMode::Free),
332 "Beat" => Some(QuantizationMode::Beat),
333 "Measure" => Some(QuantizationMode::Measure),
334 _ => None,
335 })
336 .ok_or(
337 "SetQuantizationMode expects a sync mode (one of Free, Beat, or Measure)"
338 .to_string(),
339 )?;
340 Box::new(move |_| Command::SetQuantizationMode(arg))
341 }
342
343 "SetMetronomeLevel" => {
344 let arg = args.get(0).and_then(|s| u8::from_str(s).ok()).ok_or(
345 "SetMetronomeLevel expects a single numeric argument, the level between 0-100"
346 .to_string(),
347 )?;
348 Box::new(move |_| Command::SetMetronomeLevel(arg))
349 }
350
351 _ => {
352 return LooperCommand::from_str(command, args);
353 }
354 })
355 }
356}
357
358#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize, Hash)]
359pub enum Part {
360 A,
361 B,
362 C,
363 D,
364}
365
366impl Part {
367 pub fn name(&self) -> &'static str {
368 match self {
369 Part::A => "A",
370 Part::B => "B",
371 Part::C => "C",
372 Part::D => "D",
373 }
374 }
375}
376
377#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
378pub struct PartSet {
379 a: bool,
380 b: bool,
381 c: bool,
382 d: bool,
383}
384
385impl PartSet {
386 pub fn new() -> PartSet {
387 PartSet {
388 a: true,
389 b: false,
390 c: false,
391 d: false,
392 }
393 }
394
395 pub fn with(part: Part) -> PartSet {
396 let mut parts = PartSet {
397 a: false,
398 b: false,
399 c: false,
400 d: false,
401 };
402 parts[part] = true;
403 parts
404 }
405
406 pub fn is_empty(&self) -> bool {
407 !(self.a || self.b || self.c || self.c)
408 }
409}
410
411impl Default for PartSet {
412 fn default() -> Self {
413 PartSet::new()
414 }
415}
416
417impl Index<Part> for PartSet {
418 type Output = bool;
419
420 fn index(&self, index: Part) -> &Self::Output {
421 match index {
422 Part::A => &self.a,
423 Part::B => &self.b,
424 Part::C => &self.c,
425 Part::D => &self.d,
426 }
427 }
428}
429
430impl IndexMut<Part> for PartSet {
431 fn index_mut(&mut self, index: Part) -> &mut Self::Output {
432 match index {
433 Part::A => &mut self.a,
434 Part::B => &mut self.b,
435 Part::C => &mut self.c,
436 Part::D => &mut self.d,
437 }
438 }
439}
440
441pub static PARTS: [Part; 4] = [Part::A, Part::B, Part::C, Part::D];
442
443#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq, Hash)]
444pub enum LooperMode {
445 Recording,
446 Overdubbing,
447 Muted,
448 Playing,
449 Soloed,
450}
451
452#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq, Hash)]
453pub enum LooperSpeed {
454 Half,
455 One,
456 Double,
457}
458
459fn looper_speed_default() -> LooperSpeed {
460 LooperSpeed::One
461}
462
463#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq, Hash)]
464pub enum QuantizationMode {
465 Free,
466 Beat,
467 Measure,
468}
469
470fn sync_mode_default() -> QuantizationMode {
471 QuantizationMode::Measure
472}
473
474fn level_default() -> f32 {
475 1.0
476}
477
478#[derive(Serialize, Deserialize, Clone, Debug)]
479pub struct SavedLooper {
480 pub id: u32,
481 pub mode: LooperMode,
482 #[serde(default = "looper_speed_default")]
483 pub speed: LooperSpeed,
484 #[serde(default)]
485 pub pan: f32,
486 #[serde(default = "level_default")]
487 pub level: f32,
488 #[serde(default)]
489 pub parts: PartSet,
490 pub samples: Vec<PathBuf>,
491 #[serde(default)]
492 pub offset_samples: i64,
493}
494
495#[derive(Serialize, Deserialize, Clone, Debug)]
496pub struct SavedSession {
497 pub save_time: i64,
498 #[serde(default)]
499 pub metronome_volume: u8,
500 pub metric_structure: SavedMetricStructure,
501 #[serde(default = "sync_mode_default")]
502 pub sync_mode: QuantizationMode,
503 #[serde(default)]
504 pub sample_rate: usize,
505 pub loopers: Vec<SavedLooper>,
506}