MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
Filter.cpp
Go to the documentation of this file.
1#include "Filter.hpp"
2
4
6
7Filter::Filter(const std::shared_ptr<Node>& input, const std::vector<double>& a_coef, const std::vector<double>& b_coef)
8 : m_input_node(input)
9 , m_coef_a(a_coef)
10 , m_coef_b(b_coef)
11 , m_context(0.0, m_input_history, m_output_history, m_coef_a, m_coef_b)
12 , m_context_gpu(0.0, m_input_history, m_output_history, m_coef_a, m_coef_b, get_gpu_data_buffer())
13{
14 if (m_coef_a.empty() || m_coef_b.empty()) {
15 error<std::invalid_argument>(Journal::Component::Nodes, Journal::Context::Configuration, std::source_location::current(),
16 "IIR coefficients cannot be empty. Received a_coef size: {}, b_coef size: {}", m_coef_a.size(), m_coef_b.size());
17 }
18 if (m_coef_a[0] == 0.0F) {
19 error<std::invalid_argument>(Journal::Component::Nodes, Journal::Context::Configuration, std::source_location::current(),
20 "First denominator coefficient (a[0]) cannot be zero. Received a[0]: {}", m_coef_a[0]);
21 }
22 m_input_history.assign(m_coef_b.size(), 0.0);
23 m_output_history.assign(m_coef_a.size(), 0.0);
24}
25
26Filter::Filter(const std::vector<double>& a_coef, const std::vector<double>& b_coef)
27 : Filter(nullptr, a_coef, b_coef)
28{
29}
30
31void Filter::set_coefs(const std::vector<double>& new_coefs, coefficients type)
32{
33 if (type == coefficients::OUTPUT) {
34 setACoefficients(new_coefs);
35 } else if (type == coefficients::INPUT) {
36 setBCoefficients(new_coefs);
37 } else {
38 setACoefficients(new_coefs);
39 setBCoefficients(new_coefs);
40 }
41}
42
43void Filter::build_input_history(double current_sample)
44{
46 size_t available = m_external_input_context.size();
47 size_t lookback = std::min(available, m_input_history.size() - 1);
48
49 m_input_history[0] = current_sample;
50
51 for (size_t i = 0; i < lookback; ++i) {
52 m_input_history[i + 1] = m_external_input_context[available - 1 - i];
53 }
54 } else {
55 update_inputs(current_sample);
56 }
57}
58
59void Filter::update_inputs(double current_sample)
60{
61 for (unsigned int i = m_input_history.size() - 1; i > 0; i--) {
63 }
64 m_input_history[0] = current_sample;
65}
66
67void Filter::update_outputs(double current_sample)
68{
69 m_output_history[0] = current_sample;
70 for (unsigned int i = m_output_history.size() - 1; i > 0; i--) {
72 }
73}
74
75void Filter::setACoefficients(const std::vector<double>& new_coefs)
76{
77 if (new_coefs.empty()) {
78 error<std::invalid_argument>(Journal::Component::Nodes, Journal::Context::Configuration, std::source_location::current(),
79 "Denominator coefficients cannot be empty. Received size: {}", new_coefs.size());
80 }
81 if (new_coefs[0] == 0.0F) {
82 error<std::invalid_argument>(Journal::Component::Nodes, Journal::Context::Configuration, std::source_location::current(),
83 "First denominator coefficient (a[0]) cannot be zero. Received a[0]: {}", new_coefs[0]);
84 }
85
86 m_coef_a = new_coefs;
87 m_output_history.assign(m_coef_a.size(), 0.0);
88}
89
90void Filter::setBCoefficients(const std::vector<double>& new_coefs)
91{
92 if (new_coefs.empty()) {
93 error<std::invalid_argument>(Journal::Component::Nodes, Journal::Context::Configuration, std::source_location::current(),
94 "Numerator coefficients cannot be empty. Received size: {}", new_coefs.size());
95 }
96
97 m_coef_b = new_coefs;
98 m_input_history.assign(m_coef_b.size(), 0.0);
99}
100
101void Filter::update_coefs_from_node(int length, const std::shared_ptr<Node>& source, coefficients type)
102{
103 std::vector<double> samples = source->process_batch(length);
104 set_coefs(samples, type);
105}
106
108{
109 if (m_input_node) {
110 std::vector<double> samples = m_input_node->process_batch(length);
111 set_coefs(samples, type);
112 } else {
113 MF_WARN(Journal::Component::Nodes, Journal::Context::NodeProcessing, "No input node set for Filter. Use Filter::setInputNode() to set an input node. Alternatively, use Filter::updateCoefficientsFromNode() to specify a different source node.");
114 }
115}
116
117void Filter::add_coef_internal(uint64_t index, double value, std::vector<double>& buffer)
118{
119 if (index > buffer.size()) {
120 buffer.resize(index + 1, 1.F);
121 }
122 buffer.at(index) = value;
123}
124
125void Filter::add_coef(int index, double value, coefficients type)
126{
127 switch (type) {
129 add_coef_internal(index, value, m_coef_a);
130 break;
132 add_coef_internal(index, value, m_coef_b);
133 break;
134 default:
135 add_coef_internal(index, value, m_coef_a);
136 add_coef_internal(index, value, m_coef_b);
137 break;
138 }
139}
140
142{
143 std::ranges::fill(m_input_history, 0.0);
144 std::ranges::fill(m_output_history, 0.0);
145}
146
148{
149 if (type == coefficients::OUTPUT || type == coefficients::ALL) {
150 if (!m_coef_a.empty() && m_coef_a[0] != 0.0) {
151 double a0 = m_coef_a[0];
152 for (auto& coef : m_coef_a) {
153 coef /= a0;
154 }
155 }
156 }
157
158 if (type == coefficients::INPUT || type == coefficients::ALL) {
159 if (!m_coef_b.empty()) {
160 double max_coef = 0.0;
161 for (const auto& coef : m_coef_b) {
162 max_coef = std::max(max_coef, std::abs(coef));
163 }
164
165 if (max_coef > 0.0) {
166 for (auto& coef : m_coef_b) {
167 coef /= max_coef;
168 }
169 }
170 }
171 }
172}
173
174std::complex<double> Filter::get_frequency_response(double frequency, double sample_rate) const
175{
176 double omega = 2.0 * M_PI * frequency / sample_rate;
177 std::complex<double> z = std::exp(std::complex<double>(0, omega));
178
179 std::complex<double> numerator = 0.0;
180 for (size_t i = 0; i < m_coef_b.size(); ++i) {
181 numerator += m_coef_b[i] * std::pow(z, -static_cast<int>(i));
182 }
183
184 std::complex<double> denominator = 0.0;
185 for (size_t i = 0; i < m_coef_a.size(); ++i) {
186 denominator += m_coef_a[i] * std::pow(z, -static_cast<int>(i));
187 }
188
189 return numerator / denominator;
190}
191
192double Filter::get_magnitude_response(double frequency, double sample_rate) const
193{
194 return std::abs(get_frequency_response(frequency, sample_rate));
195}
196
197double Filter::get_phase_response(double frequency, double sample_rate) const
198{
199 return std::arg(get_frequency_response(frequency, sample_rate));
200}
201
202std::vector<double> Filter::process_batch(unsigned int num_samples)
203{
204 std::vector<double> output(num_samples);
205 for (unsigned int i = 0; i < num_samples; ++i) {
206 output[i] = process_sample(0.0); // The input value doesn't matter since we have an input node
207 }
208 return output;
209}
210
211void Filter::update_context(double value)
212{
213 if (m_gpu_compatible) {
214 m_context_gpu.value = value;
215
216 const auto& src = m_context_gpu.input_history;
217 m_context_gpu.gpu_float_buffer.resize(src.size());
218 for (size_t i = 0; i < src.size(); ++i)
219 m_context_gpu.gpu_float_buffer[i] = static_cast<float>(src[i]);
220
221 m_context_gpu.m_gpu_data = std::span<const float>(m_context_gpu.gpu_float_buffer);
222 } else {
223 m_context.value = value;
224 }
225}
226
227void Filter::notify_tick(double value)
228{
229 update_context(value);
230 auto& ctx = get_last_context();
231
232 for (auto& callback : m_callbacks) {
233 callback(ctx);
234 }
235 for (auto& [callback, condition] : m_conditional_callbacks) {
236 if (condition(ctx)) {
237 callback(ctx);
238 }
239 }
240}
241
243{
244 if (m_gpu_compatible) {
245 return m_context_gpu;
246 }
247 return m_context;
248}
249
251{
252 m_callbacks.emplace_back([callback](NodeContext& ctx) {
253 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast)
254 callback(static_cast<FilterContext&>(ctx));
255 });
256}
257
258void Filter::on_tick_if(const NodeCondition& condition, const TypedHook<FilterContext>& callback)
259{
260 m_conditional_callbacks.emplace_back([callback](NodeContext& ctx) {
261 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast)
262 callback(static_cast<FilterContext&>(ctx));
263 },
264 condition);
265}
266
267std::vector<std::pair<ModulatorRole, std::shared_ptr<Node>>>
269{
270 if (m_input_node)
272 return {};
273}
274
275}
#define MF_WARN(comp, ctx,...)
Core::GlobalInputConfig input
Definition Config.cpp:36
double frequency
const std::vector< double > & input_history
Current input history buffer.
Definition Filter.hpp:63
Specialized context for filter node callbacks.
Definition Filter.hpp:29
void add_coef_internal(uint64_t index, double value, std::vector< double > &buffer)
Modifies a specific coefficient in a coefficient buffer.
Definition Filter.cpp:117
Filter(const std::shared_ptr< Node > &input, const std::vector< double > &a_coef, const std::vector< double > &b_coef)
Constructor using explicit coefficient vectors.
Definition Filter.cpp:7
void add_coef(int index, double value, coefficients type=coefficients::ALL)
Modifies a specific coefficient.
Definition Filter.cpp:125
void on_tick(const TypedHook< FilterContext > &callback)
Registers a callback to be called on each tick with the filter context.
Definition Filter.cpp:250
std::vector< double > process_batch(unsigned int num_samples) override
Calculates the phase response at a specific frequency.
Definition Filter.cpp:202
virtual void reset()
Resets the filter's internal state.
Definition Filter.cpp:141
void build_input_history(double current_sample)
Builds input history from external context or internal accumulation.
Definition Filter.cpp:43
std::vector< double > m_coef_b
Feedforward (numerator) coefficients.
Definition Filter.hpp:601
std::vector< double > m_output_history
Buffer storing previous output samples.
Definition Filter.hpp:576
virtual void update_outputs(double current_sample)
Updates the output history buffer with a new sample.
Definition Filter.cpp:67
std::vector< std::pair< ModulatorRole, std::shared_ptr< Node > > > get_modulators() const override
Retrieves the current modulators connected to this node.
Definition Filter.cpp:268
std::shared_ptr< Node > m_input_node
The most recent sample value generated by this oscillator.
Definition Filter.hpp:560
void update_coef_from_input(int length, coefficients type=coefficients::ALL)
Updates coefficients from the filter's own input.
Definition Filter.cpp:107
std::complex< double > get_frequency_response(double frequency, double sample_rate) const
Calculates the complex frequency response at a specific frequency.
Definition Filter.cpp:174
void set_coefs(const std::vector< double > &new_coefs, coefficients type=coefficients::ALL)
Updates filter coefficients.
Definition Filter.cpp:31
void normalize_coefficients(coefficients type=coefficients::ALL)
Normalizes filter coefficients.
Definition Filter.cpp:147
void setACoefficients(const std::vector< double > &new_coefs)
Updates the feedback (denominator) coefficients.
Definition Filter.cpp:75
std::vector< double > m_input_history
Buffer storing previous input samples.
Definition Filter.hpp:568
void setBCoefficients(const std::vector< double > &new_coefs)
Updates the feedforward (numerator) coefficients.
Definition Filter.cpp:90
virtual void update_inputs(double current_sample)
Updates the input history buffer with a new sample.
Definition Filter.cpp:59
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:101
void notify_tick(double value) override
Notifies all registered callbacks with the current filter context.
Definition Filter.cpp:227
FilterContextGpu m_context_gpu
Definition Filter.hpp:625
std::span< double > m_external_input_context
External input context for input history.
Definition Filter.hpp:585
void on_tick_if(const NodeCondition &condition, const TypedHook< FilterContext > &callback)
Registers a conditional callback to be called on each tick if the condition is met.
Definition Filter.cpp:258
double get_phase_response(double frequency, double sample_rate) const
Calculates the phase response at a specific frequency.
Definition Filter.cpp:197
void update_context(double value) override
Updates filter-specific context object.
Definition Filter.cpp:211
std::vector< double > m_coef_a
Feedback (denominator) coefficients.
Definition Filter.hpp:593
double get_magnitude_response(double frequency, double sample_rate) const
Calculates the magnitude response at a specific frequency.
Definition Filter.cpp:192
double process_sample(double input=0.) override=0
Processes a single sample through the filter.
NodeContext & get_last_context() override
Gets the last created context object.
Definition Filter.cpp:242
Base class for computational signal transformers implementing difference equations.
Definition Filter.hpp:139
std::span< const float > m_gpu_data
double value
Current sample value.
Definition Node.hpp:63
Base context class for node callbacks.
Definition Node.hpp:53
std::vector< NodeHook > m_callbacks
Collection of standard callback functions.
Definition Node.hpp:434
std::vector< std::pair< NodeHook, NodeCondition > > m_conditional_callbacks
Collection of conditional callback functions with their predicates.
Definition Node.hpp:444
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:414
@ Configuration
Configuration and parameter updates.
@ NodeProcessing
Node graph processing (Nodes::NodeGraphManager)
@ Nodes
DSP Generator and Filter Nodes, graph pipeline, node management.
std::function< void(ContextT &)> TypedHook
Callback function type for node processing events, parameterised on context type.
Definition NodeUtils.hpp:28
std::function< bool(NodeContext &)> NodeCondition
Predicate function type for conditional callbacks.
Definition NodeUtils.hpp:54