Loading...
Searching...
No Matches
Looper.hpp
1#pragma once
2#include <Fx/Types.hpp>
3
4#include <ossia/dataflow/exec_state_facade.hpp>
5#include <ossia/dataflow/nodes/media.hpp>
6#include <ossia/dataflow/token_request.hpp>
7
8#include <halp/audio.hpp>
9#include <halp/controls.hpp>
10#include <halp/layout.hpp>
11#include <halp/meta.hpp>
12
13namespace Nodes::AudioLooper
14{
15struct Node
16{
17 halp_meta(name, "Looper (audio)")
18 halp_meta(c_name, "Looper (audio)")
19 halp_meta(category, "Audio/Utilities")
20 halp_meta(author, "ossia score")
21 halp_meta(manual_url, "https://ossia.io/score-docs/processes/audio-looper.html")
22 halp_meta(description, "Loop audio")
23 halp_meta(uuid, "a0ad4227-ac3d-448b-a19b-19581ed4e2c6");
24 halp_meta(recommended_height, 65);
25
26 enum class LoopMode
27 {
28 Play,
29 Record,
30 Overdub,
31 Stop
32 };
33
34 enum class Postaction
35 {
36 Play,
37 Overdub
38 };
39
40 struct ins
41 {
42 halp::dynamic_audio_bus<"in", double> audio;
43 halp::enum_t<LoopMode, "Loop"> mode;
44 quant_selector<"Quantif"> quantif;
45 halp::toggle<"Passthrough", halp::toggle_setup{.init = true}> passthrough;
46 halp::enum_t<Postaction, "Post-action"> postaction;
47 halp::spinbox_i32<"Bars", halp::irange{0, 64, 4}> postaction_bars;
48 } inputs;
49 struct
50 {
51 halp::mimic_audio_bus<"out", &ins::audio> audio;
52 } outputs;
53
54 struct State
55 {
56 LoopMode quantizedPlayMode{LoopMode::Stop};
57 LoopMode actualMode{LoopMode::Stop};
58 ossia::audio_vector audio;
59 int64_t playbackPos{};
60 ossia::time_value recordStart{};
61 ossia::quarter_note recordStartBar{-1.};
62 ossia::quarter_note recordEndBar{-1.};
63 int actualChannels = 0;
64 float quantif{0.0};
65 std::optional<ossia::time_value> this_buffer_quantif_time;
66 std::optional<int64_t> this_buffer_quantif_sample;
67 int postaction_bars{};
68 double sampleRate{48000.};
69 bool isPostRecording{false};
70
71 static constexpr int64_t default_buffer_size = 192000 * 32;
72 void reset_elapsed() { }
73 int channels() const noexcept { return actualChannels; }
74 void set_channels(int chans)
75 {
76 const int64_t cur_channels = std::ssize(audio);
77 actualChannels = chans;
78 if(actualChannels > cur_channels)
79 {
80 audio.resize(actualChannels);
81
82 int64_t min_size = audio[0].size();
83 int64_t min_capa = std::max(int64_t(audio[0].capacity()), default_buffer_size);
84 for(int i = cur_channels; i < actualChannels; i++)
85 {
86 audio[i].reserve(min_capa);
87 audio[i].resize(min_size);
88 }
89 }
90 else if(actualChannels < cur_channels)
91 {
92 for(int i = actualChannels; i < cur_channels; i++)
93 {
94 audio[i].resize(0);
95 }
96 }
97 }
98
99 State()
100 {
101 audio.resize(2);
102 for(auto& vec : audio)
103 vec.reserve(default_buffer_size);
104 }
105 } state;
106
107 void fade(const ossia::token_request& tk)
108 {
109 const double sr = state.sampleRate;
110 const double bar_samples
111 = sr * 4. * (double(tk.signature.upper) / tk.signature.lower) * (60. / tk.tempo);
112 const double total_samples = std::floor(state.postaction_bars * bar_samples);
113
114 // If there are more samples than expected we crop
115 const bool quantify_length = (state.quantif > 0.f) && (state.channels() > 0);
116 if(quantify_length)
117 {
118 if(total_samples < state.audio[0].size())
119 {
120 for(auto& a : state.audio)
121 a.resize(total_samples);
122 }
123 }
124
125 // Apply a small fade on the first and last samples
126 for(auto& chan : state.audio)
127 {
128 int samples = chan.size();
129 if(int min_n = std::min(samples, (int)128); min_n > 0)
130 {
131 float f = 1. / min_n;
132 float ff = 0.;
133 for(int i = 0; i < min_n; i++)
134 {
135 chan[i] *= ff;
136 ff += f;
137 }
138
139 for(int i = samples - min_n; i < samples; i++)
140 {
141 chan[i] *= ff;
142 ff -= f;
143 }
144 }
145 }
146
147 // If there are less samples than expected we extend
148 if(quantify_length)
149 {
150 if(total_samples > state.audio[0].size())
151 {
152 for(auto& a : state.audio)
153 a.resize(total_samples);
154 }
155 }
156 }
157
158 void changeAction(const ossia::token_request& tk)
159 {
160 if(state.quantizedPlayMode == LoopMode::Record)
161 {
162 state.recordStart = tk.prev_date;
163 state.recordStartBar = tk.musical_start_position;
164 state.recordEndBar = tk.musical_start_position
165 + (4. * double(tk.signature.upper) / tk.signature.lower)
166 * state.postaction_bars;
167 state.reset_elapsed();
168 }
169 else
170 {
171 state.recordStart = ossia::time_value{-1LL};
172 state.recordStartBar = -1.;
173 state.recordEndBar = -1.;
174 state.reset_elapsed();
175 }
176
177 fade(tk);
178 }
179
180 void checkPostAction(
181 const std::string& postaction, int postaction_bars, const ossia::token_request& tk,
182 State& st)
183 {
184 }
185
186 ossia::exec_state_facade ossia_state;
187
188 using tick = ossia::token_request;
189 void operator()(const tick& tk)
190 {
191 using namespace ossia;
192
193 if(tk.date == tk.prev_date)
194 return;
195
196 const LoopMode m = this->inputs.mode;
197 const int postaction_bars = this->inputs.postaction_bars;
198 const Postaction postaction = this->inputs.postaction;
199 const float quantif = this->inputs.quantif.value;
200 const bool passthrough = this->inputs.passthrough;
201
202 state.this_buffer_quantif_time = std::nullopt;
203 state.this_buffer_quantif_sample = std::nullopt;
204 state.postaction_bars = postaction_bars;
205 state.sampleRate = ossia_state.sampleRate();
206
207 if(quantif != 0 && tk.prev_date != 0_tv)
208 {
209 state.quantif = quantif;
210 if(auto time = tk.get_quantification_date(1. / quantif);
211 time && time > tk.prev_date)
212 {
213 state.this_buffer_quantif_time = *time;
214 state.this_buffer_quantif_sample
215 = tk.to_physical_time_in_tick(*time, ossia_state.modelToSamples());
216 }
217 }
218 else
219 {
220 state.quantif = 0.0f;
221 }
222
223 if(m != state.quantizedPlayMode)
224 {
225 if(quantif != 0 && tk.prev_date != 0_tv)
226 {
227 if(auto& time = state.this_buffer_quantif_time)
228 {
229 // tempo = 60 -> 1 quarter = 1 second
230 // tempo = 100 -> 1 quarter = 1 * 60/100 = 0.6 second
231 // tempo = 100 -> 1 bar = 2 second
232 // tempo = 120 -> 1 bar = 2 second
233 // tempo = 120 -> 1 bar = 2 second
234 if(*time > tk.prev_date)
235 {
236 // Finish what we were doing until the quantization date
237 {
238 auto sub_tk = tk;
239 sub_tk.set_end_time(*time - 1_tv);
240
241 preAction(sub_tk, postaction, postaction_bars, passthrough);
242 }
243
244 // We can switch to the new mode
245 state.quantizedPlayMode = m;
246 state.actualMode = m;
247 state.playbackPos = 0;
248
249 // Remaining of the tick
250 {
251 auto sub_tk = tk;
252 sub_tk.set_start_time(*time);
253
254 changeAction(sub_tk);
255 preAction(sub_tk, postaction, postaction_bars, passthrough);
256 }
257 }
258 else
259 {
260 // We can switch to the new mode
261 state.quantizedPlayMode = m;
262 state.actualMode = m;
263 state.playbackPos = 0;
264
265 changeAction(tk);
266 preAction(tk, postaction, postaction_bars, passthrough);
267 }
268 }
269 else
270 {
271 // We cannot switch yet
272 preAction(tk, postaction, postaction_bars, passthrough);
273 }
274 }
275 else
276 {
277 // No quantization, we can switch to the new mode
278 state.quantizedPlayMode = m;
279 state.actualMode = m;
280 state.playbackPos = 0;
281
282 changeAction(tk);
283 preAction(tk, postaction, postaction_bars, passthrough);
284 }
285 }
286 else
287 {
288 // No change
289 preAction(tk, postaction, postaction_bars, passthrough);
290 }
291 }
292
293 void preAction(
294 ossia::token_request tk, Postaction postaction, int postaction_bars,
295 bool passthrough)
296 {
297 using namespace ossia;
298 if(state.recordStartBar == -1.)
299 {
300 action(tk, passthrough);
301 }
302 else
303 {
304 // If we are in the token request which steps into the bar in which
305 // we change because we are e.g. 4 bars after the recording started
306 auto switch_to_main_mode = [&] {
307 switch(postaction)
308 {
309 case Postaction::Play:
310 state.actualMode = LoopMode::Play;
311 break;
312 case Postaction::Overdub:
313 state.actualMode = LoopMode::Overdub;
314 break;
315 default:
316 state.actualMode = LoopMode::Stop;
317 break;
318 }
319
320 state.recordStart = ossia::time_value{-1};
321 state.recordStartBar = -1.;
322 state.reset_elapsed();
323 state.playbackPos = 0;
324 };
325
326 // Change of bar at the first sample
327 if(tk.musical_start_last_bar >= state.recordEndBar)
328 {
329 switch_to_main_mode();
330 fade(tk);
331 action(tk, passthrough);
332 }
333
334 // Change of bar in the middle
335 else if(tk.musical_end_last_bar >= state.recordEndBar)
336 {
337 if(auto quant_date = tk.get_quantification_date(1.0))
338 {
339 ossia::time_value t = *quant_date;
340 if(t > tk.prev_date)
341 {
342 // Finish what we were doing until the quantization date
343 {
344 auto sub_tk = tk;
345 sub_tk.set_end_time(t - 1_tv);
346 action(sub_tk, passthrough);
347 }
348
349 // We can switch to the new mode
350 switch_to_main_mode();
351 fade(tk);
352
353 // Remaining of the tick
354 {
355 auto sub_tk = tk;
356 sub_tk.set_start_time(t);
357
358 action(sub_tk, passthrough);
359 }
360 }
361 else
362 {
363 qDebug("very weird");
364 }
365 }
366 else
367 {
368 qDebug("weird");
369 }
370 // just in case:
371 switch_to_main_mode();
372 }
373
374 // No change of bar yet, we continue
375 else
376 {
377 action(tk, passthrough);
378 }
379 }
380 }
381
382 void action(const ossia::token_request& tk, bool echoRecord)
383 {
384 auto timings = ossia_state.timings(tk);
385 action(timings.start_sample, timings.length, echoRecord);
386 }
387
388 void action(int64_t start, int64_t length, bool echoRecord)
389 {
390 switch(state.actualMode)
391 {
392 case LoopMode::Play:
393 if(state.channels() == 0 || state.audio[0].size() == 0)
394 stop(start, length);
395 else
396 play(start, length);
397 break;
398 case LoopMode::Stop:
399 stop(start, length);
400 break;
401 case LoopMode::Record:
402 echoRecord ? record(start, length) : record_noecho(start, length);
403 break;
404 case LoopMode::Overdub:
405 echoRecord ? overdub(start, length) : overdub_noecho(start, length);
406 break;
407 }
408 }
409
410 void play(int64_t first_pos, int64_t samples)
411 {
412 auto& p2 = outputs.audio;
413 // Copy input to output, and append input to buffer
414 const auto chans = state.channels();
415
416 if(chans == 0)
417 return;
418
419 int64_t k = state.playbackPos;
420 for(int i = 0; i < chans; i++)
421 {
422 auto& out = p2.samples[i];
423 auto& record = state.audio[i];
424 const int64_t chan_samples = record.size();
425
426 k = state.playbackPos;
427 if(state.playbackPos + samples < chan_samples)
428 {
429 for(int64_t j = first_pos; j < samples; j++)
430 {
431 out[j] = record[k];
432 k++;
433 }
434 }
435 else
436 {
437 int64_t max = chan_samples - state.playbackPos;
438 int64_t j = first_pos;
439 for(; j < max; j++)
440 {
441 out[j] = record[k];
442 k++;
443 }
444
445 //if(state.quantif == 0.f)
446 {
447 // No quantification, we directly loop the content
448 k = 0;
449
450 // TODO refactor sound_reader so that we can use it to have the proper repeated loop behaviour here...
451 for(; j < std::min(samples, chan_samples); j++)
452 {
453 out[j] = record[k];
454 k++;
455 }
456 }
457
458 /*
459 else if(state.this_buffer_quantif_sample)
460 {
461 // Quantification in this tick
462 int64_t last_silence = std::min(N, *state.this_buffer_quantif_sample);
463
464 // First silence
465 for (; j < last_silence; j++)
466 {
467 out[j] = 0.f;
468 k++;
469 }
470
471 // Then loop our content back
472 k = 0;
473 for (; j < std::min(N, chan_samples); j++)
474 {
475 out[j] = record[k];
476 k++;
477 }
478 }
479 else
480 {
481 // Quantification not in this tick, just silence
482 for (; j < N; j++)
483 {
484 out[j] = 0.f;
485 k++;
486 }
487 }
488 */
489 }
490 }
491
492 state.playbackPos = k;
493 }
494
495 // We just copy input to output
496 void stop(int64_t first_pos, int64_t samples)
497 {
498 auto& p1 = inputs.audio;
499 auto& p2 = outputs.audio;
500 const auto chans = p1.channels;
501
502 for(int i = 0; i < chans; i++)
503 {
504 auto& in = p1.samples[i];
505 auto& out = p2.samples[i];
506
507 for(int64_t j = first_pos; j < samples; j++)
508 {
509 out[j] = in[j];
510 }
511 }
512 }
513
514 void record(int64_t first_pos, int64_t samples)
515 {
516 auto& p1 = inputs.audio;
517 auto& p2 = outputs.audio;
518 // Copy input to output, and append input to buffer
519 const auto chans = p1.channels;
520 state.set_channels(chans);
521
522 for(int i = 0; i < chans; i++)
523 {
524 auto& in = p1.samples[i];
525 auto& out = p2.samples[i];
526 auto& record = state.audio[i];
527
528 record.resize(state.playbackPos + samples);
529 int64_t k = state.playbackPos;
530
531 for(int64_t j = first_pos; j < samples; j++)
532 {
533 out[j] = in[j];
534 record[k] = in[j];
535 k++;
536 }
537 }
538 state.playbackPos += samples;
539 }
540
541 void record_noecho(int64_t first_pos, int64_t samples)
542 {
543 auto& p1 = inputs.audio;
544 // Copy input to output, and append input to buffer
545 const auto chans = p1.channels;
546 state.set_channels(chans);
547
548 for(int i = 0; i < chans; i++)
549 {
550 auto& in = p1.samples[i];
551 auto& record = state.audio[i];
552
553 record.resize(state.playbackPos + samples);
554 int64_t k = state.playbackPos;
555
556 for(int64_t j = first_pos; j < samples; j++)
557 {
558 record[k] = in[j];
559 k++;
560 }
561 }
562 state.playbackPos += samples;
563 }
564
565 void overdub(int64_t first_pos, int64_t samples)
566 {
567 auto& p1 = inputs.audio;
568 auto& p2 = outputs.audio;
571
572 // Copy input to output, and append input to buffer
573 const auto chans = p1.channels;
574 state.set_channels(chans);
575
576 for(int i = 0; i < chans; i++)
577 {
578 auto& in = p1.samples[i];
579 auto& out = p2.samples[i];
580 auto& record = state.audio[i];
581 const int64_t record_samples = record.size();
582
583 int64_t k = state.playbackPos;
584
585 for(int64_t j = first_pos; j < samples; j++)
586 {
587 if(k >= record_samples)
588 k = 0;
589
590 record[k] += in[j];
591 out[j] = record[k];
592
593 k++;
594 }
595 }
596 state.playbackPos += samples;
597 }
598
599 void overdub_noecho(int64_t first_pos, int64_t samples)
600 {
601 auto& p1 = inputs.audio;
602 auto& p2 = outputs.audio;
605
606 // Copy input to output, and append input to buffer
607 const auto chans = p1.channels;
608 state.set_channels(chans);
609
610 for(int i = 0; i < chans; i++)
611 {
612 auto& in = p1.samples[i];
613 auto& out = p2.samples[i];
614 auto& record = state.audio[i];
615 const int64_t record_samples = record.size();
616
617 int64_t k = state.playbackPos;
618
619 for(int64_t j = first_pos; j < samples; j++)
620 {
621 if(k >= record_samples)
622 k = 0;
623
624 out[j] = record[k];
625 record[k] += in[j];
626
627 k++;
628 }
629 }
630 state.playbackPos += samples;
631 }
632
633 struct ui
634 {
635 halp_meta(layout, halp::layouts::hbox)
636 struct
637 {
638 halp_meta(layout, halp::layouts::vbox)
639 halp_meta(background, halp::colors::mid)
640 halp::control<&ins::mode> f;
641 halp::control<&ins::quantif> q;
642 halp::control<&ins::passthrough> p;
643 } left;
644 struct
645 {
646 halp_meta(layout, halp::layouts::vbox)
647 halp_meta(background, halp::colors::mid)
648 halp::control<&ins::postaction> p;
649 halp::control<&ins::postaction_bars> b;
650 } right;
651 };
652};
653}
Utilities for OSSIA data structures.
Definition DeviceInterface.hpp:33
Definition Looper.hpp:41
Definition Looper.hpp:634
Definition Looper.hpp:16
void overdub_noecho(int64_t first_pos, int64_t samples)
Definition Looper.hpp:599
void overdub(int64_t first_pos, int64_t samples)
Definition Looper.hpp:565
Definition Types.hpp:48