OpenShot Library | libopenshot  0.7.0
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 
37 using namespace openshot;
38 
39 namespace {
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);
119  if (style == AUDIO_VISUALIZATION_STYLE_MINIMAL) {
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) {
144  if (style == AUDIO_VISUALIZATION_STYLE_NEON)
145  return clampf(std::max(glow, 0.45f), 0.0f, 1.0f);
146  if (style == AUDIO_VISUALIZATION_STYLE_SOFT)
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;
156  if (style == AUDIO_VISUALIZATION_STYLE_SOFT)
157  return 2.4f + glow * 1.4f;
158  if (style == AUDIO_VISUALIZATION_STYLE_NEON)
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;
166  if (style == AUDIO_VISUALIZATION_STYLE_NEON)
167  return 0.82f;
168  if (style == AUDIO_VISUALIZATION_STYLE_SOFT)
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 
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),
414  color_mode(AUDIO_VISUALIZATION_COLOR_SEED),
415  channel_layout(AUDIO_VISUALIZATION_CHANNEL_AUTO),
416  frequency_low(0.0),
417  frequency_high(1.0),
419 {
420  init_effect_details();
421 }
422 
423 AudioVisualization::AudioVisualization(int visualization_type, Color color) :
425 {
426  this->visualization_type = visualization_type;
427  this->color = color;
428 }
429 
430 void AudioVisualization::init_effect_details()
431 {
432  InitEffectInfo();
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 
440 std::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 ||
461  mode == AUDIO_VISUALIZATION_RADIAL ||
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());
469  const bool split = channel_layout == AUDIO_VISUALIZATION_CHANNEL_SPLIT ||
470  (channel_layout == AUDIO_VISUALIZATION_CHANNEL_AUTO && channels > 1 &&
472  const bool overlay = channel_layout == AUDIO_VISUALIZATION_CHANNEL_OVERLAY;
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());
487  if (background == AUDIO_VISUALIZATION_BACKGROUND_SOURCE && frame_image &&
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 
909 std::string AudioVisualization::Json() const {
910  return JsonValue().toStyledString();
911 }
912 
913 Json::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 
932 void 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 
944 void 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 
974 std::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 }
openshot::ClipBase::add_property_json
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:96
openshot::stringToJson
const Json::Value stringToJson(const std::string value)
Definition: Json.cpp:16
openshot::AudioVisualization::background
int background
Definition: AudioVisualization.h:84
openshot::AudioVisualization::JsonValue
Json::Value JsonValue() const override
Generate Json::Value for this object.
Definition: AudioVisualization.cpp:913
openshot::ClipBase::timeline
openshot::TimelineBase * timeline
Pointer to the parent timeline instance (if any)
Definition: ClipBase.h:40
openshot::AUDIO_VISUALIZATION_STYLE_MINIMAL
@ AUDIO_VISUALIZATION_STYLE_MINIMAL
Definition: AudioVisualization.h:43
openshot::AudioVisualization::visualization_type
int visualization_type
Definition: AudioVisualization.h:72
openshot::EffectBase::info
EffectInfoStruct info
Information about the current effect.
Definition: EffectBase.h:110
openshot::AudioVisualization::SetJson
void SetJson(const std::string value) override
Load JSON string into this object.
Definition: AudioVisualization.cpp:932
openshot::AUDIO_VISUALIZATION_RADIAL
@ AUDIO_VISUALIZATION_RADIAL
Definition: AudioVisualization.h:31
openshot::AudioVisualization::channel_layout
int channel_layout
Definition: AudioVisualization.h:81
openshot
This namespace is the default namespace for all code in the openshot library.
Definition: AnimatedCurve.h:24
openshot::AudioVisualization::AudioVisualization
AudioVisualization()
Definition: AudioVisualization.cpp:405
openshot::AUDIO_VISUALIZATION_SPECTRUM
@ AUDIO_VISUALIZATION_SPECTRUM
Definition: AudioVisualization.h:32
openshot::ClipBase::add_property_choice_json
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:132
openshot::EffectBase::JsonValue
virtual Json::Value JsonValue() const
Generate Json::Value for this object.
Definition: EffectBase.cpp:96
openshot::AudioVisualization::GetFrame
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...
Definition: AudioVisualization.h:89
Timeline.h
Header file for Timeline class.
openshot::Keyframe::SetJsonValue
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: KeyFrame.cpp:372
openshot::AUDIO_VISUALIZATION_BARS
@ AUDIO_VISUALIZATION_BARS
Definition: AudioVisualization.h:30
openshot::AUDIO_VISUALIZATION_STYLE_CLEAN
@ AUDIO_VISUALIZATION_STYLE_CLEAN
Definition: AudioVisualization.h:40
openshot::AudioVisualization::color_spread
Keyframe color_spread
Definition: AudioVisualization.h:79
openshot::AudioVisualization::smoothing
Keyframe smoothing
Definition: AudioVisualization.h:76
openshot::Keyframe::JsonValue
Json::Value JsonValue() const
Generate Json::Value for this object.
Definition: KeyFrame.cpp:339
openshot::AUDIO_VISUALIZATION_PARTICLES
@ AUDIO_VISUALIZATION_PARTICLES
Definition: AudioVisualization.h:34
openshot::AUDIO_VISUALIZATION_BACKGROUND_SOLID
@ AUDIO_VISUALIZATION_BACKGROUND_SOLID
Definition: AudioVisualization.h:55
openshot::AUDIO_VISUALIZATION_CHANNEL_OVERLAY
@ AUDIO_VISUALIZATION_CHANNEL_OVERLAY
Definition: AudioVisualization.h:50
openshot::AUDIO_VISUALIZATION_PHASE_SCOPE
@ AUDIO_VISUALIZATION_PHASE_SCOPE
Definition: AudioVisualization.h:33
openshot::Color
This class represents a color (used on the timeline and clips)
Definition: Color.h:27
openshot::AUDIO_VISUALIZATION_BACKGROUND_TRANSPARENT
@ AUDIO_VISUALIZATION_BACKGROUND_TRANSPARENT
Definition: AudioVisualization.h:54
openshot::AudioVisualization::Json
std::string Json() const override
Generate JSON string of this object.
Definition: AudioVisualization.cpp:909
openshot::EffectBase::BasePropertiesJSON
Json::Value BasePropertiesJSON(int64_t requested_frame) const
Generate JSON object of base properties (recommended to be used by all effects)
Definition: EffectBase.cpp:236
openshot::ClipBase::position
float position
The position on the timeline where this clip should start playing.
Definition: ClipBase.h:35
AudioVisualization.h
Header file for AudioVisualization effect class.
openshot::Color::SetJsonValue
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: Color.cpp:117
openshot::AUDIO_VISUALIZATION_VU_METER
@ AUDIO_VISUALIZATION_VU_METER
Definition: AudioVisualization.h:35
openshot::InvalidJSON
Exception for invalid JSON.
Definition: Exceptions.h:223
openshot::AudioVisualization::frequency_low
Keyframe frequency_low
Definition: AudioVisualization.h:82
openshot::Timeline
This class represents a timeline.
Definition: Timeline.h:153
openshot::EffectBase::InitEffectInfo
void InitEffectInfo()
Definition: EffectBase.cpp:37
openshot::Color::green
openshot::Keyframe green
Curve representing the green value (0 - 255)
Definition: Color.h:31
openshot::EffectInfoStruct::has_audio
bool has_audio
Determines if this effect manipulates the audio of a frame.
Definition: EffectBase.h:44
openshot::AudioVisualization::intensity
Keyframe intensity
Definition: AudioVisualization.h:75
openshot::ClipBase::end
float end
The position in seconds to end playing (used to trim the ending of a clip)
Definition: ClipBase.h:38
path
path
Definition: FFmpegWriter.cpp:1474
openshot::AudioVisualization::detail
Keyframe detail
Definition: AudioVisualization.h:77
openshot::AudioVisualization::style
int style
Definition: AudioVisualization.h:73
openshot::ClipBase::start
float start
The position in seconds to start playing (used to trim the beginning of a clip)
Definition: ClipBase.h:37
openshot::AUDIO_VISUALIZATION_BACKGROUND_SOURCE
@ AUDIO_VISUALIZATION_BACKGROUND_SOURCE
Definition: AudioVisualization.h:58
openshot::AUDIO_VISUALIZATION_COLOR_RAINBOW
@ AUDIO_VISUALIZATION_COLOR_RAINBOW
Definition: AudioVisualization.h:63
openshot::AudioVisualization::SetJsonValue
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
Definition: AudioVisualization.cpp:944
openshot::EffectInfoStruct::class_name
std::string class_name
The class name of the effect.
Definition: EffectBase.h:39
openshot::Color::JsonValue
Json::Value JsonValue() const
Generate Json::Value for this object.
Definition: Color.cpp:86
openshot::Keyframe::GetInt
int GetInt(int64_t index) const
Get the rounded INT value at a specific index.
Definition: KeyFrame.cpp:282
openshot::AudioVisualization::color_mode
int color_mode
Definition: AudioVisualization.h:80
openshot::EffectInfoStruct::description
std::string description
The description of this effect and what it does.
Definition: EffectBase.h:41
openshot::AUDIO_VISUALIZATION_BACKGROUND_FADE
@ AUDIO_VISUALIZATION_BACKGROUND_FADE
Definition: AudioVisualization.h:56
openshot::EffectInfoStruct::has_video
bool has_video
Determines if this effect manipulates the image of a frame.
Definition: EffectBase.h:43
openshot::AUDIO_VISUALIZATION_RADIAL_BARS
@ AUDIO_VISUALIZATION_RADIAL_BARS
Definition: AudioVisualization.h:36
openshot::AUDIO_VISUALIZATION_COLOR_SEED
@ AUDIO_VISUALIZATION_COLOR_SEED
Definition: AudioVisualization.h:62
openshot::AUDIO_VISUALIZATION_BACKGROUND_GRADIENT
@ AUDIO_VISUALIZATION_BACKGROUND_GRADIENT
Definition: AudioVisualization.h:57
openshot::AudioVisualization::PropertiesJSON
std::string PropertiesJSON(int64_t requested_frame) const override
Definition: AudioVisualization.cpp:974
openshot::AUDIO_VISUALIZATION_CHANNEL_AUTO
@ AUDIO_VISUALIZATION_CHANNEL_AUTO
Definition: AudioVisualization.h:47
openshot::EffectInfoStruct::name
std::string name
The name of the effect.
Definition: EffectBase.h:40
openshot::AUDIO_VISUALIZATION_STYLE_SOFT
@ AUDIO_VISUALIZATION_STYLE_SOFT
Definition: AudioVisualization.h:41
openshot::AudioVisualization
Definition: AudioVisualization.h:66
openshot::AudioVisualization::glow
Keyframe glow
Definition: AudioVisualization.h:78
openshot::Color::alpha
openshot::Keyframe alpha
Curve representing the alpha value (0 - 255)
Definition: Color.h:33
openshot::AUDIO_VISUALIZATION_CHANNEL_SPLIT
@ AUDIO_VISUALIZATION_CHANNEL_SPLIT
Definition: AudioVisualization.h:49
openshot::AUDIO_VISUALIZATION_STYLE_NEON
@ AUDIO_VISUALIZATION_STYLE_NEON
Definition: AudioVisualization.h:42
openshot::AUDIO_VISUALIZATION_WAVEFORM
@ AUDIO_VISUALIZATION_WAVEFORM
Definition: AudioVisualization.h:28
openshot::Color::red
openshot::Keyframe red
Curve representing the red value (0 - 255)
Definition: Color.h:30
openshot::AudioVisualization::frequency_high
Keyframe frequency_high
Definition: AudioVisualization.h:83
openshot::AudioVisualization::color
Color color
Definition: AudioVisualization.h:74
openshot::AUDIO_VISUALIZATION_CHANNEL_COMBINED
@ AUDIO_VISUALIZATION_CHANNEL_COMBINED
Definition: AudioVisualization.h:48
openshot::AUDIO_VISUALIZATION_FILLED_WAVEFORM
@ AUDIO_VISUALIZATION_FILLED_WAVEFORM
Definition: AudioVisualization.h:29
openshot::Color::blue
openshot::Keyframe blue
Curve representing the red value (0 - 255)
Definition: Color.h:32
Exceptions.h
Header file for all Exception classes.
openshot::EffectBase::SetJsonValue
virtual void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: EffectBase.cpp:139
openshot::Keyframe::GetValue
double GetValue(int64_t index) const
Get the value at a specific index.
Definition: KeyFrame.cpp:258