/** * @file * @brief Source file for ParametricEQ audio effect class * @author * * @ref License */ /* LICENSE * * Copyright (c) 2008-2019 OpenShot Studios, LLC * . This file is part of * OpenShot Library (libopenshot), an open-source project dedicated to * delivering high quality video editing and animation solutions to the * world. For more information visit . * * OpenShot Library (libopenshot) is free software: you can redistribute it * and/or modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * OpenShot Library (libopenshot) is distributed in the hope that it will be * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with OpenShot Library. If not, see . */ #include "ParametricEQ.h" #include "Exceptions.h" using namespace openshot; /// Blank constructor, useful when using Json to load the effect properties ParametricEQ::ParametricEQ() : filter_type(LOW_PASS), frequency(500), gain(0), q_factor(0) { // Init effect properties init_effect_details(); } // Default constructor ParametricEQ::ParametricEQ(openshot::FilterType new_filter_type, Keyframe new_frequency, Keyframe new_gain, Keyframe new_q_factor) : filter_type(new_filter_type), frequency(new_frequency), gain(new_gain), q_factor(new_q_factor) { // Init effect properties init_effect_details(); } // Init effect settings void ParametricEQ::init_effect_details() { /// Initialize the values of the EffectInfo struct. InitEffectInfo(); /// Set the effect info info.class_name = "ParametricEQ"; info.name = "Parametric EQ"; info.description = "Filter that allows you to adjust the volume level of a frequency in the audio track."; info.has_audio = true; info.has_video = false; initialized = false; } // This method is required for all derived classes of EffectBase, and returns a // modified openshot::Frame object std::shared_ptr ParametricEQ::GetFrame(std::shared_ptr frame, int64_t frame_number) { if (!initialized) { filters.clear(); for (int i = 0; i < frame->audio->getNumChannels(); ++i) { Filter *filter; filters.add(filter = new Filter()); } initialized = true; } const int num_input_channels = frame->audio->getNumChannels(); const int num_output_channels = frame->audio->getNumChannels(); const int num_samples = frame->audio->getNumSamples(); updateFilters(frame_number, num_samples); for (int channel = 0; channel < frame->audio->getNumChannels(); channel++) { auto *channel_data = frame->audio->getWritePointer(channel); filters[channel]->processSamples(channel_data, num_samples); } for (int channel = num_input_channels; channel < num_output_channels; ++channel) { frame->audio->clear(channel, 0, num_samples); } // return the modified frame return frame; } void ParametricEQ::Filter::updateCoefficients ( const double discrete_frequency, const double q_factor, const double gain, const int filter_type) { double bandwidth = jmin (discrete_frequency / q_factor, M_PI * 0.99); double two_cos_wc = -2.0 * cos (discrete_frequency); double tan_half_bw = tan (bandwidth / 2.0); double tan_half_wc = tan (discrete_frequency / 2.0); double sqrt_gain = sqrt (gain); switch (filter_type) { case 0 /* LOW_PASS */: { coefficients = IIRCoefficients (/* b0 */ tan_half_wc, /* b1 */ tan_half_wc, /* b2 */ 0.0, /* a0 */ tan_half_wc + 1.0, /* a1 */ tan_half_wc - 1.0, /* a2 */ 0.0); break; } case 1 /* HIGH_PASS */: { coefficients = IIRCoefficients (/* b0 */ 1.0, /* b1 */ -1.0, /* b2 */ 0.0, /* a0 */ tan_half_wc + 1.0, /* a1 */ tan_half_wc - 1.0, /* a2 */ 0.0); break; } case 2 /* LOW_SHELF */: { coefficients = IIRCoefficients (/* b0 */ gain * tan_half_wc + sqrt_gain, /* b1 */ gain * tan_half_wc - sqrt_gain, /* b2 */ 0.0, /* a0 */ tan_half_wc + sqrt_gain, /* a1 */ tan_half_wc - sqrt_gain, /* a2 */ 0.0); break; } case 3 /* HIGH_SHELF */: { coefficients = IIRCoefficients (/* b0 */ sqrt_gain * tan_half_wc + gain, /* b1 */ sqrt_gain * tan_half_wc - gain, /* b2 */ 0.0, /* a0 */ sqrt_gain * tan_half_wc + 1.0, /* a1 */ sqrt_gain * tan_half_wc - 1.0, /* a2 */ 0.0); break; } case 4 /* BAND_PASS */: { coefficients = IIRCoefficients (/* b0 */ tan_half_bw, /* b1 */ 0.0, /* b2 */ -tan_half_bw, /* a0 */ 1.0 + tan_half_bw, /* a1 */ two_cos_wc, /* a2 */ 1.0 - tan_half_bw); break; } case 5 /* BAND_STOP */: { coefficients = IIRCoefficients (/* b0 */ 1.0, /* b1 */ two_cos_wc, /* b2 */ 1.0, /* a0 */ 1.0 + tan_half_bw, /* a1 */ two_cos_wc, /* a2 */ 1.0 - tan_half_bw); break; } case 6 /* PEAKING_NOTCH */: { coefficients = IIRCoefficients (/* b0 */ sqrt_gain + gain * tan_half_bw, /* b1 */ sqrt_gain * two_cos_wc, /* b2 */ sqrt_gain - gain * tan_half_bw, /* a0 */ sqrt_gain + tan_half_bw, /* a1 */ sqrt_gain * two_cos_wc, /* a2 */ sqrt_gain - tan_half_bw); break; } } setCoefficients(coefficients); } void ParametricEQ::updateFilters(int64_t frame_number, double sample_rate) { double discrete_frequency = 2.0 * M_PI * (double)frequency.GetValue(frame_number) / sample_rate; double q_value = (double)q_factor.GetValue(frame_number); double gain_value = pow(10.0, (double)gain.GetValue(frame_number) * 0.05); int filter_type_value = (int)filter_type; for (int i = 0; i < filters.size(); ++i) filters[i]->updateCoefficients(discrete_frequency, q_value, gain_value, filter_type_value); } // Generate JSON string of this object std::string ParametricEQ::Json() const { // Return formatted string return JsonValue().toStyledString(); } // Generate Json::Value for this object Json::Value ParametricEQ::JsonValue() const { // Create root json object Json::Value root = EffectBase::JsonValue(); // get parent properties root["type"] = info.class_name; root["filter_type"] = filter_type; root["frequency"] = frequency.JsonValue();; root["q_factor"] = q_factor.JsonValue(); root["gain"] = gain.JsonValue(); // return JsonValue return root; } // Load JSON string into this object void ParametricEQ::SetJson(const std::string value) { // Parse JSON string into JSON objects try { const Json::Value root = openshot::stringToJson(value); // Set all values that match SetJsonValue(root); } catch (const std::exception& e) { // Error parsing JSON (or missing keys) throw InvalidJSON("JSON is invalid (missing keys or invalid data types)"); } } // Load Json::Value into this object void ParametricEQ::SetJsonValue(const Json::Value root) { // Set parent data EffectBase::SetJsonValue(root); // Set data from Json (if key is found) if (!root["filter_type"].isNull()) filter_type = (FilterType)root["filter_type"].asInt(); if (!root["frequency"].isNull()) frequency.SetJsonValue(root["frequency"]); if (!root["gain"].isNull()) gain.SetJsonValue(root["gain"]); if (!root["q_factor"].isNull()) q_factor.SetJsonValue(root["q_factor"]); } // Get all properties for a specific frame std::string ParametricEQ::PropertiesJSON(int64_t requested_frame) const { // Generate JSON properties list Json::Value root; root["id"] = add_property_json("ID", 0.0, "string", Id(), NULL, -1, -1, true, requested_frame); root["layer"] = add_property_json("Track", Layer(), "int", "", NULL, 0, 20, false, requested_frame); root["start"] = add_property_json("Start", Start(), "float", "", NULL, 0, 1000 * 60 * 30, false, requested_frame); root["end"] = add_property_json("End", End(), "float", "", NULL, 0, 1000 * 60 * 30, false, requested_frame); root["duration"] = add_property_json("Duration", Duration(), "float", "", NULL, 0, 1000 * 60 * 30, true, requested_frame); // Keyframes root["filter_type"] = add_property_json("Filter Type", filter_type, "int", "", NULL, 0, 3, false, requested_frame); root["frequency"] = add_property_json("Frequency (Hz)", frequency.GetValue(requested_frame), "int", "", &frequency, 20, 20000, false, requested_frame); root["gain"] = add_property_json("Gain (dB)", gain.GetValue(requested_frame), "int", "", &gain, -24, 24, false, requested_frame); root["q_factor"] = add_property_json("Q Factor", q_factor.GetValue(requested_frame), "float", "", &q_factor, 0, 20, false, requested_frame); // Add filter_type choices (dropdown style) root["filter_type"]["choices"].append(add_property_choice_json("Low Pass", LOW_PASS, filter_type)); root["filter_type"]["choices"].append(add_property_choice_json("High Pass", HIGH_PASS, filter_type)); root["filter_type"]["choices"].append(add_property_choice_json("Low Shelf", LOW_SHELF, filter_type)); root["filter_type"]["choices"].append(add_property_choice_json("High Shelf", HIGH_SHELF, filter_type)); root["filter_type"]["choices"].append(add_property_choice_json("Band Pass", BAND_PASS, filter_type)); root["filter_type"]["choices"].append(add_property_choice_json("Band Stop", BAND_STOP, filter_type)); root["filter_type"]["choices"].append(add_property_choice_json("Peaking Notch", PEAKING_NOTCH, filter_type)); // Return formatted string return root.toStyledString(); }