Loading...
Searching...
No Matches
ValueLooper.hpp
1#pragma once
2#include <Fx/Looper/LooperNode.hpp>
3#include <Fx/Looper/ValueLoop.hpp>
4#include <Fx/Looper/ValueTake.hpp>
5#include <Fx/Types.hpp>
6
7#include <ossia/dataflow/exec_state_facade.hpp>
8#include <ossia/dataflow/token_request.hpp>
9#include <ossia/dataflow/value_port.hpp>
10
11#include <halp/controls.hpp>
12#include <halp/layout.hpp>
13#include <halp/meta.hpp>
14
15#include <functional>
16#include <limits>
17#include <memory>
18
19namespace Nodes::ValueLooper
20{
22{
23 halp_meta(name, "Looper (values)")
24 halp_meta(c_name, "value_looper")
25 halp_meta(category, "Control/Mappings")
26 halp_meta(author, "ossia score")
27 halp_meta(manual_url, "https://ossia.io/score-docs/processes/value-looper.html")
28 halp_meta(
29 description,
30 "Loop a stream of values in passes: numbers and vectors held as a "
31 "curve, impulses fired where they came, and turn the loop into "
32 "automations of the timeline")
33 halp_meta(uuid, "399796c5-9e86-4a00-8671-3e759ad6fb33");
34 halp_meta(recommended_height, 200);
35 halp_flag(process_exec);
36
37 using LoopMode = Looper::LoopMode;
39
41 static constexpr int max_keys = 1;
44 static constexpr double input_hold_seconds = 0.2;
45
46 struct ins
47 {
48 ossia_port<"in", ossia::value_port> values;
49
50 struct : halp::enum_t<LoopMode, "Mode">
51 {
52 using halp::enum_t<LoopMode, "Mode">::operator=;
53 halp_meta(
54 description,
55 "What the looper is doing, or about to do on the grid: playing the "
56 "loop, recording it, overdubbing on top of it, or nothing. It follows "
57 "every change, by an action or a recording's end. Record on a loop "
58 "overdubs it: Clear it first to record a new one. Overdub on an "
59 "empty loop records it, then overdubs.")
61 std::function<void(std::string_view)> update_controller;
63 void update(Node& n) noexcept { n.loop_arrived(); }
64 } mode;
65
67
68 struct : Looper::enum_combobox<"Passthrough", Passthrough, Passthrough::Full>
69 {
70 using enum_combobox::operator=;
71 halp_meta(
72 description,
73 "Whether the values that come in go out as well as the loop: never, "
74 "only while recording or overdubbing, or always. Always, a value "
75 "that comes in wins over the loop while the input moves; once it "
76 "stops, the loop takes over again from the value it holds there.")
77 } passthrough;
78
80
81 struct : halp::spinbox_i32<"Record bars", halp::irange{0, 64, 4}>
82 {
83 halp_meta(
84 description,
85 "How long a recording runs before After record takes over. Zero "
86 "leaves it to be ended by hand.")
87 } postaction_bars;
88
90
91 struct : halp::impulse_button<"Undo">
92 {
93 halp_meta(description, "Undoes the last pass, erase or mute")
94 } undo;
95 struct : halp::impulse_button<"Redo">
96 {
97 halp_meta(description, "Redoes what Undo took back")
98 } redo;
99 struct : halp::impulse_button<"Clear">
100 {
101 halp_meta(description, "Empties the loop and forgets every pass")
102 } clear;
103
104 struct : halp::spinbox_i32<"Late stop (ms)", halp::irange{0, 500, 120}>
105 {
106 halp_meta(
107 description,
108 "A recording asked to stop this long after a point of the grid ends "
109 "on that point.")
110 } late;
111
112 struct : halp::spinbox_i32<"Capture bars", halp::irange{1, 64, 4}>
113 {
114 halp_meta(
115 description,
116 "How many bars Capture makes a loop of when there is none. Over a "
117 "loop, Capture takes one round of it, whatever this says.")
118 } capture_bars;
119
120 struct : halp::impulse_button<"Capture">
121 {
122 halp_meta(
123 description,
124 "Takes the values that came in lately, recorded or not, without having "
125 "to press Record first. With no loop, the last Capture bars become "
126 "the loop, starting from the value held before them, played at once. "
127 "Over a loop, the values of its last round become a new layer, where "
128 "they came in the loop. Undo takes it out. Pressed just after a grid "
129 "point, it ends there, as Late says.")
130 } capture;
131
132 struct : Looper::enum_combobox<"Export", Looper::TakeFiles, Looper::TakeFiles::Mix>
133 {
134 using enum_combobox::operator=;
135 halp_meta(
136 description,
137 "What Send, or a drag from it, places: what is heard of the loop "
138 "(Mix), each pass in a file of its own (Layers), or both, a take of "
139 "numbers, vectors or lists of numbers as an automation for each "
140 "number, a box for each file. A take written as a pass closes has "
141 "these files; one Send or a drag asks for, or the score's stop "
142 "leaves, has them all, so that Export can still be changed once the "
143 "score is stopped. Dragging a lane places its layer alone. A take "
144 "with impulses, text or maps is kept as a file only.")
145 } exported;
146
148 {
149 using enum_combobox::operator=;
150 halp_meta(
151 description,
152 "What an overdub does at the end of the loop: go round it again "
153 "(Wrap), or make the loop longer by its own length each time, the "
154 "passes under it playing again (Extend). An extended loop ends when "
155 "overdubbing stops.")
156 } past_end;
157
158 struct : halp::toggle<"Follow tempo", halp::default_on_toggle>
159 {
160 halp_meta(
161 description,
162 "Plays the loop at the tempo of the score: it keeps to the bars when "
163 "the tempo changes. Off, it keeps the speed it was recorded at.")
164 } follow_tempo;
165
166 Looper::erase_layer_button erase_layer;
167
168 struct : halp::spinbox_i32<"Layer", halp::irange{1, 32, 1}>
169 {
170 halp_meta(
171 description,
172 "The layer Mute layer, Erase layer and Solo layer act on, counting "
173 "from the oldest one: the lane of that number from the top while "
174 "there are no more layers than lanes, and the file of that number of "
175 "a take written as layers. It moves to each new layer as it is "
176 "added.")
177 std::function<void(int)> update_controller;
178 } layer;
179
180 struct : halp::toggle<"Reverse">
181 {
182 halp_meta(
183 description,
184 "Plays the loop backwards, from where the playhead is: a value goes "
185 "back to the one it had before, impulses fire where they were. An "
186 "overdub records where the playhead goes, as on tape: what is played "
187 "over the reversed loop plays reversed once it goes forwards again.")
188 std::function<void(bool)> update_controller;
189 } reverse;
190
191 struct : Looper::speed_selector<"Speed">
192 {
193 halp_meta(
194 description,
195 "Plays the loop slower or faster. An overdub records where the "
196 "playhead goes: what is played at x1/2 plays twice as fast at x1. "
197 "The grid, the bars and the loop's length stay in the score's time; "
198 "combines with Follow tempo.")
199 std::function<void(float)> update_controller;
200 } speed;
201
203 {
204 using enum_combobox::operator=;
205 struct range
206 {
207 std::string_view values[2] = {"Whole", "Per cycle"};
208 Looper::Passes init = Looper::Passes::Whole;
209 };
210 halp_meta(
211 description,
212 "What an overdub that goes round the loop several times leaves: one "
213 "layer (Whole), each round writing over the one before, or a layer "
214 "for each time round (Per cycle), cut on the loop's start, to keep "
215 "the best one with Solo layer or Mute layer, erase the others, or "
216 "splice them in the timeline from a take written as layers. With "
217 "Past the end on Extend, a cut is made each time the loop grows.")
218 } passes;
219
220 Looper::mute_layer_button mute_layer;
221
222 // The Switches tab: actions for footswitches.
223 Looper::rec_play_dub_button rec_play_dub;
224 Looper::after_overdub_selector after_overdub;
225 Looper::play_stop_button play_stop;
229 Looper::new_layer_button new_layer;
232 Looper::retrigger_button retrigger;
233 Looper::previous_layer_button previous_layer;
234 Looper::next_layer_button next_layer;
235 Looper::solo_layer_button solo_layer;
236 Looper::reverse_button reverse_toggle;
237 Looper::half_speed_button half_speed;
238 Looper::double_speed_button double_speed;
239 Looper::at_max_layers_selector at_max_layers;
240
241 struct
242 : halp::spinbox_i32<"Max layers", halp::irange{2, Looper::value_loop::max_layers, 8}>
243 {
244 halp_meta(
245 description,
246 "Layers kept apart, for undo, mute, solo and erase. What a pass does "
247 "past it is what At max layers says.")
248 } max_layers;
249 } inputs;
250
251 struct
252 {
253 ossia_port<"out", ossia::value_port> values;
254 struct : halp::val_port<"Phase", float>
255 {
256 halp_meta(description, "Position in the loop, from 0 to 1")
257 } phase;
258 } outputs;
259
260 struct worker_t
261 {
262 std::function<void(Looper::value_take_job)> request;
264 static bool must_run(const Looper::value_take_job& job) noexcept { return job.alone; }
265 static std::function<void(Node&)> work(Looper::value_take_job&& job)
266 {
267 const bool ok = job.run();
268 return [slot = job.slot, number = job.number, alone = job.alone, layer = job.layer,
269 placeable = job.components > 0, nothing = job.nothing, ok](Node& self) {
270 // A take goes in the timeline when each of its files that holds
271 // something does: a layer whose values are all replaced holds none.
272 const bool first = alone || layer == self.m_export_first;
273 if(first)
274 self.m_written_placeable = placeable;
275 else if(!nothing)
276 self.m_written_placeable = self.m_written_placeable && placeable;
277 self.take_written(slot, number, ok, alone);
278 };
279 }
280 } worker;
281
282 std::function<void(Looper::view_message)> send_message;
286
289
290 const score::DocumentContext* ossia_document_context{};
291 ossia::exec_state_facade ossia_state;
292
294 std::shared_ptr<Looper::take_writer> writer;
295 std::shared_ptr<Looper::value_export> export_data;
298 std::shared_ptr<Looper::value_stop_take> stop_take;
299
300 Node()
301 {
302 export_data = std::make_shared<Looper::value_export>();
303 stop_take = std::make_shared<Looper::value_stop_take>();
304 }
305
306 void prepare(halp::setup)
307 {
308 if(ossia_document_context)
309 writer = Looper::take_writer::make(*ossia_document_context, "Value looper", "values");
310 if(writer)
311 writer->tell_views = send_message_from_worker;
312 }
313
316 void stop() { m_stop_requested = true; }
320 {
321 if(std::exchange(m_stop_requested, false))
322 stop_score();
323 }
324
325 int64_t looper_run(int64_t start, int64_t length);
326 void looper_trim_recording(int64_t frames);
328 static constexpr double max_loop_seconds = 3600.;
329
330 void operator()(const tick& tk)
331 {
332 m_out_count = 0;
333 const auto timings = ossia_state.timings(tk);
334 m_tick_frames = timings.length;
335 m_tick_start = timings.start_sample;
336 if(std::exchange(m_stop_requested, false))
337 stop_score();
338 if(tk.date == tk.prev_date)
339 {
340 push_outputs();
341 return;
342 }
343 run_tick(tk);
344 m_now += m_tick_frames;
345 send_view(m_tick_frames);
346 push_outputs();
347 }
348
349 struct ui
350 {
351 halp_meta(layout, halp::layouts::hbox)
352 halp_meta(background, halp::colors::background_dark)
353 struct
354 {
355 halp_meta(layout, halp::layouts::vbox)
356 struct
357 {
358 halp_meta(layout, halp::layouts::vbox)
360 struct
361 {
362 halp_meta(layout, halp::layouts::hbox)
363 halp::item<&ins::quantif> quantif;
364 halp::item<&ins::exported> exported;
365 } timing;
366 } transport;
367 struct
368 {
369 halp_meta(name, "Settings")
370 halp_meta(layout, halp::layouts::tabs)
371 halp_meta(background, halp::colors::background_darker)
372 struct
373 {
374 halp_meta(name, "Length")
375 halp_meta(layout, halp::layouts::grid)
376 halp_meta(alignment, halp::alignments::left)
377 halp_meta(spacing, 6)
378 halp_meta(columns, 3)
379 halp::item<&ins::after_record> after_record;
380 halp::item<&ins::postaction_bars> bars;
381 halp::item<&ins::late> late;
382 halp::item<&ins::capture> capture;
383 halp::item<&ins::capture_bars> capture_bars;
384 } length;
385 struct
386 {
387 halp_meta(name, "Play")
388 halp_meta(layout, halp::layouts::grid)
389 halp_meta(alignment, halp::alignments::left)
390 halp_meta(spacing, 6)
391 halp_meta(columns, 2)
392 halp::item<&ins::start_from> start_from;
393 halp::item<&ins::passthrough> passthrough;
394 } play;
398 struct
399 {
400 halp_meta(name, "Overdub")
401 halp_meta(layout, halp::layouts::grid)
402 halp_meta(alignment, halp::alignments::left)
403 halp_meta(spacing, 6)
404 halp_meta(columns, 2)
405 halp::item<&ins::past_end> past_end;
406 halp::item<&ins::passes> passes;
407 halp::item<&ins::max_layers> max_layers;
408 halp::item<&ins::at_max_layers> at_max_layers;
409 } overdub;
410 } settings;
411 } controls;
412
413 struct
414 {
415 halp_meta(layout, halp::layouts::container)
416 halp_meta(background, halp::colors::background_mid)
418 } view;
419
420 struct bus
421 {
422 void init(ui& self)
423 {
424 self.view.view.erase = [this](Looper::erase_request r) {
425 if(send_message)
426 send_message(r);
427 };
428 }
429 static void process_message(ui& self, Looper::view_message msg)
430 {
431 self.view.view.receive(msg);
432 }
433 std::function<void(Looper::erase_request)> send_message;
434 };
435 };
436
437private:
438 friend struct Looper::looper_node<Node>;
439
440 // looper_node's hooks
442 static constexpr bool passes_wait_for_material = true;
443 Looper::value_loop& stack() noexcept { return loop; }
444 const Looper::value_loop& stack() const noexcept { return loop; }
447 void layers_changing() noexcept { }
448 bool busy() const noexcept { return false; }
449 void cleared() noexcept { }
450 void pass_dropped() noexcept
451 {
452 loop.taking = false;
453 loop.touch_count = 0;
454 }
455 void begin_overdub(int64_t start, bool oldest, int replaced)
456 {
457 loop.begin_overdub(start, max_layers(), oldest, replaced);
458 forget_recorded();
459 }
460 bool can_merge_newest() const noexcept { return !loop.taking; }
461 void merge_newest() noexcept { loop.merge(loop.count - 2, true); }
462 bool pass_closing() noexcept { return true; }
464 void pass_closed() { loop.end_pass(); }
465 void tick_started(const tick&);
466 void prepare_loop() noexcept { }
467 void forget_carried() noexcept
468 {
469 for(int i = 0; i < m_carried_count; i++)
470 m_carried[i].value.reset();
471 m_carried_count = 0;
472 m_carried_span = 0;
473 }
474 void tick_finished(const tick&)
475 {
476 // Input past the last span, e.g. on a sub-sample tick.
477 handle_input(m_tick_frames, m_tick_frames + 1, -1, false);
478 }
479 void close_recording(int64_t bars)
480 {
481 if(bars > 0)
482 loop.length = bars;
483 loop.pass_written = loop.length;
484 loop.end_pass();
485 }
486 void close_overdub()
487 {
488 if(loop.pass_backwards && loop.taking && !m_cycle_pending)
489 close_backwards(int64_t(std::ceil(m_phase - 1e-9)));
490 close_pass();
491 }
492 bool cut_after(int64_t end) const noexcept { return loop.taking && loop.pass_start >= end; }
493 void cut_pass_dropped() noexcept { }
494 void trim_top_pass(int64_t end);
495 void head_moved(int64_t pos) noexcept
496 {
497 m_phase = double(pos);
498 m_expected_pos = pos;
499 }
500 double head_y(int64_t len) const noexcept
501 {
502 const double l = double(len);
503 const double ph = std::fmod(std::max(0., m_phase), l);
504 double y = m_reverse ? l - ph : ph;
505 if(y >= l)
506 y -= l;
507 return y;
508 }
509 double rate_for(LoopMode) const noexcept { return rate(); }
510 bool by_quarters() const noexcept { return inputs.follow_tempo.value && m_loop_tempo > 0.; }
512 void set_head(double y, int64_t len) noexcept
513 {
514 double h = m_reverse ? double(len) - y : y;
515 if(h >= double(len))
516 h -= double(len);
517 transport.playbackPos = std::clamp<int64_t>(int64_t(h), 0, len - 1);
518 }
519 int64_t capture_clock() const noexcept { return m_now; }
520 int64_t capture_limit() const noexcept { return std::numeric_limits<int64_t>::max(); }
521 void capture_into(int64_t end, int64_t n);
522 void captured() noexcept { head_moved(transport.playbackPos); }
523 bool capture_layer_ready(const Looper::capture_window& w) const noexcept;
524 void capture_layer_into(const Looper::capture_window& w);
527 template <typename F>
528 void for_each_captured(const Looper::capture_window& w, F&& f) const;
530 void stopping() noexcept
531 {
532 for(auto& k : m_keys)
533 {
534 k.out.reset();
535 k.owned_until = std::numeric_limits<int64_t>::min();
536 }
537 }
538 void close_on_stop(bool recording)
539 {
540 if(recording)
541 loop.end_pass();
542 else
543 close_pass();
544 }
545 bool export_ready() const noexcept { return true; }
546 void start_export(int passes, bool open, bool recording);
547 void continue_export() noexcept { }
548 void export_item(int item) { write_export_item(item); }
549 bool take_cancelled() noexcept { return false; }
550 void take_released() noexcept { }
551 void stop_take_written() noexcept { }
552 const Looper::stop_take_notice& stop_notice() const noexcept { return stop_take->notice; }
554 bool take_placeable() const noexcept { return m_written_placeable; }
555 static constexpr const char* take_extension = "values";
557 static constexpr halp::clip_stretch take_stretch() noexcept
558 {
559 return halp::clip_stretch::none;
560 }
561 bool view_full() const noexcept { return loop.full; }
562 bool view_writing() const noexcept { return m_writing; }
563
564 int max_layers() const noexcept
565 {
566 return std::clamp(inputs.max_layers.value, 2, Looper::value_loop::max_layers);
567 }
568
572 struct key_state
573 {
574 Looper::value_slot in;
575 Looper::value_slot out;
576 int64_t owned_until{std::numeric_limits<int64_t>::min()};
577 Looper::value_slot recorded;
578 };
579 std::array<key_state, max_keys> m_keys{};
580 void forget_recorded() noexcept
581 {
582 for(auto& k : m_keys)
583 k.recorded.reset();
584 }
585
586 // What goes out this tick.
587 static constexpr int max_out = 1024;
588 struct out_value
589 {
590 int64_t ts;
591 Looper::value_slot value;
592 int order;
593 };
594 std::array<out_value, max_out> m_out{};
595 int m_out_count{};
596 void emit(int64_t ts, const ossia::value& v)
597 {
598 if(m_out_count < max_out)
599 {
600 auto& o = m_out[m_out_count++];
601 o.ts = ts;
602 o.value.set(v);
603 }
604 }
607 void send_held(uint16_t key, const Looper::value_slot& v, int64_t ts)
608 {
609 auto& k = m_keys[key];
610 if(v.empty() || m_now + ts < k.owned_until)
611 return;
612 if(!k.out.empty() && k.out.same(v))
613 return;
614 k.out = v;
615 emit(ts, v.get());
616 }
617 void push_outputs();
618
619 // The input of the tick, by time.
620 static constexpr int max_in = 1024;
621 std::array<int, max_in> m_in_order{};
622 int m_in_count{};
623 int m_in_index{};
624 int64_t input_ts(int i) const noexcept;
625
626 // What the loop sends over the span being played, by time: a held value
627 // changing, from point `ref` of the timeline, or an impulse, event `ref`.
628 struct due
629 {
630 int64_t ts;
631 int ref;
632 bool impulse;
633 int order;
634 };
635 static constexpr int max_due = 2048;
636 std::array<due, max_due> m_due{};
637 int m_due_count{};
638 int m_due_index{};
639 void add_due(int64_t ts, int ref, bool impulse) noexcept
640 {
641 if(m_due_count < max_due)
642 {
643 m_due[m_due_count] = {ts, ref, impulse, m_due_count};
644 m_due_count++;
645 }
646 }
647 void send_due(int64_t until);
648
649 Looper::held_timeline m_timeline;
650 Looper::held_scratch m_scratch;
651 void update_timeline() { m_timeline.update(loop, 0, m_scratch); }
653 void chase(int64_t ts);
654
655 // What a recording cut back to a grid point had recorded past it, from the
656 // grid point on, and how many frames were cut.
657 struct carried_value
658 {
659 int64_t pos;
660 Looper::value_slot value;
661 uint16_t key;
662 };
663 std::array<carried_value, 1024> m_carried{};
664 int m_carried_count{};
665 int64_t m_carried_span{};
666
667 // What came in lately, recorded or not, for Capture, and for each key the
668 // value it held before the oldest of it.
669 struct played
670 {
671 int64_t frame;
672 Looper::value_slot value;
673 uint16_t key;
674 };
675 static constexpr int history_size = 4096;
676 std::array<played, history_size> m_history{};
677 int64_t m_history_count{};
678 std::array<Looper::value_slot, max_keys> m_history_floor{};
679
680 int64_t m_now{};
681 int64_t m_tick_frames{};
682 int64_t m_tick_start{};
683 int64_t m_expected_pos{};
684 bool m_stop_requested{};
685 bool m_written_placeable{true};
686
687 void handle_input(
688 int64_t from, int64_t to, double pos0, bool taking, double rate = 1.,
689 bool backwards = false);
690 void record_value(
691 int64_t pos, int64_t written, uint16_t key, const ossia::value& v, bool backwards);
692 void close_backwards(int64_t boundary);
693 void next_cycle_pass(int64_t start);
694 void begin_pass_carried(int64_t k);
695 void begin_recording();
696
698 bool m_was_reverse{};
699 double m_phase{};
700 void advance(int64_t start, int64_t length, bool overdub);
701 double rate() const noexcept;
702 void play_range(double a, double b, double out0, double r);
703 void play_range_reverse(double a, double b, double out0, double r);
704
705 void write_export_item(int item);
706 void fill_export(
707 const Looper::value_loop& snap, int item, Looper::value_export& e);
708 void write_stop_take(bool recording, double date, int64_t pos);
711 Looper::value_loop m_export_loop;
712 Looper::held_timeline m_export_timeline;
713 void send_view(int64_t frames);
714 void fill_message(Looper::view_message& v);
715};
716
717inline int64_t Node::input_ts(int i) const noexcept
718{
719 const auto& data = inputs.values.value->get_data();
720 return data[std::size_t(m_in_order[i])].timestamp - m_tick_start;
721}
722
724inline void Node::tick_started(const tick&)
725{
726 m_in_count = 0;
727 m_in_index = 0;
728 if(!inputs.values.value)
729 return;
730 const auto& data = inputs.values.value->get_data();
731 for(std::size_t i = 0; i < data.size() && m_in_count < max_in; i++)
732 if(data[i].value.valid())
733 m_in_order[m_in_count++] = int(i);
734 std::sort(m_in_order.begin(), m_in_order.begin() + m_in_count, [&](int a, int b) {
735 const auto ta = data[std::size_t(a)].timestamp, tb = data[std::size_t(b)].timestamp;
736 return ta != tb ? ta < tb : a < b;
737 });
738}
739
740inline void Node::push_outputs()
741{
742 for(int i = 0; i < m_out_count; i++)
743 m_out[i].order = i;
744 std::sort(m_out.begin(), m_out.begin() + m_out_count, [](const out_value& a, const out_value& b) {
745 return a.ts != b.ts ? a.ts < b.ts : a.order < b.order;
746 });
747 auto* port = outputs.values.value;
748 for(int i = 0; i < m_out_count; i++)
749 {
750 if(port)
751 port->write_value(
752 m_out[i].value.get(),
753 m_tick_start + std::clamp<int64_t>(m_out[i].ts, 0, std::max<int64_t>(0, m_tick_frames - 1)));
754 m_out[i].value.reset();
755 }
756 m_out_count = 0;
757}
758
760inline void Node::send_due(int64_t until)
761{
762 for(; m_due_index < m_due_count && m_due[m_due_index].ts <= until; m_due_index++)
763 {
764 const auto& d = m_due[m_due_index];
765 if(d.impulse)
766 {
767 if(d.ref < loop.event_count)
768 emit(d.ts, loop.events[d.ref].value.get());
769 continue;
770 }
771 const auto& p = m_timeline.points[d.ref];
772 if(p.event >= 0 && p.event < loop.event_count)
773 send_held(loop.events[p.event].key, loop.events[p.event].value, d.ts);
774 }
775}
776
777inline void Node::chase(int64_t ts)
778{
779 if(loop.empty())
780 return;
781 update_timeline();
782 const int64_t x = std::clamp<int64_t>(int64_t(m_phase), 0, loop.length - 1);
783 const int i = m_reverse ? m_timeline.before(x) : m_timeline.at(x);
784 if(i < 0)
785 return;
786 const int e = m_timeline.points[i].event;
787 if(e >= 0 && e < loop.event_count)
788 send_held(loop.events[e].key, loop.events[e].value, ts);
789}
790
794inline void Node::handle_input(
795 int64_t from, int64_t to, double pos0, bool taking, double rate, bool backwards)
796{
797 const Passthrough pt = inputs.passthrough;
798 const bool echo = taking ? pt != Passthrough::None : pt == Passthrough::Full;
799 const auto hold = int64_t(input_hold_seconds * m_rate);
800
801 for(; m_in_index < m_in_count; m_in_index++)
802 {
803 const int64_t ts = input_ts(m_in_index);
804 if(ts >= to)
805 break;
806 send_due(ts);
807 const auto& v = inputs.values.value->get_data()[std::size_t(m_in_order[m_in_index])].value;
808 const uint16_t key = 0;
809 auto& k = m_keys[key];
810 const bool impulse = v.get_type() == ossia::val_type::IMPULSE;
811
812 {
813 auto& h = m_history[m_history_count++ % history_size];
814 if(m_history_count > history_size && !h.value.impulse())
815 m_history_floor[h.key] = h.value;
816 h.frame = m_now + ts;
817 h.key = key;
818 h.value.set(v);
819 }
820
821 // Where the value falls in the loop. A recording's loop is as long as
822 // what was recorded so far, so its positions are not wrapped.
823 // Backwards, pos0 counts from the loop's end, and a value falls on the
824 // edge of the position the playhead has just gone over.
825 int64_t pos = -1;
826 int64_t written = 0;
827 if(pos0 >= 0)
828 {
829 const double into = (std::max(ts, from) - from) * rate;
830 pos = int64_t(pos0 + into);
831 written = int64_t(pass_progress() + into);
832 if(backwards && loop.length > 0)
833 {
834 pos = (loop.length - pos) % loop.length;
835 if(pos < 0)
836 pos += loop.length;
837 }
838 else if(transport.actualMode != LoopMode::Record && loop.length > 0)
839 pos %= loop.length;
840 else
841 written = pos;
842 }
843
844 // A value is new unless it is the one the key held: only a new value
845 // is recorded.
846 const bool fresh = impulse || k.in.empty() || !k.in.same(v);
847 if(!impulse)
848 k.in.set(v);
849 if(taking && pos >= 0 && fresh)
850 record_value(pos, written, key, v, backwards);
851
852 if(echo)
853 {
854 emit(ts, v);
855 if(!impulse)
856 {
857 k.out.set(v);
858 k.owned_until = m_now + ts + hold;
859 }
860 }
861 }
862 send_due(to - 1);
863}
864
865inline void Node::record_value(
866 int64_t pos, int64_t written, uint16_t key, const ossia::value& v, bool backwards)
867{
869 if(v.get_type() == ossia::val_type::IMPULSE)
870 {
871 loop.add_value(pos, written, key, v);
872 return;
873 }
874 auto& k = m_keys[key];
875 if(!loop.touched(key))
876 loop.touch_key(key, pos, written, backwards);
877 // Backwards, a value holds for the positions under the one it came at:
878 // the loop keeps, at that position, the value from before, which is what
879 // it goes back to going forwards; and where the playhead stops going
880 // backwards, the last value that came.
881 if(!backwards)
882 loop.add_value(pos, written, key, v);
883 else if(!k.recorded.empty())
884 loop.add_value(pos, written, key, k.recorded.get());
885 k.recorded.set(v);
886}
887
890inline void Node::close_backwards(int64_t boundary)
891{
892 if(loop.length <= 0 || pass_pending())
893 return;
894 boundary %= loop.length;
895 for(int key = 0; key < max_keys; key++)
896 if(!m_keys[key].recorded.empty())
897 loop.add_value(
898 boundary, int64_t(loop.pass_progress), uint16_t(key), m_keys[key].recorded.get());
899}
900
903inline void Node::begin_recording()
904{
905 loop.begin_recording();
906 forget_recorded();
907 for(int key = 0; key < max_keys; key++)
908 {
909 const auto& in = m_keys[key].in;
910 if(in.empty())
911 continue;
912 loop.touch_key(uint16_t(key), 0, 0, false);
913 loop.add_value(0, 0, uint16_t(key), in.get());
914 }
915}
916
917inline void Node::looper_trim_recording(int64_t frames)
918{
919 const int64_t end = std::max<int64_t>(0, loop.length - frames);
920 // What came after the grid point belongs to the next pass: it is kept
921 // aside, from the grid point on, for an overdub that starts there.
922 const int64_t cut = loop.length - end;
923 for(int i = 0; i < m_carried_count; i++)
924 m_carried[i].pos += cut;
925 m_carried_span += cut;
926 for(int i = 0; i < loop.event_count; i++)
927 {
928 const auto& e = loop.events[i];
929 if(e.pos < end || e.impulse)
930 continue;
931 if(m_carried_count < int(m_carried.size()))
932 {
933 auto& c = m_carried[m_carried_count++];
934 c.pos = e.pos - end;
935 c.value = e.value;
936 c.key = e.key;
937 }
938 }
939 loop.remove_events([end](const Looper::value_event& e) { return e.pos >= end; });
940 loop.length = end;
941 loop.pass_written = end;
942 loop.edits++;
943}
944
949inline void Node::begin_pass_carried(int64_t k)
950{
951 const int64_t len = loop.length;
952 const bool from_point = m_carried_span > 0 && m_carried_span < len && k == m_carried_span;
953 begin_pass(from_point ? 0 : k);
954 for(int i = 0; i < m_carried_count; i++)
955 {
956 const auto& c = m_carried[i];
957 const int64_t pos = from_point ? c.pos % len : k;
958 record_value(pos, from_point ? c.pos : 0, c.key, c.value.get(), false);
959 }
960 if(from_point)
961 pass_advanced(double(m_carried_span), false);
962 forget_carried();
963}
964
965inline int64_t Node::looper_run(int64_t start, int64_t length)
966{
967 if(!loop.empty() && transport.playbackPos % loop.length != m_expected_pos)
968 m_phase = double(transport.playbackPos % loop.length);
969 if(m_reverse != m_was_reverse)
970 {
971 if(m_was_reverse && loop.taking && !loop.empty())
972 close_backwards(int64_t(std::ceil(m_phase - 1e-9)));
973 m_was_reverse = m_reverse;
974 }
975
976 const LoopMode mode = transport.actualMode;
977 switch(mode)
978 {
979 case LoopMode::Stop:
980 handle_input(start, start + length, -1, false);
981 break;
982
983 case LoopMode::Play:
984 advance(start, length, false);
985 break;
986
987 case LoopMode::Record: {
988 if(!transport.is_taking())
989 {
990 begin_recording();
991 m_switches.soloed.forget();
992 transport.playbackPos = 0;
993 m_export_pending = false;
994 }
995 transport.taking(LoopMode::Record);
996 const int64_t pos0 = loop.length;
997 handle_input(start, start + length, double(pos0), true);
998 loop.length += length;
999 loop.pass_written = loop.length;
1000 loop.pass_progress = double(loop.length);
1001 loop.update_open_stretches();
1002 transport.playbackPos = loop.length;
1003 m_phase = 0.;
1004 m_expected_pos = 0;
1005 return length;
1006 }
1007
1008 case LoopMode::Overdub: {
1009 if(loop.empty())
1010 {
1011 handle_input(start, start + length, -1, false);
1012 break;
1013 }
1014 if(!transport.is_taking())
1015 {
1016 const int64_t k = int64_t(m_phase) % loop.length;
1017 begin_pass_carried(k);
1018 m_cycle_pending = false;
1019 // An extending pass grows the loop by the loop it started over.
1020 m_extend_unit = loop.length;
1021 m_extended = false;
1022 }
1023 transport.taking(LoopMode::Overdub);
1024 advance(start, length, true);
1025 break;
1026 }
1027 }
1028
1029 if(!loop.empty())
1030 {
1031 transport.playbackPos = int64_t(m_phase) % loop.length;
1032 m_expected_pos = transport.playbackPos;
1033 }
1034 return length;
1035}
1036
1039inline double Node::rate() const noexcept
1040{
1041 if(!inputs.follow_tempo.value || loop.empty() || m_loop_tempo <= 0.)
1042 return m_speed;
1043 return std::clamp(m_tempo / m_loop_tempo, 0.05, 20.) * m_speed;
1044}
1045
1048inline void Node::next_cycle_pass(int64_t start)
1049{
1050 close_pass(true);
1051 begin_pass(start);
1052}
1053
1059inline void Node::advance(int64_t start, int64_t length, bool overdub)
1060{
1061 const double r = rate();
1062 const bool back = m_reverse;
1063 // Backwards, the playhead reaches the loop's start, not its end.
1064 const bool extend = overdub && inputs.past_end.value == Looper::PastEnd::Extend && !back;
1065 const bool per_cycle = overdub && inputs.passes.value == Looper::Passes::PerCycle;
1066 // Undoing an extension can leave the playhead past the end of the loop.
1067 const double ph = std::fmod(std::max(0., m_phase), double(loop.length));
1068 m_phase = ph;
1069 double y = back ? double(loop.length) - ph : ph;
1070 if(y >= double(loop.length))
1071 y -= double(loop.length);
1072 double out = double(start);
1073 const double out_end = double(start + length);
1074
1075 chase(start);
1076 while(out < out_end - 1e-9)
1077 {
1078 const double L = double(loop.length);
1079 const double b = std::min(L, y + (out_end - out) * r);
1080 const double seg_out = (b - y) / r;
1081 if(overdub && std::exchange(m_cycle_pending, false))
1082 next_cycle_pass(back ? 0 : int64_t(y));
1083
1084 // Gone round again, the pass writes over what it recorded a round
1085 // before.
1086 if(overdub && !back && !pass_pending())
1087 loop.write_over(int64_t(std::ceil(y)), int64_t(std::ceil(b)), loop.pass_progress);
1088 if(back)
1089 play_range_reverse(y, b, out, r);
1090 else
1091 play_range(y, b, out, r);
1092 // The input of the frames this part of the loop covers.
1093 const int64_t from = int64_t(std::ceil(out - 1e-9));
1094 const int64_t to = b >= L ? int64_t(std::ceil(out + seg_out - 1e-9))
1095 : int64_t(std::ceil(out_end - 1e-9));
1096 handle_input(from, to, y + (from - out) * r, overdub, r, back);
1097 send_due(std::numeric_limits<int64_t>::max());
1098 if(overdub)
1099 {
1100 pass_advanced(b - y, back);
1101 if(!pass_pending())
1102 loop.update_open_stretches();
1103 }
1104
1105 out += seg_out;
1106 y = b;
1107 if(y >= L - 1e-9)
1108 {
1109 if(extend
1110 && loop.extend(m_extend_unit, int64_t(max_loop_seconds * m_rate), !pass_pending()))
1111 {
1112 // A cycle is the loop's length when the overdub started: the loop
1113 // growing by one is a cycle gone round as well.
1114 m_extended = true;
1115 loop.edits++;
1116 if(per_cycle)
1117 m_cycle_pending = true;
1118 }
1119 else
1120 {
1121 y -= L;
1122 if(y < 0.)
1123 y = 0.;
1124 if(per_cycle)
1125 {
1126 // The pass is over at the loop's start.
1127 if(back)
1128 close_backwards(0);
1129 m_cycle_pending = true;
1130 }
1131 }
1132 }
1133 }
1134 // Input on the last frame, when the span ended on the loop's end.
1135 if(!overdub)
1136 handle_input(start + length, start + length, -1, false);
1137 if(back)
1138 {
1139 y = double(loop.length) - y;
1140 if(y >= double(loop.length))
1141 y -= double(loop.length);
1142 }
1143 m_phase = y;
1144}
1145
1150inline void Node::play_range(double a, double b, double out0, double r)
1151{
1152 m_due_count = 0;
1153 m_due_index = 0;
1154 update_timeline();
1155 const int64_t len = loop.length;
1156 auto frame = [&](int64_t q) { return int64_t(out0 + (q - a) / r); };
1157 const int64_t lo = int64_t(std::ceil(a));
1158 const int64_t hi = std::min(len, int64_t(std::ceil(b)));
1159 if(lo >= hi)
1160 return;
1161
1162 for(int i = m_timeline.at(lo); i >= 0 && i < m_timeline.count; i++)
1163 {
1164 const auto& p = m_timeline.points[i];
1165 if(p.pos < lo)
1166 continue;
1167 if(p.pos >= hi)
1168 break;
1169 add_due(frame(p.pos), i, false);
1170 }
1171 for(int i = 0; i < loop.event_count; i++)
1172 {
1173 const auto& e = loop.events[i];
1174 if(!e.impulse || !loop.heard(e))
1175 continue;
1176 const int64_t period = loop.period_for(e.pass);
1177 const int64_t first = e.pos % period;
1178 for(int64_t q = first + std::max<int64_t>(0, (lo - first + period - 1) / period) * period;
1179 q < hi; q += period)
1180 add_due(frame(q), i, true);
1181 }
1182 std::sort(m_due.begin(), m_due.begin() + m_due_count, [](const due& x, const due& y) {
1183 return x.ts != y.ts ? x.ts < y.ts : x.order < y.order;
1184 });
1185}
1186
1191inline void Node::play_range_reverse(double a, double b, double out0, double r)
1192{
1193 m_due_count = 0;
1194 m_due_index = 0;
1195 update_timeline();
1196 const int64_t len = loop.length;
1197 auto frame = [&](int64_t q) { return int64_t(out0 + (q - a) / r); };
1198 const int64_t lo = int64_t(std::ceil(a));
1199 const int64_t hi = std::min(len, int64_t(std::ceil(b)));
1200 if(lo >= hi)
1201 return;
1202 auto mirrored = [](int64_t pos, int64_t period) {
1203 int64_t m = (-pos) % period;
1204 if(m < 0)
1205 m += period;
1206 return m;
1207 };
1208
1209 for(int i = 0; i < m_timeline.count; i++)
1210 {
1211 const int64_t q = mirrored(m_timeline.points[i].pos, len);
1212 if(q >= lo && q < hi)
1213 add_due(frame(q), m_timeline.previous(i), false);
1214 }
1215 for(int i = 0; i < loop.event_count; i++)
1216 {
1217 const auto& e = loop.events[i];
1218 if(!e.impulse || !loop.heard(e))
1219 continue;
1220 int64_t period = loop.period_for(e.pass);
1221 if(len % period != 0)
1222 period = len;
1223 const int64_t first = mirrored(e.pos % period, period);
1224 for(int64_t q = first + std::max<int64_t>(0, (lo - first + period - 1) / period) * period;
1225 q < hi; q += period)
1226 add_due(frame(q), i, true);
1227 }
1228 std::sort(m_due.begin(), m_due.begin() + m_due_count, [](const due& x, const due& y) {
1229 return x.ts != y.ts ? x.ts < y.ts : x.order < y.order;
1230 });
1231}
1232
1235inline void Node::capture_into(int64_t end, int64_t n)
1236{
1237 const int64_t from = end - n;
1238 loop.begin_recording();
1239 loop.length = n;
1240 forget_recorded();
1241
1242 const int64_t count = std::min<int64_t>(m_history_count, history_size);
1243 // The value held before the window.
1244 for(int key = 0; key < max_keys; key++)
1245 {
1246 const Looper::value_slot* before = m_history_floor[key].empty() ? nullptr : &m_history_floor[key];
1247 for(int64_t i = m_history_count - count; i < m_history_count; i++)
1248 {
1249 const auto& h = m_history[i % history_size];
1250 if(h.key == key && h.frame < from && !h.value.impulse())
1251 before = &h.value;
1252 }
1253 if(before)
1254 record_value(0, 0, uint16_t(key), before->get(), false);
1255 }
1256 for(int64_t i = m_history_count - count; i < m_history_count; i++)
1257 {
1258 const auto& h = m_history[i % history_size];
1259 if(h.frame < from || h.frame >= end)
1260 continue;
1261 const int64_t pos = h.frame - from;
1262 const auto& last = m_keys[h.key].recorded;
1263 if(!h.value.impulse() && !last.empty() && last.same(h.value))
1264 continue;
1265 record_value(pos, pos, h.key, h.value.get(), false);
1266 }
1267 loop.pass_written = n;
1268 loop.pass_progress = double(n);
1269 loop.end_pass();
1270}
1271
1272template <typename F>
1273void Node::for_each_captured(const Looper::capture_window& w, F&& f) const
1274{
1275 const int64_t count = std::min<int64_t>(m_history_count, history_size);
1276 std::array<const Looper::value_slot*, max_keys> held{};
1277 for(int key = 0; key < max_keys; key++)
1278 if(!m_history_floor[key].empty())
1279 held[key] = &m_history_floor[key];
1280 for(int64_t i = m_history_count - count; i < m_history_count; i++)
1281 {
1282 const auto& h = m_history[i % history_size];
1283 if(h.frame >= w.end)
1284 break;
1285 const bool impulse = h.value.impulse();
1286 const bool fresh = impulse || !held[h.key] || !held[h.key]->same(h.value);
1287 if(!impulse)
1288 held[h.key] = &h.value;
1289 if(fresh && w.contains(h.frame))
1290 if(!f(h))
1291 return;
1292 }
1293}
1294
1296inline bool Node::capture_layer_ready(const Looper::capture_window& w) const noexcept
1297{
1298 bool any = false;
1299 for_each_captured(w, [&any](const played&) {
1300 any = true;
1301 return false;
1302 });
1303 return any;
1304}
1305
1309inline void Node::capture_layer_into(const Looper::capture_window& w)
1310{
1311 for_each_captured(w, [&](const played& h) {
1312 record_value(
1313 w.position(h.frame), w.progress(h.frame), h.key, h.value.get(), w.backwards);
1314 return true;
1315 });
1316 loop.pass_written = w.positions();
1317 loop.pass_progress = double(loop.pass_written);
1318 loop.pass_backwards = w.backwards;
1319 if(w.backwards)
1320 close_backwards(w.position(w.end));
1321}
1322
1324inline void Node::trim_top_pass(int64_t end)
1325{
1326 const int pass = loop.count - 1;
1327 loop.remove_events(
1328 [=](const Looper::value_event& e) { return e.pass == pass && e.pos >= end; });
1329 loop.edits++;
1330 // The pass went from where it started to the old end, and no further.
1331 loop.pass_written = std::clamp<int64_t>(end - loop.pass_start, 0, loop.pass_written);
1332 loop.pass_progress = double(loop.pass_written);
1333}
1334
1337inline void Node::start_export(int passes, bool open, bool)
1338{
1339 m_export_loop.assign(loop);
1340 if(!open && passes < loop.count)
1341 {
1342 m_export_loop.count = passes;
1343 m_export_loop.total = passes;
1344 m_export_loop.taking = false;
1345 m_export_loop.edits++;
1346 }
1347 write_export_item(m_export_first);
1348}
1349
1352inline void Node::write_export_item(int item)
1353{
1354 m_export_item = item;
1355 m_writing = true;
1356 fill_export(m_export_loop, item, *export_data);
1357
1358 Looper::value_take_job job;
1359 job.writer = writer;
1360 job.data = export_data;
1361 job.slot = m_export_slot;
1362 job.number = m_export_number;
1363 job.layer = item;
1364 worker.request(std::move(job));
1365}
1366
1370inline void Node::write_stop_take(bool recording, double date, int64_t)
1371{
1372 auto& s = *stop_take;
1373 if(!writer || loop.empty() || s.in_use)
1374 return;
1375 s.loop.assign(loop);
1376 s.rate = m_rate;
1377 s.tempo = recording ? m_tempo : m_loop_tempo;
1378 s.meter_numerator = m_signature.upper;
1379 s.meter_denominator = m_signature.lower;
1380 prepare_stop_notice(s.notice, date);
1381
1382 Looper::value_take_job job;
1383 job.writer = writer;
1384 job.stop = stop_take;
1385 job.slot = claim_take_slot();
1386 job.alone = true;
1387 s.in_use = true;
1388 worker.request(std::move(job));
1389}
1390
1393inline void Node::fill_export(
1394 const Looper::value_loop& snap, int item, Looper::value_export& e)
1395{
1396 Looper::fill_value_export(snap, item, m_export_timeline, m_scratch, e);
1397 e.rate = m_rate;
1398 e.tempo = m_export_tempo;
1399 e.meter_numerator = m_export_signature.upper;
1400 e.meter_denominator = m_export_signature.lower;
1401}
1402
1403inline void Node::send_view(int64_t frames)
1404{
1405 if(!view_due(frames))
1406 return;
1407 fill_message(m_view);
1408 send_message(m_view);
1409}
1410
1412inline void Node::fill_message(Looper::view_message& v)
1413{
1414 fill_view(v);
1415 v.values = true;
1416 v.lanes = std::min(loop.count, Looper::view_message::max_lanes);
1417 v.max_seconds = 0.f;
1418 v.notes = 0;
1419
1420 constexpr int bins = Looper::view_message::bins;
1421 const int lanes = v.lanes;
1422 const int merged = loop.count - lanes;
1423 const int64_t len = std::max<int64_t>(1, loop.length);
1424 v.peaks.fill(-1.f);
1425 v.value_flags.fill(0);
1426 if(loop.empty())
1427 return;
1428 update_timeline();
1429
1430 // What each lane holds, sampled at the start of each bin: its passes
1431 // alone, muted or not, and whether that is what is heard there.
1432 m_scratch.prepare(loop, 0, false);
1433 for(int l = 0; l < lanes; l++)
1434 {
1435 const int first = l == 0 ? 0 : l + merged;
1436 const int last = l + merged;
1437 float* y = v.peaks.data() + l * bins;
1438 uint8_t* f = v.value_flags.data() + l * bins;
1439 double lo = 0., hi = 0.;
1440 bool any = false;
1441 for(int b = 0; b < bins; b++)
1442 {
1443 const int64_t x = std::min<int64_t>(len - 1, int64_t(double(b) * double(len) / bins));
1444 const int e = m_scratch.value_at(loop, 0, x, first, last);
1445 if(e < 0)
1446 continue;
1447 const auto& val = loop.events[e].value.get();
1448 const int at = m_timeline.at(x);
1449 if(at >= 0 && m_timeline.points[at].event == e)
1452 {
1453 const double n = Looper::value_export::number(val, 0);
1454 y[b] = float(n);
1456 lo = any ? std::min(lo, n) : n;
1457 hi = any ? std::max(hi, n) : n;
1458 any = true;
1459 }
1460 else
1461 {
1463 }
1464 }
1465 // Scaled to the values seen in the lane.
1466 for(int b = 0; b < bins; b++)
1468 y[b] = hi > lo ? float((y[b] - lo) / (hi - lo)) : 0.5f;
1469 }
1470
1471 // The impulses, and the values that are not numbers, as marks where they
1472 // are.
1473 for(int i = 0; i < loop.event_count; i++)
1474 {
1475 const auto& e = loop.events[i];
1476 if(e.pass >= loop.count)
1477 continue;
1478 const bool number = !e.impulse && Looper::value_export::numbers_of(e.value.get()) > 0;
1479 if(number)
1480 continue;
1481 const int l = std::clamp(e.pass - merged, 0, lanes - 1);
1482 const int64_t period = loop.period_for(e.pass);
1483 for(int64_t q = e.pos % period; q < len; q += period)
1484 {
1485 const int b = std::clamp(int(q * bins / len), 0, bins - 1);
1486 auto& f = v.value_flags[l * bins + b];
1488 if(e.impulse && loop.heard(e))
1490 }
1491 }
1492}
1493}
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
void fill_value_export(const value_loop &snap, int item, held_timeline &tl, held_scratch &scratch, value_export &e)
Definition ValueTake.hpp:374
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.
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
double pass_progress() const noexcept
The frames the pass being taken in went over.
Definition LooperNode.hpp:262
void prepare_stop_notice(Looper::stop_take_notice &n, double date)
Definition LooperNode.hpp:1021
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
void fill_view(Looper::view_message &v) const noexcept
Definition LooperNode.hpp:1177
bool view_due(int64_t frames) noexcept
Whether the view is due a message: some 30 a second.
Definition LooperNode.hpp:358
Definition LooperView.hpp:1331
Definition Switches.hpp:420
Definition Switches.hpp:390
Definition Switches.hpp:415
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
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
static double number(const ossia::value &v, int component) noexcept
Number component of a value, which it has.
Definition ValueTake.hpp:127
static int numbers_of(const ossia::value &v) noexcept
Definition ValueTake.hpp:101
Definition ValueLoop.hpp:117
void remove_events(F &&gone) noexcept
The values gone says are let go of.
Definition ValueLoop.hpp:193
value_event * add_value(int64_t pos, int64_t written, uint16_t key, const ossia::value &v) noexcept
Definition ValueLoop.hpp:323
void update_open_stretches() noexcept
Definition ValueLoop.hpp:266
void end_pass() noexcept
Definition ValueLoop.hpp:296
bool heard(const T &e, bool own_mute=true) const noexcept
Definition ValueLoop.hpp:214
void touch_key(uint16_t key, int64_t pos, int64_t at, bool backwards) noexcept
Definition ValueLoop.hpp:242
void write_over(int64_t lo, int64_t hi, double at) noexcept
Definition ValueLoop.hpp:364
Definition ValueTake.hpp:414
Definition LooperView.hpp:77
@ flag_number
A number is held, at the height in peaks.
Definition LooperView.hpp:111
@ flag_impulse
An impulse fires in the bin.
Definition LooperView.hpp:114
@ flag_heard_mark
The impulse is heard.
Definition LooperView.hpp:116
@ flag_mark
A value that is not a number starts in the bin.
Definition LooperView.hpp:115
@ flag_heard
What is held is what the loop plays there.
Definition LooperView.hpp:113
@ flag_other
Something else is held: text, a list, a map.
Definition LooperView.hpp:112
Definition ValueLooper.hpp:47
Definition ValueLooper.hpp:421
Definition ValueLooper.hpp:350
Definition ValueLooper.hpp:261
static bool must_run(const Looper::value_take_job &job) noexcept
The take the score's stop leaves is written once the looper is gone.
Definition ValueLooper.hpp:264
Definition ValueLooper.hpp:22
~Node()
Definition ValueLooper.hpp:319
std::shared_ptr< Looper::value_stop_take > stop_take
Definition ValueLooper.hpp:298
static constexpr double max_loop_seconds
Longest loop an extending overdub makes, as the audio looper's longest.
Definition ValueLooper.hpp:328
static constexpr double input_hold_seconds
Definition ValueLooper.hpp:44
void stop()
Definition ValueLooper.hpp:316
static constexpr int max_keys
Keys of the values looped: one stream for now.
Definition ValueLooper.hpp:41
void process_message(const Looper::erase_request &r)
From the view: applied at the start of the next tick.
Definition ValueLooper.hpp:288
std::function< void(Looper::view_message)> send_message_from_worker
Definition ValueLooper.hpp:285
Definition Types.hpp:40
Definition MIDISync.hpp:149
Definition DocumentContext.hpp:18