2 #include <Engine/Node/SimpleApi.hpp>
4 namespace Nodes::AudioLooper
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;
21 static const constexpr
auto controls = tuplet::make_tuple(
22 Control::Widgets::LoopChooser(), Control::Widgets::QuantificationChooser(),
23 Control::Toggle(
"Passthrough",
true), Control::Widgets::LoopPostActionChooser(),
25 static const constexpr audio_in audio_ins[]{
"in"};
26 static const constexpr audio_out audio_outs[]{
"out"};
29 using control_policy = ossia::safe_nodes::last_tick;
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;
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};
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)
53 const int64_t cur_channels = std::ssize(audio);
54 actualChannels = chans;
55 if(actualChannels > cur_channels)
57 audio.resize(actualChannels);
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++)
63 audio[i].reserve(min_capa);
64 audio[i].resize(min_size);
67 else if(actualChannels < cur_channels)
69 for(
int i = actualChannels; i < cur_channels; i++)
79 for(
auto& vec : audio)
80 vec.reserve(default_buffer_size);
84 static void fade(
const ossia::token_request& tk,
State& state)
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);
92 const bool quantify_length = (state.quantif > 0.f) && (state.channels() > 0);
95 if(total_samples < state.audio[0].size())
97 for(
auto& a : state.audio)
98 a.resize(total_samples);
103 for(
auto& chan : state.audio)
105 int samples = chan.size();
106 if(
int min_n = std::min(samples, (
int)128); min_n > 0)
108 float f = 1. / min_n;
110 for(
int i = 0; i < min_n; i++)
116 for(
int i = samples - min_n; i < samples; i++)
127 if(total_samples > state.audio[0].size())
129 for(
auto& a : state.audio)
130 a.resize(total_samples);
135 static void changeAction(
const ossia::token_request& tk,
State& state)
137 if(state.quantizedPlayMode == Control::Widgets::LoopMode::Record)
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();
148 state.recordStart = ossia::time_value{-1LL};
149 state.recordStartBar = -1.;
150 state.recordEndBar = -1.;
151 state.reset_elapsed();
157 static void checkPostAction(
158 const std::string& postaction,
int postaction_bars,
const ossia::token_request& tk,
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,
168 using namespace ossia;
169 if(tk.date == tk.prev_date)
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();
179 if(quantif != 0 && tk.prev_date != 0_tv)
181 state.quantif = quantif;
182 if(
auto time = tk.get_quantification_date(1. / quantif);
183 time && time > tk.prev_date)
185 state.this_buffer_quantif_time = *time;
186 state.this_buffer_quantif_sample
187 = tk.to_physical_time_in_tick(*time, st.modelToSamples());
192 state.quantif = 0.0f;
195 auto m = Control::Widgets::GetLoopMode(mode);
196 if(m != state.quantizedPlayMode)
198 if(quantif != 0 && tk.prev_date != 0_tv)
200 if(
auto& time = state.this_buffer_quantif_time)
207 if(*time > tk.prev_date)
212 sub_tk.set_end_time(*time - 1_tv);
215 p1, p2, sub_tk, st, state, postaction, postaction_bars, passthrough);
219 state.quantizedPlayMode = m;
220 state.actualMode = m;
221 state.playbackPos = 0;
226 sub_tk.set_start_time(*time);
228 changeAction(sub_tk, state);
230 p1, p2, sub_tk, st, state, postaction, postaction_bars, passthrough);
236 state.quantizedPlayMode = m;
237 state.actualMode = m;
238 state.playbackPos = 0;
240 changeAction(tk, state);
241 preAction(p1, p2, tk, st, state, postaction, postaction_bars, passthrough);
247 preAction(p1, p2, tk, st, state, postaction, postaction_bars, passthrough);
253 state.quantizedPlayMode = m;
254 state.actualMode = m;
255 state.playbackPos = 0;
257 changeAction(tk, state);
258 preAction(p1, p2, tk, st, state, postaction, postaction_bars, passthrough);
264 preAction(p1, p2, tk, st, state, postaction, postaction_bars, passthrough);
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)
273 using namespace ossia;
274 if(state.recordStartBar == -1.)
276 action(p1, p2, tk, st, state, passthrough);
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;
292 if(tk.musical_start_last_bar >= state.recordEndBar)
294 switch_to_main_mode();
296 action(p1, p2, tk, st, state, passthrough);
300 else if(tk.musical_end_last_bar >= state.recordEndBar)
302 if(
auto quant_date = tk.get_quantification_date(1.0))
304 ossia::time_value t = *quant_date;
310 sub_tk.set_end_time(t - 1_tv);
311 action(p1, p2, sub_tk, st, state, passthrough);
315 switch_to_main_mode();
321 sub_tk.set_start_time(t);
323 action(p1, p2, sub_tk, st, state, passthrough);
328 qDebug(
"very weird");
336 switch_to_main_mode();
342 action(p1, p2, tk, st, state, passthrough);
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)
351 auto timings = st.timings(tk);
352 action(p1, p2, state, timings.start_sample, timings.length, echoRecord);
356 const ossia::audio_port& p1, ossia::audio_port& p2,
State& state, int64_t start,
357 int64_t length,
bool echoRecord)
359 switch(state.actualMode)
361 case Control::Widgets::LoopMode::Play:
362 if(state.channels() == 0 || state.audio[0].size() == 0)
363 stop(p1, p2, state, start, length);
365 play(p1, p2, state, start, length);
367 case Control::Widgets::LoopMode::Stop:
368 stop(p1, p2, state, start, length);
370 case Control::Widgets::LoopMode::Record:
371 echoRecord ? record(p1, p2, state, start, length)
372 : record_noecho(p1, p2, state, start, length);
374 case Control::Widgets::LoopMode::Overdub:
375 echoRecord ?
overdub(p1, p2, state, start, length)
382 const ossia::audio_port& p1, ossia::audio_port& p2,
State& state,
383 int64_t first_pos, int64_t N)
386 const auto chans = state.channels();
387 p2.set_channels(chans);
392 int64_t k = state.playbackPos;
393 for(
int i = 0; i < chans; i++)
395 auto& out = p2.channel(i);
396 auto& record = state.audio[i];
397 const int64_t chan_samples = record.size();
400 k = state.playbackPos;
401 if(state.playbackPos + N < chan_samples)
403 for(int64_t j = first_pos; j < N; j++)
411 int64_t max = chan_samples - state.playbackPos;
412 int64_t j = first_pos;
425 for(; j < std::min(N, chan_samples); j++)
466 state.playbackPos = k;
471 const ossia::audio_port& p1, ossia::audio_port& p2,
State& state,
472 int64_t first_pos, int64_t N)
474 const auto chans = p1.channels();
475 p2.set_channels(chans);
477 for(std::size_t i = 0; i < chans; i++)
479 auto& in = p1.channel(i);
480 auto& out = p2.channel(i);
482 const int64_t samples = in.size();
483 int64_t max = std::min(N, samples);
487 for(int64_t j = first_pos; j < max; j++)
495 const ossia::audio_port& p1, ossia::audio_port& p2,
State& state,
496 int64_t first_pos, int64_t N)
499 const auto chans = p1.channels();
500 p2.set_channels(chans);
501 state.set_channels(chans);
503 for(std::size_t i = 0; i < chans; i++)
505 auto& in = p1.channel(i);
506 auto& out = p2.channel(i);
507 auto& record = state.audio[i];
509 const int64_t samples = in.size();
510 int64_t max = std::min(N, samples);
513 record.resize(state.playbackPos + samples);
514 int64_t k = state.playbackPos;
516 for(int64_t j = first_pos; j < max; j++)
523 state.playbackPos += N;
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)
531 const auto chans = p1.channels();
532 p2.set_channels(chans);
533 state.set_channels(chans);
535 for(std::size_t i = 0; i < chans; i++)
537 auto& in = p1.channel(i);
538 auto& record = state.audio[i];
540 const int64_t samples = in.size();
541 int64_t max = std::min(N, samples);
543 record.resize(state.playbackPos + samples);
544 int64_t k = state.playbackPos;
546 for(int64_t j = first_pos; j < max; j++)
552 state.playbackPos += N;
556 const ossia::audio_port& p1, ossia::audio_port& p2,
State& state,
557 int64_t first_pos, int64_t N)
563 const auto chans = p1.channels();
564 p2.set_channels(chans);
565 state.set_channels(chans);
567 for(std::size_t i = 0; i < chans; i++)
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();
574 const int64_t samples = in.size();
575 int64_t max = std::min(N, samples);
578 int64_t k = state.playbackPos;
580 for(int64_t j = first_pos; j < max; j++)
582 if(k >= record_samples)
591 state.playbackPos += N;
595 const ossia::audio_port& p1, ossia::audio_port& p2,
State& state,
596 int64_t first_pos, int64_t N)
602 const auto chans = p1.channels();
603 p2.set_channels(chans);
604 state.set_channels(chans);
606 for(std::size_t i = 0; i < chans; i++)
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();
613 const int64_t samples = in.size();
614 int64_t max = std::min(N, samples);
617 int64_t k = state.playbackPos;
619 for(int64_t j = first_pos; j < max; j++)
621 if(k >= record_samples)
630 state.playbackPos += N;
634 Process::Enum& mode, Process::ComboBox& quantif, Process::Toggle& echo,
635 Process::Enum& playmode, Process::IntSpinBox& playmode_bars,
641 using namespace tuplet;
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});
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});
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});
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: score-lib-process/Control/Widgets.hpp:337
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