1 /**
2 * @file
3 * @brief Source file for ParametricEQ audio effect class
4 * @author
5 *
6 * @ref License
7 */
8
9 /* LICENSE
10 *
11 * Copyright (c) 2008-2019 OpenShot Studios, LLC
12 * <http://www.openshotstudios.com/>. This file is part of
13 * OpenShot Library (libopenshot), an open-source project dedicated to
14 * delivering high quality video editing and animation solutions to the
15 * world. For more information visit <http://www.openshot.org/>.
16 *
17 * OpenShot Library (libopenshot) is free software: you can redistribute it
18 * and/or modify it under the terms of the GNU Lesser General Public License
19 * as published by the Free Software Foundation, either version 3 of the
20 * License, or (at your option) any later version.
21 *
22 * OpenShot Library (libopenshot) is distributed in the hope that it will be
23 * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU Lesser General Public License for more details.
26 *
27 * You should have received a copy of the GNU Lesser General Public License
28 * along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
29 */
30
31 #include "ParametricEQ.h"
32 #include "Exceptions.h"
33
34 using namespace openshot;
35
36 /// Blank constructor, useful when using Json to load the effect properties
ParametricEQ()37 ParametricEQ::ParametricEQ() : filter_type(LOW_PASS), frequency(500), gain(0), q_factor(0) {
38 // Init effect properties
39 init_effect_details();
40 }
41
42
43 // Default constructor
ParametricEQ(openshot::FilterType new_filter_type,Keyframe new_frequency,Keyframe new_gain,Keyframe new_q_factor)44 ParametricEQ::ParametricEQ(openshot::FilterType new_filter_type, Keyframe new_frequency, Keyframe new_gain, Keyframe new_q_factor) :
45 filter_type(new_filter_type), frequency(new_frequency), gain(new_gain), q_factor(new_q_factor)
46 {
47 // Init effect properties
48 init_effect_details();
49 }
50
51 // Init effect settings
init_effect_details()52 void ParametricEQ::init_effect_details()
53 {
54 /// Initialize the values of the EffectInfo struct.
55 InitEffectInfo();
56
57 /// Set the effect info
58 info.class_name = "ParametricEQ";
59 info.name = "Parametric EQ";
60 info.description = "Filter that allows you to adjust the volume level of a frequency in the audio track.";
61 info.has_audio = true;
62 info.has_video = false;
63 initialized = false;
64 }
65
66 // This method is required for all derived classes of EffectBase, and returns a
67 // modified openshot::Frame object
GetFrame(std::shared_ptr<openshot::Frame> frame,int64_t frame_number)68 std::shared_ptr<openshot::Frame> ParametricEQ::GetFrame(std::shared_ptr<openshot::Frame> frame, int64_t frame_number)
69 {
70 if (!initialized)
71 {
72 filters.clear();
73
74 for (int i = 0; i < frame->audio->getNumChannels(); ++i) {
75 Filter *filter;
76 filters.add(filter = new Filter());
77 }
78
79 initialized = true;
80 }
81
82 const int num_input_channels = frame->audio->getNumChannels();
83 const int num_output_channels = frame->audio->getNumChannels();
84 const int num_samples = frame->audio->getNumSamples();
85 updateFilters(frame_number, num_samples);
86
87 for (int channel = 0; channel < frame->audio->getNumChannels(); channel++)
88 {
89 auto *channel_data = frame->audio->getWritePointer(channel);
90 filters[channel]->processSamples(channel_data, num_samples);
91 }
92
93 for (int channel = num_input_channels; channel < num_output_channels; ++channel)
94 {
95 frame->audio->clear(channel, 0, num_samples);
96 }
97
98 // return the modified frame
99 return frame;
100 }
101
updateCoefficients(const double discrete_frequency,const double q_factor,const double gain,const int filter_type)102 void ParametricEQ::Filter::updateCoefficients (
103 const double discrete_frequency,
104 const double q_factor,
105 const double gain,
106 const int filter_type)
107 {
108 double bandwidth = jmin (discrete_frequency / q_factor, M_PI * 0.99);
109 double two_cos_wc = -2.0 * cos (discrete_frequency);
110 double tan_half_bw = tan (bandwidth / 2.0);
111 double tan_half_wc = tan (discrete_frequency / 2.0);
112 double sqrt_gain = sqrt (gain);
113
114 switch (filter_type) {
115 case 0 /* LOW_PASS */: {
116 coefficients = IIRCoefficients (/* b0 */ tan_half_wc,
117 /* b1 */ tan_half_wc,
118 /* b2 */ 0.0,
119 /* a0 */ tan_half_wc + 1.0,
120 /* a1 */ tan_half_wc - 1.0,
121 /* a2 */ 0.0);
122 break;
123 }
124 case 1 /* HIGH_PASS */: {
125 coefficients = IIRCoefficients (/* b0 */ 1.0,
126 /* b1 */ -1.0,
127 /* b2 */ 0.0,
128 /* a0 */ tan_half_wc + 1.0,
129 /* a1 */ tan_half_wc - 1.0,
130 /* a2 */ 0.0);
131 break;
132 }
133 case 2 /* LOW_SHELF */: {
134 coefficients = IIRCoefficients (/* b0 */ gain * tan_half_wc + sqrt_gain,
135 /* b1 */ gain * tan_half_wc - sqrt_gain,
136 /* b2 */ 0.0,
137 /* a0 */ tan_half_wc + sqrt_gain,
138 /* a1 */ tan_half_wc - sqrt_gain,
139 /* a2 */ 0.0);
140 break;
141 }
142 case 3 /* HIGH_SHELF */: {
143 coefficients = IIRCoefficients (/* b0 */ sqrt_gain * tan_half_wc + gain,
144 /* b1 */ sqrt_gain * tan_half_wc - gain,
145 /* b2 */ 0.0,
146 /* a0 */ sqrt_gain * tan_half_wc + 1.0,
147 /* a1 */ sqrt_gain * tan_half_wc - 1.0,
148 /* a2 */ 0.0);
149 break;
150 }
151 case 4 /* BAND_PASS */: {
152 coefficients = IIRCoefficients (/* b0 */ tan_half_bw,
153 /* b1 */ 0.0,
154 /* b2 */ -tan_half_bw,
155 /* a0 */ 1.0 + tan_half_bw,
156 /* a1 */ two_cos_wc,
157 /* a2 */ 1.0 - tan_half_bw);
158 break;
159 }
160 case 5 /* BAND_STOP */: {
161 coefficients = IIRCoefficients (/* b0 */ 1.0,
162 /* b1 */ two_cos_wc,
163 /* b2 */ 1.0,
164 /* a0 */ 1.0 + tan_half_bw,
165 /* a1 */ two_cos_wc,
166 /* a2 */ 1.0 - tan_half_bw);
167 break;
168 }
169 case 6 /* PEAKING_NOTCH */: {
170 coefficients = IIRCoefficients (/* b0 */ sqrt_gain + gain * tan_half_bw,
171 /* b1 */ sqrt_gain * two_cos_wc,
172 /* b2 */ sqrt_gain - gain * tan_half_bw,
173 /* a0 */ sqrt_gain + tan_half_bw,
174 /* a1 */ sqrt_gain * two_cos_wc,
175 /* a2 */ sqrt_gain - tan_half_bw);
176 break;
177 }
178 }
179
180 setCoefficients(coefficients);
181 }
182
updateFilters(int64_t frame_number,double sample_rate)183 void ParametricEQ::updateFilters(int64_t frame_number, double sample_rate)
184 {
185 double discrete_frequency = 2.0 * M_PI * (double)frequency.GetValue(frame_number) / sample_rate;
186 double q_value = (double)q_factor.GetValue(frame_number);
187 double gain_value = pow(10.0, (double)gain.GetValue(frame_number) * 0.05);
188 int filter_type_value = (int)filter_type;
189
190 for (int i = 0; i < filters.size(); ++i)
191 filters[i]->updateCoefficients(discrete_frequency, q_value, gain_value, filter_type_value);
192 }
193
194 // Generate JSON string of this object
Json() const195 std::string ParametricEQ::Json() const {
196
197 // Return formatted string
198 return JsonValue().toStyledString();
199 }
200
201 // Generate Json::Value for this object
JsonValue() const202 Json::Value ParametricEQ::JsonValue() const {
203
204 // Create root json object
205 Json::Value root = EffectBase::JsonValue(); // get parent properties
206 root["type"] = info.class_name;
207 root["filter_type"] = filter_type;
208 root["frequency"] = frequency.JsonValue();;
209 root["q_factor"] = q_factor.JsonValue();
210 root["gain"] = gain.JsonValue();
211
212 // return JsonValue
213 return root;
214 }
215
216 // Load JSON string into this object
SetJson(const std::string value)217 void ParametricEQ::SetJson(const std::string value) {
218
219 // Parse JSON string into JSON objects
220 try
221 {
222 const Json::Value root = openshot::stringToJson(value);
223 // Set all values that match
224 SetJsonValue(root);
225 }
226 catch (const std::exception& e)
227 {
228 // Error parsing JSON (or missing keys)
229 throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
230 }
231 }
232
233 // Load Json::Value into this object
SetJsonValue(const Json::Value root)234 void ParametricEQ::SetJsonValue(const Json::Value root) {
235
236 // Set parent data
237 EffectBase::SetJsonValue(root);
238
239 // Set data from Json (if key is found)
240 if (!root["filter_type"].isNull())
241 filter_type = (FilterType)root["filter_type"].asInt();
242
243 if (!root["frequency"].isNull())
244 frequency.SetJsonValue(root["frequency"]);
245
246 if (!root["gain"].isNull())
247 gain.SetJsonValue(root["gain"]);
248
249 if (!root["q_factor"].isNull())
250 q_factor.SetJsonValue(root["q_factor"]);
251 }
252
253 // Get all properties for a specific frame
PropertiesJSON(int64_t requested_frame) const254 std::string ParametricEQ::PropertiesJSON(int64_t requested_frame) const {
255
256 // Generate JSON properties list
257 Json::Value root;
258 root["id"] = add_property_json("ID", 0.0, "string", Id(), NULL, -1, -1, true, requested_frame);
259 root["layer"] = add_property_json("Track", Layer(), "int", "", NULL, 0, 20, false, requested_frame);
260 root["start"] = add_property_json("Start", Start(), "float", "", NULL, 0, 1000 * 60 * 30, false, requested_frame);
261 root["end"] = add_property_json("End", End(), "float", "", NULL, 0, 1000 * 60 * 30, false, requested_frame);
262 root["duration"] = add_property_json("Duration", Duration(), "float", "", NULL, 0, 1000 * 60 * 30, true, requested_frame);
263
264 // Keyframes
265 root["filter_type"] = add_property_json("Filter Type", filter_type, "int", "", NULL, 0, 3, false, requested_frame);
266 root["frequency"] = add_property_json("Frequency (Hz)", frequency.GetValue(requested_frame), "int", "", &frequency, 20, 20000, false, requested_frame);
267 root["gain"] = add_property_json("Gain (dB)", gain.GetValue(requested_frame), "int", "", &gain, -24, 24, false, requested_frame);
268 root["q_factor"] = add_property_json("Q Factor", q_factor.GetValue(requested_frame), "float", "", &q_factor, 0, 20, false, requested_frame);
269
270 // Add filter_type choices (dropdown style)
271 root["filter_type"]["choices"].append(add_property_choice_json("Low Pass", LOW_PASS, filter_type));
272 root["filter_type"]["choices"].append(add_property_choice_json("High Pass", HIGH_PASS, filter_type));
273 root["filter_type"]["choices"].append(add_property_choice_json("Low Shelf", LOW_SHELF, filter_type));
274 root["filter_type"]["choices"].append(add_property_choice_json("High Shelf", HIGH_SHELF, filter_type));
275 root["filter_type"]["choices"].append(add_property_choice_json("Band Pass", BAND_PASS, filter_type));
276 root["filter_type"]["choices"].append(add_property_choice_json("Band Stop", BAND_STOP, filter_type));
277 root["filter_type"]["choices"].append(add_property_choice_json("Peaking Notch", PEAKING_NOTCH, filter_type));
278
279 // Return formatted string
280 return root.toStyledString();
281 }
282