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