Loading...
Searching...
No Matches
MidiLooper.hpp
1#pragma once
2#include <Fx/Looper/LooperNode.hpp>
3#include <Fx/Looper/MidiLoop.hpp>
4#include <Fx/Looper/MidiTake.hpp>
5#include <Fx/Types.hpp>
6
7#include <ossia/dataflow/exec_state_facade.hpp>
8#include <ossia/dataflow/token_request.hpp>
9
10#include <halp/controls.hpp>
11#include <halp/layout.hpp>
12#include <halp/meta.hpp>
13#include <halp/midi.hpp>
14
15#include <Advanced/MidiScaler/MidiCommon/Messages.hpp>
16
17#include <functional>
18#include <limits>
19#include <memory>
20
21namespace Nodes::MidiLooper
22{
24{
25 halp_meta(name, "Looper (MIDI)")
26 halp_meta(c_name, "midi_looper")
27 halp_meta(category, "Midi/Sequencers")
28 halp_meta(author, "ossia score")
29 halp_meta(manual_url, "https://ossia.io/score-docs/processes/midi-looper.html")
30 halp_meta(
31 description,
32 "Loop notes and controllers in passes, and turn the loop into a MIDI clip of "
33 "the timeline")
34 halp_meta(uuid, "0c0b7d3e-5a52-4b8f-9d1f-6e3a2b8c4f71");
35 halp_meta(recommended_height, 200);
36 halp_flag(process_exec);
37
40 static constexpr std::array<int, 1> removed_inlets() noexcept { return {20}; }
41
42 using LoopMode = Looper::LoopMode;
43 using Passthrough = Looper::Passthrough;
44 using OverdubMode = Looper::OverdubMode;
45 using HeldNotes = Looper::HeldNotes;
46
47 struct ins
48 {
49 halp::midi_bus<"in", libremidi::message> midi;
50
51 struct : halp::enum_t<LoopMode, "Mode">
52 {
53 using halp::enum_t<LoopMode, "Mode">::operator=;
54 static constexpr std::string_view former_name() noexcept { return "Loop"; }
55 halp_meta(
56 description,
57 "What the looper is doing, or about to do on the grid: playing the "
58 "loop, recording it, overdubbing on top of it, or nothing. It follows "
59 "every change, by an action or a recording's end. Record on a loop "
60 "overdubs it: Clear it first to record a new one. Overdub on an "
61 "empty loop records it, then overdubs.")
63 std::function<void(std::string_view)> update_controller;
65 void update(Node& n) noexcept { n.loop_arrived(); }
66 } mode;
67
69
70 struct : Looper::enum_combobox<"Passthrough", Passthrough, Passthrough::Full>
71 {
72 using enum_combobox::operator=;
73 halp_meta(
74 description,
75 "Whether what is played is heard as well as the loop: never, only "
76 "while recording or overdubbing, or always.")
77 } passthrough;
78
80
81 struct : halp::spinbox_i32<"Record bars", halp::irange{0, 64, 4}>
82 {
83 static constexpr std::string_view former_name() noexcept { return "Bars"; }
84 halp_meta(
85 description,
86 "How long a recording runs before After record takes over. Zero "
87 "leaves it to be ended by hand.")
88 } postaction_bars;
89
91
92 struct : halp::impulse_button<"Undo">
93 {
94 halp_meta(description, "Undoes the last pass, erase or mute")
95 } undo;
96 struct : halp::impulse_button<"Redo">
97 {
98 halp_meta(description, "Redoes what Undo took back")
99 } redo;
100 struct : halp::impulse_button<"Clear">
101 {
102 halp_meta(description, "Empties the loop and forgets every pass")
103 } clear;
104
105 struct : halp::enum_t<OverdubMode, "Overdub mode">
106 {
107 using halp::enum_t<OverdubMode, "Overdub mode">::operator=;
108 halp_meta(
109 description,
110 "Merge keeps every pass. Replace silences a pitch of earlier passes "
111 "where a pass plays it. Controllers always replace their own earlier "
112 "values where a pass moves them.")
113 } overdub_mode;
114
115 struct : halp::enum_t<HeldNotes, "Held notes">
116 {
117 using halp::enum_t<HeldNotes, "Held notes">::operator=;
118 halp_meta(
119 description,
120 "A note held across the end of the loop carries on from its start "
121 "(Wrap), or ends with the loop (Truncate).")
122 } held;
123
125
126 struct : halp::knob_f32<"Strength", halp::range{0., 1., 0.}>
127 {
128 static constexpr std::string_view former_name() noexcept { return "Quantize"; }
129 halp_meta(
130 description,
131 "How far notes are pulled towards Note grid when played back: 0 "
132 "leaves them where they were played, 1 puts them on the grid. The "
133 "recording keeps its own timing.")
134 } strength;
135
136 struct : halp::toggle<"Note ends">
137 {
138 static constexpr std::string_view former_name() noexcept { return "Quantize ends"; }
139 halp_meta(description, "Pulls the ends of the notes to Note grid as well")
140 } quantize_ends;
141
142 struct : halp::spinbox_i32<"Grace (ms)", halp::irange{0, 250, 60}>
143 {
144 halp_meta(
145 description,
146 "A note played this long before a recording or an overdub starts "
147 "belongs to its first beat. A note still held when it starts always "
148 "does.")
149 } grace;
150
151 struct : halp::spinbox_i32<"Late stop (ms)", halp::irange{0, 500, 120}>
152 {
153 halp_meta(
154 description,
155 "A recording asked to stop this long after a point of the grid ends "
156 "on that point.")
157 } late;
158
159 struct : halp::spinbox_i32<"Transpose", halp::irange{-48, 48, 0}>
160 {
161 halp_meta(description, "Semitones added to the notes of the loop")
162 } transpose;
163
164 struct : halp::spinbox_i32<"In channel", halp::irange{0, 16, 0}>
165 {
166 halp_meta(description, "Channel recorded; 0 records them all")
167 } in_channel;
168
169 struct : halp::spinbox_i32<"Out channel", halp::irange{0, 16, 0}>
170 {
171 halp_meta(description, "Channel the loop is played on; 0 keeps the recorded ones")
172 } out_channel;
173
174 struct : halp::spinbox_i32<"Capture bars", halp::irange{1, 64, 4}>
175 {
176 halp_meta(
177 description,
178 "How many bars Capture makes a loop of when there is none. Over a "
179 "loop, Capture takes one round of it, whatever this says.")
180 } capture_bars;
181
182 struct : halp::impulse_button<"Capture">
183 {
184 halp_meta(
185 description,
186 "Takes what was played lately, recorded or not, without having to "
187 "press Record first. With no loop, the last Capture bars become the "
188 "loop, played at once. Over a loop, what was played over its last "
189 "round becomes a new layer, where it was played in the loop. Undo "
190 "takes it out. Pressed just after a grid point, it ends there, as "
191 "Late says.")
192 } capture;
193
194 // Controls added after the first presets go last: presets address the
195 // inputs by their index.
196 struct : Looper::enum_combobox<"Export", Looper::TakeFiles, Looper::TakeFiles::Mix>
197 {
198 using enum_combobox::operator=;
199 static constexpr std::string_view former_name() noexcept { return "Takes"; }
200 halp_meta(
201 description,
202 "What Send, or a drag from it, places: what is heard of the loop "
203 "(Mix), each pass in a MIDI file of its own (Layers), or both, each "
204 "on a lane of its own. A take written as a pass closes has these "
205 "files; one Send or a drag asks for, or the score's stop leaves, has "
206 "them all, so that Export can still be changed once the score is "
207 "stopped. Dragging a lane places its layer alone.")
208 } exported;
209
211 {
212 using enum_combobox::operator=;
213 halp_meta(
214 description,
215 "What an overdub does at the end of the loop: go round it again "
216 "(Wrap), or make the loop longer by its own length each time, the "
217 "passes under it playing again (Extend). An extended loop ends when "
218 "overdubbing stops.")
219 } past_end;
220
221 struct : halp::toggle<"Follow tempo", halp::default_on_toggle>
222 {
223 halp_meta(
224 description,
225 "Plays the loop at the tempo of the score: it keeps to the bars when "
226 "the tempo changes. Off, it keeps the speed it was recorded at.")
227 } follow_tempo;
228
229 Looper::erase_layer_button erase_layer;
230
231 struct : halp::spinbox_i32<"Layer", halp::irange{1, 32, 1}>
232 {
233 halp_meta(
234 description,
235 "The layer Mute layer, Erase layer and Solo layer act on, counting "
236 "from the oldest one: the lane of that number from the top while "
237 "there are no more layers than lanes, and the file of that number of "
238 "a take written as layers. It moves to each new layer as it is "
239 "added.")
240 std::function<void(int)> update_controller;
241 } layer;
242
243 struct : halp::toggle<"Reverse">
244 {
245 halp_meta(
246 description,
247 "Plays the loop backwards, from where the playhead is: notes start "
248 "where they ended, controllers go back the way they came. An overdub "
249 "records where the playhead goes, as on tape: what is played over "
250 "the reversed loop plays reversed once it goes forwards again.")
251 std::function<void(bool)> update_controller;
252 } reverse;
253
254 struct : Looper::speed_selector<"Speed">
255 {
256 halp_meta(
257 description,
258 "Plays the loop slower or faster. An overdub records where the "
259 "playhead goes: what is played at x1/2 plays twice as fast at x1. "
260 "The grid, the bars and the loop's length stay in the score's time; "
261 "combines with Follow tempo.")
262 std::function<void(float)> update_controller;
263 } speed;
264
266 {
267 using enum_combobox::operator=;
268 struct range
269 {
270 std::string_view values[2] = {"Whole", "Per cycle"};
271 Looper::Passes init = Looper::Passes::Whole;
272 };
273 halp_meta(
274 description,
275 "What an overdub that goes round the loop several times leaves: one "
276 "layer (Whole), or a layer for each time round (Per cycle), cut on "
277 "the loop's start, to keep the best one with Solo layer or Mute "
278 "layer, erase the others, or splice them in the timeline from a take "
279 "written as layers. With Past the end on Extend, a cut is made each "
280 "time the loop grows.")
281 } passes;
282
283 Looper::mute_layer_button mute_layer;
284
285 // The Switches tab: actions for footswitches.
286 Looper::rec_play_dub_button rec_play_dub;
287 Looper::after_overdub_selector after_overdub;
288 Looper::play_stop_button play_stop;
292 Looper::new_layer_button new_layer;
295 Looper::retrigger_button retrigger;
296 Looper::previous_layer_button previous_layer;
297 Looper::next_layer_button next_layer;
298 Looper::solo_layer_button solo_layer;
299 Looper::reverse_button reverse_toggle;
300 Looper::half_speed_button half_speed;
301 Looper::double_speed_button double_speed;
302 Looper::at_max_layers_selector at_max_layers;
303
304 struct : halp::spinbox_i32<"Max layers", halp::irange{2, Looper::midi_loop::max_layers, 8}>
305 {
306 halp_meta(
307 description,
308 "Layers kept apart, for undo, mute, solo and erase. What a pass does "
309 "past it is what At max layers says.")
310 } max_layers;
311 } inputs;
312
313 struct
314 {
315 halp::midi_out_bus<"out", libremidi::message> midi;
316 struct : halp::val_port<"Phase", float>
317 {
318 halp_meta(description, "Position in the loop, from 0 to 1")
319 } phase;
320 } outputs;
321
322 struct worker_t
323 {
324 std::function<void(Looper::midi_take_job)> request;
326 static bool must_run(const Looper::midi_take_job& job) noexcept { return job.alone; }
327 static std::function<void(Node&)> work(Looper::midi_take_job&& job)
328 {
329 const bool ok = job.run();
330 return [slot = job.slot, number = job.number, alone = job.alone, ok](Node& self) {
331 self.take_written(slot, number, ok, alone);
332 };
333 }
334 } worker;
335
336 std::function<void(Looper::view_message)> send_message;
340
343
344 const score::DocumentContext* ossia_document_context{};
345 ossia::exec_state_facade ossia_state;
346
348 std::shared_ptr<Looper::take_writer> writer;
349 std::shared_ptr<Looper::midi_export> export_data;
350
351 Node()
352 {
353 export_data = std::make_shared<Looper::midi_export>();
354 stop_take = std::make_shared<Looper::midi_stop_take>();
355 }
358 std::shared_ptr<Looper::midi_stop_take> stop_take;
359
360 void prepare(halp::setup s)
361 {
362 if(ossia_document_context)
363 writer = Looper::take_writer::make(*ossia_document_context, "MIDI looper", "mid");
364 if(writer)
365 writer->tell_views = send_message_from_worker;
366 }
367
370 void stop() { m_stop_requested = true; }
374 {
375 if(std::exchange(m_stop_requested, false))
376 stop_score();
377 }
378
379 int64_t looper_run(int64_t start, int64_t length);
380 void looper_trim_recording(int64_t frames)
381 {
382 const int64_t end = std::max<int64_t>(0, loop.length - frames);
383 // What was played after the grid point belongs to the next pass: it is
384 // kept aside, from the grid point on, for an overdub that starts there.
385 const int64_t cut = loop.length - end;
386 for(int i = 0; i < m_carried_note_count; i++)
387 m_carried_notes[i].start += cut;
388 for(int i = 0; i < m_carried_control_count; i++)
389 m_carried_controls[i].pos += cut;
390 m_carried_span += cut;
391 auto carry_note = [this, end](const Looper::midi_note& n) {
392 if(n.start < end)
393 return false;
394 if(m_carried_note_count < int(m_carried_notes.size()))
395 {
396 auto& c = m_carried_notes[m_carried_note_count++];
397 c = n;
398 c.start -= end;
399 }
400 return true;
401 };
402 auto carry_control = [this, end](const Looper::midi_control& c) {
403 if(c.pos < end)
404 return false;
405 if(m_carried_control_count < int(m_carried_controls.size()))
406 {
407 auto& k = m_carried_controls[m_carried_control_count++];
408 k = c;
409 k.pos -= end;
410 }
411 return true;
412 };
413 loop.note_count = int(
414 std::remove_if(
415 loop.notes.begin(), loop.notes.begin() + loop.note_count, carry_note)
416 - loop.notes.begin());
417 loop.control_count = int(
418 std::remove_if(
419 loop.controls.begin(), loop.controls.begin() + loop.control_count,
420 carry_control)
421 - loop.controls.begin());
422 loop.length = end;
423 loop.pass_written = end;
424 loop.edits++;
425 }
427 static constexpr double max_loop_seconds = 3600.;
428
429 void operator()(const tick& tk)
430 {
431 m_out_count = 0;
432 m_tick_frames = ossia_state.timings(tk).length;
433 if(std::exchange(m_stop_requested, false))
434 stop_score();
435 if(tk.date == tk.prev_date)
436 {
437 push_outputs();
438 return;
439 }
440 run_tick(tk);
441 m_now += m_tick_frames;
442 send_view(m_tick_frames);
443 push_outputs();
444 }
445
446 struct ui
447 {
448 halp_meta(layout, halp::layouts::hbox)
449 halp_meta(background, halp::colors::background_dark)
450 struct
451 {
452 halp_meta(layout, halp::layouts::vbox)
453 struct
454 {
455 halp_meta(layout, halp::layouts::vbox)
457 struct
458 {
459 halp_meta(layout, halp::layouts::hbox)
460 halp::item<&ins::quantif> quantif;
461 halp::item<&ins::exported> exported;
462 } timing;
463 } transport;
464 struct
465 {
466 halp_meta(name, "Settings")
467 halp_meta(layout, halp::layouts::tabs)
468 halp_meta(background, halp::colors::background_darker)
469 struct
470 {
471 halp_meta(name, "Length")
472 halp_meta(layout, halp::layouts::grid)
473 halp_meta(alignment, halp::alignments::left)
474 halp_meta(spacing, 6)
475 halp_meta(columns, 3)
476 halp::item<&ins::after_record> after_record;
477 halp::item<&ins::postaction_bars> bars;
478 halp::item<&ins::late> late;
479 halp::item<&ins::grace> grace;
480 halp::item<&ins::capture> capture;
481 halp::item<&ins::capture_bars> capture_bars;
482 } length;
483 struct
484 {
485 halp_meta(name, "Play")
486 halp_meta(layout, halp::layouts::grid)
487 halp_meta(alignment, halp::alignments::left)
488 halp_meta(spacing, 6)
489 halp_meta(columns, 2)
490 halp::item<&ins::start_from> start_from;
491 } play;
495 struct
496 {
497 halp_meta(name, "Overdub")
498 halp_meta(layout, halp::layouts::grid)
499 halp_meta(alignment, halp::alignments::left)
500 halp_meta(spacing, 6)
501 halp_meta(columns, 3)
502 halp::item<&ins::overdub_mode> overdub_mode;
503 halp::item<&ins::held> held;
504 halp::item<&ins::past_end> past_end;
505 halp::item<&ins::passes> passes;
506 halp::item<&ins::max_layers> max_layers;
507 halp::item<&ins::at_max_layers> at_max_layers;
508 } overdub;
509 struct
510 {
511 halp_meta(name, "Note grid")
512 halp_meta(layout, halp::layouts::hbox)
513 halp::item<&ins::grid> grid;
514 halp::item<&ins::strength> strength;
515 halp::item<&ins::quantize_ends> ends;
516 } quantize;
517 struct
518 {
519 halp_meta(name, "Notes")
520 halp_meta(layout, halp::layouts::grid)
521 halp_meta(alignment, halp::alignments::left)
522 halp_meta(spacing, 6)
523 halp_meta(columns, 2)
524 halp::item<&ins::passthrough> passthrough;
525 halp::item<&ins::transpose> transpose;
526 halp::item<&ins::in_channel> in_channel;
527 halp::item<&ins::out_channel> out_channel;
528 } notes;
529 } settings;
530 } controls;
531
532 struct
533 {
534 halp_meta(layout, halp::layouts::container)
535 halp_meta(background, halp::colors::background_mid)
537 } view;
538
539 struct bus
540 {
541 void init(ui& self)
542 {
543 self.view.view.erase = [this](Looper::erase_request r) {
544 if(send_message)
545 send_message(r);
546 };
547 }
548 static void process_message(ui& self, Looper::view_message msg)
549 {
550 self.view.view.receive(msg);
551 }
552 std::function<void(Looper::erase_request)> send_message;
553 };
554 };
555
556private:
557 friend struct Looper::looper_node<Node>;
558
559 // looper_node's hooks
561 static constexpr bool passes_wait_for_material = true;
562 Looper::midi_loop& stack() noexcept { return loop; }
563 const Looper::midi_loop& stack() const noexcept { return loop; }
566 void layers_changing() { flush_loop(0); }
567 bool busy() const noexcept { return false; }
568 void cleared() noexcept { }
569 void pass_dropped() noexcept { loop.taking = false; }
570 void begin_overdub(int64_t start, bool oldest, int replaced)
571 {
572 loop.begin_overdub(start, max_layers(), oldest, replaced);
573 }
574 bool can_merge_newest() const noexcept { return !loop.taking; }
575 void merge_newest() noexcept { loop.merge(loop.count - 2, true); }
576 bool pass_closing() noexcept { return true; }
579 void pass_closed() { loop.end_pass(inputs.overdub_mode); }
580 void tick_started(const tick&)
581 {
582 m_in_index = 0;
583 if(inputs.transpose.value != m_transpose || inputs.out_channel.value != m_out_channel)
584 {
585 flush_loop(0);
586 m_transpose = inputs.transpose.value;
587 m_out_channel = inputs.out_channel.value;
588 }
589 }
590 void prepare_loop() noexcept { }
591 void forget_carried() noexcept
592 {
593 m_carried_note_count = 0;
594 m_carried_control_count = 0;
595 m_carried_span = 0;
596 }
597 void tick_finished(const tick&)
598 {
599 // Input past the last span, e.g. on a sub-sample tick.
600 handle_input(m_tick_frames, m_tick_frames + 1, -1, false);
601 }
602 void close_recording(int64_t bars)
603 {
604 if(bars > 0)
605 loop.length = bars;
606 loop.pass_written = loop.length;
607 loop.end_pass(inputs.overdub_mode);
608 }
609 void close_overdub()
610 {
611 if(loop.pass_backwards && loop.taking && !m_cycle_pending)
612 close_backwards(int64_t(std::ceil(m_phase - 1e-9)));
613 close_pass();
614 }
615 bool cut_after(int64_t end) const noexcept { return loop.taking && loop.pass_start >= end; }
616 void cut_pass_dropped() noexcept { }
617 void trim_top_pass(int64_t end);
618 void head_moved(int64_t pos) noexcept
619 {
620 m_phase = double(pos);
621 m_expected_pos = pos;
622 }
623 double head_y(int64_t len) const noexcept
624 {
625 const double l = double(len);
626 const double ph = std::fmod(std::max(0., m_phase), l);
627 double y = m_reverse ? l - ph : ph;
628 if(y >= l)
629 y -= l;
630 return y;
631 }
632 double rate_for(LoopMode) const noexcept { return rate(); }
633 bool by_quarters() const noexcept { return inputs.follow_tempo.value && m_loop_tempo > 0.; }
635 void set_head(double y, int64_t len) noexcept
636 {
637 double h = m_reverse ? double(len) - y : y;
638 if(h >= double(len))
639 h -= double(len);
640 transport.playbackPos = std::clamp<int64_t>(int64_t(h), 0, len - 1);
641 }
642 int64_t capture_clock() const noexcept { return m_now; }
643 int64_t capture_limit() const noexcept { return std::numeric_limits<int64_t>::max(); }
644 void capture_into(int64_t end, int64_t n);
645 void captured() noexcept { head_moved(transport.playbackPos); }
646 bool capture_layer_ready(const Looper::capture_window& w) const noexcept;
647 void capture_layer_into(const Looper::capture_window& w);
648 void stopping()
649 {
650 flush_loop(0);
651 flush_input(0);
652 }
653 void close_on_stop(bool recording)
654 {
655 if(recording)
656 loop.end_pass(inputs.overdub_mode);
657 else
658 close_pass();
659 }
660 bool export_ready() const noexcept { return true; }
661 void start_export(int passes, bool open, bool recording);
662 void continue_export() noexcept { }
663 void export_item(int item) { write_export_item(item); }
664 bool take_cancelled() noexcept { return false; }
665 void take_released() noexcept { }
666 void stop_take_written() noexcept { }
667 const Looper::stop_take_notice& stop_notice() const noexcept { return stop_take->notice; }
668 static constexpr const char* take_extension = "mid";
670 static constexpr halp::clip_stretch take_stretch() noexcept
671 {
672 return halp::clip_stretch::automatic;
673 }
674 bool view_full() const noexcept { return loop.full; }
675 bool view_writing() const noexcept { return m_writing; }
676
677 static constexpr int max_out = 1024;
678 struct out_event
679 {
680 int64_t ts;
681 libremidi::message m;
682 int order;
683 };
684 std::array<out_event, max_out> m_out{};
685 int m_out_count{};
686
688 struct range_event
689 {
690 int64_t pos;
691 int64_t end;
692 int note;
693 };
694 std::array<range_event, 2048> m_range{};
695
696 // Notes sounding on the output, and how many of them the loop started.
697 std::array<std::array<uint16_t, 128>, 16> m_sounding{};
698 std::array<std::array<uint16_t, 128>, 16> m_loop_sounding{};
699
704 struct playing_note
705 {
708 int64_t end;
711 int64_t start;
712 int note;
713 uint8_t note_channel, note_pitch;
715 uint8_t channel, pitch;
716 };
717 static constexpr int max_playing = 512;
719 std::array<playing_note, max_playing> m_playing{};
720 int m_playing_count{};
721
722 // Note-ons played before a pass started: the ones still held, and the
723 // ones of the grace window.
724 struct early_note
725 {
726 int64_t on;
727 int64_t off;
728 uint8_t channel, pitch, velocity;
729 };
730 std::array<early_note, 16> m_early{};
731 int m_early_count{};
732
733 // What a recording cut back to a grid point had recorded past it, from the
734 // grid point on, and how many frames were cut.
735 std::array<Looper::midi_note, 128> m_carried_notes{};
736 std::array<Looper::midi_control, 1024> m_carried_controls{};
737 int m_carried_note_count{};
738 int m_carried_control_count{};
739 int64_t m_carried_span{};
740
741 int64_t m_now{};
742 int64_t m_tick_frames{};
743 int64_t m_expected_pos{};
744 int m_loop_total{};
745 std::size_t m_in_index{};
746 int m_transpose{};
747 int m_out_channel{};
748 bool m_stop_requested{};
749
750 // What was played lately, recorded or not, for Capture.
751 struct played
752 {
753 int64_t frame;
754 uint8_t bytes[3];
755 uint8_t size;
756 };
757 static constexpr int history_size = 8192;
758 std::array<played, history_size> m_history{};
759 int64_t m_history_count{};
762 static bool history_message(const played& h, libremidi::message& m) noexcept
763 {
764 if(h.size == 3)
765 m = libremidi::message{h.bytes[0], h.bytes[1], h.bytes[2]};
766 else if(h.size == 2)
767 m = libremidi::message{h.bytes[0], h.bytes[1]};
768 else
769 return false;
770 return true;
771 }
772 void handle_input(
773 int64_t from, int64_t to, double pos0, bool taking, double rate = 1.,
774 bool backwards = false);
775 void record_note_on(int64_t pos, const libremidi::message& m);
776 void record_note_off(int64_t pos, const libremidi::message& m);
777 void play_range(double a, double b, double out0, double r);
778 void play_range_reverse(double a, double b, double out0, double r);
779 void update_reverse_controls();
783 std::array<uint16_t, Looper::midi_loop::max_controls> m_ctl_sorted{};
784 std::array<int16_t, Looper::midi_loop::max_controls> m_ctl_prev{};
785 std::array<int64_t, 6> m_ctl_signature{-1};
786 void next_cycle_pass(int64_t start);
787
788 // What an overdub last set each controller it moved to. Backwards, a value
789 // holds for the positions under the one it was set at: the loop keeps, at
790 // that position, the value from before, which is what it goes back to
791 // going forwards; and where the playhead stops going backwards, the last
792 // value set.
793 struct last_control
794 {
795 uint16_t key;
796 uint8_t status, data1, data2;
797 };
798 std::array<last_control, Looper::midi_loop::max_touched> m_last_ctl{};
799 int m_last_ctl_count{};
800 void record_control(int64_t pos, const libremidi::message& m, bool backwards);
801 void close_backwards(int64_t boundary);
802 int max_layers() const noexcept
803 {
804 return std::clamp(inputs.max_layers.value, 2, Looper::midi_loop::max_layers);
805 }
807 bool m_was_reverse{};
808 void advance(int64_t start, int64_t length, bool overdub);
809 double rate() const noexcept;
810 double m_phase{};
811 void begin_pass_grace(int64_t pos, int64_t ts);
812 void begin_pass_carried(int64_t k, int64_t ts);
813
814 struct placed_note
815 {
816 int64_t start;
817 int64_t length;
818 };
819 double grid_frames() const noexcept;
820 int64_t pull(int64_t pos) const noexcept;
821 placed_note place(const Looper::midi_note& n, int64_t period = 0) const noexcept;
822
823 void emit(int64_t ts, const libremidi::message& m)
824 {
825 if(m_out_count < max_out)
826 m_out[m_out_count++] = {ts, m, 0};
827 }
828 void note_on(int ch, int pitch, int vel, int64_t ts, bool from_loop);
829 void note_off(int ch, int pitch, int64_t ts, bool from_loop);
830 void flush_loop(int64_t ts);
831 bool playing(int note) const noexcept;
832 void start_playing(int note, int64_t end, int64_t ts);
833 template <typename Frame>
834 void end_playing(int64_t last, int64_t lo, Frame frame);
835 void wrap_playing(int64_t len, int64_t ts);
836 template <typename Frame>
837 void play_notes(int count, int64_t lo, int64_t hi, Frame frame);
838 void flush_input(int64_t ts);
839 void push_outputs();
840
841 void write_export_item(int item);
842 void fill_export(
843 const Looper::midi_loop& snap, int item, int64_t pos, Looper::midi_export& e) const;
844 void write_stop_take(bool recording, double date, int64_t pos);
847 Looper::midi_loop m_export_loop;
848 int64_t m_export_pos{};
849 void send_view(int64_t frames);
850 void fill_message(Looper::view_message& v);
851};
852
853inline void Node::note_on(int ch, int pitch, int vel, int64_t ts, bool from_loop)
854{
855 if(ch < 1 || ch > 16 || pitch < 0 || pitch > 127)
856 return;
857 if(from_loop)
858 {
859 m_loop_sounding[ch - 1][pitch]++;
860 m_loop_total++;
861 }
862 if(m_sounding[ch - 1][pitch]++ == 0)
863 emit(ts, libremidi::channel_events::note_on(ch, pitch, std::clamp(vel, 1, 127)));
864}
865
866inline void Node::note_off(int ch, int pitch, int64_t ts, bool from_loop)
867{
868 if(ch < 1 || ch > 16 || pitch < 0 || pitch > 127)
869 return;
870 if(from_loop)
871 {
872 if(m_loop_sounding[ch - 1][pitch] == 0)
873 return;
874 m_loop_sounding[ch - 1][pitch]--;
875 m_loop_total--;
876 }
877 auto& s = m_sounding[ch - 1][pitch];
878 if(s == 0)
879 return;
880 if(--s == 0)
881 emit(ts, libremidi::channel_events::note_off(ch, pitch, 0));
882}
883
884inline void Node::flush_loop(int64_t ts)
885{
886 m_playing_count = 0;
887 if(m_loop_total == 0)
888 return;
889 for(int c = 0; c < 16; c++)
890 for(int p = 0; p < 128; p++)
891 while(m_loop_sounding[c][p] > 0)
892 note_off(c + 1, p, ts, true);
893}
894
895inline bool Node::playing(int note) const noexcept
896{
897 const auto& n = loop.notes[note];
898 for(int k = 0; k < m_playing_count; k++)
899 {
900 const auto& p = m_playing[k];
901 if(p.note == note && p.start == n.start && p.note_pitch == n.pitch
902 && p.note_channel == n.channel)
903 return true;
904 }
905 return false;
906}
907
909inline void Node::start_playing(int note, int64_t end, int64_t ts)
910{
911 if(m_playing_count >= max_playing || playing(note))
912 return;
913 const auto& n = loop.notes[note];
914 const int ch = m_out_channel > 0 ? m_out_channel : n.channel;
915 const int pitch = n.pitch + m_transpose;
916 if(ch < 1 || ch > 16 || pitch < 0 || pitch > 127)
917 return;
918
919 auto it = std::upper_bound(
920 m_playing.begin(), m_playing.begin() + m_playing_count, end,
921 [](int64_t e, const playing_note& p) { return e < p.end; });
922 std::move_backward(it, m_playing.begin() + m_playing_count, m_playing.begin() + m_playing_count + 1);
923 *it = playing_note{
924 .end = end,
925 .start = n.start,
926 .note = note,
927 .note_channel = n.channel,
928 .note_pitch = n.pitch,
929 .channel = uint8_t(ch),
930 .pitch = uint8_t(pitch)};
931 m_playing_count++;
932 note_on(ch, pitch, n.velocity, ts, true);
933}
934
937template <typename Frame>
938inline void Node::end_playing(int64_t last, int64_t lo, Frame frame)
939{
940 int k = 0;
941 for(; k < m_playing_count && m_playing[k].end <= last; k++)
942 {
943 const auto& p = m_playing[k];
944 note_off(p.channel, p.pitch, frame(std::max(p.end, lo)), true);
945 }
946 if(k == 0)
947 return;
948 std::move(m_playing.begin() + k, m_playing.begin() + m_playing_count, m_playing.begin());
949 m_playing_count -= k;
950}
951
954inline void Node::wrap_playing(int64_t len, int64_t ts)
955{
956 for(int k = 0; k < m_playing_count; k++)
957 m_playing[k].end = std::min(m_playing[k].end - len, len - 1);
958 // A note of the loop counted as sounding with none playing would mute its
959 // key for good: every later note-on of it would be a second one.
960 if(m_playing_count == 0 && m_loop_total != 0)
961 flush_loop(ts);
962}
963
967template <typename Frame>
968inline void Node::play_notes(int count, int64_t lo, int64_t hi, Frame frame)
969{
970 std::sort(m_range.begin(), m_range.begin() + count, [](const range_event& a, const range_event& b) {
971 if(a.pos != b.pos)
972 return a.pos < b.pos;
973 return a.note < b.note;
974 });
975 for(int i = 0; i < count; i++)
976 {
977 const auto& e = m_range[i];
978 end_playing(e.pos, lo, frame);
979 start_playing(e.note, e.end, frame(e.pos));
980 }
981 end_playing(hi - 1, lo, frame);
982}
983
984inline void Node::flush_input(int64_t ts)
985{
986 for(int c = 0; c < 16; c++)
987 for(int p = 0; p < 128; p++)
988 while(m_sounding[c][p] > m_loop_sounding[c][p])
989 note_off(c + 1, p, ts, false);
990}
991
992inline void Node::push_outputs()
993{
994 // Note-offs first at a given time, so that a note ending where the same
995 // note starts again is heard twice; otherwise in the order they came.
996 for(int i = 0; i < m_out_count; i++)
997 m_out[i].order = i;
998 std::sort(m_out.begin(), m_out.begin() + m_out_count, [](const out_event& a, const out_event& b) {
999 if(a.ts != b.ts)
1000 return a.ts < b.ts;
1001 const bool a_off = mtk::classify(a.m) == mtk::midi_kind::note_off;
1002 const bool b_off = mtk::classify(b.m) == mtk::midi_kind::note_off;
1003 if(a_off != b_off)
1004 return a_off;
1005 return a.order < b.order;
1006 });
1007 for(int i = 0; i < m_out_count; i++)
1008 {
1009 auto m = m_out[i].m;
1010 m.timestamp = std::clamp<int64_t>(m_out[i].ts, 0, std::max<int64_t>(0, m_tick_frames - 1));
1011 outputs.midi.push_back(std::move(m));
1012 }
1013 m_out_count = 0;
1014}
1015
1017inline void Node::capture_into(int64_t end, int64_t n)
1018{
1019 const int64_t from = end - n;
1020
1021 loop.begin_recording();
1022 loop.length = n;
1023 const int64_t count = std::min<int64_t>(m_history_count, history_size);
1024 for(int64_t i = m_history_count - count; i < m_history_count; i++)
1025 {
1026 const auto& h = m_history[i % history_size];
1027 if(h.frame < from || h.frame >= end)
1028 continue;
1029 const int64_t pos = h.frame - from;
1030 libremidi::message m;
1031 if(!history_message(h, m))
1032 continue;
1033 switch(mtk::classify(m))
1034 {
1035 case mtk::midi_kind::note_on:
1036 loop.add_note(pos, mtk::channel_of(m), m.bytes[1], m.bytes[2]);
1037 break;
1038 case mtk::midi_kind::note_off:
1039 if(auto* note = loop.open_note(mtk::channel_of(m), m.bytes[1]))
1040 note->length = std::max<int64_t>(1, pos - note->start);
1041 break;
1042 case mtk::midi_kind::control_change:
1043 case mtk::midi_kind::pitch_bend:
1044 case mtk::midi_kind::channel_pressure:
1045 case mtk::midi_kind::poly_pressure:
1046 loop.add_control(pos, m.bytes[0], m.bytes[1], h.size > 2 ? m.bytes[2] : 0);
1047 break;
1048 default:
1049 break;
1050 }
1051 }
1052 // Notes let go after the window, e.g. when it was moved back to a grid
1053 // point, end there; the ones still held end when they are let go.
1054 for(int64_t i = m_history_count - count; i < m_history_count; i++)
1055 {
1056 const auto& h = m_history[i % history_size];
1057 if(h.frame < end || h.size < 2)
1058 continue;
1059 const libremidi::message m{h.bytes[0], h.bytes[1], h.size > 2 ? h.bytes[2] : uint8_t(0)};
1060 if(mtk::classify(m) == mtk::midi_kind::note_off)
1061 if(auto* note = loop.open_note(mtk::channel_of(m), m.bytes[1]))
1062 note->length = std::max<int64_t>(1, h.frame - from - note->start);
1063 }
1064
1065 loop.pass_written = n;
1066 loop.end_pass(inputs.overdub_mode);
1067}
1068
1070inline bool Node::capture_layer_ready(const Looper::capture_window& w) const noexcept
1071{
1072 const int64_t count = std::min<int64_t>(m_history_count, history_size);
1073 for(int64_t i = m_history_count - count; i < m_history_count; i++)
1074 {
1075 const auto& h = m_history[i % history_size];
1076 libremidi::message m;
1077 if(!w.contains(h.frame) || !history_message(h, m))
1078 continue;
1079 switch(mtk::classify(m))
1080 {
1081 case mtk::midi_kind::note_on:
1082 case mtk::midi_kind::control_change:
1083 case mtk::midi_kind::pitch_bend:
1084 case mtk::midi_kind::channel_pressure:
1085 case mtk::midi_kind::poly_pressure:
1086 return true;
1087 default:
1088 break;
1089 }
1090 }
1091 return false;
1092}
1093
1098inline void Node::capture_layer_into(const Looper::capture_window& w)
1099{
1100 m_last_ctl_count = 0;
1101 const int64_t count = std::min<int64_t>(m_history_count, history_size);
1102 for(int64_t i = m_history_count - count; i < m_history_count; i++)
1103 {
1104 const auto& h = m_history[i % history_size];
1105 libremidi::message m;
1106 if(h.frame < w.end - w.n || !history_message(h, m))
1107 continue;
1108 const int64_t pos = w.position(h.frame);
1109 switch(mtk::classify(m))
1110 {
1111 case mtk::midi_kind::note_on:
1112 if(w.contains(h.frame))
1113 record_note_on(pos, m);
1114 break;
1115 case mtk::midi_kind::note_off:
1116 record_note_off(pos, m);
1117 break;
1118 case mtk::midi_kind::control_change:
1119 case mtk::midi_kind::pitch_bend:
1120 case mtk::midi_kind::channel_pressure:
1121 case mtk::midi_kind::poly_pressure:
1122 if(w.contains(h.frame))
1123 record_control(pos, m, w.backwards);
1124 break;
1125 default:
1126 break;
1127 }
1128 }
1129 loop.pass_written = w.positions();
1130 loop.pass_progress = double(loop.pass_written);
1131 loop.pass_backwards = w.backwards;
1132 if(w.backwards)
1133 close_backwards(w.position(w.end));
1134}
1135
1136inline double Node::grid_frames() const noexcept
1137{
1138 // The loop's own beats: it keeps the speed it was recorded at.
1139 const double tempo = loop.empty() ? m_tempo : m_loop_tempo;
1140 return m_rate * 60. / std::max(1., tempo) * 4. * inputs.grid.value.value;
1141}
1142
1144inline int64_t Node::pull(int64_t pos) const noexcept
1145{
1146 const float strength = std::clamp(inputs.strength.value, 0.f, 1.f);
1147 const double grid = grid_frames();
1148 if(strength <= 0.f || grid < 1.)
1149 return pos;
1150 const double target = std::round(pos / grid) * grid;
1151 return int64_t(std::llround(pos + strength * (target - pos)));
1152}
1153
1158inline Node::placed_note
1159Node::place(const Looper::midi_note& n, int64_t period) const noexcept
1160{
1161 const int64_t len = std::max<int64_t>(1, period > 0 ? period : loop.length);
1162 const int64_t length = std::clamp<int64_t>(n.length, 1, len - 1);
1163 const int64_t on = pull(n.start);
1164 int64_t played = length;
1165 if(inputs.quantize_ends.value)
1166 {
1167 played = pull(n.start + length) - on;
1168 const int64_t shortest
1169 = std::max<int64_t>(1, std::min<int64_t>(length, int64_t(grid_frames())));
1170 played = std::clamp<int64_t>(played, shortest, len - 1);
1171 }
1172 int64_t start = on % len;
1173 if(start < 0)
1174 start += len;
1175 return {start, played};
1176}
1177
1180inline void Node::handle_input(
1181 int64_t from, int64_t to, double pos0, bool taking, double rate, bool backwards)
1182{
1183 const Passthrough pt = inputs.passthrough;
1184 const bool echo = taking ? pt != Passthrough::None : pt == Passthrough::Full;
1185 const int in_ch = inputs.in_channel.value;
1186 auto& msgs = inputs.midi.midi_messages;
1187
1188 for(; m_in_index < msgs.size(); m_in_index++)
1189 {
1190 const auto& m = msgs[m_in_index];
1191 const int64_t ts = m.timestamp;
1192 if(ts >= to)
1193 return;
1194
1195 const auto kind = mtk::classify(m);
1196 const int ch = mtk::channel_of(m);
1197 const bool ours = ch > 0 && (in_ch == 0 || ch == in_ch);
1198 if(ours && m.size() > 0)
1199 {
1200 auto& h = m_history[m_history_count++ % history_size];
1201 h.frame = m_now + ts;
1202 h.size = uint8_t(std::min<std::size_t>(3, m.size()));
1203 for(int b = 0; b < h.size; b++)
1204 h.bytes[b] = m.bytes[b];
1205 }
1206
1207 // Where the message falls in the loop. A recording's loop is as long as
1208 // what was recorded so far, so its positions are not wrapped.
1209 // Backwards, pos0 counts from the loop's end, and a message falls on the
1210 // edge of the position the playhead has just gone over: a note played
1211 // there starts on it when the loop goes forwards again.
1212 int64_t pos = -1;
1213 if(pos0 >= 0)
1214 {
1215 pos = int64_t(pos0 + (std::max(ts, from) - from) * rate);
1216 if(backwards && loop.length > 0)
1217 {
1218 pos = (loop.length - pos) % loop.length;
1219 if(pos < 0)
1220 pos += loop.length;
1221 }
1222 else if(transport.actualMode != LoopMode::Record && loop.length > 0)
1223 pos %= loop.length;
1224 }
1225
1226 switch(kind)
1227 {
1228 case mtk::midi_kind::note_on:
1229 if(ours)
1230 {
1231 if(taking && pos >= 0)
1232 record_note_on(pos, m);
1233 else
1234 {
1235 // Full: the oldest note let go makes room, as a key still held
1236 // is recorded whenever it was pressed.
1237 if(m_early_count == int(m_early.size()))
1238 {
1239 auto it = std::find_if(
1240 m_early.begin(), m_early.end(), [](const early_note& e) { return e.off >= 0; });
1241 if(it == m_early.end())
1242 it = m_early.begin();
1243 std::rotate(it, it + 1, m_early.end());
1244 m_early_count--;
1245 }
1246 m_early[m_early_count++]
1247 = {m_now + ts, -1, uint8_t(ch), m.bytes[1], m.bytes[2]};
1248 }
1249 }
1250 if(echo)
1251 note_on(ch, m.bytes[1], m.bytes[2], ts, false);
1252 break;
1253 case mtk::midi_kind::note_off:
1254 if(ours)
1255 {
1256 record_note_off(pos, m);
1257 for(int i = 0; i < m_early_count; i++)
1258 if(m_early[i].channel == ch && m_early[i].pitch == m.bytes[1]
1259 && m_early[i].off < 0)
1260 m_early[i].off = m_now + ts;
1261 }
1262 // A note let go is let go even if the passthrough changed meanwhile.
1263 if(m_sounding[(ch - 1) & 15][m.bytes[1] & 127]
1264 > m_loop_sounding[(ch - 1) & 15][m.bytes[1] & 127])
1265 note_off(ch, m.bytes[1], ts, false);
1266 break;
1267 case mtk::midi_kind::control_change:
1268 case mtk::midi_kind::pitch_bend:
1269 case mtk::midi_kind::channel_pressure:
1270 case mtk::midi_kind::poly_pressure:
1271 if(ours && taking && pos >= 0)
1272 record_control(pos, m, backwards);
1273 if(echo)
1274 emit(ts, m);
1275 break;
1276 default:
1277 if(echo)
1278 emit(ts, m);
1279 break;
1280 }
1281 }
1282}
1283
1284inline void Node::record_control(int64_t pos, const libremidi::message& m, bool backwards)
1285{
1287 const Looper::midi_control c{
1288 .pos = pos,
1289 .status = m.bytes[0],
1290 .data1 = m.size() > 1 ? m.bytes[1] : uint8_t(0),
1291 .data2 = m.size() > 2 ? m.bytes[2] : uint8_t(0),
1292 .pass = 0,
1293 .replaced_by = 0,
1294 .merged = 0};
1295 const uint16_t key = c.key();
1296 last_control* last = nullptr;
1297 for(int i = 0; i < m_last_ctl_count; i++)
1298 if(m_last_ctl[i].key == key)
1299 last = &m_last_ctl[i];
1300
1301 if(!backwards)
1302 loop.add_control(pos, c.status, c.data1, c.data2);
1303 else if(last)
1304 loop.add_control(pos, last->status, last->data1, last->data2);
1305 else
1306 loop.touch(key);
1307
1308 if(!last && m_last_ctl_count < int(m_last_ctl.size()))
1309 last = &m_last_ctl[m_last_ctl_count++];
1310 if(last)
1311 *last = {key, c.status, c.data1, c.data2};
1312}
1313
1316inline void Node::close_backwards(int64_t boundary)
1317{
1318 if(loop.length <= 0 || pass_pending())
1319 return;
1320 boundary %= loop.length;
1321 for(int i = 0; i < m_last_ctl_count; i++)
1322 {
1323 const auto& c = m_last_ctl[i];
1324 loop.add_control(boundary, c.status, c.data1, c.data2);
1325 }
1326}
1327
1328inline void Node::record_note_on(int64_t pos, const libremidi::message& m)
1329{
1331 loop.add_note(pos, mtk::channel_of(m), m.bytes[1], m.bytes[2]);
1332}
1333
1334inline void Node::record_note_off(int64_t pos, const libremidi::message& m)
1335{
1336 auto* n = loop.open_note(mtk::channel_of(m), m.bytes[1]);
1337 if(!n)
1338 return;
1339 if(pos < 0)
1340 pos = transport.playbackPos;
1341 if(transport.actualMode == LoopMode::Record || loop.length <= 0)
1342 n->length = std::max<int64_t>(1, pos - n->start);
1343 else if(m_reverse)
1344 {
1345 // Played while the playhead went backwards: the note-off is where the
1346 // note starts on the loop.
1347 const int64_t on = n->start;
1348 n->start = pos;
1349 n->length = loop.distance(pos, on);
1350 }
1351 else
1352 n->length = loop.distance(n->start, pos);
1353}
1354
1359inline void Node::begin_pass_grace(int64_t pos, int64_t ts)
1360{
1361 const int64_t grace = int64_t(inputs.grace.value * m_rate / 1000.);
1362 const bool echo = inputs.passthrough != Passthrough::None;
1363 for(int i = 0; i < m_early_count; i++)
1364 {
1365 const auto& e = m_early[i];
1366 const bool held = e.off < 0;
1367 if(!held && m_now - e.on > grace)
1368 continue;
1370 if(auto* n = loop.add_note(pos, e.channel, e.pitch, e.velocity); n && !held)
1371 n->length = std::max<int64_t>(1, e.off - e.on);
1372 if(held && echo
1373 && m_sounding[e.channel - 1][e.pitch] <= m_loop_sounding[e.channel - 1][e.pitch])
1374 note_on(e.channel, e.pitch, e.velocity, ts, false);
1375 }
1376 m_early_count = 0;
1377}
1378
1384inline void Node::begin_pass_carried(int64_t k, int64_t ts)
1385{
1386 const int64_t len = loop.length;
1387 const bool from_point
1388 = m_carried_span > 0 && m_carried_span < len && k == m_carried_span;
1389 begin_pass(from_point ? 0 : k);
1390 begin_pass_grace(from_point ? 0 : k, ts);
1391 for(int i = 0; i < m_carried_note_count; i++)
1392 {
1393 const auto& c = m_carried_notes[i];
1394 const int64_t pos = from_point ? c.start % len : k;
1396 if(auto* n = loop.add_note(pos, c.channel, c.pitch, c.velocity); n && c.length >= 0)
1397 n->length = std::max<int64_t>(1, c.length);
1398 }
1399 for(int i = 0; i < m_carried_control_count; i++)
1400 {
1401 const auto& c = m_carried_controls[i];
1403 loop.add_control(from_point ? c.pos % len : k, c.status, c.data1, c.data2);
1404 }
1405 if(from_point)
1406 pass_advanced(double(m_carried_span), false);
1407 m_carried_note_count = 0;
1408 m_carried_control_count = 0;
1409 m_carried_span = 0;
1410}
1411
1412inline int64_t Node::looper_run(int64_t start, int64_t length)
1413{
1414 // The playhead jumped, e.g. a mode change read the loop from its start
1415 // again: the notes the loop was playing would end at the wrong time. So do
1416 // they when it turns round.
1417 if(!loop.empty() && transport.playbackPos % loop.length != m_expected_pos)
1418 {
1419 flush_loop(start);
1420 m_phase = double(transport.playbackPos % loop.length);
1421 }
1422 if(m_reverse != m_was_reverse)
1423 {
1424 flush_loop(start);
1425 if(m_was_reverse && loop.taking && !loop.empty())
1426 close_backwards(int64_t(std::ceil(m_phase - 1e-9)));
1427 m_was_reverse = m_reverse;
1428 }
1429
1430 const LoopMode mode = transport.actualMode;
1431 switch(mode)
1432 {
1433 case LoopMode::Stop:
1434 flush_loop(start);
1435 handle_input(start, start + length, -1, false);
1436 break;
1437
1438 case LoopMode::Play:
1439 advance(start, length, false);
1440 break;
1441
1442 case LoopMode::Record: {
1443 if(!transport.is_taking())
1444 {
1445 flush_loop(start);
1446 loop.begin_recording();
1447 m_switches.soloed.forget();
1448 transport.playbackPos = 0;
1449 m_export_pending = false;
1450 begin_pass_grace(0, start);
1451 }
1452 transport.taking(LoopMode::Record);
1453 const int64_t pos0 = loop.length;
1454 handle_input(start, start + length, double(pos0), true);
1455 loop.length += length;
1456 loop.pass_written = loop.length;
1457 transport.playbackPos = loop.length;
1458 m_phase = 0.;
1459 m_expected_pos = 0;
1460 return length;
1461 }
1462
1463 case LoopMode::Overdub: {
1464 if(loop.empty())
1465 {
1466 handle_input(start, start + length, -1, false);
1467 break;
1468 }
1469 if(!transport.is_taking())
1470 {
1471 const int64_t k = int64_t(m_phase) % loop.length;
1472 begin_pass_carried(k, start);
1473 m_cycle_pending = false;
1474 m_last_ctl_count = 0;
1475 // An extending pass grows the loop by the loop it started over.
1476 m_extend_unit = loop.length;
1477 m_extended = false;
1478 }
1479 transport.taking(LoopMode::Overdub);
1480 advance(start, length, true);
1481 break;
1482 }
1483 }
1484
1485 if(!loop.empty())
1486 {
1487 transport.playbackPos = int64_t(m_phase) % loop.length;
1488 m_expected_pos = transport.playbackPos;
1489 }
1490 return length;
1491}
1492
1495inline double Node::rate() const noexcept
1496{
1497 if(!inputs.follow_tempo.value || loop.empty() || m_loop_tempo <= 0.)
1498 return m_speed;
1499 return std::clamp(m_tempo / m_loop_tempo, 0.05, 20.) * m_speed;
1500}
1501
1504inline void Node::next_cycle_pass(int64_t start)
1505{
1506 close_pass(true);
1507 begin_pass(start);
1508}
1509
1515inline void Node::advance(int64_t start, int64_t length, bool overdub)
1516{
1517 const double r = rate();
1518 const bool back = m_reverse;
1519 // Backwards, the playhead reaches the loop's start, not its end.
1520 const bool extend = overdub && inputs.past_end.value == Looper::PastEnd::Extend && !back;
1521 const bool per_cycle = overdub && inputs.passes.value == Looper::Passes::PerCycle;
1522 // Undoing an extension can leave the playhead past the end of the loop.
1523 const double ph = std::fmod(std::max(0., m_phase), double(loop.length));
1524 double y = back ? double(loop.length) - ph : ph;
1525 if(y >= double(loop.length))
1526 y -= double(loop.length);
1527 double out = double(start);
1528 const double out_end = double(start + length);
1529
1530 while(out < out_end - 1e-9)
1531 {
1532 const double L = double(loop.length);
1533 const double b = std::min(L, y + (out_end - out) * r);
1534 const double seg_out = (b - y) / r;
1535 if(overdub && std::exchange(m_cycle_pending, false))
1536 next_cycle_pass(back ? 0 : int64_t(y));
1537
1538 if(back)
1539 play_range_reverse(y, b, out, r);
1540 else
1541 play_range(y, b, out, r);
1542 // The input of the frames this part of the loop covers.
1543 const int64_t from = int64_t(std::ceil(out - 1e-9));
1544 const int64_t to = b >= L ? int64_t(std::ceil(out + seg_out - 1e-9))
1545 : int64_t(std::ceil(out_end - 1e-9));
1546 handle_input(from, to, y + (from - out) * r, overdub, r, back);
1547 if(overdub)
1548 pass_advanced(b - y, back);
1549
1550 out += seg_out;
1551 y = b;
1552 if(y >= L - 1e-9)
1553 {
1554 if(extend
1555 && loop.extend(m_extend_unit, int64_t(max_loop_seconds * m_rate), !pass_pending()))
1556 {
1557 // A cycle is the loop's length when the overdub started: the loop
1558 // growing by one is a cycle gone round as well.
1559 m_extended = true;
1560 if(per_cycle)
1561 m_cycle_pending = true;
1562 }
1563 else
1564 {
1565 y -= L;
1566 if(y < 0.)
1567 y = 0.;
1568 wrap_playing(loop.length, int64_t(out));
1569 if(per_cycle)
1570 {
1571 // The pass is over at the loop's start.
1572 if(back)
1573 close_backwards(0);
1574 m_cycle_pending = true;
1575 }
1576 }
1577 }
1578 }
1579 // Input on the last frame, when the span ended on the loop's end.
1580 if(!overdub)
1581 handle_input(start + length, start + length, -1, false);
1582 if(back)
1583 {
1584 y = double(loop.length) - y;
1585 if(y >= double(loop.length))
1586 y -= double(loop.length);
1587 }
1588 m_phase = y;
1589}
1590
1593inline void Node::play_range(double a, double b, double out0, double r)
1594{
1595 const int64_t len = loop.length;
1596 const auto mode = inputs.overdub_mode.value;
1597 const bool truncate = inputs.held.value == HeldNotes::Truncate;
1598 auto frame = [&](int64_t q) { return int64_t(out0 + (q - a) / r); };
1599 // The whole positions in [a; b[.
1600 const int64_t lo = int64_t(std::ceil(a));
1601 const int64_t hi = std::min(len, int64_t(std::ceil(b)));
1602 // The repeats first + i * period of the loop that fall in [from; to[: a
1603 // long loop over a short pass is not gone through repeat by repeat.
1604 auto repeats = [len](int64_t first, int64_t period, int64_t from, int64_t to, auto f) {
1605 from = std::max(from, first);
1606 to = std::min(to, len);
1607 if(from >= to)
1608 return;
1609 for(int64_t q = first + (from - first + period - 1) / period * period; q < to;
1610 q += period)
1611 f(q);
1612 };
1613
1614 int count = 0;
1615 auto add = [&](int64_t pos, int64_t end, int note) {
1616 if(count < int(m_range.size()))
1617 m_range[count++] = {pos, end, note};
1618 };
1619 for(int i = 0; i < loop.note_count; i++)
1620 {
1621 const auto& n = loop.notes[i];
1622 // A note still held on the input is heard from the input: playing it
1623 // again each round would start it with nothing to end it. A note an
1624 // overdub replaces may be sounding when it is replaced: it ends where
1625 // it was going to.
1626 if(n.length < 0 || n.pass >= loop.count || !loop.heard(n, mode))
1627 continue;
1628
1629 // A pass shorter than the loop plays again over the rest of it.
1630 const int64_t period = std::clamp<int64_t>(loop.period_of(n.pass), 1, len);
1631 const auto [first, length] = place(n, period);
1632 // A note held past the loop's end wraps to its start, or, truncated,
1633 // ends on the loop's end, which is where the next round starts.
1634 repeats(first, period, lo, hi, [&](int64_t on) {
1635 add(on, truncate ? std::min(on + length, len) : on + length, i);
1636 });
1637 }
1638 play_notes(count, lo, hi, frame);
1639
1640 for(int i = 0; i < loop.control_count; i++)
1641 {
1642 const auto& c = loop.controls[i];
1643 if(!loop.heard(c))
1644 continue;
1645 const int64_t period = std::clamp<int64_t>(loop.period_of(c.pass), 1, len);
1646 repeats(c.pos % period, period, lo, hi, [&](int64_t q) {
1647 libremidi::message m;
1648 const uint8_t kind = c.status & 0xF0;
1649 const int ch = m_out_channel > 0 ? m_out_channel : (c.status & 0x0F) + 1;
1650 const uint8_t status = uint8_t(kind | ((ch - 1) & 0x0F));
1651 if(kind == 0xD0)
1652 m = libremidi::message{status, c.data1};
1653 else
1654 m = libremidi::message{status, c.data1, c.data2};
1655 emit(frame(q), m);
1656 });
1657 }
1658}
1659
1666inline void Node::play_range_reverse(double a, double b, double out0, double r)
1667{
1668 const int64_t len = loop.length;
1669 const auto mode = inputs.overdub_mode.value;
1670 const bool truncate = inputs.held.value == HeldNotes::Truncate;
1671 auto frame = [&](int64_t q) { return int64_t(out0 + (q - a) / r); };
1672 const int64_t lo = int64_t(std::ceil(a));
1673 const int64_t hi = std::min(len, int64_t(std::ceil(b)));
1674 auto repeats = [len](int64_t first, int64_t period, int64_t from, int64_t to, auto f) {
1675 from = std::max(from, first);
1676 to = std::min(to, len);
1677 if(from >= to)
1678 return;
1679 for(int64_t q = first + (from - first + period - 1) / period * period; q < to;
1680 q += period)
1681 f(q);
1682 };
1683 // A pattern repeats the same way backwards only over a whole number of its
1684 // periods; a pass that does not fit is played once.
1685 auto period_of = [&](int pass) {
1686 const int64_t p = std::clamp<int64_t>(loop.period_of(pass), 1, len);
1687 return len % p == 0 ? p : len;
1688 };
1689 auto mirrored = [](int64_t pos, int64_t period) {
1690 int64_t m = (-pos) % period;
1691 if(m < 0)
1692 m += period;
1693 return m;
1694 };
1695
1696 int count = 0;
1697 auto add = [&](int64_t pos, int64_t end, int note) {
1698 if(count < int(m_range.size()))
1699 m_range[count++] = {pos, end, note};
1700 };
1701 for(int i = 0; i < loop.note_count; i++)
1702 {
1703 const auto& n = loop.notes[i];
1704 if(n.length < 0 || n.pass >= loop.count || !loop.heard(n, mode))
1705 continue;
1706 const int64_t period = period_of(n.pass);
1707 const auto [start, length] = place(n, period);
1708 const int64_t first = mirrored(start + length, period);
1709 if(!truncate)
1710 {
1711 repeats(first, period, lo, hi, [&](int64_t on) { add(on, on + length, i); });
1712 }
1713 else
1714 {
1715 // Forwards, a note held across the loop's end ends with it: what is
1716 // heard of it is its start. Backwards, that is from the loop's start
1717 // to where the note started.
1718 repeats(first, period, lo, std::min(hi, len - length + 1), [&](int64_t on) {
1719 add(on, on + length, i);
1720 });
1721 if(lo <= 0 && 0 < hi)
1722 repeats(first, period, len - length + 1, len, [&](int64_t on) {
1723 add(0, on + length - len, i);
1724 });
1725 }
1726 }
1727 play_notes(count, lo, hi, frame);
1728
1729 update_reverse_controls();
1730 for(int i = 0; i < loop.control_count; i++)
1731 {
1732 const auto& c = loop.controls[i];
1733 if(m_ctl_prev[i] < 0)
1734 continue;
1735 const auto& v = loop.controls[m_ctl_prev[i]];
1736 const int64_t period = period_of(c.pass);
1737 repeats(mirrored(c.pos, period), period, lo, hi, [&](int64_t q) {
1738 libremidi::message m;
1739 const uint8_t kind = v.status & 0xF0;
1740 const int ch = m_out_channel > 0 ? m_out_channel : (v.status & 0x0F) + 1;
1741 const uint8_t status = uint8_t(kind | ((ch - 1) & 0x0F));
1742 if(kind == 0xD0)
1743 m = libremidi::message{status, v.data1};
1744 else
1745 m = libremidi::message{status, v.data1, v.data2};
1746 emit(frame(q), m);
1747 });
1748 }
1749}
1750
1753inline void Node::update_reverse_controls()
1754{
1755 const std::array<int64_t, 6> signature{
1756 int64_t(loop.edits), loop.control_count, loop.count,
1757 loop.taking ? loop.pass_written : -1, loop.pass_start, loop.length};
1758 if(signature == m_ctl_signature)
1759 return;
1760 m_ctl_signature = signature;
1761
1762 int n = 0;
1763 for(int i = 0; i < loop.control_count; i++)
1764 {
1765 m_ctl_prev[i] = -1;
1766 if(loop.heard(loop.controls[i]))
1767 m_ctl_sorted[n++] = uint16_t(i);
1768 }
1769 const auto& ctl = loop.controls;
1770 std::sort(m_ctl_sorted.begin(), m_ctl_sorted.begin() + n, [&](uint16_t a, uint16_t b) {
1771 const auto ka = ctl[a].key(), kb = ctl[b].key();
1772 if(ka != kb)
1773 return ka < kb;
1774 if(ctl[a].pos != ctl[b].pos)
1775 return ctl[a].pos < ctl[b].pos;
1776 return a < b;
1777 });
1778 for(int g = 0; g < n;)
1779 {
1780 int e = g + 1;
1781 while(e < n && ctl[m_ctl_sorted[e]].key() == ctl[m_ctl_sorted[g]].key())
1782 e++;
1783 for(int j = g; j < e; j++)
1784 m_ctl_prev[m_ctl_sorted[j]] = int16_t(m_ctl_sorted[j == g ? e - 1 : j - 1]);
1785 g = e;
1786 }
1787}
1788
1790inline void Node::trim_top_pass(int64_t end)
1791{
1792 const int pass = loop.count - 1;
1793 loop.note_count = int(
1794 std::remove_if(
1795 loop.notes.begin(), loop.notes.begin() + loop.note_count,
1796 [=](const Looper::midi_note& n) { return n.pass == pass && n.start >= end; })
1797 - loop.notes.begin());
1798 loop.control_count = int(
1799 std::remove_if(
1800 loop.controls.begin(), loop.controls.begin() + loop.control_count,
1801 [=](const Looper::midi_control& c) { return c.pass == pass && c.pos >= end; })
1802 - loop.controls.begin());
1803 loop.edits++;
1804 // The pass went from where it started to the old end, and no further.
1805 loop.pass_written = std::clamp<int64_t>(end - loop.pass_start, 0, loop.pass_written);
1806 loop.pass_progress = double(loop.pass_written);
1807}
1808
1811inline void Node::start_export(int passes, bool open, bool)
1812{
1813 m_export_loop.assign(loop);
1814 if(!open && passes < loop.count)
1815 {
1816 m_export_loop.count = passes;
1817 m_export_loop.total = passes;
1818 m_export_loop.taking = false;
1819 }
1820 m_export_pos = loop.length > 0 ? transport.playbackPos % loop.length : 0;
1821 write_export_item(m_export_first);
1822}
1823
1826inline void Node::write_export_item(int item)
1827{
1828 m_export_item = item;
1829 m_writing = true;
1830 fill_export(m_export_loop, item, m_export_pos, *export_data);
1831
1832 Looper::midi_take_job job;
1833 job.writer = writer;
1834 job.data = export_data;
1835 job.slot = m_export_slot;
1836 job.number = m_export_number;
1837 job.layer = item;
1838 worker.request(std::move(job));
1839}
1840
1845inline void Node::write_stop_take(bool recording, double date, int64_t pos)
1846{
1847 auto& s = *stop_take;
1848 if(!writer || loop.empty() || s.in_use)
1849 return;
1850 s.loop.assign(loop);
1851 for(int i = 0; i < s.loop.note_count; i++)
1852 {
1853 auto& n = s.loop.notes[i];
1854 if(n.length < 0)
1855 n.length = s.loop.distance(n.start, pos);
1856 const auto placed
1857 = place(n, std::clamp<int64_t>(s.loop.period_of(n.pass), 1, s.loop.length));
1858 n.start = placed.start;
1859 n.length = placed.length;
1860 }
1861 s.mode = inputs.overdub_mode.value;
1862 s.rate = m_rate;
1863 s.tempo = recording ? m_tempo : m_loop_tempo;
1864 s.meter_numerator = m_signature.upper;
1865 s.meter_denominator = m_signature.lower;
1866 s.wrap = inputs.held.value == HeldNotes::Wrap;
1867 prepare_stop_notice(s.notice, date);
1868
1869 Looper::midi_take_job job;
1870 job.writer = writer;
1871 job.stop = stop_take;
1872 job.slot = claim_take_slot();
1873 job.alone = true;
1874 s.in_use = true;
1875 worker.request(std::move(job));
1876}
1877
1880inline void Node::fill_export(
1881 const Looper::midi_loop& snap, int item, int64_t pos, Looper::midi_export& e) const
1882{
1884 snap, item, pos, inputs.overdub_mode.value,
1885 [this](const Looper::midi_note& n, int64_t period) { return place(n, period); }, e);
1886 e.rate = m_rate;
1887 e.tempo = m_export_tempo;
1888 e.meter_numerator = m_export_signature.upper;
1889 e.meter_denominator = m_export_signature.lower;
1890 e.wrap = inputs.held.value == HeldNotes::Wrap;
1891}
1892
1893inline void Node::send_view(int64_t frames)
1894{
1895 if(!view_due(frames))
1896 return;
1897 fill_message(m_view);
1898 send_message(m_view);
1899}
1900
1902inline void Node::fill_message(Looper::view_message& v)
1903{
1904 fill_view(v);
1905 v.midi = true;
1906 v.lanes = std::min(loop.count, Looper::view_message::max_lanes);
1907 v.max_seconds = 0.f;
1908
1909 // One lane per pass, the oldest passes in the first one when there are more
1910 // passes than lanes, as the audio looper's.
1911 constexpr int max_lanes = Looper::view_message::max_lanes;
1912 constexpr int max_shown = Looper::view_message::max_notes;
1913 const int lanes = v.lanes;
1914 const int merged = loop.count - lanes;
1915 const auto mode = inputs.overdub_mode.value;
1916 const int64_t len = std::max<int64_t>(1, loop.length);
1917 auto lane_of = [&](int pass) { return std::max(0, pass - merged); };
1918 // A pass shorter than the loop is drawn each time it plays.
1919 auto period_of = [&](const Looper::midi_note& n) {
1920 return std::max<int64_t>(1, std::min<int64_t>(loop.period_of(n.pass), len));
1921 };
1922
1923 // Each lane gets as many of the notes as it has, up to an even share of
1924 // what the message holds; the share a lane does not use goes to the others.
1925 std::array<int64_t, max_lanes> wanted{};
1926 for(int i = 0; i < loop.note_count; i++)
1927 {
1928 const auto& n = loop.notes[i];
1929 if(n.pass >= loop.count)
1930 continue;
1931 const int64_t period = period_of(n);
1932 wanted[lane_of(n.pass)] += (len - 1 - n.start % period) / period + 1;
1933 }
1934 std::array<int, max_lanes> quota{};
1935 {
1936 std::array<bool, max_lanes> settled{};
1937 int left = max_shown;
1938 for(bool changed = true; changed;)
1939 {
1940 changed = false;
1941 int open = 0;
1942 for(int l = 0; l < lanes; l++)
1943 open += !settled[l];
1944 if(open == 0)
1945 break;
1946 const int share = left / open;
1947 for(int l = 0; l < lanes; l++)
1948 {
1949 if(!settled[l] && wanted[l] <= share)
1950 {
1951 quota[l] = int(wanted[l]);
1952 left -= quota[l];
1953 settled[l] = true;
1954 changed = true;
1955 }
1956 }
1957 if(!changed)
1958 for(int l = 0; l < lanes; l++)
1959 if(!settled[l])
1960 quota[l] = share;
1961 }
1962 }
1963
1964 // The notes heard first, so that a lane over its share keeps those.
1965 int shown = 0;
1966 std::array<int, max_lanes> used{};
1967 for(int heard = 1; heard >= 0; heard--)
1968 {
1969 for(int i = 0; i < loop.note_count; i++)
1970 {
1971 const auto& n = loop.notes[i];
1972 if(n.pass >= loop.count || loop.heard(n, mode) != bool(heard))
1973 continue;
1974 const int lane = lane_of(n.pass);
1975 // A key still held goes from where it was pressed to the playhead, which
1976 // is before it when the loop plays backwards.
1977 const int64_t head = transport.playbackPos % len;
1978 int64_t first = n.start;
1979 int64_t l = std::min(n.length, len - 1);
1980 if(n.length < 0)
1981 {
1982 l = m_reverse ? loop.distance(head, n.start) : loop.distance(n.start, head);
1983 if(m_reverse)
1984 first = head;
1985 }
1986 const int64_t period = period_of(n);
1987 for(int64_t on = first % period; on < len && used[lane] < quota[lane];
1988 on += period)
1989 {
1990 auto& d = v.note[shown++];
1991 used[lane]++;
1992 d.start = float(on) / float(len);
1993 d.end = float((on + l) % len) / float(len);
1994 d.pitch = n.pitch;
1995 d.lane = uint8_t(lane);
1996 d.velocity = n.velocity;
1997 d.heard = uint8_t(heard);
1998 }
1999 }
2000 }
2001 v.notes = shown;
2002}
2003}
PastEnd
What an overdub does at the end of the loop.
Definition score-plugin-fx/Fx/Looper/Transport.hpp:197
@ Extend
Makes the loop longer by the loop's length, the passes under it repeating.
@ Wrap
Goes round the loop again.
TakeFiles
Definition LooperView.hpp:46
HeldNotes
A note still held at the end of the loop.
Definition MidiLoop.hpp:25
void fill_midi_export(const midi_loop &snap, int item, int64_t pos, OverdubMode mode, Place &&place, midi_export &e)
Definition MidiTake.hpp:138
Passthrough
Definition score-plugin-fx/Fx/Looper/Transport.hpp:189
Passes
What an overdub that goes round the loop several times leaves.
Definition score-plugin-fx/Fx/Looper/Transport.hpp:131
@ PerCycle
A pass for each time it goes round, cut on the loop's start.
@ Whole
One pass, from when the overdub starts to when it stops.
OverdubMode
What the MIDI looper does with notes of earlier passes under an overdub.
Definition MidiLoop.hpp:17
STL namespace.
Definition Switches.hpp:344
Definition Switches.hpp:161
Definition Switches.hpp:300
Definition Switches.hpp:458
An enumeration shown as a combobox: compact where the choices are many.
Definition Switches.hpp:59
Definition Switches.hpp:430
Definition LooperView.hpp:64
Definition Switches.hpp:453
Definition Switches.hpp:50
Definition Switches.hpp:828
Definition LooperNode.hpp:169
int64_t m_extend_unit
Definition LooperNode.hpp:398
void stop_score()
Definition LooperNode.hpp:990
void begin_pass(int64_t start)
Definition LooperNode.hpp:677
bool m_cycle_pending
Definition LooperNode.hpp:402
void material_arriving()
Definition LooperNode.hpp:237
void close_pass(bool cycle=false)
Definition LooperNode.hpp:705
void pass_advanced(double frames, bool backwards)
The pass being taken in went over frames more frames of the loop.
Definition LooperNode.hpp:251
bool pass_pending() const noexcept
Definition LooperNode.hpp:219
void view_request(const Looper::erase_request &r) noexcept
From the view: applied at the start of the next tick.
Definition LooperNode.hpp:285
double m_speed
The playhead goes at m_speed, backwards when m_reverse is set.
Definition LooperNode.hpp:389
double m_loop_tempo
Tempo the loop was recorded at: the loop keeps its own speed.
Definition LooperNode.hpp:395
void capture(const tick &tk)
Definition LooperNode.hpp:888
Definition LooperView.hpp:1331
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
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
Definition MidiTake.hpp:200
Definition Switches.hpp:420
Definition Switches.hpp:390
Definition Switches.hpp:415
Definition Switches.hpp:274
Definition Switches.hpp:372
static constexpr int max_layers
Most layers kept: Max layers is at most this.
Definition PassStack.hpp:83
int64_t length
Frames in the loop.
Definition PassStack.hpp:94
int count
Passes heard.
Definition PassStack.hpp:90
bool extend(int64_t unit, int64_t max_length, bool top_open=true) noexcept
Definition PassStack.hpp:227
Definition Switches.hpp:380
Definition Switches.hpp:358
Definition Switches.hpp:407
The grid a change of mode waits for. The Quantif of older scores.
Definition Switches.hpp:232
Definition Switches.hpp:318
Definition Switches.hpp:363
Definition Switches.hpp:398
Definition Switches.hpp:448
Definition Switches.hpp:439
Definition score-plugin-fx/Fx/Looper/Transport.hpp:166
Definition Switches.hpp:850
Definition Switches.hpp:194
Definition Switches.hpp:329
Definition Switches.hpp:385
Definition Switches.hpp:778
void merge(int at, bool undoable=false) noexcept
Definition PassStack.hpp:1032
void begin_overdub(int64_t start, int max=stack::max_layers, bool oldest=true, int replace=-1) noexcept
Definition PassStack.hpp:1004
uint64_t edits
Changes each time what is heard of the loop may have changed.
Definition PassStack.hpp:889
bool pass_backwards
Definition PassStack.hpp:899
double pass_progress
Definition PassStack.hpp:896
int64_t distance(int64_t from, int64_t to) const noexcept
Definition PassStack.hpp:1101
static std::shared_ptr< take_writer > make(const score::DocumentContext &ctx, std::string_view prefix, std::string_view extension)
Main thread only.
Definition TakeWriter.hpp:70
void taking(LoopMode m) noexcept
Called by the looper whenever it records or overdubs.
Definition score-plugin-fx/Fx/Looper/Transport.hpp:336
Definition LooperView.hpp:77
Definition MidiLooper.hpp:48
Definition MidiLooper.hpp:540
Definition MidiLooper.hpp:447
Definition MidiLooper.hpp:323
static bool must_run(const Looper::midi_take_job &job) noexcept
The take the score's stop leaves is written once the looper is gone.
Definition MidiLooper.hpp:326
Definition MidiLooper.hpp:24
~Node()
Definition MidiLooper.hpp:373
static constexpr double max_loop_seconds
Longest loop an extending overdub makes, as the audio looper's longest.
Definition MidiLooper.hpp:427
void stop()
Definition MidiLooper.hpp:370
std::shared_ptr< Looper::midi_stop_take > stop_take
Definition MidiLooper.hpp:358
void process_message(const Looper::erase_request &r)
From the view: applied at the start of the next tick.
Definition MidiLooper.hpp:342
static constexpr std::array< int, 1 > removed_inlets() noexcept
Definition MidiLooper.hpp:40
std::function< void(Looper::view_message)> send_message_from_worker
Definition MidiLooper.hpp:339
Definition MIDISync.hpp:149
Definition DocumentContext.hpp:18