Loading...
Searching...
No Matches
LooperNode.hpp
1#pragma once
2#include <Fx/Looper/LooperView.hpp>
3#include <Fx/Looper/Switches.hpp>
4#include <Fx/Looper/TakeWriter.hpp>
5#include <Fx/Looper/Transport.hpp>
6
7#include <ossia/dataflow/token_request.hpp>
8
9#include <algorithm>
10#include <atomic>
11#include <cmath>
12#include <cstdint>
13#include <utility>
14
15namespace Nodes::Looper
16{
126{
127 int64_t end{};
128 int64_t n{};
129 int64_t length{};
130 double y_end{};
131 double rate{1.};
132 bool backwards{};
133
134 static int64_t wrap(int64_t k, int64_t len) noexcept
135 {
136 k %= len;
137 return k < 0 ? k + len : k;
138 }
140 int64_t positions() const noexcept
141 {
142 return std::clamp<int64_t>(std::llround(double(n) * rate), 0, length);
143 }
145 int64_t start() const noexcept
146 {
147 const auto y = int64_t(std::floor(y_end));
148 return backwards ? wrap(length - y, length) : wrap(y - positions(), length);
149 }
151 int64_t position(int64_t t) const noexcept
152 {
153 double y = std::fmod(y_end - double(end - t) * rate, double(length));
154 if(y < 0.)
155 y += double(length);
156 const int64_t k = std::min(int64_t(y), length - 1);
157 return backwards ? wrap(length - k, length) : k;
158 }
160 int64_t progress(int64_t t) const noexcept
161 {
162 return std::clamp<int64_t>(int64_t(double(t - (end - n)) * rate), 0, positions());
163 }
164 bool contains(int64_t t) const noexcept { return t >= end - n && t < end; }
165};
166
167template <typename Node>
169{
170 using LoopMode = Looper::LoopMode;
171 using tick = ossia::token_request;
172
174
176 void loop_arrived() noexcept { m_loop.arrived = true; }
177
178 // The transport's engine
179 void looper_close_take(
180 const ossia::token_request& tk, LoopMode mode, bool ended_by_postaction);
181 int64_t looper_trim_extension(int64_t max_frames);
182 void looper_rephase(const ossia::token_request& tk);
183 int64_t looper_frames_to_end() const noexcept;
184 Looper::loop_period looper_period(const ossia::token_request& tk) const noexcept;
185 void looper_point()
186 {
187 m_switches.apply(self().inputs, self());
188 m_speed = Looper::clamp_speed(self().inputs.speed.value);
189 m_reverse = self().inputs.reverse.value;
190 }
191
192 // The switches' engine
193 int looper_passes() const noexcept { return stack().count; }
194 bool looper_muted(int pass) const noexcept { return stack().layers[pass].muted; }
195 void looper_set_muted(int pass, bool muted)
196 {
197 if(stack().layers[pass].muted == muted)
198 return;
200 stack().set_muted(pass, muted);
201 }
202 void looper_mute(int pass) { mute_pass(pass); }
203 void looper_erase(int pass) { erase_pass(pass); }
204
205protected:
206 static constexpr bool lazy_passes() noexcept
207 {
208 if constexpr(requires { Node::passes_wait_for_material; })
209 return Node::passes_wait_for_material;
210 else
211 return false;
212 }
213
216 bool pass_pending() const noexcept { return m_pass_pending; }
218 bool pass_open() const noexcept { return transport.is_taking() && !m_pass_pending; }
219
223 {
224 if(!std::exchange(m_pass_pending, false))
225 return;
226 begin_pass_now(m_pending_start);
227 // Undoing it gives back the loop as it was when the overdub began, before
228 // it grew.
229 if(auto* top = stack().top())
230 top->loop_before = m_pending_length;
231 if constexpr(lazy_passes())
232 if(m_pending_progress > 0.)
233 stack().advance_pass(m_pending_progress, m_pending_backwards);
234 }
236 void pass_advanced(double frames, bool backwards)
237 {
239 {
240 m_pending_progress += frames;
241 m_pending_backwards = backwards;
242 }
243 else if constexpr(lazy_passes())
244 stack().advance_pass(frames, backwards);
245 }
247 double pass_progress() const noexcept
248 {
250 return m_pending_progress;
251 if constexpr(lazy_passes())
252 return stack().pass_progress;
253 else
254 return 0.;
255 }
256
259 {
260 m_version++;
261 self().layers_changing();
262 }
263
264 Node& self() noexcept { return static_cast<Node&>(*this); }
265 const Node& self() const noexcept { return static_cast<const Node&>(*this); }
266 auto& stack() noexcept { return self().stack(); }
267 const auto& stack() const noexcept { return self().stack(); }
268
270 void view_request(const Looper::erase_request& r) noexcept
271 {
272 if(r.take > 0 && r.last >= 0)
273 {
274 m_part_request = r;
275 return;
276 }
277 if(r.take > 0)
278 {
279 m_take_request = std::max(m_take_request, r.take);
280 return;
281 }
282 m_erase_request = r;
283 m_erase_requested = true;
284 }
285
286 void run_tick(const tick& tk);
287 void apply_buttons();
288 void follow_merges() { m_switches.follow_merges(self().inputs, stack().merged_passes); }
289
290 void clear_loop();
291 void drop_taken_pass();
292 void undo_pass();
293 void erase_pass(int pass);
294 void mute_pass(int pass);
295 int make_room();
296 void begin_pass(int64_t start);
297 void begin_pass_now(int64_t start);
298 void close_pass(bool cycle = false);
299 void merge_extra_pass();
300 void capture(const tick& tk);
301 void capture_layer(const tick& tk, int64_t now, int64_t end);
302 void stop_score();
303
308 double rounds_since_made(const ossia::token_request& tk) const noexcept
309 {
310 const double len = double(stack().length);
311 const auto now = transport.timeline(tk);
312 const double rounds
313 = self().by_quarters()
314 ? (now.quarters - m_origin.quarters) / (len / m_rate * m_loop_tempo / 60.)
315 : (now.frames - m_origin.frames) / len;
316 return rounds * m_speed;
317 }
318
319 void run_export();
320 void take_written(int slot, int number, bool ok, bool alone);
322 bool last_take_whole() const noexcept
323 {
324 return m_view.take > 0 && m_view.take_mix && m_view.take_layers >= stack().count
325 && m_view.take_version == m_version && !m_export_pending
326 && !self().view_writing();
327 }
328 void prepare_stop_notice(Looper::stop_take_notice& n, double date);
329 static uint64_t next_looper_id() noexcept
330 {
331 static std::atomic<uint64_t> ids{};
332 return ++ids;
333 }
334 int claim_take_slot() noexcept
335 {
336 const int slot = m_next_take_slot;
337 m_next_take_slot = (slot + 1) % Looper::take_writer::slots;
338 return slot;
339 }
340
342 bool view_due(int64_t frames) noexcept
343 {
344 if(!self().send_message)
345 return false;
346 m_view_countdown -= frames;
347 if(m_view_countdown > 0)
348 return false;
349 m_view_countdown = int64_t(m_rate / 30.);
350 return true;
351 }
352 void fill_view(Looper::view_message& v) const noexcept;
355 bool placeable_take() const noexcept
356 {
357 if constexpr(requires(const Node& n) { n.take_placeable(); })
358 return self().take_placeable();
359 else
360 return true;
361 }
362
363 double m_rate{48000.};
364 double m_tempo{120.};
365 ossia::time_signature m_signature{4, 4};
367 LoopMode m_mode{LoopMode::Stop};
370 Looper::switches m_switches;
371
373 double m_speed{1.};
374 bool m_reverse{};
379 double m_loop_tempo{120.};
382 int64_t m_extend_unit{};
383 bool m_extended{};
387
388 // The takes: one is made each time a pass closes, and when the view asks
389 // for one. Each file of it is written by the worker, one after the other
390 // under one take number: the mix (item -1), then the layers.
391 bool m_export_pending{};
392 bool m_writing{};
393 double m_export_date{};
396 double m_job_date{};
397 double m_export_tempo{120.};
398 ossia::time_signature m_export_signature{4, 4};
399 int m_export_item{};
400 int m_export_layers{};
401 bool m_export_mix{};
402 int m_export_number{};
403 int m_export_slot{};
406 halp::clip_stretch m_job_stretch{};
407 halp::clip_stretch m_stop_stretch{};
408 int m_takes_written{};
412 int m_job_request{};
415 Looper::erase_request m_part_request{.pass = -1, .layers = -1};
416 bool m_job_part{};
417 int m_export_first{};
421 int m_job_version{};
425 int64_t m_pending_start{};
426 int64_t m_pending_length{};
427 double m_pending_progress{};
428 bool m_pending_backwards{};
429 int m_next_take_slot{};
430
431 int64_t m_view_countdown{};
433 uint64_t m_id{next_looper_id()};
434 int m_stops{};
435 Looper::view_message m_view{};
436 Looper::erase_request m_erase_request{};
437 bool m_erase_requested{};
438};
439
440template <typename Node>
441void looper_node<Node>::run_tick(const tick& tk)
442{
443 auto& in = self().inputs;
444 auto& st = stack();
445 const auto state = self().ossia_state;
446 m_rate = state.sampleRate();
447 m_tempo = tk.tempo;
448 m_signature = tk.signature;
449 m_speed = Looper::clamp_speed(in.speed.value);
450 m_reverse = in.reverse.value;
451 self().tick_started(tk);
452
453 apply_buttons();
454 follow_merges();
455 if(in.capture.value)
456 capture(tk);
457 self().prepare_loop();
458
459 const bool empty = st.empty() || self().busy();
460 Looper::read_loop(in.mode, transport, m_loop, empty);
461 m_switches.read(in, transport, self(), empty);
462 transport.settle(empty);
463
464 m_mode = transport.wanted;
465 const Looper::transport_settings settings{
466 .mode = m_mode,
467 .quantif = in.quantif.value,
468 .after_record = in.after_record,
469 .postaction_bars = in.postaction_bars,
470 .start_from = in.start_from,
471 .late_seconds = in.late.value / 1000.,
472 .stop_at = in.stop_at.value};
473 transport(tk, state, settings, self());
474 // Only a pass that starts with the take closing gets what it cut off.
475 self().forget_carried();
476 Looper::show_loop(in.mode, transport, m_loop);
477 follow_merges();
478 m_switches.follow(in, st.count);
479
480 self().tick_finished(tk);
481 follow_merges();
482 run_export();
483
484 self().outputs.phase.value
485 = st.length > 0 ? float(transport.playbackPos % st.length) / float(st.length) : 0.f;
486}
487
488template <typename Node>
489void looper_node<Node>::apply_buttons()
490{
491 auto& in = self().inputs;
492 if(in.clear.value)
493 clear_loop();
494
495 // The m and x of a lane are layer actions as any: on the grid.
496 if(std::exchange(m_erase_requested, false))
497 m_switches.add(
498 {m_erase_request.mute ? Looper::layer_action::mute : Looper::layer_action::erase,
499 m_erase_request.pass, m_erase_request.layers},
500 transport);
501
502 if(in.undo.value || in.redo.value)
503 m_switches.soloed.forget();
504 if(in.undo.value)
505 {
506 if(pass_open())
507 drop_taken_pass();
508 else
509 {
510 passes_changing();
511 undo_pass();
512 }
513 }
514
515 if(in.redo.value && !pass_open())
516 {
517 passes_changing();
518 const int out = stack().leaves_on_redo();
519 if(stack().redo())
520 {
521 if(out >= 0)
522 m_switches.removed(in, out);
523 merge_extra_pass();
524 }
525 }
526}
527
530template <typename Node>
532{
533 m_switches.soloed.forget();
534 passes_changing();
535 self().cleared();
536 stack().clear();
537 m_pass_pending = false;
538 transport.playbackPos = 0;
539 // Whatever was being taken in is gone with the rest.
540 if(transport.is_taking())
541 transport.taking(LoopMode::Stop);
543}
544
548template <typename Node>
550{
551 passes_changing();
552 if(transport.actualMode == LoopMode::Record)
553 {
554 stack().clear();
555 transport.playbackPos = 0;
557 }
558 else
559 {
560 self().pass_dropped();
561 undo_pass();
562 stack().drop_redo();
563 }
564 transport.taking(LoopMode::Stop);
565}
566
569template <typename Node>
571{
572 const int back = stack().returns_on_undo();
573 if(stack().undo() && back >= 0)
574 m_switches.inserted(self().inputs, back, stack().count);
575}
576
579template <typename Node>
581{
582 auto& st = stack();
583 if(pass < 0 || pass >= st.count || self().busy())
584 return;
585 m_switches.soloed.forget();
586 if(st.count == 1)
587 clear_loop();
588 else if(pass_open() && pass == st.count - 1)
589 drop_taken_pass();
590 else
591 {
592 passes_changing();
593 if(st.erase(pass))
594 m_switches.removed(self().inputs, pass);
595 }
596}
597
599template <typename Node>
601{
602 auto& st = stack();
603 if(pass < 0 || pass >= st.count || self().busy())
604 return;
605 m_switches.soloed.forget();
606 passes_changing();
607 st.set_muted(pass, !st.layers[pass].muted);
608}
609
613template <typename Node>
615{
616 auto& st = stack();
617 if(self().inputs.at_max_layers.value != Looper::AtMaxLayers::ReplaceMuted
618 || st.count < self().max_layers() || st.count < 2)
619 return -1;
620 int pass = st.count - 1;
621 for(int i = 0; i < st.count; i++)
622 if(st.layers[i].muted)
623 {
624 pass = i;
625 break;
626 }
627 m_switches.soloed.forget();
628 passes_changing();
629 m_switches.removed(self().inputs, pass);
630 return pass;
631}
632
635template <typename Node>
637{
638 if constexpr(lazy_passes())
639 {
640 m_pass_pending = true;
641 m_pending_start = start;
642 m_pending_length = stack().length;
643 m_pending_progress = 0.;
644 m_pending_backwards = false;
645 return;
646 }
647 begin_pass_now(start);
648}
649
650template <typename Node>
651void looper_node<Node>::begin_pass_now(int64_t start)
652{
653 m_version++;
654 const int replaced = make_room();
655 self().begin_overdub(
656 start, self().inputs.at_max_layers.value == Looper::AtMaxLayers::MergeOldest,
657 replaced);
658}
659
663template <typename Node>
665{
666 // Nothing came in: there is no pass to close.
667 if(std::exchange(m_pass_pending, false))
668 {
669 if(!cycle)
670 m_extended = false;
671 return;
672 }
673 auto* top = stack().top();
674 if(!top)
675 return;
676 // A loop grown by the cycle that ended can still be cut back by a late
677 // stop.
678 if(!cycle)
679 m_extended = false;
680 m_version++;
681 m_export_pending = true;
682 if(!self().pass_closing())
683 return;
684 top->loop_after = stack().length;
685 self().pass_closed();
686 merge_extra_pass();
687}
688
689template <typename Node>
691{
692 if(self().inputs.at_max_layers.value == Looper::AtMaxLayers::MergeIntoNewest
693 && stack().count > self().max_layers() && self().can_merge_newest())
694 {
695 passes_changing();
696 self().merge_newest();
697 }
698}
699
700template <typename Node>
701void looper_node<Node>::looper_close_take(
702 const ossia::token_request& tk, LoopMode mode, bool ended_by_postaction)
703{
704 auto& st = stack();
705 if(st.count == 0)
706 return;
707
708 if(mode == LoopMode::Record)
709 {
710 m_loop_tempo = transport.loop_tempo(tk, st.length);
711 const double bar_frames = m_rate * 4. * (double(tk.signature.upper) / tk.signature.lower)
712 * (60. / tk.tempo);
713 const auto bars = int64_t(std::floor(transport.postaction_bars * bar_frames));
714
715 // The bar count says when the post-action takes over, so it is the length
716 // of a recording the post-action ended: what was recorded past the bars,
717 // e.g. the tempo having dropped while it went on, is cut off as a late
718 // stop cuts it, for the overdub that follows. A recording stopped by hand
719 // is as long as it was played for.
720 const bool to_bars = ended_by_postaction && transport.quantized()
721 && transport.postaction_bars > 0 && bars > 0;
722 if(to_bars && bars < st.length)
723 self().looper_trim_recording(st.length - bars);
724 self().close_recording(to_bars ? bars : 0);
725 m_export_date = transport.recordStartDate.impl / double(ossia::flicks_per_second<int64_t>);
726
727 // Position 0 of the loop on the timeline: the loop plays on from
728 // playbackPos at the point the take closes.
729 const auto at = double(transport.playbackPos % std::max<int64_t>(1, st.length));
730 m_origin = transport.timeline(tk);
731 m_origin.frames -= at;
732 m_origin.quarters -= at / m_rate * m_loop_tempo / 60.;
733 }
734 else
735 {
736 // An overdub that got nothing leaves no take.
737 if(m_pass_pending)
738 {
739 self().close_overdub();
740 m_extended = false;
741 return;
742 }
743 self().close_overdub();
744 }
745 m_version++;
746 m_extended = false;
747 m_export_pending = true;
748}
749
752template <typename Node>
754{
755 auto& st = stack();
756 if(!m_extended || m_extend_unit <= 0 || st.count == 0)
757 return -1;
758 const int64_t end = st.length - m_extend_unit;
759 const int64_t into_last = transport.playbackPos - end;
760 if(into_last < 0 || into_last > max_frames || end < m_extend_unit)
761 return -1;
762 if(m_pass_pending)
763 {
764 // The pass after the cut got nothing: it was never a layer.
765 m_pass_pending = false;
766 self().cut_pass_dropped();
767 st.length = end;
768 if(auto* t = st.top())
769 t->loop_after = end;
770 }
771 else if(st.count > 1 && self().cut_after(end))
772 {
773 // A pass per cycle cut the overdub on the old end: the pass after the cut
774 // has nothing left, and goes as undo takes a pass out. The one before it
775 // ends on the old end, closed already.
776 self().pass_dropped();
777 undo_pass();
778 st.drop_redo();
779 self().cut_pass_dropped();
780 st.length = end;
781 if(auto* t = st.top())
782 t->loop_after = end;
783 }
784 else
785 {
786 self().trim_top_pass(end);
787 st.length = end;
788 }
789 m_version++;
790 m_extended = false;
791 self().head_moved(into_last);
792 return into_last;
793}
794
796template <typename Node>
798{
799 const LoopMode m = transport.actualMode;
800 const auto& st = stack();
801 if(st.empty() || self().busy() || (m != LoopMode::Play && m != LoopMode::Overdub))
802 return -1;
803 if(m == LoopMode::Overdub && self().inputs.past_end.value == Looper::PastEnd::Extend
804 && !m_reverse)
805 return -1;
806 return Looper::frames_to_loop_end(self().head_y(st.length), st.length, self().rate_for(m));
807}
808
813template <typename Node>
815looper_node<Node>::looper_period(const ossia::token_request& tk) const noexcept
816{
817 const LoopMode m = transport.actualMode;
818 const auto& st = stack();
819 if(st.empty() || self().busy() || st.length <= 0 || m == LoopMode::Record)
820 return {};
821 const double rate = self().rate_for(m);
822 const int64_t round = Looper::loop_round(st.length, rate);
823 if(m == LoopMode::Stop)
824 return {
825 round, int64_t(std::floor(rounds_since_made(tk) * double(round) + 1e-6)), false};
826 const int64_t to_end = Looper::frames_to_loop_end(self().head_y(st.length), st.length, rate);
827 return {round, std::clamp<int64_t>(round - to_end, 0, round), true};
828}
829
832template <typename Node>
833void looper_node<Node>::looper_rephase(const ossia::token_request& tk)
834{
835 const auto& st = stack();
836 if(st.empty())
837 return;
838 const double rounds = rounds_since_made(tk);
839 // A whole position: what is left of a frame is rounding.
840 self().set_head(
841 std::floor((rounds - std::floor(rounds)) * double(st.length) + 1e-6), st.length);
842}
843
847template <typename Node>
848void looper_node<Node>::capture(const tick& tk)
849{
850 if(transport.is_taking() || self().busy())
851 return;
852 auto& in = self().inputs;
853 const double bar = m_rate * 4. * (double(tk.signature.upper) / tk.signature.lower)
854 * (60. / std::max(1., tk.tempo));
855 const int64_t now = self().capture_clock();
856 int64_t end = now;
857 if(transport.quantized() && transport.lastPointFrame)
858 {
859 const int64_t late = transport.frame - *transport.lastPointFrame;
860 if(late >= 0 && late <= int64_t(in.late.value * m_rate / 1000.))
861 end -= late;
862 }
863 if(!stack().empty())
864 {
865 capture_layer(tk, now, end);
866 return;
867 }
868 const int64_t n = std::min<int64_t>(
869 {int64_t(in.capture_bars.value * bar), end, self().capture_limit()});
870 if(n <= 0)
871 return;
872
873 passes_changing();
874 m_switches.soloed.forget();
875 self().capture_into(end, n);
876
877 transport.play_captured((now - end) % n);
878 m_loop_tempo = tk.tempo;
879 m_origin = Looper::timeline_start(tk, self().ossia_state);
880 m_origin.frames -= double(transport.playbackPos);
881 m_origin.quarters -= double(transport.playbackPos) / m_rate * tk.tempo / 60.;
882 const double model_per_frame = self().ossia_state.samplesToModel();
883 m_export_date = (tk.prev_date.impl - (now - end + n) * model_per_frame)
884 / double(ossia::flicks_per_second<int64_t>);
885 m_export_pending = true;
886 self().captured();
887}
888
895template <typename Node>
896void looper_node<Node>::capture_layer(const tick& tk, int64_t now, int64_t end)
897{
898 auto& st = stack();
899 const int64_t len = st.length;
900 const LoopMode m = transport.actualMode;
901 const bool stopped = m != LoopMode::Play;
902 const double rate = self().rate_for(m);
903 if(len <= 0 || !(rate > 0.))
904 return;
905 double y_now{};
906 if(stopped)
907 {
908 const double rounds = rounds_since_made(tk);
909 y_now = std::floor((rounds - std::floor(rounds)) * double(len) + 1e-6);
910 }
911 else
912 y_now = self().head_y(len);
913
914 const auto round = int64_t(std::ceil(double(len) / rate - 1e-9));
915 double y_end = std::fmod(y_now - double(now - end) * rate, double(len));
916 if(y_end < 0.)
917 y_end += double(len);
919 .end = end,
920 .n = std::min<int64_t>({round, end, self().capture_limit()}),
921 .length = len,
922 .y_end = y_end,
923 .rate = rate,
924 .backwards = m_reverse};
925 if(w.n <= 0 || w.positions() <= 0 || !self().capture_layer_ready(w))
926 return;
927
928 passes_changing();
929 const auto pushes = st.pushes;
930 begin_pass_now(w.start());
931 if(st.pushes == pushes)
932 return;
933 self().capture_layer_into(w);
934 close_pass();
935
936 if(stopped)
937 {
938 self().set_head(y_now, len);
939 transport.play_captured(transport.playbackPos);
940 self().captured();
941 }
942}
943
949template <typename Node>
951{
952 auto& st = stack();
953 // A pass that got nothing is no take.
954 const bool taking = pass_open();
955 m_pass_pending = false;
956 const bool recording = taking && transport.actualMode == LoopMode::Record;
957 const double date
958 = recording ? transport.recordStartDate.impl / double(ossia::flicks_per_second<int64_t>)
959 : m_export_date;
960 const int64_t pos = st.length > 0 ? transport.playbackPos % st.length : 0;
961 self().stopping();
962 if(taking)
963 self().close_on_stop(recording);
964 const bool take = !st.empty() && (taking || !last_take_whole());
965 transport.reset();
966 m_switches.reset();
967 transport.stopped(st.empty());
968 Looper::show_loop(self().inputs.mode, transport, m_loop);
969 m_stops++;
970 if(take)
971 {
972 m_stop_stretch = self().take_stretch();
973 self().write_stop_take(recording, date, pos);
974 }
975 m_view_countdown = 0;
976}
977
980template <typename Node>
982{
983 n.view = m_view;
984 self().fill_message(n.view);
985 n.view.stopped = true;
986 n.take = m_takes_written + 1;
987 n.date = date;
988 n.stretch = m_stop_stretch;
989 n.extension = Node::take_extension;
990}
991
997template <typename Node>
999{
1000 auto& st = stack();
1001 const bool asked = m_take_request > m_view.answered;
1002 if(asked && st.empty())
1003 {
1004 // Nothing to take: the last take is what there is.
1005 m_view.answered = m_take_request;
1006 m_view_countdown = 0;
1007 }
1008 // Passes asked for alone, of the loop the view showed: one that changed
1009 // since has nothing to give.
1010 auto& part = m_part_request;
1011 const bool part_asked = part.take > m_view.part_answered;
1012 if(part_asked
1013 && (st.empty() || part.layers != st.count || part.pass < 0 || part.last < part.pass
1014 || part.last >= st.count))
1015 {
1016 m_view.part_answered = part.take;
1017 part.take = 0;
1018 m_view_countdown = 0;
1019 }
1020 else if(
1021 part_asked && !m_writing && self().writer && self().export_ready())
1022 {
1023 // The files of the passes as they are now, the one taken in as far as it
1024 // went, under a take number of their own.
1025 m_job_part = true;
1026 m_job_request = part.take;
1027 part.take = 0;
1028 const bool open = pass_open();
1029 const bool recording = open && transport.actualMode == LoopMode::Record;
1030 m_job_stretch = self().take_stretch();
1031 m_export_tempo = recording ? m_tempo : m_loop_tempo;
1032 m_export_signature = m_signature;
1033 m_export_mix = false;
1034 m_export_first = part.pass;
1035 m_export_layers = part.last + 1;
1036 m_export_number = 0;
1037 m_export_slot = claim_take_slot();
1038 self().start_export(st.count, open, recording);
1039 }
1040 else if(
1041 (m_export_pending || asked) && !m_writing && self().writer && !st.empty()
1042 && self().export_ready())
1043 {
1044 m_export_pending = false;
1045 m_job_part = false;
1046 m_job_request = m_take_request;
1047 m_job_version = m_version;
1048 const bool open = asked && pass_open();
1049 const bool recording = open && transport.actualMode == LoopMode::Record;
1050 if(recording)
1051 m_export_date = transport.recordStartDate.impl / double(ossia::flicks_per_second<int64_t>);
1052 m_job_date = m_export_date;
1053 m_job_stretch = self().take_stretch();
1054 m_export_tempo = recording ? m_tempo : m_loop_tempo;
1055 m_export_signature = m_signature;
1056 const int passes
1057 = open || !pass_open() ? st.count : std::max(1, st.count - 1);
1058 // A take the view asks for has every file, which Export only chooses
1059 // from; one a pass closing makes has the ones Export says.
1060 const auto files = self().inputs.exported.value;
1061 m_export_mix = asked || files != Looper::TakeFiles::Layers;
1062 m_export_layers = asked || files != Looper::TakeFiles::Mix ? passes : 0;
1063 m_export_first = m_export_mix ? -1 : 0;
1064 m_export_number = 0;
1065 m_export_slot = claim_take_slot();
1066 self().start_export(passes, open, recording);
1067 }
1068 self().continue_export();
1069}
1070
1071template <typename Node>
1072void looper_node<Node>::take_written(int slot, int number, bool ok, bool alone)
1073{
1074 auto& writer = self().writer;
1075 if(alone)
1076 {
1077 self().stop_take_written();
1078 if(!ok || !writer)
1079 return;
1080 // The views were told of it from the worker: a looper that plays on
1081 // after its stop shows it in its own messages as well.
1082 const auto& v = self().stop_notice().view;
1083 m_takes_written++;
1084 m_view.set_take(
1085 v.take_base.data(), v.take_ext.data(), v.take_mix, v.take_layers, v.take_stretch,
1086 v.take_placeable);
1087 m_view.take = m_takes_written;
1088 m_view.take_version = v.take_version;
1089 m_view.take_date = v.take_date;
1090 m_view_countdown = 0;
1091 return;
1092 }
1093 m_writing = false;
1094 if(self().take_cancelled() || !ok || !writer)
1095 {
1096 // What was being written changed, or the file could not be: the take
1097 // stops there.
1098 self().take_released();
1099 auto& answered = m_job_part ? m_view.part_answered : m_view.answered;
1100 answered = std::max(answered, m_job_request);
1101 m_view_countdown = 0;
1102 return;
1103 }
1104
1105 m_export_number = number;
1106 if(m_export_item + 1 < m_export_layers)
1107 {
1108 self().export_item(m_export_item + 1);
1109 return;
1110 }
1111
1112 self().take_released();
1113 if(m_job_part)
1114 {
1115 m_view.set_part(
1116 writer->take_base(slot), m_export_first, m_export_layers - 1, m_job_request,
1117 m_job_stretch, placeable_take());
1118 m_view.part_answered = std::max(m_view.part_answered, m_job_request);
1119 m_view_countdown = 0;
1120 return;
1121 }
1122 m_takes_written++;
1123 m_view.take_version = m_job_version;
1124 m_view.set_take(
1125 writer->take_base(slot), Node::take_extension, m_export_mix, m_export_layers,
1126 m_job_stretch, placeable_take());
1127 m_view.take = m_takes_written;
1128 m_view.take_date = m_job_date;
1129 m_view.answered = std::max(m_view.answered, m_job_request);
1130 // Force a view update with the new take.
1131 m_view_countdown = 0;
1132}
1133
1136template <typename Node>
1138{
1139 const auto& st = stack();
1140 v.empty = st.empty();
1141 v.mode = int(transport.actualMode);
1142 v.requested = int(transport.quantizedPlayMode);
1143 v.wanted = int(m_mode);
1144 v.layers = st.count;
1145 v.redo = st.total - st.count;
1146 v.full = self().view_full();
1147 v.length_seconds = float(st.length / m_rate);
1148 v.position
1149 = st.length > 0 ? float(transport.playbackPos % st.length) / float(st.length) : 0.f;
1150 v.tempo = float(m_tempo);
1151 v.writing = self().view_writing();
1152 v.version = m_version;
1153 v.reverse = m_reverse;
1154 v.speed = float(m_speed);
1155 v.selected = self().inputs.layer.value - 1;
1156 v.stopped = false;
1157 v.looper = m_id;
1158 v.stops = m_stops;
1159 v.restart_pending = transport.restart_pending();
1160 v.new_pass_pending = transport.new_pass_pending();
1161 const int lanes = std::min(st.count, Looper::view_message::max_lanes);
1162 const int merged = st.count - lanes;
1163 v.muted.fill(0);
1164 for(int l = merged > 0 ? 1 : 0; l < lanes; l++)
1165 v.muted[l] = st.layers[l + merged].muted;
1166}
1167}
Definition AudioLayers.hpp:14
@ Extend
Makes the loop longer by the loop's length, the passes under it repeating.
@ MergeOldest
The two oldest layers are merged into one.
@ MergeIntoNewest
The pass is merged into the newest layer once it ends.
double clamp_speed(float s) noexcept
A speed the loopers can play at, whatever a port sends.
Definition score-plugin-fx/Fx/Looper/Transport.hpp:181
transport::timeline_point timeline_start(const ossia::token_request &tk, ossia::exec_state_facade st) noexcept
Definition score-plugin-fx/Fx/Looper/Transport.hpp:1052
void show_loop(Field &f, const transport &t, loop_input &l)
Mode shows what is asked for, once the tick has run.
Definition Switches.hpp:145
int64_t frames_to_loop_end(double y, int64_t len, double rate) noexcept
Definition score-plugin-fx/Fx/Looper/Transport.hpp:248
int64_t loop_round(int64_t len, double rate) noexcept
Definition score-plugin-fx/Fx/Looper/Transport.hpp:240
Definition LooperNode.hpp:126
int64_t start() const noexcept
The first position of what it goes over, in the loop's own order.
Definition LooperNode.hpp:145
int64_t positions() const noexcept
Positions of the loop the window goes over, a round at most.
Definition LooperNode.hpp:140
int64_t progress(int64_t t) const noexcept
The positions gone over from the window's start to input frame t.
Definition LooperNode.hpp:160
int64_t position(int64_t t) const noexcept
The loop position input frame t falls on, as an overdub records it.
Definition LooperNode.hpp:151
Definition LooperView.hpp:61
Definition Switches.hpp:116
bool arrived
A value came in this tick, whatever it was.
Definition Switches.hpp:118
Definition score-plugin-fx/Fx/Looper/Transport.hpp:228
Definition LooperNode.hpp:169
void clear_loop()
Definition LooperNode.hpp:531
int64_t m_extend_unit
Definition LooperNode.hpp:382
double m_job_date
Definition LooperNode.hpp:396
int m_take_request
Definition LooperNode.hpp:411
uint64_t m_id
Tells this looper's messages from another's, and counts its stops.
Definition LooperNode.hpp:433
void looper_rephase(const ossia::token_request &tk)
Definition LooperNode.hpp:833
void stop_score()
Definition LooperNode.hpp:950
void capture_layer(const tick &tk, int64_t now, int64_t end)
Definition LooperNode.hpp:896
int m_version
Definition LooperNode.hpp:420
bool pass_open() const noexcept
A pass is open in the stack.
Definition LooperNode.hpp:218
bool placeable_take() const noexcept
Definition LooperNode.hpp:355
void begin_pass(int64_t start)
Definition LooperNode.hpp:636
halp::clip_stretch m_job_stretch
Definition LooperNode.hpp:406
LoopMode m_mode
The mode asked for, as the transport takes it up.
Definition LooperNode.hpp:367
void passes_changing()
The passes, or what is heard of them, are about to change.
Definition LooperNode.hpp:258
bool m_cycle_pending
Definition LooperNode.hpp:386
void undo_pass()
Definition LooperNode.hpp:570
void material_arriving()
Definition LooperNode.hpp:222
bool m_pass_pending
Definition LooperNode.hpp:424
double rounds_since_made(const ossia::token_request &tk) const noexcept
Definition LooperNode.hpp:308
void close_pass(bool cycle=false)
Definition LooperNode.hpp:664
void pass_advanced(double frames, bool backwards)
The pass being taken in went over frames more frames of the loop.
Definition LooperNode.hpp:236
double pass_progress() const noexcept
The frames the pass being taken in went over.
Definition LooperNode.hpp:247
void erase_pass(int pass)
Definition LooperNode.hpp:580
void prepare_stop_notice(Looper::stop_take_notice &n, double date)
Definition LooperNode.hpp:981
int64_t looper_trim_extension(int64_t max_frames)
Definition LooperNode.hpp:753
bool pass_pending() const noexcept
Definition LooperNode.hpp:216
void run_export()
Definition LooperNode.hpp:998
void view_request(const Looper::erase_request &r) noexcept
From the view: applied at the start of the next tick.
Definition LooperNode.hpp:270
void mute_pass(int pass)
A muted pass is kept as it is; muting it again lets it be heard.
Definition LooperNode.hpp:600
void loop_arrived() noexcept
A value came in on Mode.
Definition LooperNode.hpp:176
Looper::transport::timeline_point m_origin
Definition LooperNode.hpp:377
double m_speed
The playhead goes at m_speed, backwards when m_reverse is set.
Definition LooperNode.hpp:373
Looper::erase_request m_part_request
Definition LooperNode.hpp:415
Looper::loop_input m_loop
What came in on Mode, and what it shows.
Definition LooperNode.hpp:369
double m_loop_tempo
Tempo the loop was recorded at: the loop keeps its own speed.
Definition LooperNode.hpp:379
void drop_taken_pass()
Definition LooperNode.hpp:549
Looper::loop_period looper_period(const ossia::token_request &tk) const noexcept
Definition LooperNode.hpp:815
int64_t looper_frames_to_end() const noexcept
An extending overdub grows the loop at its end instead of reaching it.
Definition LooperNode.hpp:797
bool last_take_whole() const noexcept
The last take has the mix and every pass as they are now.
Definition LooperNode.hpp:322
void capture(const tick &tk)
Definition LooperNode.hpp:848
void fill_view(Looper::view_message &v) const noexcept
Definition LooperNode.hpp:1137
bool view_due(int64_t frames) noexcept
Whether the view is due a message: some 30 a second.
Definition LooperNode.hpp:342
int make_room()
Definition LooperNode.hpp:614
Definition TakeWriter.hpp:153
Definition Switches.hpp:529
void apply(Inputs &in, Engine &e)
On the grid point.
Definition Switches.hpp:615
void follow_merges(Inputs &in, Log &log)
Definition Switches.hpp:691
static constexpr int slots
Definition TakeWriter.hpp:47
Definition score-plugin-fx/Fx/Looper/Transport.hpp:317
Definition score-plugin-fx/Fx/Looper/Transport.hpp:269
double loop_tempo(const ossia::token_request &tk, int64_t frames) const noexcept
Definition score-plugin-fx/Fx/Looper/Transport.hpp:302
ossia::time_value recordStartDate
Model date at which the current recording started.
Definition score-plugin-fx/Fx/Looper/Transport.hpp:295
void stopped(bool empty) noexcept
Definition score-plugin-fx/Fx/Looper/Transport.hpp:481
void restart_recording() noexcept
Definition score-plugin-fx/Fx/Looper/Transport.hpp:437
void taking(LoopMode m) noexcept
Called by the looper whenever it records or overdubs.
Definition score-plugin-fx/Fx/Looper/Transport.hpp:336
std::optional< int64_t > lastPointFrame
Where the last grid point fell, on the same count.
Definition score-plugin-fx/Fx/Looper/Transport.hpp:293
LoopMode wanted
Definition score-plugin-fx/Fx/Looper/Transport.hpp:273
int64_t frame
Frames run since the transport was created, at the start of this tick.
Definition score-plugin-fx/Fx/Looper/Transport.hpp:291
void play_captured(int64_t position) noexcept
Definition score-plugin-fx/Fx/Looper/Transport.hpp:464
bool restart_pending() const noexcept
What waits for the next grid point besides a change of mode.
Definition score-plugin-fx/Fx/Looper/Transport.hpp:451
void settle(bool empty) noexcept
A loop emptied while Overdub is asked for, e.g. by Clear: it is recorded.
Definition score-plugin-fx/Fx/Looper/Transport.hpp:383
Definition LooperView.hpp:72
int take
Takes written so far; the files of the last one are take_files().
Definition LooperView.hpp:86