MayaFlux 0.1.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
Filter.cpp
Go to the documentation of this file.
1#include "Filter.hpp"
2
3#include <algorithm>
4
6
7std::pair<int, int> shift_parser(const std::string& str)
8{
9 size_t underscore = str.find('_');
10 if (underscore == std::string::npos) {
11 throw std::invalid_argument("Invalid format. Supply numberical format of nInputs_nOutpits like 25_2");
12 }
13
14 int inputs = std::stoi(str.substr(0, underscore));
15 int outputs = std::stoi(str.substr(underscore + 1));
16 return std::make_pair(inputs, outputs);
17}
18
19Filter::Filter(const std::shared_ptr<Node>& input, const std::string& zindex_shifts)
20 : m_input_node(input)
21{
22 m_shift_config = shift_parser(zindex_shifts);
24 m_coef_b.resize(m_input_history.size(), 1);
25 m_coef_a.resize(m_output_history.size(), 1);
26}
27
28Filter::Filter(const std::shared_ptr<Node>& input, const std::vector<double>& a_coef, const std::vector<double>& b_coef)
29 : m_input_node(input)
30 , m_coef_a(a_coef)
31 , m_coef_b(b_coef)
32{
33 m_shift_config = shift_parser(std::to_string(b_coef.size() - 1) + "_" + std::to_string(a_coef.size() - 1));
34
35 if (m_coef_a.empty() || m_coef_b.empty()) {
36 throw std::invalid_argument("IIR coefficients cannot be empty");
37 }
38 if (m_coef_a[0] == 0.0F) {
39 throw std::invalid_argument("First denominator coefficient (a[0]) cannot be zero");
40 }
41
43}
44
45Filter::Filter(const std::vector<double>& a_coef, const std::vector<double>& b_coef)
46 : Filter(nullptr, a_coef, b_coef)
47{
48}
49
51{
52 m_input_history.resize(m_shift_config.first + 1, 0.0F);
53 m_output_history.resize(m_shift_config.second + 1, 0.0F);
54}
55
56void Filter::set_coefs(const std::vector<double>& new_coefs, coefficients type)
57{
58 if (type == coefficients::OUTPUT) {
59 setACoefficients(new_coefs);
60 } else if (type == coefficients::INPUT) {
61 setBCoefficients(new_coefs);
62 } else {
63 setACoefficients(new_coefs);
64 setBCoefficients(new_coefs);
65 }
66}
67
68void Filter::build_input_history(double current_sample)
69{
71 size_t available = m_external_input_context.size();
72 size_t lookback = std::min(available, m_input_history.size() - 1);
73
74 m_input_history[0] = current_sample;
75
76 for (size_t i = 0; i < lookback; ++i) {
77 m_input_history[i + 1] = m_external_input_context[available - 1 - i];
78 }
79 } else {
80 update_inputs(current_sample);
81 }
82}
83
84void Filter::update_inputs(double current_sample)
85{
86 for (unsigned int i = m_input_history.size() - 1; i > 0; i--) {
88 }
89 m_input_history[0] = current_sample;
90}
91
92void Filter::update_outputs(double current_sample)
93{
94 m_output_history[0] = current_sample;
95 for (unsigned int i = m_output_history.size() - 1; i > 0; i--) {
97 }
98}
99
100void Filter::setACoefficients(const std::vector<double>& new_coefs)
101{
102 if (new_coefs.empty()) {
103 throw std::invalid_argument("Denominator coefficients cannot be empty");
104 }
105 if (new_coefs[0] == 0.0F) {
106 throw std::invalid_argument("First denominator coefficient (a[0]) cannot be zero");
107 }
108
109 m_coef_a = new_coefs;
110
111 if (static_cast<int>(m_coef_a.size()) - 1 != m_shift_config.second) {
112 m_shift_config.second = static_cast<int>(m_coef_a.size()) - 1;
114 }
115}
116
117void Filter::setBCoefficients(const std::vector<double>& new_coefs)
118{
119 if (new_coefs.empty()) {
120 throw std::invalid_argument("Numerator coefficients cannot be empty");
121 }
122
123 m_coef_b = new_coefs;
124
125 if (static_cast<int>(m_coef_b.size()) - 1 != m_shift_config.first) {
126 m_shift_config.first = static_cast<int>(m_coef_b.size()) - 1;
128 }
129}
130
131void Filter::update_coefs_from_node(int length, const std::shared_ptr<Node>& source, coefficients type)
132{
133 std::vector<double> samples = source->process_batch(length);
134 set_coefs(samples, type);
135}
136
138{
139 if (m_input_node) {
140 std::vector<double> samples = m_input_node->process_batch(length);
141 set_coefs(samples, type);
142 } else {
143 std::cerr << "No input node set for Filter. Use Filter::setInputNode() to set an input node.\n Alternatively, use Filter::updateCoefficientsFromNode() to specify a different source node.\n";
144 }
145}
146
147void Filter::add_coef_internal(uint64_t index, double value, std::vector<double>& buffer)
148{
149 if (index > buffer.size()) {
150 buffer.resize(index + 1, 1.F);
151 }
152 buffer.at(index) = value;
153}
154
155void Filter::add_coef(int index, double value, coefficients type)
156{
157 switch (type) {
159 add_coef_internal(index, value, m_coef_a);
160 break;
162 add_coef_internal(index, value, m_coef_b);
163 break;
164 default:
165 add_coef_internal(index, value, m_coef_a);
166 add_coef_internal(index, value, m_coef_b);
167 break;
168 }
169}
170
172{
173 std::ranges::fill(m_input_history, 0.0);
174 std::ranges::fill(m_output_history, 0.0);
175}
176
178{
179 if (type == coefficients::OUTPUT || type == coefficients::ALL) {
180 if (!m_coef_a.empty() && m_coef_a[0] != 0.0) {
181 double a0 = m_coef_a[0];
182 for (auto& coef : m_coef_a) {
183 coef /= a0;
184 }
185 }
186 }
187
188 if (type == coefficients::INPUT || type == coefficients::ALL) {
189 if (!m_coef_b.empty()) {
190 double max_coef = 0.0;
191 for (const auto& coef : m_coef_b) {
192 max_coef = std::max(max_coef, std::abs(coef));
193 }
194
195 if (max_coef > 0.0) {
196 for (auto& coef : m_coef_b) {
197 coef /= max_coef;
198 }
199 }
200 }
201 }
202}
203
204std::complex<double> Filter::get_frequency_response(double frequency, double sample_rate) const
205{
206 double omega = 2.0 * M_PI * frequency / sample_rate;
207 std::complex<double> z = std::exp(std::complex<double>(0, omega));
208
209 std::complex<double> numerator = 0.0;
210 for (size_t i = 0; i < m_coef_b.size(); ++i) {
211 numerator += m_coef_b[i] * std::pow(z, -static_cast<int>(i));
212 }
213
214 std::complex<double> denominator = 0.0;
215 for (size_t i = 0; i < m_coef_a.size(); ++i) {
216 denominator += m_coef_a[i] * std::pow(z, -static_cast<int>(i));
217 }
218
219 return numerator / denominator;
220}
221
222double Filter::get_magnitude_response(double frequency, double sample_rate) const
223{
224 return std::abs(get_frequency_response(frequency, sample_rate));
225}
226
227double Filter::get_phase_response(double frequency, double sample_rate) const
228{
229 return std::arg(get_frequency_response(frequency, sample_rate));
230}
231
232std::vector<double> Filter::process_batch(unsigned int num_samples)
233{
234 std::vector<double> output(num_samples);
235 for (unsigned int i = 0; i < num_samples; ++i) {
236 output[i] = process_sample(0.0); // The input value doesn't matter since we have an input node
237 }
238 return output;
239}
240
241std::unique_ptr<NodeContext> Filter::create_context(double value)
242{
243 if (m_gpu_compatible) {
244 return std::make_unique<FilterContextGpu>(value, m_input_history, m_output_history, m_coef_a, m_coef_b,
246 }
247 return std::make_unique<FilterContext>(value, m_input_history, m_output_history, m_coef_a, m_coef_b);
248}
249
250void Filter::notify_tick(double value)
251{
253
254 for (auto& callback : m_callbacks) {
255 callback(*m_last_context);
256 }
257 for (auto& [callback, condition] : m_conditional_callbacks) {
258 if (condition(*m_last_context)) {
259 callback(*m_last_context);
260 }
261 }
262}
263
264}
void add_coef_internal(uint64_t index, double value, std::vector< double > &buffer)
Modifies a specific coefficient in a coefficient buffer.
Definition Filter.cpp:147
void add_coef(int index, double value, coefficients type=coefficients::ALL)
Modifies a specific coefficient.
Definition Filter.cpp:155
std::vector< double > process_batch(unsigned int num_samples) override
Calculates the phase response at a specific frequency.
Definition Filter.cpp:232
virtual void reset()
Resets the filter's internal state.
Definition Filter.cpp:171
void build_input_history(double current_sample)
Builds input history from external context or internal accumulation.
Definition Filter.cpp:68
std::vector< double > m_coef_b
Feedforward (numerator) coefficients.
Definition Filter.hpp:618
std::vector< double > m_output_history
Buffer storing previous output samples.
Definition Filter.hpp:593
virtual void update_outputs(double current_sample)
Updates the output history buffer with a new sample.
Definition Filter.cpp:92
std::shared_ptr< Node > m_input_node
The most recent sample value generated by this oscillator.
Definition Filter.hpp:569
void update_coef_from_input(int length, coefficients type=coefficients::ALL)
Updates coefficients from the filter's own input.
Definition Filter.cpp:137
virtual void initialize_shift_buffers()
Initializes the input and output history buffers.
Definition Filter.cpp:50
std::complex< double > get_frequency_response(double frequency, double sample_rate) const
Calculates the complex frequency response at a specific frequency.
Definition Filter.cpp:204
std::unique_ptr< NodeContext > create_context(double value) override
Creates a filter-specific context object.
Definition Filter.cpp:241
void set_coefs(const std::vector< double > &new_coefs, coefficients type=coefficients::ALL)
Updates filter coefficients.
Definition Filter.cpp:56
Filter(const std::shared_ptr< Node > &input, const std::string &zindex_shifts)
Constructor using string-based filter configuration.
Definition Filter.cpp:19
void normalize_coefficients(coefficients type=coefficients::ALL)
Normalizes filter coefficients.
Definition Filter.cpp:177
void setACoefficients(const std::vector< double > &new_coefs)
Updates the feedback (denominator) coefficients.
Definition Filter.cpp:100
std::vector< double > m_input_history
Buffer storing previous input samples.
Definition Filter.hpp:585
void setBCoefficients(const std::vector< double > &new_coefs)
Updates the feedforward (numerator) coefficients.
Definition Filter.cpp:117
virtual void update_inputs(double current_sample)
Updates the input history buffer with a new sample.
Definition Filter.cpp:84
void update_coefs_from_node(int length, const std::shared_ptr< Node > &source, coefficients type=coefficients::ALL)
Updates coefficients from another node's output.
Definition Filter.cpp:131
void notify_tick(double value) override
Notifies all registered callbacks with the current filter context.
Definition Filter.cpp:250
std::pair< int, int > m_shift_config
Configuration for input and output buffer sizes.
Definition Filter.hpp:577
std::span< double > m_external_input_context
External input context for input history.
Definition Filter.hpp:602
double get_phase_response(double frequency, double sample_rate) const
Calculates the phase response at a specific frequency.
Definition Filter.cpp:227
std::vector< double > m_coef_a
Feedback (denominator) coefficients.
Definition Filter.hpp:610
double get_magnitude_response(double frequency, double sample_rate) const
Calculates the magnitude response at a specific frequency.
Definition Filter.cpp:222
double process_sample(double input=0.) override=0
Processes a single sample through the filter.
Base class for computational signal transformers implementing difference equations.
Definition Filter.hpp:138
std::vector< NodeHook > m_callbacks
Collection of standard callback functions.
Definition Node.hpp:416
std::vector< std::pair< NodeHook, NodeCondition > > m_conditional_callbacks
Collection of conditional callback functions with their predicates.
Definition Node.hpp:426
bool m_gpu_compatible
Flag indicating if the node supports GPU processing This flag is set by derived classes to indicate w...
Definition Node.hpp:387
std::unique_ptr< NodeContext > m_last_context
The last context object created for callbacks.
Definition Node.hpp:396
std::span< const float > get_gpu_data_buffer() const
Provides access to the GPU data buffer.
Definition Node.cpp:78
std::pair< int, int > shift_parser(const std::string &str)
Parses a string representation of filter order into input/output shift configuration.
Definition Filter.cpp:7