229 static constexpr double width() {
return 360.; }
232 static constexpr double height() {
return 238.; }
233 static constexpr double header = 26.;
234 static constexpr double send_w = 56.;
239 std::function<void()> update;
253 bool m_send_waiting{};
259 void watch(
const auto& exported)
261 const auto files =
TakeFiles(exported.value);
272 int m_stopped_stops{-1};
278 using clock = std::chrono::steady_clock;
279 static constexpr auto silent_after = std::chrono::milliseconds(300);
280 clock::time_point m_last_message{};
281 bool live() const noexcept
283 return has_state && clock::now() - m_last_message < silent_after;
286 bool waiting() const noexcept {
return m_waiting > 0 && live(); }
296 m_stopped_stops = m.stops;
299 m_send_waiting =
false;
312 m_send_waiting =
false;
315 m_last_message = clock::now();
318 if(m_waiting > 0 && m.answered >= m_waiting)
321 if(std::exchange(m_send_waiting,
false))
329 m_part_files = m.part_files();
330 m_part_placeable = m.part_placeable;
331 m_part_stretch = m.part_stretch;
334 m_part_files.clear();
347 int m_part_first{-1};
349 std::vector<std::string> m_part_files;
350 bool m_part_placeable{};
351 halp::clip_stretch m_part_stretch{};
355 mutable bool m_dragged{};
361 const auto& s = state;
362 if(!has_state || s.empty || lane < 0 || lane >= s.lanes)
364 const int merged = s.layers - s.lanes;
365 if(merged > 0 && lane == 0)
367 return {lane + std::max(0, merged), lane + std::max(0, merged)};
374 && state.take_version == state.
version && state.take_layers > 0;
387 if(
m_part_waiting == 0 && m_part_first == first && m_part_last == last
402 m_part_first = first;
404 m_part_files.clear();
406 .pass = first, .layers = state.layers, .mute =
false, .take =
m_part_waiting,
411 bool preparing(
int lane)
const noexcept
416 bool has_take() const noexcept {
return has_state && state.
take > 0 && state.
take_base[0]; }
423 if(!live() || state.empty)
425 const auto m = LoopMode(state.
mode);
426 return m == LoopMode::Record || m == LoopMode::Overdub;
428 bool saving() const noexcept {
return waiting() || (live() && state.writing); }
434 return (
m_export == TakeFiles::Layers || state.take_mix)
435 && (
m_export == TakeFiles::Mix || state.take_layers > 0);
452 std::vector<std::string> files;
453 const std::string base{state.
take_base.data()}, ext{state.take_ext.data()};
457 files.push_back(base +
"." + ext);
461 files.insert(files.end(), layers.begin(), layers.end());
474 has_state ? state.part_answered : 0})
487 m_send_waiting =
true;
498 std::string_view base{state.
take_base.data()};
499 if(
auto slash = base.find_last_of(
"/\\"); slash != std::string_view::npos)
500 base.remove_prefix(slash + 1);
504 .name = std::string{base},
508 static rgba mode_color(
int mode,
bool empty)
noexcept
510 if(empty && mode !=
int(LoopMode::Record))
511 return {110, 110, 116, 255};
512 switch(LoopMode(mode))
514 case LoopMode::Record:
515 return {232, 64, 60, 255};
516 case LoopMode::Overdub:
517 return {255, 150, 40, 255};
519 return {88, 200, 112, 255};
522 return {150, 150, 156, 255};
526 static const char* mode_name(
int mode)
noexcept
528 switch(LoopMode(mode))
530 case LoopMode::Record:
532 case LoopMode::Overdub:
545 constexpr rgba bg{34, 34, 38, 255};
547 uint8_t((c.r + 2 * bg.r) / 3), uint8_t((c.g + 2 * bg.g) / 3),
548 uint8_t((c.b + 2 * bg.b) / 3), 255};
551 static rgba lane_color(
int lane,
int lanes)
noexcept
553 static constexpr rgba palette[]{
554 {74, 160, 220, 255}, {86, 190, 170, 255}, {150, 200, 90, 255},
555 {220, 190, 70, 255}, {230, 140, 70, 255}, {210, 100, 130, 255},
556 {160, 110, 210, 255}, {110, 130, 230, 255}};
558 return palette[lane % 8];
563 const double w = width(), h =
height();
564 ctx.set_fill_color({24, 24, 26, 255});
566 ctx.draw_rect(0., 0., w, h);
569 const auto& s = state;
570 const auto col = mode_color(has_state ? s.mode : int(LoopMode::
Stop), !has_state || s.empty);
574 const double cx = 13., cy = header / 2., r = 8.;
575 ctx.set_stroke_color({60, 60, 64, 255});
576 ctx.set_stroke_width(3.);
578 ctx.draw_circle(cx, cy, r);
580 if(has_state && !s.empty)
582 ctx.set_stroke_color({col.r, col.g, col.b, col.a});
584 ctx.move_to(cx, cy - r);
585 ctx.arc_to(cx - r, cy - r, 2. * r, 2. * r, 90., -360. * s.position);
588 ctx.set_stroke_width(1.);
592 std::snprintf(text,
sizeof(text),
"Play to start looping");
594 std::snprintf(text,
sizeof(text),
"Stopped with the score");
595 else if(s.wanted != s.requested)
597 text,
sizeof(text),
"%s, then %s on the grid", mode_name(s.mode),
598 mode_name(s.wanted));
599 else if(s.new_pass_pending || s.restart_pending)
601 text,
sizeof(text),
"%s, then %s on the grid", mode_name(s.mode),
602 s.new_pass_pending ? (s.restart_pending ?
"new layer from the start" :
"new layer")
606 text,
sizeof(text),
"%s%s%s%s", mode_name(s.mode),
609 ctx.set_fill_color({col.r, col.g, col.b, col.a});
611 ctx.draw_text(28., cy + 4., text);
619 info,
sizeof(info),
"%.1f s%s%s", s.length_seconds, s.full ?
", full" :
"",
620 s.redo > 0 ?
" (redo)" :
"");
621 double info_w = 6.5 * std::strlen(info);
622 if constexpr(
requires { ctx.text_width(std::string_view{}); })
623 info_w = ctx.text_width(info);
624 ctx.set_fill_color({170, 170, 176, 255});
626 ctx.draw_text(w - send_w - 10. - info_w, cy + 4., info);
633 const rgba button = enabled ? rgba{60, 110, 160, 255} : rgba{44, 44, 48, 255};
634 ctx.set_fill_color({button.r, button.g, button.b, button.a});
636 ctx.draw_rounded_rect(w - send_w - 4., 4., send_w, header - 8., 3.);
638 const rgba label = enabled ? rgba{235, 240, 245, 255} : rgba{110, 110, 116, 255};
639 ctx.set_fill_color({label.r, label.g, label.b, label.a});
642 for(
int row = 0; row < 3; row++)
643 for(
int col = 0; col < 2; col++)
644 ctx.draw_rect(w - send_w + 1. + col * 3., cy - 4. + row * 3.5, 1.5, 1.5);
648 w - send_w + 9., cy + 4., saving() ?
"Saving" : (file_only ?
"File" :
"Send"));
652 const double top = header, lanes_h = h - header - 4.;
653 if(!has_state || s.empty)
657 ctx.set_fill_color({120, 120, 126, 255});
659 ctx.draw_text(10., top + lanes_h / 2.,
"The last take can still be sent.");
670 paint_lanes(ctx, top, lanes_h);
673 const double x = 4. + s.position * (w - 8.);
674 ctx.set_stroke_color({col.r, col.g, col.b, col.a});
675 ctx.set_stroke_width(2.);
678 ctx.line_to(x, top + lane_count() * lane_height());
680 ctx.set_stroke_width(1.);
682 const double dir = s.reverse ? -1. : 1.;
683 ctx.set_fill_color({col.r, col.g, col.b, col.a});
686 ctx.line_to(x + dir * 7., top + 4.);
687 ctx.line_to(x, top + 8.);
692 paint_erase_boxes(ctx);
699 if(
m_part_waiting <= 0 || !live() || m_part_lane < 0 || m_part_lane >= lane_count())
701 const double lh = lane_height();
702 const double y = lanes_top + m_part_lane * lh;
703 ctx.set_fill_color({24, 24, 26, 170});
705 ctx.draw_rect(lanes_x, y + 1., lanes_width(), lh - 2.);
707 ctx.set_fill_color({235, 240, 245, 255});
709 ctx.draw_text(lanes_x + 8., y + lh / 2. + 4.,
"Preparing");
716 for(
auto& [name, value] :
speeds)
717 if(value != 1.f && std::abs(value - state.speed) < 1e-4f)
724 static constexpr double lanes_top = header;
725 static constexpr double lanes_x = 4.;
726 static double lanes_height() noexcept {
return height() - header - 4.; }
727 static double lanes_width() noexcept {
return width() - 8.; }
728 int lane_count() const noexcept {
return std::max(1, state.lanes); }
732 static constexpr double min_lane_height = 6.;
733 double lane_height() const noexcept
735 return std::clamp(lanes_height() / lane_count(), min_lane_height,
max_lane_height);
741 const auto& s = state;
742 if(!has_state || s.empty || lane < 0 || lane >= s.lanes)
744 const int merged = s.layers - s.lanes;
745 if(merged > 0 && lane == 0)
747 return lane + merged;
753 bool contains(
double px,
double py)
const noexcept
755 return px >= x - 1. && px <= x + size + 1. && py >= y - 1. && py <= y + size + 1.;
762 const double lh = lane_height();
763 const double size = std::clamp(lh - 3., 5., 12.);
764 const double y = lanes_top + lane * lh + (lh - size) / 2.;
765 return {lanes_x + lanes_width() - size - 3., y, size};
772 return {e.x - e.size - 4., e.y, e.size};
775 bool lane_muted(
int lane)
const noexcept
777 return lane >= 0 && lane < view_message::max_lanes && state.
muted[lane];
783 const int lane = state.
selected - (state.layers - state.lanes);
789 static constexpr rgba selected_bg{46, 46, 54, 255};
790 rgba lane_background(
int lane)
const noexcept
794 void outline_selected(
auto& ctx,
double top,
double lh)
const
799 ctx.set_stroke_color({190, 190, 200, 150});
800 ctx.set_stroke_width(1.);
802 ctx.draw_rect(4.5, top + l * lh + 1.5, width() - 9., lh - 3.);
811 for(
int l = 0; l < lane_count(); l++)
822 for(
int l = 0; l < lane_count(); l++)
828 void paint_erase_boxes(
auto& ctx)
832 for(
int l = 0; l < lane_count(); l++)
838 const bool muted = lane_muted(l);
839 const rgba fill = muted ?
rgba{230, 160, 60, 255} : rgba{44, 44, 50, 230};
840 const rgba ink = muted ? rgba{24, 24, 26, 255} : rgba{210, 210, 216, 255};
841 ctx.set_fill_color({fill.r, fill.g, fill.b, fill.a});
843 ctx.draw_rounded_rect(b.x, b.y, b.size, b.size, 2.);
846 const double m = std::max(2., b.size * 0.25);
847 const double x0 = b.x + m, x1 = b.x + b.size - m, xm = (x0 + x1) / 2.;
848 const double y0 = b.y + m, y1 = b.y + b.size - m;
849 ctx.set_stroke_color({ink.r, ink.g, ink.b, ink.a});
850 ctx.set_stroke_width(1.5);
854 ctx.line_to(xm, (y0 + y1) / 2.);
858 ctx.set_stroke_width(1.);
861 ctx.set_fill_color({44, 44, 50, 230});
863 ctx.draw_rounded_rect(b.x, b.y, b.size, b.size, 2.);
865 const double m = std::max(2., b.size * 0.28);
866 ctx.set_stroke_color({210, 210, 216, 255});
867 ctx.set_stroke_width(1.5);
869 ctx.move_to(b.x + m, b.y + m);
870 ctx.line_to(b.x + b.size - m, b.y + b.size - m);
871 ctx.move_to(b.x + b.size - m, b.y + m);
872 ctx.line_to(b.x + m, b.y + b.size - m);
874 ctx.set_stroke_width(1.);
878 void paint_lanes(
auto& ctx,
double top,
double)
880 const auto& s = state;
881 const int lanes = std::max(1, s.lanes);
882 const double lh = lane_height();
883 const double x0 = 4., lw = width() - 8.;
884 const double bw = lw / view_message::bins;
885 for(
int l = 0; l < lanes; l++)
887 const double y = top + l * lh;
888 const double mid = y + lh / 2.;
889 const auto bg = lane_background(l);
890 ctx.set_fill_color({bg.r, bg.g, bg.b, bg.a});
892 ctx.draw_rect(x0, y + 1., lw, lh - 2.);
895 auto c = lane_color(l, lanes);
898 ctx.set_fill_color({c.r, c.g, c.b, c.a});
900 const float* p = s.peaks.data() + l * view_message::bins;
901 for(
int b = 0; b < view_message::bins; b++)
903 const double a = std::min(1.f, p[b]) * (lh / 2. - 1.5);
905 ctx.draw_rect(x0 + b * bw, mid - a, std::max(1., bw - 0.5), 2. * a);
909 outline_selected(ctx, top, lh);
916 const auto& s = state;
917 const int lanes = std::max(1, s.lanes);
918 const double lh = lane_height();
919 const double x0 = 4., lw = width() - 8.;
921 std::array<int, view_message::max_lanes> lo, hi;
924 for(
int i = 0; i < s.notes; i++)
926 const auto& n = s.note[i];
927 const int l = std::min<int>(n.lane, view_message::max_lanes - 1);
928 lo[l] = std::min<int>(lo[l], n.pitch);
929 hi[l] = std::max<int>(hi[l], n.pitch);
932 for(
int l = 0; l < lanes; l++)
934 const double y = top + l * lh;
935 const auto lbg = lane_background(l);
936 ctx.set_fill_color({lbg.r, lbg.g, lbg.b, lbg.a});
938 ctx.draw_rect(x0, y + 1., lw, lh - 2.);
942 lo[l] = std::max(0, lo[l] - 1);
943 hi[l] = std::min(127, hi[l] + 1);
947 for(
int heard = 0; heard < 2; heard++)
949 for(
int i = 0; i < s.notes; i++)
951 const auto& n = s.note[i];
952 if(
bool(n.heard) !=
bool(heard))
954 const int l = std::min<int>(n.lane, lanes - 1);
955 const double rh = (lh - 3.) / (hi[l] - lo[l] + 1);
956 const double y = top + l * lh + 1.5 + (hi[l] - n.pitch) * rh;
957 const double nh = std::clamp(rh - 0.5, 2., 6.);
958 auto c = lane_color(l, lanes);
961 ctx.set_fill_color({c.r, c.g, c.b, c.a});
963 const double a = x0 + n.start * lw;
964 const double yc = std::round(y + (rh - nh) / 2.);
966 ctx.draw_rect(a, yc, std::max(2., (n.end - n.start) * lw), nh);
969 ctx.draw_rect(a, yc, std::max(2., (1. - n.start) * lw), nh);
970 ctx.draw_rect(x0, yc, std::max(2., n.end * lw), nh);
975 outline_selected(ctx, top, lh);
984 const auto& s = state;
985 const int lanes = std::max(1, s.lanes);
986 const double lh = lane_height();
987 const double x0 = 4., lw = width() - 8.;
988 const double bw = lw / view_message::bins;
989 for(
int l = 0; l < lanes; l++)
991 const double y = top + l * lh;
992 const auto bg = lane_background(l);
993 ctx.set_fill_color({bg.r, bg.g, bg.b, bg.a});
995 ctx.draw_rect(x0, y + 1., lw, lh - 2.);
998 const float* p = s.peaks.data() + l * view_message::bins;
999 const uint8_t* f = s.value_flags.data() + l * view_message::bins;
1000 const auto base = lane_color(l, lanes);
1001 const auto quiet =
dimmed(base);
1002 const double inner = std::max(2., lh - 8.);
1003 auto height = [&](
int b) {
1005 return y + 4. + (1. - std::clamp(
double(p[b]), 0., 1.)) * inner;
1009 for(
int heard = 0; heard < 2; heard++)
1011 auto c = heard ? base : quiet;
1014 ctx.set_stroke_color({c.r, c.g, c.b, c.a});
1015 ctx.set_stroke_width(heard ? 1.5 : 1.);
1017 for(
int b = 0; b < view_message::bins; b++)
1022 const double xa = x0 + b * bw, ya =
height(b);
1023 ctx.move_to(xa, ya);
1024 ctx.line_to(xa + bw, ya);
1026 if(b + 1 < view_message::bins
1029 const double yb =
height(b + 1);
1030 if(std::abs(yb - ya) > 0.5)
1032 ctx.move_to(xa + bw, ya);
1033 ctx.line_to(xa + bw, yb);
1039 ctx.set_stroke_width(1.);
1042 for(
int b = 0; b < view_message::bins; b++)
1048 auto c = heard ? base : quiet;
1049 ctx.set_fill_color({c.r, c.g, c.b, c.a});
1051 const double xa = x0 + b * bw;
1053 ctx.draw_rect(xa, y + 2., std::max(1.5, bw), lh - 4.);
1055 ctx.draw_rect(xa, y + lh / 2. - 3., std::max(2., bw), 6.);
1059 outline_selected(ctx, top, lh);
1062 bool in_send(
double x,
double y)
const noexcept
1064 return x >= width() - send_w - 4. && x <= width() - 4. && y >= 4. && y <= header - 4.;
1070 if(!has_state || state.empty || x < lanes_x || x > lanes_x + lanes_width())
1072 const double lh = lane_height();
1073 if(y < lanes_top || y >= lanes_top + lane_count() * lh)
1075 return std::min(lane_count() - 1,
int((y - lanes_top) / lh));
1078 bool mouse_press(
double x,
double y)
1084 m_part_files.clear();
1085 m_part_first = m_part_last = -1;
1097 erase(erase_request{.pass =
pass_of_lane(lane), .layers = state.layers, .mute =
false});
1102 erase(erase_request{.pass =
pass_of_lane(lane), .layers = state.layers, .mute =
true});
1106 const int lane =
lane_at(x, y);
1118 void mouse_move(
double,
double) { }
1119 void mouse_release(
double x,
double y)
1125 halp::drag_payload drag_payload(
double x,
double y)
const
1127 halp::drag_payload p;
1136 else if(
const int lane =
lane_at(x, y);
1143 stretch = m_part_stretch;