442 const std::shared_ptr<QImage> frame_image = frame->GetImage();
443 int width = frame_image ? std::max(1, frame_image->width()) : 1;
444 int height = frame_image ? std::max(1, frame_image->height()) : 1;
453 const float intensity_value = clampf(
intensity.
GetValue(frame_number), 0.0f, 10.0f);
454 const float smoothing_value = clampf(
smoothing.
GetValue(frame_number), 0.0f, 1.0f);
455 const float detail_value = clampf(
detail.
GetValue(frame_number), 0.0f, 1.0f);
456 const float glow_value = clampf(
glow.
GetValue(frame_number), 0.0f, 1.0f);
464 const float low_hz = uses_frequency ? normalized_frequency_to_hz(
frequency_low.
GetValue(frame_number),
false) : 20.0f;
465 const float high_hz = uses_frequency
468 const int channels = std::max(1, frame->GetAudioChannelsCount());
475 auto visual = std::make_shared<QImage>(width, height,
476 use_filled_waveform_argb ? QImage::Format_ARGB32_Premultiplied : QImage::Format_RGBA8888_Premultiplied);
478 if (frame_image->width() == width && frame_image->height() == height)
479 *visual = frame_image->copy();
481 *visual = frame_image->scaled(width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
483 visual->fill(Qt::transparent);
486 QPainter painter(visual.get());
488 (frame_image->width() != width || frame_image->height() != height)) {
489 painter.setRenderHint(QPainter::SmoothPixmapTransform,
true);
492 const QColor base = color_at(
color, frame_number);
493 const float styled_glow = style_glow_amount(
style, glow_value);
494 const Palette palette = make_palette(base, styled_glow,
style, color_spread_value);
497 painter.fillRect(visual->rect(), palette.dark);
499 QLinearGradient grad(0, 0, 0, height);
500 grad.setColorAt(0.0, alpha_color(palette.dark, 0.0f));
501 grad.setColorAt(1.0, alpha_color(palette.dark, 0.55f));
502 painter.fillRect(visual->rect(), grad);
504 QLinearGradient grad(0, 0, width, height);
505 grad.setColorAt(0.0, alpha_color(palette.dark, 0.55f));
506 grad.setColorAt(1.0, alpha_color(palette.accent, 0.35f));
507 painter.fillRect(visual->rect(), grad);
510 if (frame->GetAudioSamplesCount() <= 0) {
512 frame->AddImage(visual);
516 const float gain = std::max(0.01f, intensity_value);
517 const float stroke = style_stroke_width(
style, styled_glow);
519 painter.setRenderHint(QPainter::Antialiasing, !is_waveform_mode || styled_glow > 0.01f || stroke > 1.05f);
521 if (is_waveform_mode) {
522 painter.setRenderHint(QPainter::Antialiasing,
false);
523 const int lanes = split ? std::min(channels, 8) : (overlay ? std::min(channels, 8) : 1);
524 const int columns = clampi(std::lround(width * (0.75f + detail_value * 0.25f)), 16, std::max(16, width));
525 for (
int lane = 0; lane < lanes; ++lane) {
526 const std::vector<float> values = (split || overlay) ? channel_samples(frame, lane, columns, gain, smoothing_value)
527 : combined_samples(frame, columns, gain, smoothing_value);
528 const float lane_top = split ? height * lane /
static_cast<float>(lanes) : 0.0f;
529 const float lane_height = split ? height /
static_cast<float>(lanes) :
static_cast<float>(height);
530 const float center_y = lane_top + lane_height * 0.5f;
531 const float amplitude = lane_height * 0.44f;
533 ? rainbow_color(base, lane /
static_cast<float>(std::max(1, lanes)), color_spread_value)
534 : (overlay ? mix_color(base, hue_shift(base, lane * 18), color_spread_value * 0.18f) : (lane % 2 ? palette.accent : base));
535 const Palette lane_palette = make_palette(lane_base, styled_glow,
style, color_spread_value);
538 top_edge.reserve(
static_cast<int>(values.size()));
539 for (
int i = 0; i < static_cast<int>(values.size()); ++i) {
540 const float x = (values.size() <= 1) ? 0.0f : (width - 1) * i /
static_cast<float>(values.size() - 1);
541 top_edge.append(QPointF(x, center_y - values[i] * amplitude));
544 const float alpha_scale = overlay ? 0.48f : 1.0f;
547 painter.setPen(Qt::NoPen);
548 if (styled_glow > 0.01f) {
550 QLinearGradient glow_grad(0, 0, width, 0);
551 set_rainbow_stops(glow_grad, base, color_spread_value, 0.78f * alpha_scale);
552 painter.setBrush(QBrush(glow_grad));
554 painter.setBrush(alpha_color(lane_palette.glow, 0.78f * alpha_scale));
556 const int glow_step = 4;
557 const int glow_pad = std::max(1,
static_cast<int>(std::ceil(styled_glow * 5.0f)));
558 for (
int i = 0; i < static_cast<int>(values.size()); i += glow_step) {
559 const int x0 = (i * width) /
static_cast<int>(values.size());
560 const int x1 = ((std::min<int>(i + glow_step, values.size())) * width) /
static_cast<int>(values.size());
561 float envelope = 0.0f;
562 for (
int j = i; j < std::min<int>(i + glow_step, values.size()); ++j) {
563 const float magnitude = std::fabs(values[j]) * amplitude;
564 envelope = std::max(envelope, values[j] == 0.0f ? 0.0f : std::max(1.0f, magnitude));
566 const int y0 = clampi(
static_cast<int>(std::floor(center_y - envelope)) - glow_pad, 0, height);
567 const int y1 = clampi(
static_cast<int>(std::ceil(center_y + envelope)) + glow_pad, y0 + 1, height);
568 painter.drawRect(QRect(x0, y0, std::max(1, x1 - x0), y1 - y0));
573 QLinearGradient fill_grad(0, 0, width, 0);
574 set_rainbow_stops(fill_grad, base, color_spread_value, style_fill_alpha(
style) * alpha_scale);
575 painter.setBrush(QBrush(fill_grad));
577 painter.setBrush(vertical_style_fill(0, lane_top, 0, lane_top + lane_height, lane_palette,
style, alpha_scale));
579 for (
int i = 0; i < static_cast<int>(values.size()); ++i) {
580 const int x0 = (i * width) /
static_cast<int>(values.size());
581 const int x1 = ((i + 1) * width) /
static_cast<int>(values.size());
582 const float magnitude = std::fabs(values[i]) * amplitude;
583 const float envelope = values[i] == 0.0f ? 0.0f : std::max(1.0f, magnitude);
584 const int y0 = clampi(
static_cast<int>(std::floor(center_y - envelope)), 0, height);
585 const int y1 = clampi(
static_cast<int>(std::ceil(center_y + envelope)), y0 + 1, height);
586 painter.drawRect(QRect(x0, y0, std::max(1, x1 - x0), y1 - y0));
589 const float line_width = std::max(1.0f, stroke);
591 if (styled_glow > 0.01f) {
592 QLinearGradient glow_grad(0, 0, width, 0);
593 set_rainbow_stops(glow_grad, base, color_spread_value, 0.88f * alpha_scale);
594 painter.setPen(QPen(QBrush(glow_grad), line_width + styled_glow * 16.0f, Qt::SolidLine, Qt::FlatCap));
595 painter.drawPolyline(top_edge);
597 QLinearGradient line_grad(0, 0, width, 0);
598 set_rainbow_stops(line_grad, base, color_spread_value, alpha_scale);
599 painter.setPen(QPen(QBrush(line_grad), line_width, Qt::SolidLine, Qt::FlatCap));
600 painter.drawPolyline(top_edge);
602 if (styled_glow > 0.01f) {
603 painter.setPen(QPen(alpha_color(lane_palette.glow, 0.88f * alpha_scale),
604 line_width + styled_glow * 16.0f, Qt::SolidLine, Qt::FlatCap));
605 painter.drawPolyline(top_edge);
607 painter.setPen(QPen(alpha_color(lane_palette.base, alpha_scale),
608 line_width, Qt::SolidLine, Qt::FlatCap));
609 painter.drawPolyline(top_edge);
614 const int bars = clampi(std::lround(16 + detail_value * 112), 8, std::max(8, width / 3));
615 const std::vector<float> bins = spectrum_bins(frame, bars, gain, smoothing_value, low_hz, high_hz);
616 const float gap = std::max(1.0f, width /
static_cast<float>(bars) * 0.18f);
617 const float bar_width = std::max(1.0f, width /
static_cast<float>(bars) - gap);
618 for (
int i = 0; i < bars; ++i) {
619 const float x = i * (bar_width + gap);
620 const float h = std::max(1.0f, bins[i] * height * 0.88f);
621 QRectF rect(x, height - h, bar_width, h);
623 ? make_palette(rainbow_color(base, i /
static_cast<float>(std::max(1, bars - 1)), color_spread_value), styled_glow,
style, color_spread_value)
625 if (styled_glow > 0.01f)
626 painter.fillRect(rect.adjusted(-styled_glow * 2.0f, -styled_glow * 5.0f, styled_glow * 2.0f, 0), alpha_color(bar_palette.glow, 0.55f));
627 painter.fillRect(rect, vertical_style_fill(0, rect.top(), 0, height, bar_palette,
style));
630 const int bins_count = clampi(std::lround(56 + detail_value * 220), 40, std::max(40, width / 2));
631 const std::vector<float> bins = spectrum_bins(frame, bins_count, gain, smoothing_value, low_hz, high_hz);
632 std::vector<QPointF> points;
633 points.reserve(bins_count);
634 for (
int i = 0; i < bins_count; ++i) {
635 const float x = i * width /
static_cast<float>(std::max(1, bins_count - 1));
636 const float h = std::max(1.0f, bins[i] * height * 0.84f);
637 points.emplace_back(x, height - h);
641 if (!points.empty()) {
642 ridge.moveTo(points.front());
643 for (
size_t i = 0; i + 1 < points.size(); ++i) {
644 const QPointF& p0 = points[i == 0 ? i : i - 1];
645 const QPointF& p1 = points[i];
646 const QPointF& p2 = points[i + 1];
647 const QPointF& p3 = points[std::min(i + 2, points.size() - 1)];
648 const QPointF c1 = p1 + (p2 - p0) / 6.0;
649 const QPointF c2 = p2 - (p3 - p1) / 6.0;
650 ridge.cubicTo(c1, c2, p2);
654 QBrush terrain_brush;
656 QLinearGradient rainbow_fill(0, 0, width, 0);
657 set_rainbow_stops(rainbow_fill, base, color_spread_value, style_fill_alpha(
style));
658 terrain_brush = QBrush(rainbow_fill);
660 terrain_brush = vertical_style_fill(0, 0, 0, height, palette,
style);
662 if (points.size() > 1) {
663 QImage fill_layer(width, height, QImage::Format_RGBA8888_Premultiplied);
664 fill_layer.fill(Qt::transparent);
665 QPainter fill_painter(&fill_layer);
666 fill_painter.fillRect(fill_layer.rect(), terrain_brush);
669 QImage mask(width, height, QImage::Format_RGBA8888_Premultiplied);
670 mask.fill(Qt::transparent);
671 QPainter mask_painter(&mask);
672 mask_painter.setRenderHint(QPainter::Antialiasing,
false);
673 mask_painter.setPen(QPen(Qt::white, 1.0f));
674 for (
int x = 0; x < width; ++x) {
675 const float position = x * (points.size() - 1) /
static_cast<float>(std::max(1, width - 1));
676 const int index = clampi(
static_cast<int>(std::floor(
position)), 0,
static_cast<int>(points.size()) - 2);
678 const float y = points[index].y() * (1.0f - mix) + points[index + 1].y() * mix;
679 mask_painter.drawLine(QPointF(x + 0.5f, y), QPointF(x + 0.5f, height));
683 fill_painter.begin(&fill_layer);
684 fill_painter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
685 fill_painter.drawImage(0, 0, mask);
687 painter.drawImage(0, 0, fill_layer);
689 const float ridge_width = std::max(1.0f, stroke * 0.75f);
691 QLinearGradient glow_grad(0, 0, width, 0);
692 set_rainbow_stops(glow_grad, base, color_spread_value, 0.45f);
693 if (styled_glow > 0.01f) {
694 painter.setPen(QPen(QBrush(glow_grad), ridge_width + styled_glow * 10.0f, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
695 painter.drawPath(ridge);
697 QLinearGradient ridge_grad(0, 0, width, 0);
698 set_rainbow_stops(ridge_grad, base, color_spread_value);
699 painter.setPen(QPen(QBrush(ridge_grad), ridge_width, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
700 painter.drawPath(ridge);
702 draw_glow_path(painter, ridge, palette, ridge_width, styled_glow * 0.55f);
705 const int segments = clampi(std::lround(48 + detail_value * 192), 24, 256);
706 const std::vector<float> bins = spectrum_bins(frame, segments, gain, smoothing_value, low_hz, high_hz);
707 const QPointF center(width * 0.5, height * 0.5);
708 const float radius = std::min(width, height) * 0.24f;
709 const float spike = std::min(width, height) * 0.24f;
711 for (
int i = 0; i <= segments; ++i) {
712 const int idx = i % segments;
713 const double angle = -PI * 0.5 + (2.0 * PI * i / segments);
714 const float r = radius + bins[idx] * spike;
715 const QPointF p(center.x() + std::cos(angle) * r, center.y() + std::sin(angle) * r);
721 QConicalGradient grad(center, -90);
723 set_rainbow_stops(grad, base, color_spread_value);
725 grad.setColorAt(0.0, palette.base);
726 grad.setColorAt(0.45, palette.light);
727 grad.setColorAt(1.0, palette.accent);
729 if (styled_glow > 0.01f) {
730 QBrush glow_brush(palette.glow);
732 QConicalGradient glow_grad(center, -90);
733 set_rainbow_stops(glow_grad, base, color_spread_value, 0.48f);
734 glow_brush = QBrush(glow_grad);
736 QPen glow_pen(glow_brush, stroke + styled_glow * 16.0f, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
737 painter.setPen(glow_pen);
738 painter.drawPath(ring);
740 painter.setPen(QPen(QBrush(grad), stroke + 1.0f, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
741 painter.drawPath(ring);
743 const int bars = clampi(std::lround(36 + detail_value * 156), 24, 220);
744 const std::vector<float> bins = spectrum_bins(frame, bars, gain, smoothing_value, low_hz, high_hz);
745 const QPointF center(width * 0.5, height * 0.5);
746 const float min_dimension = std::min(width, height);
747 const float inner_radius = min_dimension * 0.22f;
748 const float max_length = min_dimension * 0.28f;
749 const float bar_width = std::max(1.0f,
static_cast<float>((2.0 * PI * inner_radius / bars) * 0.55));
750 for (
int i = 0; i < bars; ++i) {
751 const double angle = -PI * 0.5 + (2.0 * PI * i / bars);
752 const float length = std::max(min_dimension * 0.012f, bins[i] * max_length);
753 const QPointF
start(center.x() + std::cos(angle) * inner_radius, center.y() + std::sin(angle) * inner_radius);
754 const QPointF
end(center.x() + std::cos(angle) * (inner_radius + length), center.y() + std::sin(angle) * (inner_radius + length));
756 ? rainbow_color(base, i /
static_cast<float>(std::max(1, bars - 1)), color_spread_value)
757 : mix_color(palette.base, palette.light, bins[i] * 0.35f);
758 if (styled_glow > 0.01f) {
760 painter.setPen(QPen(glow_color, bar_width + styled_glow * 8.0f, Qt::SolidLine, Qt::RoundCap));
763 painter.setPen(QPen(c, bar_width, Qt::SolidLine, Qt::RoundCap));
769 auto result =
GetFrame(frame, frame_number);
773 painter.setRenderHint(QPainter::Antialiasing,
false);
774 const int count = clampi(std::lround(96 + detail_value * 224), 64, 320);
775 const int samples = frame->GetAudioSamplesCount();
776 const float *left = frame->GetAudioSampleBuffer()->getReadPointer(0);
777 const float *right = frame->GetAudioSampleBuffer()->getReadPointer(1);
779 trace.reserve(count);
780 float previous_x = width * 0.5f;
781 float previous_y = height * 0.5f;
782 for (
int i = 0; i < count; ++i) {
783 const int sample = clampi((i * samples) / count, 0, samples - 1);
784 const float raw_x = width * 0.5f + clampf((left[sample] - right[sample]) * gain * 0.75f, -1.0f, 1.0f) * width * 0.38f;
785 const float raw_y = height * 0.5f - clampf((left[sample] + right[sample]) * gain * 0.38f, -1.0f, 1.0f) * height * 0.42f;
786 const float x = previous_x * 0.72f + raw_x * 0.28f;
787 const float y = previous_y * 0.72f + raw_y * 0.28f;
788 trace.append(QPointF(x, y));
792 painter.setPen(QPen(alpha_color(palette.dark, 0.35f), 1.0f));
793 painter.drawLine(QPointF(width * 0.5f, height * 0.12f), QPointF(width * 0.5f, height * 0.88f));
794 painter.drawLine(QPointF(width * 0.12f, height * 0.5f), QPointF(width * 0.88f, height * 0.5f));
795 const float trace_width = std::max(1.0f, stroke);
797 QRectF rainbow_bounds = trace.boundingRect();
798 const qreal min_span = std::min(width, height) * 0.22;
799 if (rainbow_bounds.width() < min_span)
800 rainbow_bounds.adjust((rainbow_bounds.width() - min_span) * 0.5, 0.0, (min_span - rainbow_bounds.width()) * 0.5, 0.0);
801 if (rainbow_bounds.height() < min_span)
802 rainbow_bounds.adjust(0.0, (rainbow_bounds.height() - min_span) * 0.5, 0.0, (min_span - rainbow_bounds.height()) * 0.5);
803 rainbow_bounds = rainbow_bounds.intersected(QRectF(0, 0, width, height));
804 if (styled_glow > 0.01f) {
805 QLinearGradient glow_grad(rainbow_bounds.left(), rainbow_bounds.center().y(), rainbow_bounds.right(), rainbow_bounds.center().y());
806 set_rainbow_stops(glow_grad, base, color_spread_value, 0.42f);
807 painter.setPen(QPen(QBrush(glow_grad), trace_width + styled_glow * 10.0f, Qt::SolidLine, Qt::FlatCap, Qt::BevelJoin));
808 painter.drawPolyline(trace);
810 QLinearGradient trace_grad(rainbow_bounds.left(), rainbow_bounds.center().y(), rainbow_bounds.right(), rainbow_bounds.center().y());
811 set_rainbow_stops(trace_grad, base, color_spread_value);
812 painter.setPen(QPen(QBrush(trace_grad), trace_width, Qt::SolidLine, Qt::FlatCap, Qt::BevelJoin));
813 painter.drawPolyline(trace);
815 if (styled_glow > 0.01f) {
816 QPen glow_pen(palette.glow, trace_width + styled_glow * 10.0f, Qt::SolidLine, Qt::FlatCap, Qt::BevelJoin);
817 painter.setPen(glow_pen);
818 painter.drawPolyline(trace);
820 QPen pen(palette.base, trace_width, Qt::SolidLine, Qt::FlatCap, Qt::BevelJoin);
822 painter.drawPolyline(trace);
825 const int count = clampi(std::lround(220 + detail_value * 920), 160, 1280);
826 const float level = reactive_level(frame, -1, gain);
827 const std::vector<float> bands = spectrum_bins(frame, 36, gain, smoothing_value, low_hz, high_hz);
828 const float low = band_level(bands, 0.0f, 0.18f);
829 const float mid = band_level(bands, 0.18f, 0.56f);
830 const float high = band_level(bands, 0.56f, 1.0f);
831 const float time = frame_number * (0.028f + level * 0.075f);
832 const QPointF center(width * 0.5f, height * (0.55f - level * 0.08f));
833 const float max_radius = std::sqrt(width * width + height * height) * 0.42f;
834 const float swirl = 0.65f + low * 2.4f;
835 const float twist = 0.45f + mid * 2.1f;
836 const float sparkle = 0.2f + high * 1.8f;
837 std::uniform_real_distribution<float> dist(0.0f, 1.0f);
838 for (
int i = 0; i < count; ++i) {
839 std::mt19937 rng(
static_cast<uint32_t
>((i + 1) * 747796405U));
840 const float seed = dist(rng);
841 const float arm = std::floor(dist(rng) * 5.0f);
842 const float age = std::fmod(seed + time * (0.18f + dist(rng) * 0.62f + level * 0.55f), 1.0f);
843 const float eased_age = std::pow(age, 0.58f);
844 const float base_angle = (arm / 5.0f) * 2.0f * PI + dist(rng) * 0.42f;
845 const float orbit = base_angle + eased_age * swirl * PI + std::sin(time + seed * 12.0f) * twist * 0.22f;
846 const float wave = std::sin(eased_age * PI * (2.0f + sparkle) + seed * 10.0f);
847 const float radius = (0.06f + eased_age * (0.88f + low * 0.35f)) * max_radius * (0.62f + level * 0.74f);
848 const float ribbon = wave * (height * 0.018f + mid * height * 0.055f);
849 const QPointF direction(std::cos(orbit), std::sin(orbit));
850 const QPointF normal(-direction.y(), direction.x());
852 center.x() + direction.x() * radius + normal.x() * ribbon,
853 center.y() + direction.y() * radius + normal.y() * ribbon);
854 const float trail_length = 16.0f + low * 58.0f + level * 64.0f;
856 p.x() - direction.x() * trail_length - normal.x() * ribbon * 0.35f,
857 p.y() - direction.y() * trail_length - normal.y() * ribbon * 0.35f);
858 const float fade = std::sin(age * PI);
859 const float size = 0.42f + fade * (0.95f + high * 2.25f + level * 2.0f);
861 ? rainbow_color(base, std::fmod(age + arm / 5.0f, 1.0f), color_spread_value)
862 : mix_color(palette.base, palette.light, fade * (0.25f + color_spread_value * 0.35f));
863 c = alpha_color(c, (0.12f + level * 0.52f + high * 0.22f) * fade);
864 QPen trail_pen(alpha_color(c, 0.26f + mid * 0.16f), std::max(0.45f, size * (0.28f + low * 0.12f)), Qt::SolidLine, Qt::RoundCap);
865 painter.setPen(trail_pen);
866 painter.drawLine(tail, p);
868 painter.setBrush(alpha_color(c, 1.0f));
870 QRadialGradient grad(p, size * (2.2f + styled_glow * 4.0f));
871 grad.setColorAt(0.0, c);
872 grad.setColorAt(1.0, alpha_color(c, 0.0f));
873 painter.setBrush(grad);
875 painter.setPen(Qt::NoPen);
876 painter.drawEllipse(p, size * 2.0f, size * 2.0f);
879 const int meters = split ? std::min(channels, 8) : 1;
880 const int segments = clampi(std::lround(8 + detail_value * 32), 6, 48);
881 for (
int meter = 0; meter < meters; ++meter) {
882 const float level = reactive_level(frame, split ? meter : -1, gain);
883 const float lane_width = width /
static_cast<float>(meters);
884 const float x0 = meter * lane_width + lane_width * 0.08f;
885 const float seg_gap = std::max(1.0f, height * 0.01f);
886 const float seg_h = (height * 0.88f - seg_gap * (segments - 1)) / segments;
887 for (
int segment = 0; segment < segments; ++segment) {
888 const float t = (segment + 1) /
static_cast<float>(segments);
889 const bool on = t <= level;
894 c = t > 0.82f ? QColor(255, 86, 68, base.alpha()) : (t > 0.55f ? mix_color(palette.accent, QColor(255, 214, 90, base.alpha()), (t - 0.55f) / 0.27f) : mix_color(palette.base, palette.light, t * 1.5f));
897 c = alpha_color(palette.dark, 0.25f);
898 const float y = height * 0.94f - (segment + 1) * seg_h - segment * seg_gap;
899 painter.fillRect(QRectF(x0, y, lane_width * 0.84f, seg_h), c);
905 frame->AddImage(visual);