loopers_common/
api.rs

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    // Basic commands
106    Record,
107    Overdub,
108    Play,
109    Mute,
110    Solo,
111    Clear,
112
113    SetSpeed(LooperSpeed),
114
115    // [-1.0, 1.0]
116    SetPan(f32),
117
118    // [0.0, 1.0]
119    SetLevel(f32),
120
121    // Composite commands
122    RecordOverdubPlay,
123
124    // not currently usable from midi
125    AddToPart(Part),
126    RemoveFromPart(Part),
127
128    // delete
129    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}