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 
13 namespace Nodes::AudioLooper
14 {
15 struct 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