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