Loading...
Searching...
No Matches
MidiLoop.hpp
1#pragma once
2#include <Fx/Looper/PassStack.hpp>
3
4#include <Advanced/MidiScaler/MidiCommon/Messages.hpp>
5
6#include <algorithm>
7#include <array>
8#include <bit>
9#include <cmath>
10#include <cstdint>
11#include <utility>
12
13namespace Nodes::Looper
14{
16enum class OverdubMode
17{
18 Merge,
19 Replace
21};
22
24enum class HeldNotes
25{
26 Wrap,
28};
29
31{
33 int64_t start;
35 int64_t length;
36 uint8_t channel;
37 uint8_t pitch;
38 uint8_t velocity;
39 uint8_t pass;
41 uint64_t replaced_by;
42 uint8_t merged;
43};
44
47{
48 int64_t pos;
49 uint8_t status;
50 uint8_t data1;
51 uint8_t data2;
52 uint8_t pass;
53 uint64_t replaced_by;
54 uint8_t merged;
55
57 uint16_t key() const noexcept
58 {
59 const uint8_t kind = status & 0xF0;
60 const bool indexed = kind == 0xB0 || kind == 0xA0;
61 return uint16_t(status << 8 | (indexed ? data1 : 0));
62 }
63};
64
67
79struct midi_loop : tagged_pass_stack<midi_loop, midi_pass, 32>
80{
81 static constexpr int max_notes = 4096;
82 static constexpr int max_controls = 8192;
83 static constexpr int max_touched = 64;
84
85 std::array<midi_note, max_notes> notes{};
86 std::array<midi_control, max_controls> controls{};
87 int note_count{};
88 int control_count{};
89 bool full{};
90
91 // What the pass being recorded touched.
92 std::array<bool, 128> touched_pitches{};
93 std::array<uint16_t, max_touched> touched_keys{};
94 int touched_count{};
95
96 // tagged_pass_stack's hooks: the events are the material.
97 void clear_material() noexcept
98 {
99 note_count = 0;
100 control_count = 0;
101 full = false;
102 }
103 void pass_state_begun() noexcept
104 {
105 touched_pitches.fill(false);
106 touched_count = 0;
107 }
108 template <typename F>
109 void for_each_material(F&& f) noexcept
110 {
111 for_each_event(std::forward<F>(f));
112 }
113 template <typename F>
114 void remove_material(F&& gone) noexcept
115 {
116 remove_events(std::forward<F>(gone));
117 }
118
119 bool touched(uint16_t key) const noexcept
120 {
121 for(int i = 0; i < touched_count; i++)
122 if(touched_keys[i] == key)
123 return true;
124 return false;
125 }
126
128 bool heard(const midi_note& n, OverdubMode mode, bool own_mute = true) const noexcept
129 {
130 if(n.pass >= count || replaced(n.replaced_by, n.pass, taking)
131 || (own_mute && layers[n.pass].muted))
132 return false;
133 if(replacing() && n.pass + 1 < count && mode == OverdubMode::Replace
134 && touched_pitches[n.pitch] && in_pass(n.start))
135 return false;
136 return true;
137 }
138
139 bool heard(const midi_control& c, bool own_mute = true) const noexcept
140 {
141 if(c.pass >= count || replaced(c.replaced_by, c.pass, taking)
142 || (own_mute && layers[c.pass].muted))
143 return false;
144 if(replacing() && c.pass + 1 < count && touched(c.key()) && in_pass(c.pos))
145 return false;
146 return true;
147 }
148
149 template <typename F>
150 void remove_events(F&& gone) noexcept
151 {
152 note_count = int(
153 std::remove_if(notes.begin(), notes.begin() + note_count, gone) - notes.begin());
154 control_count = int(
155 std::remove_if(controls.begin(), controls.begin() + control_count, gone)
156 - controls.begin());
157 }
158
159 template <typename F>
160 void for_each_event(F&& f) noexcept
161 {
162 for(int i = 0; i < note_count; i++)
163 f(notes[i]);
164 for(int i = 0; i < control_count; i++)
165 f(controls[i]);
166 }
167
170 void end_pass(OverdubMode mode) noexcept
171 {
172 if(!taking)
173 return;
174 const int pass = count - 1;
175 edits++;
176 if(pass > 0)
177 {
178 const uint64_t bit = uint64_t(1) << pass;
179 for(int i = 0; i < note_count; i++)
180 {
181 auto& n = notes[i];
182 if(n.pass < pass && mode == OverdubMode::Replace && touched_pitches[n.pitch]
183 && in_pass(n.start))
184 n.replaced_by |= bit;
185 }
186 for(int i = 0; i < control_count; i++)
187 {
188 auto& c = controls[i];
189 if(c.pass < pass && touched(c.key()) && in_pass(c.pos))
190 c.replaced_by |= bit;
191 }
192 }
193 taking = false;
194 }
195
197 void assign(const midi_loop& o) noexcept
198 {
199 std::copy_n(o.notes.begin(), o.note_count, notes.begin());
200 std::copy_n(o.controls.begin(), o.control_count, controls.begin());
201 note_count = o.note_count;
202 control_count = o.control_count;
203 full = o.full;
204 assign_passes(o);
205 touched_pitches = o.touched_pitches;
206 touched_keys = o.touched_keys;
207 touched_count = o.touched_count;
208 }
209
210 midi_note* add_note(int64_t pos, int channel, int pitch, int velocity) noexcept
211 {
212 if(note_count >= max_notes)
213 {
214 full = true;
215 return nullptr;
216 }
217 touched_pitches[pitch & 127] = true;
218 auto& n = notes[note_count++];
219 n = midi_note{
220 .start = pos,
221 .length = -1,
222 .channel = uint8_t(channel),
223 .pitch = uint8_t(pitch),
224 .velocity = uint8_t(velocity),
225 .pass = uint8_t(count - 1),
226 .replaced_by = 0,
227 .merged = 0};
228 return &n;
229 }
230
232 midi_note* open_note(int channel, int pitch) noexcept
233 {
234 for(int i = 0; i < note_count; i++)
235 {
236 auto& n = notes[i];
237 if(n.length < 0 && n.channel == channel && n.pitch == pitch)
238 return &n;
239 }
240 return nullptr;
241 }
242
243 void add_control(int64_t pos, uint8_t status, uint8_t d1, uint8_t d2) noexcept
244 {
245 if(control_count >= max_controls)
246 {
247 full = true;
248 return;
249 }
250 edits++;
251 auto& c = controls[control_count++];
252 c = midi_control{
253 .pos = pos,
254 .status = status,
255 .data1 = d1,
256 .data2 = d2,
257 .pass = uint8_t(count - 1),
258 .replaced_by = 0,
259 .merged = 0};
260 const auto key = c.key();
261 if(!touched(key) && touched_count < max_touched)
262 touched_keys[touched_count++] = key;
263 }
264
266 void touch(uint16_t key) noexcept
267 {
268 if(!touched(key) && touched_count < max_touched)
269 touched_keys[touched_count++] = key;
270 }
271
273 void close_open_notes(int64_t pos) noexcept
274 {
275 for(int i = 0; i < note_count; i++)
276 {
277 auto& n = notes[i];
278 if(n.length < 0)
279 n.length = distance(n.start, pos);
280 }
281 }
282};
283}
Definition AudioLayers.hpp:14
HeldNotes
A note still held at the end of the loop.
Definition MidiLoop.hpp:25
@ Wrap
Carries on from the loop's start.
@ Truncate
Ends with the loop.
OverdubMode
What the MIDI looper does with notes of earlier passes under an overdub.
Definition MidiLoop.hpp:17
@ Merge
Every pass is heard.
A controller, pitch bend or pressure value.
Definition MidiLoop.hpp:47
uint16_t key() const noexcept
What two values have in common when they move the same thing.
Definition MidiLoop.hpp:57
Definition MidiLoop.hpp:80
void end_pass(OverdubMode mode) noexcept
Definition MidiLoop.hpp:170
bool heard(const midi_note &n, OverdubMode mode, bool own_mute=true) const noexcept
own_mute false hears a muted pass's events as if it were not muted.
Definition MidiLoop.hpp:128
void close_open_notes(int64_t pos) noexcept
Notes still held when the loop is left get the length they have now.
Definition MidiLoop.hpp:273
void assign(const midi_loop &o) noexcept
Copies what another loop holds, without its unused storage.
Definition MidiLoop.hpp:197
midi_note * open_note(int channel, int pitch) noexcept
The note-off of a held note: the oldest one of that key still open.
Definition MidiLoop.hpp:232
void touch(uint16_t key) noexcept
The pass moves a controller it has no value of yet: it replaces it.
Definition MidiLoop.hpp:266
Definition MidiLoop.hpp:31
int64_t length
Frames until the note-off; -1 while the key is held.
Definition MidiLoop.hpp:35
int64_t start
Loop position of the note-on, in frames.
Definition MidiLoop.hpp:33
uint64_t replaced_by
The passes that replace it, a bit each.
Definition MidiLoop.hpp:41
uint8_t channel
1 - 16
Definition MidiLoop.hpp:36
int count
Passes heard.
Definition PassStack.hpp:75
std::array< Pass, max_passes+1 > layers
The passes heard, then the ones redo brings back.
Definition PassStack.hpp:73
bool replaced(uint64_t by, int self_pass, bool taking) const noexcept
Definition PassStack.hpp:383
Definition PassStack.hpp:549
void assign_passes(const tagged_pass_stack &o) noexcept
Definition PassStack.hpp:718
uint64_t edits
Changes each time what is heard of the loop may have changed.
Definition PassStack.hpp:556
int64_t distance(int64_t from, int64_t to) const noexcept
Definition PassStack.hpp:736
bool replacing() const noexcept
Definition PassStack.hpp:634
bool in_pass(int64_t k) const noexcept
Position k is in what the pass being taken in went over.
Definition PassStack.hpp:620
What a loop whose material is kept apart keeps of a pass besides it.
Definition PassStack.hpp:15