25 #include <QConicalGradient>
26 #include <QLinearGradient>
29 #include <QPainterPath>
32 #include <QRadialGradient>
34 #include <AppConfig.h>
35 #include <juce_audio_basics/juce_audio_basics.h>
40 constexpr
double PI = 3.14159265358979323846;
42 float clampf(
float value,
float min_value,
float max_value) {
43 return std::max(min_value, std::min(max_value, value));
46 int clampi(
int value,
int min_value,
int max_value) {
47 return std::max(min_value, std::min(max_value, value));
50 QColor color_at(
const Color& color, int64_t frame_number) {
52 clampi(color.
red.
GetInt(frame_number), 0, 255),
54 clampi(color.
blue.
GetInt(frame_number), 0, 255),
58 QColor mix_color(
const QColor& a,
const QColor& b,
float amount) {
59 amount = clampf(amount, 0.0f, 1.0f);
60 const float inv = 1.0f - amount;
62 clampi(std::lround(a.red() * inv + b.red() * amount), 0, 255),
63 clampi(std::lround(a.green() * inv + b.green() * amount), 0, 255),
64 clampi(std::lround(a.blue() * inv + b.blue() * amount), 0, 255),
65 clampi(std::lround(a.alpha() * inv + b.alpha() * amount), 0, 255));
68 QColor alpha_color(QColor color,
float alpha_scale) {
69 color.setAlpha(clampi(std::lround(color.alpha() * alpha_scale), 0, 255));
73 QColor hue_shift(QColor color,
int degrees) {
77 int a = color.alpha();
78 color.getHsv(&h, &s, &v, &a);
81 return QColor::fromHsv((h + degrees + 360) % 360, s, v, a);
84 QColor rainbow_color(
const QColor& seed,
float position,
float spread) {
89 seed.getHsv(&h, &s, &v, &a);
92 const int hue = (h +
static_cast<int>(std::lround(clampf(position, 0.0f, 1.0f) * 360.0f))) % 360;
93 const int saturation = clampi(std::lround(s * (0.65f + clampf(spread, 0.0f, 1.0f) * 0.35f)), 0, 255);
94 const int value = clampi(std::lround(v * (0.88f + clampf(spread, 0.0f, 1.0f) * 0.12f)), 0, 255);
95 return QColor::fromHsv(hue, saturation, value, a);
98 template<
typename Gradient>
99 void set_rainbow_stops(Gradient& gradient,
const QColor& seed,
float spread,
float alpha_scale = 1.0f) {
100 const float stops[] = {0.0f, 0.16f, 0.33f, 0.50f, 0.66f, 0.83f, 1.0f};
101 for (
float stop : stops)
102 gradient.setColorAt(stop, alpha_color(rainbow_color(seed, stop, spread), alpha_scale));
113 Palette make_palette(
const QColor& base,
float glow_amount,
int style) {
116 palette.dark = mix_color(base, QColor(0, 0, 0, base.alpha()), 0.62f);
117 palette.light = mix_color(base, QColor(255, 255, 255, base.alpha()), 0.28f);
118 palette.accent = mix_color(base, palette.light, 0.35f);
120 palette.light = base;
121 palette.accent = base;
123 palette.light = mix_color(base, QColor(255, 255, 255, base.alpha()), 0.42f);
124 palette.accent = palette.light;
126 palette.light = mix_color(base, QColor(255, 255, 255, base.alpha()), 0.18f);
127 palette.accent = palette.light;
129 palette.glow = alpha_color(palette.light, 0.18f + glow_amount * 0.65f);
133 Palette make_palette(
const QColor& base,
float glow_amount,
int style,
float color_spread) {
134 Palette palette = make_palette(base, glow_amount, style);
135 color_spread = clampf(color_spread, 0.0f, 1.0f);
136 palette.dark = mix_color(base, palette.dark, color_spread);
137 palette.light = mix_color(base, palette.light, color_spread);
138 palette.accent = mix_color(base, palette.accent, color_spread);
139 palette.glow = alpha_color(mix_color(base, palette.glow, color_spread), 0.18f + glow_amount * 0.65f);
143 float style_glow_amount(
int style,
float glow) {
145 return clampf(std::max(glow, 0.45f), 0.0f, 1.0f);
147 return clampf(std::max(glow, 0.22f), 0.0f, 1.0f);
153 float style_stroke_width(
int style,
float glow) {
157 return 2.4f + glow * 1.4f;
159 return 1.8f + glow * 1.2f;
160 return 1.5f + glow * 1.6f;
163 float style_fill_alpha(
int style) {
173 QBrush vertical_style_fill(
float x0,
float y0,
float x1,
float y1,
const Palette& palette,
int style,
float alpha_scale = 1.0f) {
175 return QBrush(alpha_color(palette.base, style_fill_alpha(style) * alpha_scale));
177 QLinearGradient grad(x0, y0, x1, y1);
179 grad.setColorAt(0.0, alpha_color(palette.light, 0.92f * alpha_scale));
180 grad.setColorAt(0.5, alpha_color(palette.base, 0.70f * alpha_scale));
181 grad.setColorAt(1.0, alpha_color(palette.base, 0.28f * alpha_scale));
183 grad.setColorAt(0.0, alpha_color(palette.light, 0.48f * alpha_scale));
184 grad.setColorAt(0.5, alpha_color(palette.base, 0.78f * alpha_scale));
185 grad.setColorAt(1.0, alpha_color(palette.base, 0.48f * alpha_scale));
187 grad.setColorAt(0.0, alpha_color(palette.light, 0.72f * alpha_scale));
188 grad.setColorAt(0.62, alpha_color(palette.base, 0.72f * alpha_scale));
189 grad.setColorAt(1.0, alpha_color(palette.base, 0.38f * alpha_scale));
194 float normalized_frequency_to_hz(
float value,
bool high_frequency) {
198 const float min_hz = 20.0f;
199 const float max_hz = 20000.0f;
200 const float normalized = clampf(value, 0.0f, 1.0f);
201 if (high_frequency && normalized <= 0.0f)
202 return min_hz + 1.0f;
203 return min_hz * std::pow(max_hz / min_hz, normalized);
206 float hz_to_normalized_frequency(
float value) {
207 if (value >= 0.0f && value <= 1.0f)
210 const float min_hz = 20.0f;
211 const float max_hz = 20000.0f;
212 const float hz = clampf(value, min_hz, max_hz);
213 return clampf(std::log(hz / min_hz) / std::log(max_hz / min_hz), 0.0f, 1.0f);
216 std::vector<float> channel_samples(
const std::shared_ptr<Frame>& frame,
int channel,
int wanted_points,
float gain,
float smooth) {
217 std::vector<float> values(std::max(2, wanted_points), 0.0f);
218 const int samples = frame->GetAudioSamplesCount();
219 const int channels = frame->GetAudioChannelsCount();
220 if (samples <= 0 || channels <= 0)
223 const int safe_channel = clampi(channel, 0, channels - 1);
224 const float *audio = frame->GetAudioSampleBuffer()->getReadPointer(safe_channel);
225 float previous = 0.0f;
226 for (
int i = 0; i < static_cast<int>(values.size()); ++i) {
227 const int start = (i * samples) / values.size();
228 const int end = std::max(start + 1, ((i + 1) * samples) /
static_cast<int>(values.size()));
230 float peak_magnitude = 0.0f;
231 for (
int sample = start; sample < end && sample < samples; ++sample) {
232 const float value = audio[sample];
233 const float magnitude = std::fabs(value);
234 if (magnitude > peak_magnitude) {
236 peak_magnitude = magnitude;
239 const float scaled = clampf(peak * gain, -1.0f, 1.0f);
240 values[i] = previous * smooth + scaled * (1.0f - smooth);
241 previous = values[i];
246 std::vector<float> combined_samples(
const std::shared_ptr<Frame>& frame,
int wanted_points,
float gain,
float smooth) {
247 std::vector<float> values(std::max(2, wanted_points), 0.0f);
248 const int samples = frame->GetAudioSamplesCount();
249 const int channels = frame->GetAudioChannelsCount();
250 if (samples <= 0 || channels <= 0)
253 std::vector<const float*> channel_data;
254 channel_data.reserve(channels);
255 for (
int channel = 0; channel < channels; ++channel)
256 channel_data.push_back(frame->GetAudioSampleBuffer()->getReadPointer(channel));
258 float previous = 0.0f;
259 for (
int i = 0; i < static_cast<int>(values.size()); ++i) {
260 const int start = (i * samples) / values.size();
261 const int end = std::max(start + 1, ((i + 1) * samples) /
static_cast<int>(values.size()));
263 float peak_magnitude = 0.0f;
264 for (
int sample = start; sample < end && sample < samples; ++sample) {
265 float mixed = std::accumulate(channel_data.begin(), channel_data.end(), 0.0f,
266 [sample](
float total,
const float* channel) {
267 return total + channel[sample];
270 const float magnitude = std::fabs(mixed);
271 if (magnitude > peak_magnitude) {
273 peak_magnitude = magnitude;
276 const float scaled = clampf(peak * gain, -1.0f, 1.0f);
277 values[i] = previous * smooth + scaled * (1.0f - smooth);
278 previous = values[i];
283 float reactive_level(
const std::shared_ptr<Frame>& frame,
int channel,
float gain) {
284 const int samples = frame->GetAudioSamplesCount();
285 const int channels = frame->GetAudioChannelsCount();
286 if (samples <= 0 || channels <= 0)
291 const int first_channel = channel >= 0 ? clampi(channel, 0, channels - 1) : 0;
292 const int last_channel = channel >= 0 ? first_channel + 1 : channels;
293 for (
int c = first_channel; c < last_channel; ++c) {
294 const float *audio = frame->GetAudioSampleBuffer()->getReadPointer(c);
295 for (
int i = 0; i < samples; ++i) {
296 const float magnitude = std::fabs(audio[i]);
297 total += magnitude * magnitude;
298 peak = std::max(peak, magnitude);
302 const float rms = std::sqrt(total / (samples * (last_channel - first_channel)));
303 const float mixed = std::max(rms * 3.6f, peak * 1.15f);
304 return std::pow(clampf(mixed * gain, 0.0f, 1.0f), 0.55f);
307 float band_level(
const std::vector<float>& bins,
float start,
float end) {
311 const int first = clampi(std::lround(start * bins.size()), 0,
static_cast<int>(bins.size()) - 1);
312 const int last = clampi(std::lround(end * bins.size()), first + 1,
static_cast<int>(bins.size()));
313 const float total = std::accumulate(bins.begin() + first, bins.begin() + last, 0.0f);
314 return clampf(total / (last - first), 0.0f, 1.0f);
317 std::vector<float> spectrum_bins(
const std::shared_ptr<Frame>& frame,
int bins,
float gain,
float smooth,
float low_hz,
float high_hz) {
318 std::vector<float> result(std::max(2, bins), 0.0f);
319 const int samples = frame->GetAudioSamplesCount();
320 const int channels = frame->GetAudioChannelsCount();
321 const int sample_rate = std::max(1, frame->SampleRate());
322 if (samples <= 4 || channels <= 0)
325 const int max_source_samples = std::min(samples, 2048);
326 const float nyquist = sample_rate * 0.5f;
327 low_hz = clampf(low_hz, 1.0f, nyquist);
328 high_hz = clampf(high_hz, low_hz + 1.0f, nyquist);
330 std::vector<const float*> channel_data;
331 channel_data.reserve(channels);
332 for (
int channel = 0; channel < channels; ++channel)
333 channel_data.push_back(frame->GetAudioSampleBuffer()->getReadPointer(channel));
335 std::vector<double> mixed_windowed(max_source_samples, 0.0);
336 for (
int sample = 0; sample < max_source_samples; ++sample) {
338 for (
int channel = 0; channel < channels; ++channel)
339 mixed += channel_data[channel][sample];
341 const double window = 0.5 - 0.5 * std::cos((2.0 * PI * sample) / (max_source_samples - 1));
342 mixed_windowed[sample] = mixed * window;
345 std::vector<float> magnitudes(result.size(), 0.0f);
346 const int result_size =
static_cast<int>(result.size());
347 #pragma omp parallel for if(result_size * max_source_samples >= 32768) schedule(static)
348 for (
int bin = 0; bin < result_size; ++bin) {
349 const float t = (bin + 0.5f) / result_size;
350 const float hz = low_hz * std::pow(high_hz / low_hz, t);
351 const double phase_delta = 2.0 * PI * hz / sample_rate;
352 const double step_real = std::cos(phase_delta);
353 const double step_imag = std::sin(phase_delta);
354 double phase_real = 1.0;
355 double phase_imag = 0.0;
359 for (
int sample = 0; sample < max_source_samples; ++sample) {
360 const double value = mixed_windowed[sample];
361 real += value * phase_real;
362 imag -= value * phase_imag;
364 const double next_real = phase_real * step_real - phase_imag * step_imag;
365 phase_imag = phase_real * step_imag + phase_imag * step_real;
366 phase_real = next_real;
369 magnitudes[bin] = clampf(std::sqrt(real * real + imag * imag) / max_source_samples * gain * 8.0f, 0.0f, 1.0f);
372 float previous = 0.0f;
373 for (
int bin = 0; bin < result_size; ++bin) {
374 result[bin] = previous * smooth + magnitudes[bin] * (1.0f - smooth);
375 previous = result[bin];
380 void draw_glow_path(QPainter& painter,
const QPainterPath&
path,
const Palette& palette,
float width,
float glow) {
382 QPen glow_pen(palette.glow, width + glow * 18.0f, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
383 painter.setPen(glow_pen);
384 painter.drawPath(
path);
386 QPen pen(palette.base, width, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
388 painter.drawPath(
path);
391 void draw_glow_polyline(QPainter& painter,
const QPolygonF& points,
const Palette& palette,
float width,
float glow) {
392 if (points.size() < 2)
395 QPen glow_pen(palette.glow, width + glow * 14.0f, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
396 painter.setPen(glow_pen);
397 painter.drawPolyline(points);
399 QPen pen(palette.base, width, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
401 painter.drawPolyline(points);
408 color((unsigned char)68, (unsigned char)170, (unsigned char)255, (unsigned char)255),
420 init_effect_details();
430 void AudioVisualization::init_effect_details()
435 info.
description =
"Render waveform, spectrum, and other transparent audio visualizations.";
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);
917 root[
"style"] =
style;
938 catch (
const std::exception& e)
940 throw InvalidJSON(
"JSON is invalid (missing keys or invalid data types)");
946 if (!root[
"visualization_type"].isNull())
948 if (!root[
"style"].isNull())
949 style = root[
"style"].asInt();
950 if (!root[
"color"].isNull())
952 if (!root[
"intensity"].isNull())
954 if (!root[
"smoothing"].isNull())
956 if (!root[
"detail"].isNull())
958 if (!root[
"glow"].isNull())
960 if (!root[
"color_spread"].isNull())
962 if (!root[
"color_mode"].isNull())
964 if (!root[
"channel_layout"].isNull())
966 if (!root[
"frequency_low"].isNull())
968 if (!root[
"frequency_high"].isNull())
970 if (!root[
"background"].isNull())
1026 return root.toStyledString();
Header file for AudioVisualization effect class.
Header file for all Exception classes.
Header file for Timeline class.
std::string PropertiesJSON(int64_t requested_frame) const override
std::string Json() const override
Generate JSON string of this object.
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
void SetJson(const std::string value) override
Load JSON string into this object.
std::shared_ptr< openshot::Frame > GetFrame(int64_t frame_number) override
This method is required for all derived classes of ClipBase, and returns a new openshot::Frame object...
Json::Value JsonValue() const override
Generate Json::Value for this object.
float start
The position in seconds to start playing (used to trim the beginning of a clip)
Json::Value add_property_choice_json(std::string name, int value, int selected_value) const
Generate JSON choice for a property (dropdown properties)
openshot::TimelineBase * timeline
Pointer to the parent timeline instance (if any)
virtual openshot::TimelineBase * ParentTimeline()
Get the associated Timeline pointer (if any)
float position
The position on the timeline where this clip should start playing.
float end
The position in seconds to end playing (used to trim the ending of a clip)
Json::Value add_property_json(std::string name, float value, std::string type, std::string memo, const Keyframe *keyframe, float min_value, float max_value, bool readonly, int64_t requested_frame) const
Generate JSON for a property.
This class represents a color (used on the timeline and clips)
openshot::Keyframe blue
Curve representing the red value (0 - 255)
openshot::Keyframe red
Curve representing the red value (0 - 255)
openshot::Keyframe green
Curve representing the green value (0 - 255)
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
openshot::Keyframe alpha
Curve representing the alpha value (0 - 255)
Json::Value JsonValue() const
Generate Json::Value for this object.
virtual Json::Value JsonValue() const
Generate Json::Value for this object.
Json::Value BasePropertiesJSON(int64_t requested_frame) const
Generate JSON object of base properties (recommended to be used by all effects)
virtual void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
EffectInfoStruct info
Information about the current effect.
Exception for invalid JSON.
int GetInt(int64_t index) const
Get the rounded INT value at a specific index.
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
double GetValue(int64_t index) const
Get the value at a specific index.
Json::Value JsonValue() const
Generate Json::Value for this object.
This class represents a timeline.
This namespace is the default namespace for all code in the openshot library.
@ AUDIO_VISUALIZATION_CHANNEL_AUTO
@ AUDIO_VISUALIZATION_CHANNEL_SPLIT
@ AUDIO_VISUALIZATION_CHANNEL_COMBINED
@ AUDIO_VISUALIZATION_CHANNEL_OVERLAY
@ AUDIO_VISUALIZATION_BACKGROUND_TRANSPARENT
@ AUDIO_VISUALIZATION_BACKGROUND_SOLID
@ AUDIO_VISUALIZATION_BACKGROUND_FADE
@ AUDIO_VISUALIZATION_BACKGROUND_GRADIENT
@ AUDIO_VISUALIZATION_BACKGROUND_SOURCE
@ AUDIO_VISUALIZATION_COLOR_SEED
@ AUDIO_VISUALIZATION_COLOR_RAINBOW
@ AUDIO_VISUALIZATION_VU_METER
@ AUDIO_VISUALIZATION_BARS
@ AUDIO_VISUALIZATION_SPECTRUM
@ AUDIO_VISUALIZATION_WAVEFORM
@ AUDIO_VISUALIZATION_PHASE_SCOPE
@ AUDIO_VISUALIZATION_RADIAL_BARS
@ AUDIO_VISUALIZATION_PARTICLES
@ AUDIO_VISUALIZATION_FILLED_WAVEFORM
@ AUDIO_VISUALIZATION_RADIAL
const Json::Value stringToJson(const std::string value)
@ AUDIO_VISUALIZATION_STYLE_MINIMAL
@ AUDIO_VISUALIZATION_STYLE_CLEAN
@ AUDIO_VISUALIZATION_STYLE_SOFT
@ AUDIO_VISUALIZATION_STYLE_NEON
bool has_video
Determines if this effect manipulates the image of a frame.
bool has_audio
Determines if this effect manipulates the audio of a frame.
std::string class_name
The class name of the effect.
std::string name
The name of the effect.
std::string description
The description of this effect and what it does.