OpenShot Library | libopenshot 1.1.1
AudioVisualization.cpp
Go to the documentation of this file.
1
9// Copyright (c) 2008-2026 OpenShot Studios, LLC
10//
11// SPDX-License-Identifier: LGPL-3.0-or-later
12
13#include "AudioVisualization.h"
14#include "Exceptions.h"
15#include "Timeline.h"
16
17#include <algorithm>
18#include <cmath>
19#include <numeric>
20#include <random>
21#include <vector>
22
23#include <QBrush>
24#include <QColor>
25#include <QConicalGradient>
26#include <QLinearGradient>
27#include <QLineF>
28#include <QPainter>
29#include <QPainterPath>
30#include <QPen>
31#include <QPolygonF>
32#include <QRadialGradient>
33
34#include <AppConfig.h>
35#include <juce_audio_basics/juce_audio_basics.h>
36
37using namespace openshot;
38
39namespace {
40 constexpr double PI = 3.14159265358979323846;
41
42 float clampf(float value, float min_value, float max_value) {
43 return std::max(min_value, std::min(max_value, value));
44 }
45
46 int clampi(int value, int min_value, int max_value) {
47 return std::max(min_value, std::min(max_value, value));
48 }
49
50 QColor color_at(const Color& color, int64_t frame_number) {
51 return QColor(
52 clampi(color.red.GetInt(frame_number), 0, 255),
53 clampi(color.green.GetInt(frame_number), 0, 255),
54 clampi(color.blue.GetInt(frame_number), 0, 255),
55 clampi(color.alpha.GetInt(frame_number), 0, 255));
56 }
57
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;
61 return QColor(
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));
66 }
67
68 QColor alpha_color(QColor color, float alpha_scale) {
69 color.setAlpha(clampi(std::lround(color.alpha() * alpha_scale), 0, 255));
70 return color;
71 }
72
73 QColor hue_shift(QColor color, int degrees) {
74 int h = 0;
75 int s = 0;
76 int v = 0;
77 int a = color.alpha();
78 color.getHsv(&h, &s, &v, &a);
79 if (h < 0)
80 h = 0;
81 return QColor::fromHsv((h + degrees + 360) % 360, s, v, a);
82 }
83
84 QColor rainbow_color(const QColor& seed, float position, float spread) {
85 int h = 0;
86 int s = 0;
87 int v = 0;
88 int a = seed.alpha();
89 seed.getHsv(&h, &s, &v, &a);
90 if (h < 0)
91 h = 0;
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);
96 }
97
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));
103 }
104
105 struct Palette {
106 QColor base;
107 QColor dark;
108 QColor light;
109 QColor accent;
110 QColor glow;
111 };
112
113 Palette make_palette(const QColor& base, float glow_amount, int style) {
114 Palette palette;
115 palette.base = base;
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;
122 } else if (style == AUDIO_VISUALIZATION_STYLE_SOFT) {
123 palette.light = mix_color(base, QColor(255, 255, 255, base.alpha()), 0.42f);
124 palette.accent = palette.light;
125 } else if (style == AUDIO_VISUALIZATION_STYLE_NEON) {
126 palette.light = mix_color(base, QColor(255, 255, 255, base.alpha()), 0.18f);
127 palette.accent = palette.light;
128 }
129 palette.glow = alpha_color(palette.light, 0.18f + glow_amount * 0.65f);
130 return palette;
131 }
132
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);
140 return palette;
141 }
142
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);
149 return glow * 0.2f;
150 return glow;
151 }
152
153 float style_stroke_width(int style, float glow) {
155 return 1.0f;
157 return 2.4f + glow * 1.4f;
159 return 1.8f + glow * 1.2f;
160 return 1.5f + glow * 1.6f;
161 }
162
163 float style_fill_alpha(int style) {
165 return 1.0f;
167 return 0.82f;
169 return 0.74f;
170 return 0.72f;
171 }
172
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));
176
177 QLinearGradient grad(x0, y0, x1, y1);
178 if (style == AUDIO_VISUALIZATION_STYLE_NEON) {
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));
182 } else if (style == AUDIO_VISUALIZATION_STYLE_SOFT) {
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));
186 } else {
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));
190 }
191 return QBrush(grad);
192 }
193
194 float normalized_frequency_to_hz(float value, bool high_frequency) {
195 if (value > 1.0f)
196 return value;
197
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);
204 }
205
206 float hz_to_normalized_frequency(float value) {
207 if (value >= 0.0f && value <= 1.0f)
208 return value;
209
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);
214 }
215
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)
221 return values;
222
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()));
229 float peak = 0.0f;
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) {
235 peak = value;
236 peak_magnitude = magnitude;
237 }
238 }
239 const float scaled = clampf(peak * gain, -1.0f, 1.0f);
240 values[i] = previous * smooth + scaled * (1.0f - smooth);
241 previous = values[i];
242 }
243 return values;
244 }
245
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)
251 return values;
252
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));
257
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()));
262 float peak = 0.0f;
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];
268 });
269 mixed /= channels;
270 const float magnitude = std::fabs(mixed);
271 if (magnitude > peak_magnitude) {
272 peak = mixed;
273 peak_magnitude = magnitude;
274 }
275 }
276 const float scaled = clampf(peak * gain, -1.0f, 1.0f);
277 values[i] = previous * smooth + scaled * (1.0f - smooth);
278 previous = values[i];
279 }
280 return values;
281 }
282
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)
287 return 0.0f;
288
289 double total = 0.0;
290 float peak = 0.0f;
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);
299 }
300 }
301
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);
305 }
306
307 float band_level(const std::vector<float>& bins, float start, float end) {
308 if (bins.empty())
309 return 0.0f;
310
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);
315 }
316
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)
323 return result;
324
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);
329
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));
334
335 std::vector<double> mixed_windowed(max_source_samples, 0.0);
336 for (int sample = 0; sample < max_source_samples; ++sample) {
337 float mixed = 0.0f;
338 for (int channel = 0; channel < channels; ++channel)
339 mixed += channel_data[channel][sample];
340 mixed /= channels;
341 const double window = 0.5 - 0.5 * std::cos((2.0 * PI * sample) / (max_source_samples - 1));
342 mixed_windowed[sample] = mixed * window;
343 }
344
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;
356 double real = 0.0;
357 double imag = 0.0;
358
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;
363
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;
367 }
368
369 magnitudes[bin] = clampf(std::sqrt(real * real + imag * imag) / max_source_samples * gain * 8.0f, 0.0f, 1.0f);
370 }
371
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];
376 }
377 return result;
378 }
379
380 void draw_glow_path(QPainter& painter, const QPainterPath& path, const Palette& palette, float width, float glow) {
381 if (glow > 0.01f) {
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);
385 }
386 QPen pen(palette.base, width, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
387 painter.setPen(pen);
388 painter.drawPath(path);
389 }
390
391 void draw_glow_polyline(QPainter& painter, const QPolygonF& points, const Palette& palette, float width, float glow) {
392 if (points.size() < 2)
393 return;
394 if (glow > 0.01f) {
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);
398 }
399 QPen pen(palette.base, width, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
400 painter.setPen(pen);
401 painter.drawPolyline(points);
402 }
403}
404
405AudioVisualization::AudioVisualization() :
406 visualization_type(AUDIO_VISUALIZATION_WAVEFORM),
408 color((unsigned char)68, (unsigned char)170, (unsigned char)255, (unsigned char)255),
409 intensity(1.0),
410 smoothing(0.35),
411 detail(0.75),
412 glow(0.25),
413 color_spread(0.6),
415 channel_layout(AUDIO_VISUALIZATION_CHANNEL_AUTO),
416 frequency_low(0.0),
417 frequency_high(1.0),
419{
420 init_effect_details();
421}
422
423AudioVisualization::AudioVisualization(int visualization_type, Color color) :
425{
426 this->visualization_type = visualization_type;
427 this->color = color;
428}
429
430void AudioVisualization::init_effect_details()
431{
433 info.class_name = "AudioVisualization";
434 info.name = "Audio Visualization";
435 info.description = "Render waveform, spectrum, and other transparent audio visualizations.";
436 info.has_audio = false;
437 info.has_video = true;
438}
439
440std::shared_ptr<openshot::Frame> AudioVisualization::GetFrame(std::shared_ptr<openshot::Frame> frame, int64_t frame_number)
441{
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;
445 if ((width <= 1 || height <= 1) && ParentTimeline()) {
446 if (Timeline* timeline = dynamic_cast<Timeline*>(ParentTimeline())) {
447 if (timeline->info.width > 1 && timeline->info.height > 1) {
448 width = timeline->info.width;
449 height = timeline->info.height;
450 }
451 }
452 }
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);
457 const float color_spread_value = clampf(color_spread.GetValue(frame_number), 0.0f, 1.0f);
458 const int mode = clampi(visualization_type, 0, AUDIO_VISUALIZATION_RADIAL_BARS);
459 const bool uses_frequency = mode == AUDIO_VISUALIZATION_BARS ||
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
466 ? std::max(low_hz + 1.0f, normalized_frequency_to_hz(frequency_high.GetValue(frame_number), true))
467 : 20000.0f;
468 const int channels = std::max(1, frame->GetAudioChannelsCount());
473
474 const bool use_filled_waveform_argb = mode == AUDIO_VISUALIZATION_FILLED_WAVEFORM;
475 auto visual = std::make_shared<QImage>(width, height,
476 use_filled_waveform_argb ? QImage::Format_ARGB32_Premultiplied : QImage::Format_RGBA8888_Premultiplied);
477 if (background == AUDIO_VISUALIZATION_BACKGROUND_SOURCE && frame_image && !frame_image->isNull()) {
478 if (frame_image->width() == width && frame_image->height() == height)
479 *visual = frame_image->copy();
480 else
481 *visual = frame_image->scaled(width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
482 } else {
483 visual->fill(Qt::transparent);
484 }
485
486 QPainter painter(visual.get());
488 (frame_image->width() != width || frame_image->height() != height)) {
489 painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
490 }
491
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);
495
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);
508 }
509
510 if (frame->GetAudioSamplesCount() <= 0) {
511 painter.end();
512 frame->AddImage(visual);
513 return frame;
514 }
515
516 const float gain = std::max(0.01f, intensity_value);
517 const float stroke = style_stroke_width(style, styled_glow);
518 const bool is_waveform_mode = mode == AUDIO_VISUALIZATION_WAVEFORM || mode == AUDIO_VISUALIZATION_FILLED_WAVEFORM;
519 painter.setRenderHint(QPainter::Antialiasing, !is_waveform_mode || styled_glow > 0.01f || stroke > 1.05f);
520
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;
532 const QColor lane_base = color_mode == AUDIO_VISUALIZATION_COLOR_RAINBOW
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);
536
537 QPolygonF top_edge;
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));
542 }
543
544 const float alpha_scale = overlay ? 0.48f : 1.0f;
545 const bool rainbow = color_mode == AUDIO_VISUALIZATION_COLOR_RAINBOW;
547 painter.setPen(Qt::NoPen);
548 if (styled_glow > 0.01f) {
549 if (rainbow) {
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));
553 } else {
554 painter.setBrush(alpha_color(lane_palette.glow, 0.78f * alpha_scale));
555 }
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));
565 }
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));
569 }
570 }
571
572 if (rainbow) {
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));
576 } else {
577 painter.setBrush(vertical_style_fill(0, lane_top, 0, lane_top + lane_height, lane_palette, style, alpha_scale));
578 }
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));
587 }
588 } else {
589 const float line_width = std::max(1.0f, stroke);
590 if (rainbow) {
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);
596 }
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);
601 } else {
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);
606 }
607 painter.setPen(QPen(alpha_color(lane_palette.base, alpha_scale),
608 line_width, Qt::SolidLine, Qt::FlatCap));
609 painter.drawPolyline(top_edge);
610 }
611 }
612 }
613 } else if (mode == AUDIO_VISUALIZATION_BARS) {
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);
622 const Palette bar_palette = color_mode == AUDIO_VISUALIZATION_COLOR_RAINBOW
623 ? make_palette(rainbow_color(base, i / static_cast<float>(std::max(1, bars - 1)), color_spread_value), styled_glow, style, color_spread_value)
624 : palette;
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));
628 }
629 } else if (mode == AUDIO_VISUALIZATION_SPECTRUM) {
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);
638 }
639
640 QPainterPath ridge;
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);
651 }
652 }
653
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);
659 } else {
660 terrain_brush = vertical_style_fill(0, 0, 0, height, palette, style);
661 }
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);
667 fill_painter.end();
668
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);
677 const float mix = position - index;
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));
680 }
681 mask_painter.end();
682
683 fill_painter.begin(&fill_layer);
684 fill_painter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
685 fill_painter.drawImage(0, 0, mask);
686 fill_painter.end();
687 painter.drawImage(0, 0, fill_layer);
688 }
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);
696 }
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);
701 } else {
702 draw_glow_path(painter, ridge, palette, ridge_width, styled_glow * 0.55f);
703 }
704 } else if (mode == AUDIO_VISUALIZATION_RADIAL) {
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;
710 QPainterPath ring;
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);
716 if (i == 0)
717 ring.moveTo(p);
718 else
719 ring.lineTo(p);
720 }
721 QConicalGradient grad(center, -90);
723 set_rainbow_stops(grad, base, color_spread_value);
724 } else {
725 grad.setColorAt(0.0, palette.base);
726 grad.setColorAt(0.45, palette.light);
727 grad.setColorAt(1.0, palette.accent);
728 }
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);
735 }
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);
739 }
740 painter.setPen(QPen(QBrush(grad), stroke + 1.0f, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
741 painter.drawPath(ring);
742 } else if (mode == AUDIO_VISUALIZATION_RADIAL_BARS) {
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) {
759 const QColor glow_color = color_mode == AUDIO_VISUALIZATION_COLOR_RAINBOW ? alpha_color(c, 0.58f) : alpha_color(palette.glow, 0.65f);
760 painter.setPen(QPen(glow_color, bar_width + styled_glow * 8.0f, Qt::SolidLine, Qt::RoundCap));
761 painter.drawLine(start, end);
762 }
763 painter.setPen(QPen(c, bar_width, Qt::SolidLine, Qt::RoundCap));
764 painter.drawLine(start, end);
765 }
766 } else if (mode == AUDIO_VISUALIZATION_PHASE_SCOPE) {
767 if (channels < 2) {
769 auto result = GetFrame(frame, frame_number);
770 visualization_type = mode;
771 return result;
772 }
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);
778 QPolygonF trace;
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));
789 previous_x = x;
790 previous_y = y;
791 }
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);
809 }
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);
814 } else {
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);
819 }
820 QPen pen(palette.base, trace_width, Qt::SolidLine, Qt::FlatCap, Qt::BevelJoin);
821 painter.setPen(pen);
822 painter.drawPolyline(trace);
823 }
824 } else if (mode == AUDIO_VISUALIZATION_PARTICLES) {
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());
851 const QPointF p(
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;
855 const QPointF tail(
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));
869 } else {
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);
874 }
875 painter.setPen(Qt::NoPen);
876 painter.drawEllipse(p, size * 2.0f, size * 2.0f);
877 }
878 } else if (mode == AUDIO_VISUALIZATION_VU_METER) {
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;
890 QColor c;
892 c = palette.base;
893 } else {
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));
895 }
896 if (!on)
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);
900 }
901 }
902 }
903
904 painter.end();
905 frame->AddImage(visual);
906 return frame;
907}
908
909std::string AudioVisualization::Json() const {
910 return JsonValue().toStyledString();
911}
912
913Json::Value AudioVisualization::JsonValue() const {
914 Json::Value root = EffectBase::JsonValue();
915 root["type"] = info.class_name;
916 root["visualization_type"] = visualization_type;
917 root["style"] = style;
918 root["color"] = color.JsonValue();
919 root["intensity"] = intensity.JsonValue();
920 root["smoothing"] = smoothing.JsonValue();
921 root["detail"] = detail.JsonValue();
922 root["glow"] = glow.JsonValue();
923 root["color_spread"] = color_spread.JsonValue();
924 root["color_mode"] = color_mode;
925 root["channel_layout"] = channel_layout;
926 root["frequency_low"] = frequency_low.JsonValue();
927 root["frequency_high"] = frequency_high.JsonValue();
928 root["background"] = background;
929 return root;
930}
931
932void AudioVisualization::SetJson(const std::string value) {
933 try
934 {
935 const Json::Value root = openshot::stringToJson(value);
936 SetJsonValue(root);
937 }
938 catch (const std::exception& e)
939 {
940 throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
941 }
942}
943
944void AudioVisualization::SetJsonValue(const Json::Value root) {
946 if (!root["visualization_type"].isNull())
947 visualization_type = root["visualization_type"].asInt();
948 if (!root["style"].isNull())
949 style = root["style"].asInt();
950 if (!root["color"].isNull())
951 color.SetJsonValue(root["color"]);
952 if (!root["intensity"].isNull())
953 intensity.SetJsonValue(root["intensity"]);
954 if (!root["smoothing"].isNull())
955 smoothing.SetJsonValue(root["smoothing"]);
956 if (!root["detail"].isNull())
957 detail.SetJsonValue(root["detail"]);
958 if (!root["glow"].isNull())
959 glow.SetJsonValue(root["glow"]);
960 if (!root["color_spread"].isNull())
961 color_spread.SetJsonValue(root["color_spread"]);
962 if (!root["color_mode"].isNull())
963 color_mode = root["color_mode"].asInt();
964 if (!root["channel_layout"].isNull())
965 channel_layout = root["channel_layout"].asInt();
966 if (!root["frequency_low"].isNull())
967 frequency_low.SetJsonValue(root["frequency_low"]);
968 if (!root["frequency_high"].isNull())
969 frequency_high.SetJsonValue(root["frequency_high"]);
970 if (!root["background"].isNull())
971 background = root["background"].asInt();
972}
973
974std::string AudioVisualization::PropertiesJSON(int64_t requested_frame) const {
975 Json::Value root = BasePropertiesJSON(requested_frame);
976
977 root["visualization_type"] = add_property_json("Visualization", visualization_type, "int", "", NULL, 0, AUDIO_VISUALIZATION_RADIAL_BARS, false, requested_frame);
978 root["visualization_type"]["choices"].append(add_property_choice_json("Waveform", AUDIO_VISUALIZATION_WAVEFORM, visualization_type));
979 root["visualization_type"]["choices"].append(add_property_choice_json("Filled Waveform", AUDIO_VISUALIZATION_FILLED_WAVEFORM, visualization_type));
980 root["visualization_type"]["choices"].append(add_property_choice_json("Bars", AUDIO_VISUALIZATION_BARS, visualization_type));
981 root["visualization_type"]["choices"].append(add_property_choice_json("Radial", AUDIO_VISUALIZATION_RADIAL, visualization_type));
982 root["visualization_type"]["choices"].append(add_property_choice_json("Radial Bars", AUDIO_VISUALIZATION_RADIAL_BARS, visualization_type));
983 root["visualization_type"]["choices"].append(add_property_choice_json("Spectrum", AUDIO_VISUALIZATION_SPECTRUM, visualization_type));
984 root["visualization_type"]["choices"].append(add_property_choice_json("Phase Scope", AUDIO_VISUALIZATION_PHASE_SCOPE, visualization_type));
985 root["visualization_type"]["choices"].append(add_property_choice_json("Particles", AUDIO_VISUALIZATION_PARTICLES, visualization_type));
986 root["visualization_type"]["choices"].append(add_property_choice_json("VU Meter", AUDIO_VISUALIZATION_VU_METER, visualization_type));
987
988 root["style"] = add_property_json("Style", style, "int", "", NULL, 0, AUDIO_VISUALIZATION_STYLE_MINIMAL, false, requested_frame);
989 root["style"]["choices"].append(add_property_choice_json("Clean", AUDIO_VISUALIZATION_STYLE_CLEAN, style));
990 root["style"]["choices"].append(add_property_choice_json("Soft", AUDIO_VISUALIZATION_STYLE_SOFT, style));
991 root["style"]["choices"].append(add_property_choice_json("Neon", AUDIO_VISUALIZATION_STYLE_NEON, style));
992 root["style"]["choices"].append(add_property_choice_json("Minimal", AUDIO_VISUALIZATION_STYLE_MINIMAL, style));
993
994 root["color"] = add_property_json("Color", 0.0, "color", "", &color.red, 0, 255, false, requested_frame);
995 root["color"]["red"] = add_property_json("Red", color.red.GetValue(requested_frame), "float", "", &color.red, 0, 255, false, requested_frame);
996 root["color"]["blue"] = add_property_json("Blue", color.blue.GetValue(requested_frame), "float", "", &color.blue, 0, 255, false, requested_frame);
997 root["color"]["green"] = add_property_json("Green", color.green.GetValue(requested_frame), "float", "", &color.green, 0, 255, false, requested_frame);
998 root["color"]["alpha"] = add_property_json("Alpha", color.alpha.GetValue(requested_frame), "float", "", &color.alpha, 0, 255, false, requested_frame);
999
1000 root["intensity"] = add_property_json("Intensity", intensity.GetValue(requested_frame), "float", "", &intensity, 0.0, 10.0, false, requested_frame);
1001 root["smoothing"] = add_property_json("Smoothing", smoothing.GetValue(requested_frame), "float", "", &smoothing, 0.0, 1.0, false, requested_frame);
1002 root["detail"] = add_property_json("Detail", detail.GetValue(requested_frame), "float", "", &detail, 0.0, 1.0, false, requested_frame);
1003 root["glow"] = add_property_json("Glow", glow.GetValue(requested_frame), "float", "", &glow, 0.0, 1.0, false, requested_frame);
1004 root["color_spread"] = add_property_json("Color Spread", color_spread.GetValue(requested_frame), "float", "", &color_spread, 0.0, 1.0, false, requested_frame);
1005
1006 root["color_mode"] = add_property_json("Color Mode", color_mode, "int", "", NULL, 0, AUDIO_VISUALIZATION_COLOR_RAINBOW, false, requested_frame);
1007 root["color_mode"]["choices"].append(add_property_choice_json("Seed", AUDIO_VISUALIZATION_COLOR_SEED, color_mode));
1008 root["color_mode"]["choices"].append(add_property_choice_json("Rainbow", AUDIO_VISUALIZATION_COLOR_RAINBOW, color_mode));
1009
1010 root["channel_layout"] = add_property_json("Channel Layout", channel_layout, "int", "", NULL, 0, AUDIO_VISUALIZATION_CHANNEL_OVERLAY, false, requested_frame);
1011 root["channel_layout"]["choices"].append(add_property_choice_json("Auto", AUDIO_VISUALIZATION_CHANNEL_AUTO, channel_layout));
1012 root["channel_layout"]["choices"].append(add_property_choice_json("Combined", AUDIO_VISUALIZATION_CHANNEL_COMBINED, channel_layout));
1013 root["channel_layout"]["choices"].append(add_property_choice_json("Split", AUDIO_VISUALIZATION_CHANNEL_SPLIT, channel_layout));
1014 root["channel_layout"]["choices"].append(add_property_choice_json("Overlay", AUDIO_VISUALIZATION_CHANNEL_OVERLAY, channel_layout));
1015
1016 root["frequency_low"] = add_property_json("Low Frequency", hz_to_normalized_frequency(frequency_low.GetValue(requested_frame)), "float", "Normalized frequency floor: 0 = 20 Hz, 1 = 20 kHz", &frequency_low, 0.0, 1.0, false, requested_frame);
1017 root["frequency_high"] = add_property_json("High Frequency", hz_to_normalized_frequency(frequency_high.GetValue(requested_frame)), "float", "Normalized frequency ceiling: 0 = 20 Hz, 1 = 20 kHz", &frequency_high, 0.0, 1.0, false, requested_frame);
1018
1019 root["background"] = add_property_json("Background", background, "int", "", NULL, 0, AUDIO_VISUALIZATION_BACKGROUND_SOURCE, false, requested_frame);
1020 root["background"]["choices"].append(add_property_choice_json("Transparent", AUDIO_VISUALIZATION_BACKGROUND_TRANSPARENT, background));
1021 root["background"]["choices"].append(add_property_choice_json("Solid", AUDIO_VISUALIZATION_BACKGROUND_SOLID, background));
1022 root["background"]["choices"].append(add_property_choice_json("Fade", AUDIO_VISUALIZATION_BACKGROUND_FADE, background));
1023 root["background"]["choices"].append(add_property_choice_json("Gradient", AUDIO_VISUALIZATION_BACKGROUND_GRADIENT, background));
1024 root["background"]["choices"].append(add_property_choice_json("Source", AUDIO_VISUALIZATION_BACKGROUND_SOURCE, background));
1025
1026 return root.toStyledString();
1027}
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.
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...
void SetJson(const std::string value) override
Load JSON string into this 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)
Definition: ClipBase.h:37
Json::Value add_property_choice_json(std::string name, int value, int selected_value) const
Generate JSON choice for a property (dropdown properties)
Definition: ClipBase.cpp:136
openshot::TimelineBase * timeline
Pointer to the parent timeline instance (if any)
Definition: ClipBase.h:40
virtual openshot::TimelineBase * ParentTimeline()
Get the associated Timeline pointer (if any)
Definition: ClipBase.h:94
float position
The position on the timeline where this clip should start playing.
Definition: ClipBase.h:35
float end
The position in seconds to end playing (used to trim the ending of a clip)
Definition: ClipBase.h:38
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.
Definition: ClipBase.cpp:100
This class represents a color (used on the timeline and clips)
Definition: Color.h:27
openshot::Keyframe blue
Curve representing the red value (0 - 255)
Definition: Color.h:32
openshot::Keyframe red
Curve representing the red value (0 - 255)
Definition: Color.h:30
openshot::Keyframe green
Curve representing the green value (0 - 255)
Definition: Color.h:31
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: Color.cpp:118
openshot::Keyframe alpha
Curve representing the alpha value (0 - 255)
Definition: Color.h:33
Json::Value JsonValue() const
Generate Json::Value for this object.
Definition: Color.cpp:87
virtual Json::Value JsonValue() const
Generate Json::Value for this object.
Definition: EffectBase.cpp:106
Json::Value BasePropertiesJSON(int64_t requested_frame) const
Generate JSON object of base properties (recommended to be used by all effects)
Definition: EffectBase.cpp:261
virtual void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: EffectBase.cpp:150
EffectInfoStruct info
Information about the current effect.
Definition: EffectBase.h:114
Exception for invalid JSON.
Definition: Exceptions.h:224
int GetInt(int64_t index) const
Get the rounded INT value at a specific index.
Definition: KeyFrame.cpp:282
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: KeyFrame.cpp:372
double GetValue(int64_t index) const
Get the value at a specific index.
Definition: KeyFrame.cpp:258
Json::Value JsonValue() const
Generate Json::Value for this object.
Definition: KeyFrame.cpp:339
This class represents a timeline.
Definition: Timeline.h:154
This namespace is the default namespace for all code in the openshot library.
Definition: AnimatedCurve.h:24
@ 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)
Definition: Json.cpp:16
@ 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.
Definition: EffectBase.h:43
bool has_audio
Determines if this effect manipulates the audio of a frame.
Definition: EffectBase.h:44
std::string class_name
The class name of the effect.
Definition: EffectBase.h:39
std::string name
The name of the effect.
Definition: EffectBase.h:40
std::string description
The description of this effect and what it does.
Definition: EffectBase.h:41