loopers_engine/
lib.rs

1#[macro_use]
2extern crate lazy_static;
3#[macro_use]
4extern crate log;
5
6use std::collections::VecDeque;
7use std::fs::{create_dir_all, read_to_string, File};
8use std::io;
9use std::io::{Read, Write};
10use std::ops::Range;
11use std::path::{Path, PathBuf};
12use std::sync::Arc;
13
14use crossbeam_channel::Receiver;
15
16use loopers_common::api::QuantizationMode::Free;
17use loopers_common::api::{
18    get_sample_rate, set_sample_rate, Command, FrameTime, LooperCommand, LooperMode, LooperTarget,
19    Part, PartSet, QuantizationMode, SavedSession,
20};
21use loopers_common::config::{Config, MidiMapping, FILE_HEADER};
22use loopers_common::gui_channel::{
23    EngineState, EngineStateSnapshot, GuiCommand, GuiSender, LogMessage,
24};
25use loopers_common::midi::MidiEvent;
26use loopers_common::music::*;
27use loopers_common::Host;
28
29use crate::error::SaveLoadError;
30use crate::looper::Looper;
31use crate::metronome::Metronome;
32use crate::sample::Sample;
33use crate::session::{SaveSessionData, SessionSaver};
34use crate::trigger::{Trigger, TriggerCondition};
35
36mod error;
37pub mod looper;
38pub mod metronome;
39pub mod sample;
40pub mod session;
41mod trigger;
42
43pub struct Engine {
44    config: Config,
45
46    state: EngineState,
47
48    time: i64,
49
50    metric_structure: MetricStructure,
51
52    command_input: Receiver<Command>,
53
54    gui_sender: GuiSender,
55
56    loopers: Vec<Looper>,
57    active: u32,
58
59    current_part: Part,
60
61    sync_mode: QuantizationMode,
62
63    metronome: Option<Metronome>,
64
65    triggers: VecDeque<Trigger>,
66
67    id_counter: u32,
68
69    session_saver: SessionSaver,
70
71    tmp_left: Vec<f64>,
72    tmp_right: Vec<f64>,
73    output_left: Vec<f64>,
74    output_right: Vec<f64>,
75
76    looper_peaks: [[f32; 2]; 64],
77}
78
79#[allow(dead_code)]
80const THRESHOLD: f32 = 0.05;
81
82#[allow(dead_code)]
83fn max_abs(b: &[f32]) -> f32 {
84    b.iter()
85        .map(|v| v.abs())
86        .fold(f32::NEG_INFINITY, |a, b| a.max(b))
87}
88
89pub fn last_session_path() -> io::Result<PathBuf> {
90    let mut config_path = dirs::config_dir().unwrap_or(PathBuf::new());
91    config_path.push("loopers");
92    create_dir_all(&config_path)?;
93    config_path.push(".last-session");
94    Ok(config_path)
95}
96
97pub fn read_config() -> Result<Config, String> {
98    let mut mapping_path = dirs::config_dir().unwrap_or(PathBuf::new());
99    mapping_path.push("loopers/midi_mappings.tsv");
100
101    let mut config = Config::new();
102
103    match File::open(&mapping_path) {
104        Ok(file) => match MidiMapping::from_file(&mapping_path.to_string_lossy(), &file) {
105            Ok(mms) => config.midi_mappings.extend(mms),
106            Err(e) => {
107                return Err(format!("Failed to load midi mappings: {:?}", e));
108            }
109        },
110        Err(ref e) if e.kind() == io::ErrorKind::NotFound => {
111            // try to create an empty config file if it doesn't exist
112            if let Ok(ref mut file) = File::create(&mapping_path) {
113                writeln!(file, "{}", FILE_HEADER).unwrap();
114            }
115        }
116        Err(_) => {}
117    }
118
119    Ok(config)
120}
121
122impl Engine {
123    pub fn new<'a, H: Host<'a>>(
124        host: &mut H,
125        mut gui_sender: GuiSender,
126        command_input: Receiver<Command>,
127        beat_normal: Vec<f32>,
128        beat_emphasis: Vec<f32>,
129        restore: bool,
130        sample_rate: usize,
131    ) -> Engine {
132        let metric_structure = MetricStructure::new(4, 4, Tempo::from_bpm(120.0)).unwrap();
133
134        let config = match read_config() {
135            Ok(config) => config,
136            Err(err) => {
137                let mut error = LogMessage::error();
138                if let Err(e) = write!(error, "{}", err) {
139                    error!("Failed to report config error: {}", e);
140                } else {
141                    gui_sender.send_log(error);
142                }
143
144                Config::new()
145            }
146        };
147
148        let mut engine = Engine {
149            config,
150
151            state: EngineState::Stopped,
152            time: 0,
153
154            metric_structure,
155
156            gui_sender: gui_sender.clone(),
157            command_input,
158
159            loopers: vec![Looper::new(0, PartSet::new(), gui_sender.clone()).start()],
160            active: 0,
161            current_part: Part::A,
162
163            sync_mode: QuantizationMode::Measure,
164
165            id_counter: 1,
166
167            metronome: Some(Metronome::new(
168                metric_structure,
169                Sample::from_mono(&beat_normal),
170                Sample::from_mono(&beat_emphasis),
171            )),
172
173            triggers: VecDeque::with_capacity(128),
174
175            session_saver: SessionSaver::new(gui_sender),
176
177            tmp_left: vec![0f64; 2048],
178            tmp_right: vec![0f64; 2048],
179
180            output_left: vec![0f64; 2048],
181            output_right: vec![0f64; 2048],
182
183            looper_peaks: [[0.0; 2]; 64],
184        };
185
186        set_sample_rate(sample_rate);
187
188        engine.reset();
189
190        for l in &engine.loopers {
191            engine.session_saver.add_looper(l);
192            if let Err(e) = host.add_looper(l.id) {
193                error!("Failed to add host port for looper {}: {}", l.id, e);
194            }
195        }
196
197        if restore {
198            let mut restore_fn = || {
199                let config_path = last_session_path()?;
200                let restore_path = read_to_string(config_path)?;
201                info!("Restoring from {}", restore_path);
202                engine.load_session(host, Path::new(&restore_path))
203            };
204
205            if let Err(err) = restore_fn() {
206                warn!("Failed to restore existing session {:?}", err);
207            }
208        }
209
210        engine
211    }
212
213    fn add_trigger(triggers: &mut VecDeque<Trigger>, t: Trigger) {
214        while triggers.len() >= triggers.capacity() {
215            triggers.pop_front();
216        }
217
218        triggers.push_back(t);
219    }
220
221    fn reset(&mut self) {
222        if let Some(m) = &mut self.metronome {
223            m.reset();
224        }
225        self.triggers.clear();
226        self.set_time(FrameTime(-(self.measure_len().0 as i64)));
227        for l in &mut self.loopers {
228            l.handle_command(LooperCommand::Play);
229        }
230    }
231
232    fn looper_by_index_mut(&mut self, idx: u8) -> Option<&mut Looper> {
233        self.loopers
234            .iter_mut()
235            .filter(|l| !l.deleted)
236            .skip(idx as usize)
237            .next()
238    }
239
240    fn commands_from_midi<'a, H: Host<'a>>(&mut self, host: &mut H, events: &[MidiEvent]) {
241        for e in events {
242            debug!("midi {:?}", e);
243            for i in 0..self.config.midi_mappings.len() {
244                let mm = &self.config.midi_mappings[i];
245                if let Some(c) = mm.command_for_event(e) {
246                    self.handle_command(host, &c, false);
247                }
248            }
249        }
250    }
251
252    // possibly convert a loop command into a trigger
253    fn trigger_from_command(
254        ms: MetricStructure,
255        sync_mode: QuantizationMode,
256        time: FrameTime,
257        lc: LooperCommand,
258        target: LooperTarget,
259        looper: &Looper,
260    ) -> Option<Trigger> {
261        let trigger_condition = match sync_mode {
262            Free => if time.0 < 0 { Some(TriggerCondition::Beat) } else { None },
263            QuantizationMode::Beat => Some(TriggerCondition::Beat),
264            QuantizationMode::Measure => Some(TriggerCondition::Measure),
265        }?;
266
267        use LooperCommand::*;
268        match (looper.length() == 0, looper.mode(), lc) {
269            // SetLevel and SetPan should apply immediately
270            (_, _, SetLevel(_)) => None,
271            (_, _, SetPan(_)) => None,
272
273            (_, _, Record)
274            | (_, LooperMode::Recording, _)
275            | (true, _, RecordOverdubPlay)
276            | (_, LooperMode::Overdubbing, _) => Some(Trigger::new(
277                trigger_condition,
278                Command::Looper(lc, target),
279                ms,
280                time,
281            )),
282            (_, _, RecordOverdubPlay) => Some(Trigger::new(
283                TriggerCondition::Immediate,
284                Command::Looper(lc, target),
285                ms,
286                time,
287            )),
288            _ => None,
289        }
290    }
291
292    fn handle_loop_command(&mut self, lc: LooperCommand, target: LooperTarget, triggered: bool) {
293        debug!("Handling loop command: {:?} for {:?}", lc, target);
294
295        let ms = self.metric_structure;
296        let sync_mode = self.sync_mode;
297        let time = FrameTime(self.time);
298        let triggers = &mut self.triggers;
299        let gui_sender = &mut self.gui_sender;
300
301        fn handle_or_trigger(
302            triggered: bool,
303            ms: MetricStructure,
304            sync_mode: QuantizationMode,
305            time: FrameTime,
306            lc: LooperCommand,
307            target: LooperTarget,
308            looper: &mut Looper,
309            triggers: &mut VecDeque<Trigger>,
310            gui_sender: &mut GuiSender,
311        ) {
312            if triggered {
313                looper.handle_command(lc);
314            } else if let Some(trigger) =
315                Engine::trigger_from_command(ms, sync_mode, time, lc, target, looper)
316            {
317                Engine::add_trigger(triggers, trigger.clone());
318
319                gui_sender.send_update(GuiCommand::AddLoopTrigger(
320                    looper.id,
321                    trigger.triggered_at(),
322                    lc,
323                ));
324            } else {
325                looper.handle_command(lc);
326            }
327        }
328
329        let mut selected = None;
330        match target {
331            LooperTarget::Id(id) => {
332                if let Some(l) = self.loopers.iter_mut().find(|l| l.id == id) {
333                    handle_or_trigger(
334                        triggered, ms, sync_mode, time, lc, target, l, triggers, gui_sender,
335                    );
336                } else {
337                    warn!(
338                        "Could not find looper with id {} while handling command {:?}",
339                        id, lc
340                    );
341                }
342            }
343            LooperTarget::Index(idx) => {
344                if let Some(l) = self
345                    .loopers
346                    .iter_mut()
347                    .filter(|l| !l.deleted)
348                    .skip(idx as usize)
349                    .next()
350                {
351                    selected = Some(l.id);
352                    handle_or_trigger(
353                        triggered, ms, sync_mode, time, lc, target, l, triggers, gui_sender,
354                    );
355                } else {
356                    warn!("No looper at index {} while handling command {:?}", idx, lc);
357                }
358            }
359            LooperTarget::All => {
360                for l in &mut self.loopers {
361                    handle_or_trigger(
362                        triggered, ms, sync_mode, time, lc, target, l, triggers, gui_sender,
363                    );
364                }
365            }
366            LooperTarget::Selected => {
367                let active = self.active;
368                if let Some(l) = self.loopers.iter_mut().find(|l| l.id == active) {
369                    handle_or_trigger(
370                        triggered, ms, sync_mode, time, lc, target, l, triggers, gui_sender,
371                    );
372                } else {
373                    error!(
374                        "selected looper {} not found while handling command {:?}",
375                        self.active, lc
376                    );
377                }
378            }
379        };
380
381        if let Some(id) = selected {
382            self.active = id;
383        }
384    }
385
386    fn load_session<'a, H: Host<'a>>(
387        &mut self,
388        host: &mut H,
389        path: &Path,
390    ) -> Result<(), SaveLoadError> {
391        let mut file = File::open(&path)?;
392        let mut contents = String::new();
393        file.read_to_string(&mut contents)?;
394
395        let dir = path.parent().unwrap();
396
397        let mut session: SavedSession = serde_json::from_str(&contents).map_err(|err| {
398            warn!("Found invalid SavedSession during load: {:?}", err);
399            // TODO: improve these error messages
400            SaveLoadError::OtherError("Failed to restore session; file is invalid".to_string())
401        })?;
402
403        if session.sample_rate != get_sample_rate() {
404            let mut error = LogMessage::error();
405            if let Err(_) = write!(
406                &mut error,
407                "Session was saved with sample rate {}, but system rate \
408            is set to {}, playback will be affected",
409                session.sample_rate,
410                get_sample_rate()
411            ) {
412                error!("Different sample rate");
413            };
414            self.gui_sender.send_log(error);
415        }
416
417        debug!("Restoring session: {:?}", session);
418
419        self.metric_structure = session.metric_structure.to_ms()
420            .map_err(|e| SaveLoadError::OtherError(e))?;
421        self.sync_mode = session.sync_mode;
422
423        if let Some(metronome) = &mut self.metronome {
424            metronome.set_volume((session.metronome_volume as f32 / 100.0).min(1.0).max(0.0));
425        }
426
427        for l in &self.loopers {
428            self.session_saver.remove_looper(l.id);
429            self.gui_sender.send_update(GuiCommand::RemoveLooper(l.id));
430        }
431        self.loopers.clear();
432
433        session.loopers.sort_by_key(|l| l.id);
434
435        for l in session.loopers {
436            debug!("Restoring looper {}", l.id);
437            let looper = Looper::from_serialized(&l, dir, self.gui_sender.clone())?.start();
438            self.session_saver.add_looper(&looper);
439            if let Err(e) = host.add_looper(looper.id) {
440                error!("Failed to create host port for looper {}: {}", looper.id, e);
441            }
442            self.loopers.push(looper);
443        }
444
445        self.id_counter = self.loopers.iter().map(|l| l.id).max().unwrap_or(0) + 1;
446
447        self.reset();
448
449        Ok(())
450    }
451
452    fn handle_command<'a, H: Host<'a>>(
453        &mut self,
454        host: &mut H,
455        command: &Command,
456        triggered: bool,
457    ) {
458        fn trigger_or_run<F>(
459            engine: &mut Engine,
460            command: &Command,
461            triggered: bool,
462            queued: bool,
463            f: F,
464        ) where
465            F: FnOnce(&mut Engine),
466        {
467            if engine.state == EngineState::Stopped || triggered {
468                f(engine);
469                return;
470            }
471
472            let trigger_condition = match (queued, engine.sync_mode) {
473                (true, _) => TriggerCondition::Immediate,
474                (false, QuantizationMode::Free) => TriggerCondition::Immediate,
475                (false, QuantizationMode::Beat) => TriggerCondition::Beat,
476                (false, QuantizationMode::Measure) => TriggerCondition::Measure,
477            };
478
479            let trigger = Trigger::new(
480                trigger_condition,
481                command.clone(),
482                engine.metric_structure,
483                FrameTime(engine.time),
484            );
485
486            if trigger.triggered_at() != FrameTime(0)
487                && trigger.triggered_at() < FrameTime(engine.time)
488            {
489                f(engine);
490                return;
491            }
492
493            Engine::add_trigger(&mut engine.triggers, trigger.clone());
494            engine.gui_sender.send_update(GuiCommand::AddGlobalTrigger(
495                trigger.triggered_at(),
496                trigger.command,
497            ));
498        }
499
500        use Command::*;
501        match command {
502            Looper(lc, target) => {
503                self.handle_loop_command(*lc, *target, triggered);
504            }
505            Start => {
506                self.state = EngineState::Active;
507            }
508            Pause => {
509                self.state = EngineState::Paused;
510            }
511            Stop => {
512                self.state = EngineState::Stopped;
513                self.reset();
514            }
515            StartStop => {
516                self.state = match self.state {
517                    EngineState::Stopped | EngineState::Paused => EngineState::Active,
518                    EngineState::Active => {
519                        self.reset();
520                        EngineState::Stopped
521                    }
522                };
523            }
524            PlayPause => {
525                self.state = match self.state {
526                    EngineState::Stopped | EngineState::Paused => EngineState::Active,
527                    EngineState::Active => EngineState::Paused,
528                }
529            }
530            Reset => {
531                self.reset();
532            }
533            SetTime(time) => self.set_time(*time),
534            AddLooper => {
535                // TODO: make this non-allocating
536                let looper = crate::Looper::new(
537                    self.id_counter,
538                    PartSet::with(self.current_part),
539                    self.gui_sender.clone(),
540                )
541                .start();
542                self.session_saver.add_looper(&looper);
543                self.loopers.push(looper);
544                self.active = self.id_counter;
545                // TODO: better error handling
546                if let Err(e) = host.add_looper(self.id_counter) {
547                    error!(
548                        "failed to create host port for looper {}: {}",
549                        self.id_counter, e
550                    );
551                }
552                self.id_counter += 1;
553            }
554            SelectLooperById(id) => {
555                if self.loopers.iter().any(|l| l.id == *id) {
556                    self.active = *id;
557                } else {
558                    warn!("tried to select non-existent looper id {}", id);
559                }
560            }
561            SelectLooperByIndex(idx) => {
562                if let Some(l) = self.looper_by_index_mut(*idx) {
563                    self.active = l.id;
564                } else {
565                    warn!("tried to select non-existent looper index {}", idx);
566                }
567            }
568            SelectNextLooper | SelectPreviousLooper => {
569                trigger_or_run(self, command, triggered, true, |engine| {
570                    if let Some((i, _)) = engine
571                        .loopers
572                        .iter()
573                        .filter(|l| !l.deleted)
574                        .filter(|l| l.parts[engine.current_part])
575                        .enumerate()
576                        .find(|(_, l)| l.id == engine.active)
577                    {
578                        let count = engine
579                            .loopers
580                            .iter()
581                            .filter(|l| l.parts[engine.current_part])
582                            .filter(|l| !l.deleted)
583                            .count();
584
585                        let next = if *command == SelectNextLooper {
586                            (i + 1) % count
587                        } else {
588                            (i as isize - 1).rem_euclid(count as isize) as usize
589                        };
590
591                        if let Some(l) = engine
592                            .loopers
593                            .iter()
594                            .filter(|l| l.parts[engine.current_part])
595                            .filter(|l| !l.deleted)
596                            .skip(next)
597                            .next()
598                        {
599                            engine.active = l.id;
600                        }
601                    } else {
602                        if let Some(l) = engine
603                            .loopers
604                            .iter()
605                            .filter(|l| !l.deleted)
606                            .filter(|l| l.parts[engine.current_part])
607                            .next()
608                        {
609                            engine.active = l.id;
610                        }
611                    }
612                });
613            }
614            PreviousPart => {
615                trigger_or_run(self, command, triggered, false, |engine| {
616                    let original = engine.current_part;
617                    loop {
618                        engine.current_part = match engine.current_part {
619                            Part::A => Part::D,
620                            Part::B => Part::A,
621                            Part::C => Part::B,
622                            Part::D => Part::C,
623                        };
624                        if engine
625                            .loopers
626                            .iter()
627                            .any(|l| !l.deleted && l.parts[engine.current_part])
628                            || engine.current_part == original
629                        {
630                            break;
631                        }
632                    }
633                    engine.select_first_in_part();
634                });
635            }
636            NextPart => {
637                trigger_or_run(self, command, triggered, false, |engine| {
638                    let original = engine.current_part;
639                    loop {
640                        engine.current_part = match engine.current_part {
641                            Part::A => Part::B,
642                            Part::B => Part::C,
643                            Part::C => Part::D,
644                            Part::D => Part::A,
645                        };
646                        if engine
647                            .loopers
648                            .iter()
649                            .any(|l| !l.deleted && l.parts[engine.current_part])
650                            || engine.current_part == original
651                        {
652                            break;
653                        }
654                    }
655                    engine.select_first_in_part();
656                });
657            }
658            GoToPart(part) => {
659                trigger_or_run(self, command, triggered, false, |engine| {
660                    engine.current_part = *part;
661                    engine.select_first_in_part();
662                });
663            }
664            SetQuantizationMode(sync_mode) => {
665                self.sync_mode = *sync_mode;
666            }
667            SaveSession(path) => {
668                if let Err(e) = self.session_saver.save_session(SaveSessionData {
669                    metric_structure: self.metric_structure,
670                    metronome_volume: self
671                        .metronome
672                        .as_ref()
673                        .map(|m| (m.get_volume() * 100.0) as u8)
674                        .unwrap_or(100),
675                    sync_mode: self.sync_mode,
676                    path: Arc::clone(path),
677                    sample_rate: get_sample_rate(),
678                }) {
679                    error!("Failed to save session {:?}", e);
680                }
681            }
682            LoadSession(path) => {
683                if let Err(e) = self.load_session(host, path) {
684                    error!("Failed to load session {:?}", e);
685                }
686            }
687            SetMetronomeLevel(l) => {
688                if *l <= 100 {
689                    if let Some(metronome) = &mut self.metronome {
690                        metronome.set_volume(*l as f32 / 100.0);
691                    }
692                } else {
693                    error!("Invalid metronome volume; must be between 0 and 100");
694                }
695            }
696            SetTempoBPM(bpm) => {
697                self.metric_structure.tempo = Tempo::from_bpm(*bpm);
698                if let Some(met) = &mut self.metronome {
699                    met.set_metric_structure(self.metric_structure);
700                }
701                self.reset();
702            }
703            SetTimeSignature(upper, lower) => {
704                if let Some(ts) = TimeSignature::new(*upper, *lower) {
705                    self.metric_structure.time_signature = ts;
706                    if let Some(met) = &mut self.metronome {
707                        met.set_metric_structure(self.metric_structure);
708                    }
709                    self.reset();
710                }
711            }
712        }
713    }
714
715    // selects the first looper in the part, unless the current selection is already in the part
716    fn select_first_in_part(&mut self) {
717        if let Some(l) = self
718            .loopers
719            .iter()
720            .filter(|l| l.id == self.active && l.parts[self.current_part])
721            .next()
722            .or(self
723                .loopers
724                .iter()
725                .filter(|l| !l.deleted && l.parts[self.current_part])
726                .next())
727        {
728            self.active = l.id;
729        }
730    }
731
732    // returns length
733    fn measure_len(&self) -> FrameTime {
734        let bps = self.metric_structure.tempo.bpm() as f32 / 60.0;
735        let mspb = 1000.0 / bps;
736        let mspm = mspb * self.metric_structure.time_signature.upper as f32;
737
738        FrameTime::from_ms(mspm as f64)
739    }
740
741    fn set_time(&mut self, time: FrameTime) {
742        self.time = time.0;
743        for l in &mut self.loopers {
744            l.set_time(time);
745        }
746    }
747
748    fn perform_looper_io<'a, H: Host<'a>>(
749        &mut self,
750        host: &mut H,
751        in_bufs: &[&[f32]],
752        time: FrameTime,
753        idx_range: Range<usize>,
754        solo: bool,
755    ) {
756        if time.0 >= 0 {
757            let mut looper_index = 0;
758            for looper in self.loopers.iter_mut() {
759                if !looper.deleted {
760                    self.tmp_left.iter_mut().for_each(|i| *i = 0.0);
761                    self.tmp_right.iter_mut().for_each(|i| *i = 0.0);
762
763                    let mut o = [
764                        &mut self.tmp_left[idx_range.clone()],
765                        &mut self.tmp_right[idx_range.clone()],
766                    ];
767
768                    looper.process_output(time, &mut o, self.current_part, solo);
769
770                    // copy the output to the looper input in the host, if we can find one
771                    if let Some([l, r]) = host.output_for_looper(looper.id) {
772                        l.iter_mut()
773                            .zip(&self.tmp_left[idx_range.clone()])
774                            .for_each(|(a, b)| *a = *b as f32);
775                        r.iter_mut()
776                            .zip(&self.tmp_right[idx_range.clone()])
777                            .for_each(|(a, b)| *a = *b as f32);
778                    }
779
780                    // copy the output to the our main output
781                    self.output_left[idx_range.clone()]
782                        .iter_mut()
783                        .zip(&self.tmp_left[idx_range.clone()])
784                        .for_each(|(a, b)| *a += *b);
785                    self.output_right[idx_range.clone()]
786                        .iter_mut()
787                        .zip(&self.tmp_right[idx_range.clone()])
788                        .for_each(|(a, b)| *a += *b);
789
790                    // update our peaks
791                    let mut peaks = [0f32; 2];
792                    for (i, vs) in [&self.tmp_left, &self.tmp_right].iter().enumerate() {
793                        for v in *vs {
794                            let v_abs = v.abs() as f32;
795                            if v_abs > peaks[i] {
796                                peaks[i] = v_abs;
797                            }
798                        }
799                    }
800
801                    if let Some(p) = self.looper_peaks.get_mut(looper_index) {
802                        *p = peaks;
803                    }
804                    looper_index += 1;
805
806                    looper.process_input(
807                        time.0 as u64,
808                        &[
809                            &in_bufs[0][idx_range.clone()],
810                            &in_bufs[1][idx_range.clone()],
811                        ],
812                        self.current_part,
813                    );
814                }
815            }
816        } else {
817            error!("perform_looper_io called with negative time {}", time.0);
818        }
819    }
820
821    fn process_loopers<'a, H: Host<'a>>(&mut self, host: &mut H, in_bufs: &[&[f32]], frames: u64, solo: bool) {
822        let mut time = self.time;
823        let mut idx = 0usize;
824
825        if time < 0 {
826            time = (self.time + frames as i64).min(0);
827            if time < 0 {
828                return;
829            }
830            idx = (time - self.time) as usize;
831        }
832
833        let mut time = time as u64;
834
835        let next_time = (self.time + frames as i64) as u64;
836        while time < next_time {
837            if let Some(_) = self
838                .triggers
839                .iter()
840                .peekable()
841                .peek()
842                .filter(|t| t.triggered_at().0 < next_time as i64)
843            {
844                // The unwrap is safe due to the preceding peek
845                let trigger = self.triggers.pop_front().unwrap();
846
847                let trigger_at = trigger.triggered_at();
848                // we'll process up to this time, then trigger the trigger
849
850                if trigger_at != FrameTime(0) && trigger_at.0 < time as i64 {
851                    // we failed to trigger, but don't know if it's safe to trigger late. so we'll
852                    // just ignore it. there might be better solutions for specific triggers, but
853                    // hopefully this is rare.
854                    error!(
855                        "missed trigger for time {} (cur time = {})",
856                        trigger_at.0, time
857                    );
858                    continue;
859                }
860
861                // we know that trigger_at is non-negative from the previous condition
862                let trigger_at = trigger_at.0 as u64;
863
864                // if we're exactly on the trigger time, just trigger it immediately and continue
865                if trigger_at > time {
866                    // otherwise, we need to process the stuff before the trigger time, then trigger
867                    // the command, then continue processing the rest
868                    let idx_range = idx..(trigger_at as i64 - self.time) as usize;
869                    assert_eq!(
870                        idx_range.end - idx_range.start,
871                        (trigger_at - time) as usize
872                    );
873
874                    self.perform_looper_io(
875                        host,
876                        &in_bufs,
877                        FrameTime(time as i64),
878                        idx_range.clone(),
879                        solo,
880                    );
881                    time = trigger_at;
882                    idx = idx_range.end;
883                }
884
885                self.handle_command(host, &trigger.command, true);
886            } else {
887                // there are no more triggers for this period, so just process the rest and finish
888                self.perform_looper_io(
889                    host,
890                    &in_bufs,
891                    FrameTime(time as i64),
892                    idx..frames as usize,
893                    solo,
894                );
895                time = next_time;
896            }
897        }
898    }
899
900    fn compute_peaks(in_bufs: &[&[f32]]) -> [u8; 2] {
901        let mut peaks = [0u8; 2];
902        for c in 0..2 {
903            let mut peak = 0f32;
904            for v in in_bufs[c] {
905                let v_abs = v.abs();
906                if v_abs > peak {
907                    peak = v_abs;
908                }
909            }
910
911            peaks[c] = Self::iec_scale(peak);
912        }
913
914        peaks
915    }
916
917    fn iec_scale(amp: f32) -> u8 {
918        let db = 20.0 * amp.log10();
919
920        let d = if db < -70.0 {
921            0.0
922        } else if db < -60.0 {
923            db + 70.0 * 0.25
924        } else if db < -50.0 {
925            db + 60.0 * 0.5 + 5.0
926        } else if db < -40.0 {
927            db + 50.0 * 0.75 + 7.5
928        } else if db < -30.0 {
929            db + 40.0 * 1.5 + 15.0
930        } else if db < -20.0 {
931            db + 30.0 * 2.0 + 30.0
932        } else if db < 0.0 {
933            db + 20.0 * 2.5 + 50.0
934        } else {
935            100.0
936        };
937
938        d as u8
939    }
940
941    // Step 1: Convert midi events to commands
942    // Step 2: Handle commands
943    // Step 3: Play current samples
944    // Step 4: Record
945    // Step 5: Update GUI
946    pub fn process<'a, H: Host<'a>>(
947        &mut self,
948        host: &mut H,
949        in_bufs: [&[f32]; 2],
950        out_l: &mut [f32],
951        out_r: &mut [f32],
952        mut met_bufs: [&mut [f32]; 2],
953        frames: u64,
954        midi_events: &[MidiEvent],
955    ) {
956        // Convert midi events to commands
957        self.commands_from_midi(host, midi_events);
958
959        // Handle commands from the gui
960        loop {
961            match self.command_input.try_recv() {
962                Ok(c) => {
963                    self.handle_command(host, &c, false);
964                }
965                Err(_) => break,
966            }
967        }
968
969        // Remove any deleted loopers
970        for l in self.loopers.iter().filter(|l| l.deleted) {
971            self.session_saver.remove_looper(l.id);
972        }
973        self.loopers.retain(|l| !l.deleted);
974
975        // ensure out internal output buffer is big enough (this should only allocate when the
976        // buffer size is increased)
977        while self.output_left.len() < frames as usize {
978            self.output_left.push(0.0);
979        }
980        while self.output_right.len() < frames as usize {
981            self.output_right.push(0.0);
982        }
983        while self.tmp_left.len() < frames as usize {
984            self.output_left.push(0.0);
985        }
986        while self.tmp_right.len() < frames as usize {
987            self.output_right.push(0.0);
988        }
989
990        // copy the input to the output for monitoring
991        // TODO: should probably make this behavior configurable
992        for (i, (l, r)) in in_bufs[0].iter().zip(in_bufs[1]).enumerate() {
993            self.output_left[i] = *l as f64;
994            self.output_right[i] = *r as f64;
995        }
996
997        if (self.state != EngineState::Active && self.state != EngineState::Paused) && (!self.triggers.is_empty() ||
998            self.loopers.iter().any(|l| l.local_mode() == LooperMode::Recording ||
999                l.local_mode() == LooperMode::Overdubbing)) {
1000            self.state = EngineState::Active;
1001        }
1002
1003        let solo = self.loopers.iter()
1004            .any(|l| l.parts[self.current_part] && !l.deleted && l.mode() == LooperMode::Soloed);
1005
1006        if self.state == EngineState::Active {
1007            // process the loopers
1008            self.process_loopers(host, &in_bufs, frames, solo);
1009
1010            // Play the metronome
1011            if let Some(metronome) = &mut self.metronome {
1012                metronome.advance(&mut met_bufs);
1013            }
1014
1015            self.time += frames as i64;
1016        }
1017
1018        for i in 0..frames as usize {
1019            out_l[i] = self.output_left[i] as f32;
1020        }
1021        for i in 0..frames as usize {
1022            out_r[i] = self.output_right[i] as f32;
1023        }
1024
1025        let mut peaks = [[0u8; 2]; 64];
1026        for (i, ps) in self.looper_peaks.iter().enumerate() {
1027            peaks[i][0] = Self::iec_scale(ps[0]);
1028            peaks[i][1] = Self::iec_scale(ps[1]);
1029        }
1030
1031        // Update GUI
1032        self.gui_sender
1033            .send_update(GuiCommand::StateSnapshot(EngineStateSnapshot {
1034                engine_state: self.state,
1035                time: FrameTime(self.time),
1036                metric_structure: self.metric_structure,
1037                active_looper: self.active,
1038                looper_count: self.loopers.len(),
1039                part: self.current_part,
1040                solo,
1041                sync_mode: self.sync_mode,
1042                input_levels: Self::compute_peaks(&in_bufs),
1043                looper_levels: peaks,
1044                metronome_volume: self
1045                    .metronome
1046                    .as_ref()
1047                    .map(|m| m.get_volume())
1048                    .unwrap_or(0.0),
1049            }));
1050    }
1051}