currawong_core/
loopers.rs

1use crate::signal::*;
2use std::cell::RefCell;
3
4pub struct ClockedTriggerLooper {
5    pub clock: Trigger,
6    pub add: Gate,
7    pub remove: Gate,
8    pub length: usize,
9}
10
11impl ClockedTriggerLooper {
12    pub fn signal(self) -> Sbool {
13        let Self {
14            clock,
15            add,
16            remove,
17            length,
18        } = self;
19        struct State {
20            sequence: Vec<bool>,
21            samples_since_last_clock_pulse: usize,
22            samples_since_last_add: usize,
23            next_index: usize,
24        }
25        let state = RefCell::new(State {
26            sequence: (0..length).map(|_| false).collect::<Vec<_>>(),
27            samples_since_last_clock_pulse: 0,
28            samples_since_last_add: 0,
29            next_index: 0,
30        });
31        let add_trigger = add.to_trigger_rising_edge();
32        Signal::from_fn(move |ctx| {
33            let mut state = state.borrow_mut();
34            let add_this_sample = add_trigger.sample(ctx);
35            let mut output = add_this_sample;
36            if add_this_sample {
37                state.samples_since_last_add = 0;
38            } else {
39                state.samples_since_last_add += 1;
40            }
41            if clock.sample(ctx) {
42                let next_index = state.next_index;
43                if remove.sample(ctx) {
44                    state.sequence[next_index] = false;
45                }
46                // Set the output before updating the sequence. The sound plays when a key is
47                // pressed, and on the clock pulse imediately after we don't want to play the sound
48                // a second time. Setting the output here prevents this.
49                output = state.sequence[next_index];
50                if state.samples_since_last_add < state.samples_since_last_clock_pulse / 2 {
51                    state.sequence[next_index] = true;
52                } else {
53                    if state.samples_since_last_add < state.samples_since_last_clock_pulse {
54                        state.sequence[(next_index + length - 1) % length] = true;
55                    }
56                    if add.sample(ctx) {
57                        state.sequence[next_index] = true;
58                        // Explicitly set the output here. When holding a button we fill the
59                        // sequence on each clock tick and also play the sound.
60                        output = true;
61                    }
62                }
63                state.samples_since_last_clock_pulse = 0;
64                state.next_index = (state.next_index + 1) % length;
65            } else {
66                state.samples_since_last_clock_pulse += 1;
67            }
68            output
69        })
70    }
71
72    pub fn trigger(self) -> Trigger {
73        self.signal().to_trigger_raw()
74    }
75}
76
77pub struct ClockedMidiNoteMonophonicLooper {
78    pub clock: Trigger,
79    pub input_gate: Gate,
80    pub input_midi_index: Su8,
81    pub clear: Gate,
82    pub length: usize,
83}
84
85impl ClockedMidiNoteMonophonicLooper {
86    pub fn signal(self) -> (Gate, Su8) {
87        let Self {
88            clock,
89            input_gate,
90            input_midi_index,
91            clear,
92            length,
93        } = self;
94        #[derive(Clone, Copy)]
95        struct Entry {
96            midi_index: u8,
97            key_down: bool,
98            key_up: bool,
99        }
100        struct State {
101            sequence: Vec<Entry>,
102            samples_since_last_clock_pulse: usize,
103            next_index: usize,
104            gate_state: bool,
105            tap: bool,
106            output: (bool, u8),
107            last_period_samples: usize,
108        }
109        let state = RefCell::new(State {
110            sequence: (0..length)
111                .map(|_| Entry {
112                    midi_index: 0,
113                    key_down: false,
114                    key_up: false,
115                })
116                .collect::<Vec<_>>(),
117            samples_since_last_clock_pulse: 0,
118            next_index: 0,
119            gate_state: false,
120            tap: false,
121            output: (false, 0),
122            last_period_samples: 0,
123        });
124        let combined_signal = Signal::from_fn(move |ctx| {
125            let mut state = state.borrow_mut();
126            if state.tap {
127                state.tap = false;
128                state.output.0 = false;
129            }
130            state.samples_since_last_clock_pulse += 1;
131            if clock.sample(ctx) {
132                let next_index = state.next_index;
133                if clear.sample(ctx) {
134                    let entry = &mut state.sequence[next_index];
135                    entry.key_down = false;
136                    entry.key_up = true;
137                }
138                if input_gate.sample(ctx) {
139                    let entry = &mut state.sequence[next_index];
140                    entry.midi_index = input_midi_index.sample(ctx);
141                    entry.key_down = true;
142                    entry.key_up = false;
143                }
144                let entry = state.sequence[next_index];
145                if entry.key_down {
146                    state.output = (true, entry.midi_index);
147                    if entry.key_up {
148                        state.tap = true;
149                    }
150                } else {
151                    state.output.0 = false;
152                }
153                state.next_index = (next_index + 1) % length;
154                state.last_period_samples = state.samples_since_last_clock_pulse;
155                state.samples_since_last_clock_pulse = 0;
156            }
157            let next_gate_state = input_gate.sample(ctx);
158            if next_gate_state {
159                if !state.gate_state {
160                    // key was just pressed
161                    let index_to_update =
162                        if state.samples_since_last_clock_pulse < state.last_period_samples / 2 {
163                            (state.next_index + length - 1) % length
164                        } else {
165                            state.next_index
166                        };
167                    let entry = &mut state.sequence[index_to_update];
168                    entry.midi_index = input_midi_index.sample(ctx);
169                    entry.key_down = true;
170                    entry.key_up = false;
171                }
172                state.output = (true, input_midi_index.sample(ctx));
173            } else {
174                if state.gate_state {
175                    // key was just released
176                    state.output = (false, input_midi_index.sample(ctx));
177                    let index_to_update =
178                        if state.samples_since_last_clock_pulse < state.last_period_samples / 2 {
179                            (state.next_index + length - 1) % length
180                        } else {
181                            state.next_index
182                        };
183                    let entry = &mut state.sequence[index_to_update];
184                    entry.key_up = true;
185                }
186            }
187            state.gate_state = next_gate_state;
188            state.output
189        });
190        let gate = combined_signal.map(|s| s.0).to_gate();
191        let midi_index = combined_signal.map(|s| s.1);
192        (gate, midi_index)
193    }
194}