23 #include <AppConfig.h>
24 #include <juce_audio_basics/juce_audio_basics.h>
29 constexpr
double PI = 3.14159265358979323846;
31 inline float clampf(
float v,
float lo,
float hi) {
32 return v < lo ? lo : (v > hi ? hi : v);
35 inline int clampi(
int v,
int lo,
int hi) {
36 return v < lo ? lo : (v > hi ? hi : v);
39 inline int blend_channel(
int low,
int high,
int inv,
int blend) {
40 return (clampi(low, 0, 255) * inv + clampi(high, 0, 255) * blend) >> 8;
44 float normalized_frequency_to_hz(
float value) {
45 const float min_hz = 20.0f;
46 const float max_hz = 20000.0f;
47 const float n = clampf(value, 0.0f, 1.0f);
48 return min_hz * std::pow(max_hz / min_hz, n);
51 float hz_to_normalized_frequency(
float hz) {
52 if (hz >= 0.0f && hz <= 1.0f)
54 const float min_hz = 20.0f;
55 const float max_hz = 20000.0f;
56 hz = clampf(hz, min_hz, max_hz);
57 return clampf(std::log(hz / min_hz) / std::log(max_hz / min_hz), 0.0f, 1.0f);
63 float band_energy(
const std::shared_ptr<Frame>& frame,
float low_hz,
float high_hz,
float gain) {
64 const int samples = frame->GetAudioSamplesCount();
65 const int channels = frame->GetAudioChannelsCount();
66 const int sample_rate = std::max(1, frame->SampleRate());
67 if (samples <= 0 || channels <= 0)
70 const float nyquist = sample_rate * 0.5f;
71 low_hz = clampf(low_hz, 0.0f, nyquist - 1.0f);
72 high_hz = clampf(high_hz, low_hz + 1.0f, nyquist);
75 const float dt = 1.0f / sample_rate;
76 const float alpha_hi = dt / (dt + 1.0f / (2.0f * (float)PI * high_hz));
77 const float alpha_lo = low_hz > 1.0f
78 ? dt / (dt + 1.0f / (2.0f * (float)PI * low_hz))
81 auto* buffer = frame->GetAudioSampleBuffer();
82 std::vector<const float*> ch(channels);
83 for (
int c = 0; c < channels; ++c)
84 ch[c] = buffer->getReadPointer(c);
86 float lp_hi = 0.0f, lp_lo = 0.0f;
89 for (
int s = 0; s < samples; ++s) {
91 for (
int c = 0; c < channels; ++c)
95 lp_hi += alpha_hi * (x - lp_hi);
96 lp_lo += alpha_lo * (x - lp_lo);
97 const float filtered = lp_hi - lp_lo;
99 const float abs_v = std::fabs(filtered);
100 sum_sq += abs_v * abs_v;
101 peak = std::max(peak, abs_v);
104 const float rms = std::sqrt((
float)(sum_sq / samples));
106 const float combined = std::max(rms * 3.6f, peak * 1.15f);
107 return clampf(combined * gain, 0.0f, 1.0f);
112 low_color((unsigned char)0, (unsigned char)0, (unsigned char)0, (unsigned char)255),
113 high_color((unsigned char)255, (unsigned char)255, (unsigned char)255, (unsigned char)255),
124 init_effect_details();
134 void BeatSync::init_effect_details()
139 info.
description =
"Generates an audio-reactive color flash layer, synchronized to the beat.";
144 std::shared_ptr<openshot::Frame>
BeatSync::GetFrame(std::shared_ptr<openshot::Frame> frame, int64_t frame_number)
146 const std::shared_ptr<QImage> frame_image = frame->GetImage();
147 int width = frame_image ? std::max(1, frame_image->width()) : 1;
148 int height = frame_image ? std::max(1, frame_image->height()) : 1;
159 if (last_frame_ >= 0 && std::abs(frame_number - last_frame_) >= 2)
161 last_frame_ = frame_number;
164 const float low_hz = normalized_frequency_to_hz(clampf(
frequency_low.
GetValue(frame_number), 0.0f, 1.0f));
165 const float high_hz = std::max(low_hz + 1.0f,
168 const float raw_energy = band_energy(frame, low_hz, high_hz, gain_v);
172 const int samples = frame->GetAudioSamplesCount();
173 const int sample_rate = std::max(1, frame->SampleRate());
174 const float frame_sec = (samples > 0) ? (
float)samples / sample_rate : 1.0f / 24.0f;
176 const float atk = clampf((
float)
attack_ms.
GetValue(frame_number), 1.0f, 5000.0f);
177 const float dec = clampf((
float)
decay_ms.
GetValue(frame_number), 1.0f, 5000.0f);
178 const float atk_coef = std::exp(-frame_sec / (atk * 0.001f));
179 const float dec_coef = std::exp(-frame_sec / (dec * 0.001f));
181 if (raw_energy > envelope_)
182 envelope_ = atk_coef * envelope_ + (1.0f - atk_coef) * raw_energy;
184 envelope_ = dec_coef * envelope_ + (1.0f - dec_coef) * raw_energy;
188 float energy = envelope_;
192 energy = clampf((energy - thr) / std::max(0.001f, 1.0f - thr), 0.0f, 1.0f);
195 energy = 1.0f - energy;
198 const int blend = clampi(
static_cast<int>(response * 256.0f + 0.5f), 0, 256);
199 const int inv = 256 - blend;
200 auto out = std::make_shared<QImage>(width, height, QImage::Format_RGBA8888_Premultiplied);
207 frame->AddImage(out);
235 }
catch (
const std::exception& e) {
236 throw InvalidJSON(
"JSON is invalid (missing keys or invalid data types)");
251 if (!root[
"invert"].isNull())
invert = root[
"invert"].asBool();
278 root[
"invert"] =
add_property_json(
"Invert",
invert ? 1.0 : 0.0,
"int",
"", NULL, 0, 1,
false, requested_frame);
284 root[
"response_curve"][
"channel"] =
"all";
287 return root.toStyledString();
Header file for BeatSync effect class.
Header file for all Exception classes.
Header file for Timeline class.
void SetJsonValue(const Json::Value &root)
float Sample(float input, int64_t frame_number) const
std::string Summary(int64_t frame_number) const
Json::Value JsonValue() const
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...
std::string PropertiesJSON(int64_t requested_frame) const override
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
void SetJson(const std::string value) override
Load JSON string into this object.
AnimatedCurve response_curve
std::string Json() const override
Generate JSON string of this object.
Json::Value JsonValue() const override
Generate Json::Value for this object.
Json::Value add_property_choice_json(std::string name, int value, int selected_value) const
Generate JSON choice for a property (dropdown properties)
openshot::TimelineBase * timeline
Pointer to the parent timeline instance (if any)
virtual openshot::TimelineBase * ParentTimeline()
Get the associated Timeline pointer (if any)
Json::Value add_property_json(std::string name, float value, std::string type, std::string memo, const Keyframe *keyframe, float min_value, float max_value, bool readonly, int64_t requested_frame) const
Generate JSON for a property.
This class represents a color (used on the timeline and clips)
openshot::Keyframe blue
Curve representing the red value (0 - 255)
openshot::Keyframe red
Curve representing the red value (0 - 255)
openshot::Keyframe green
Curve representing the green value (0 - 255)
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
openshot::Keyframe alpha
Curve representing the alpha value (0 - 255)
Json::Value JsonValue() const
Generate Json::Value for this object.
virtual Json::Value JsonValue() const
Generate Json::Value for this object.
Json::Value BasePropertiesJSON(int64_t requested_frame) const
Generate JSON object of base properties (recommended to be used by all effects)
virtual void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
EffectInfoStruct info
Information about the current effect.
Exception for invalid JSON.
A Keyframe is a collection of Point instances, which is used to vary a number or property over time.
int GetInt(int64_t index) const
Get the rounded INT value at a specific index.
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
double GetValue(int64_t index) const
Get the value at a specific index.
Json::Value JsonValue() const
Generate Json::Value for this object.
This class represents a timeline.
This namespace is the default namespace for all code in the openshot library.
const Json::Value stringToJson(const std::string value)
bool has_video
Determines if this effect manipulates the image of a frame.
bool has_audio
Determines if this effect manipulates the audio of a frame.
std::string class_name
The class name of the effect.
std::string name
The name of the effect.
std::string description
The description of this effect and what it does.