MayaFlux 0.5.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
8
9Filter::Filter(const std::shared_ptr<Node>& input, const std::vector<double>& a_coef, const std::vector<double>& b_coef)
10 : m_input_node(input)
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 setACoefficients(a_coef);
15 setBCoefficients(b_coef);
16}
17
18Filter::Filter(const std::vector<double>& a_coef, const std::vector<double>& b_coef)
19 : Filter(nullptr, a_coef, b_coef)
20{
21}
22
23void Filter::set_coefs(const std::vector<double>& new_coefs, coefficients type)
24{
26 setACoefficients(new_coefs);
27 } else if (type == coefficients::INPUT) {
28 setBCoefficients(new_coefs);
29 } else {
30 setACoefficients(new_coefs);
31 setBCoefficients(new_coefs);
32 }
33}
34
35void Filter::build_input_history(double current_sample)
36{
38 size_t available = m_external_input_context.size();
39 size_t lookback = std::min(available, m_input_history.size() - 1);
40
41 m_input_history[0] = current_sample;
42
43 for (size_t i = 0; i < lookback; ++i) {
45 }
46 } else {
47 update_inputs(current_sample);
48 }
49}
50
51void Filter::update_inputs(double current_sample)
52{
53 for (unsigned int i = m_input_history.size() - 1; i > 0; i--) {
55 }
56 m_input_history[0] = current_sample;
57}
58
59void Filter::update_outputs(double current_sample)
60{
61 for (unsigned int i = m_output_history.size() - 1; i > 0; i--) {
63 }
64 m_output_history[0] = current_sample;
65}
66
67void Filter::setACoefficients(const std::vector<double>& new_coefs)
68{
69 if (new_coefs.empty()) {
70 error<std::invalid_argument>(Journal::Component::Nodes, Journal::Context::Configuration, std::source_location::current(),
71 "Denominator coefficients cannot be empty. Received size: {}", new_coefs.size());
72 }
73 if (std::abs(new_coefs[0]) < k_min_leading_coef) {
74 error<std::invalid_argument>(Journal::Component::Nodes, Journal::Context::Configuration, std::source_location::current(),
75 "First denominator coefficient (a[0]) magnitude below {}. Received a[0]: {}", k_min_leading_coef, new_coefs[0]);
76 }
77
78 m_coef_a = new_coefs;
79 const double a0 = m_coef_a[0];
80 for (auto& coef : m_coef_a) {
81 coef /= a0;
82 }
83 m_output_history.assign(m_coef_a.size(), 0.0);
84}
85
86void Filter::setBCoefficients(const std::vector<double>& new_coefs)
87{
88 if (new_coefs.empty()) {
89 error<std::invalid_argument>(Journal::Component::Nodes, Journal::Context::Configuration, std::source_location::current(),
90 "Numerator coefficients cannot be empty. Received size: {}", new_coefs.size());
91 }
92
93 m_coef_b = new_coefs;
94 m_input_history.assign(m_coef_b.size(), 0.0);
95}
96
97void Filter::update_coefs_from_node(int length, const std::shared_ptr<Node>& source, coefficients type)
98{
99 std::vector<double> samples = source->process_batch(length);
100 set_coefs(samples, type);
101}
102
104{
105 if (m_input_node) {
106 std::vector<double> samples = m_input_node->process_batch(length);
107 set_coefs(samples, type);
108 } else {
109 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.");
110 }
111}
112
113void Filter::add_coef_internal(uint64_t index, double value, std::vector<double>& buffer)
114{
115 if (index >= buffer.size()) {
116 buffer.resize(index + 1, 0.0);
117 }
118 buffer[index] = value;
119
120 if (&buffer == &m_coef_a) {
121 if (std::abs(m_coef_a[0]) < k_min_leading_coef) {
123 "Leading denominator coefficient {} below floor; restoring 1.0", m_coef_a[0]);
124 m_coef_a[0] = 1.0;
125 }
126 const double a0 = m_coef_a[0];
127 if (a0 != 1.0) {
128 for (auto& coef : m_coef_a) {
129 coef /= a0;
130 }
131 }
132 m_output_history.resize(m_coef_a.size(), 0.0);
133 } else {
134 m_input_history.resize(m_coef_b.size(), 0.0);
135 }
136}
137
139{
140 switch (type) {
143 break;
146 break;
147 default:
150 break;
151 }
152}
153
158
160{
161 std::ranges::fill(m_input_history, 0.0);
162 std::ranges::fill(m_output_history, 0.0);
163}
164
166{
168 if (!m_coef_a.empty() && m_coef_a[0] != 0.0) {
169 double a0 = m_coef_a[0];
170 for (auto& coef : m_coef_a) {
171 coef /= a0;
172 }
173 }
174 }
175
177 if (!m_coef_b.empty()) {
178 double max_coef = 0.0;
179 for (const auto& coef : m_coef_b) {
180 max_coef = std::max(max_coef, std::abs(coef));
181 }
182
183 if (max_coef > 0.0) {
184 for (auto& coef : m_coef_b) {
185 coef /= max_coef;
186 }
187 }
188 }
189 }
190}
191
192std::complex<double> Filter::get_frequency_response(double frequency, double sample_rate) const
193{
194 double omega = 2.0 * M_PI * frequency / sample_rate;
195 std::complex<double> z = std::exp(std::complex<double>(0, omega));
196
197 std::complex<double> numerator = 0.0;
198 for (size_t i = 0; i < m_coef_b.size(); ++i) {
199 numerator += m_coef_b[i] * std::pow(z, -static_cast<int>(i));
200 }
201
202 std::complex<double> denominator = 0.0;
203 for (size_t i = 0; i < m_coef_a.size(); ++i) {
204 denominator += m_coef_a[i] * std::pow(z, -static_cast<int>(i));
205 }
206
207 return numerator / denominator;
208}
209
210double Filter::get_magnitude_response(double frequency, double sample_rate) const
211{
212 return std::abs(get_frequency_response(frequency, sample_rate));
213}
214
215double Filter::get_phase_response(double frequency, double sample_rate) const
216{
217 return std::arg(get_frequency_response(frequency, sample_rate));
218}
219
220std::vector<double> Filter::process_batch(unsigned int num_samples)
221{
222 std::vector<double> output(num_samples);
223 for (unsigned int i = 0; i < num_samples; ++i) {
224 output[i] = process_sample(0.0);
225 }
226 return output;
227}
228
230{
231 if (m_gpu_compatible) {
233
234 const auto& src = m_context_gpu.input_history;
235 m_context_gpu.gpu_float_buffer.resize(src.size());
236 for (size_t i = 0; i < src.size(); ++i)
237 m_context_gpu.gpu_float_buffer[i] = static_cast<float>(src[i]);
238
239 m_context_gpu.m_gpu_data = std::span<const float>(m_context_gpu.gpu_float_buffer);
240 } else {
242 }
243}
244
246{
248 auto& ctx = get_last_context();
249
250 for (auto& callback : m_callbacks) {
251 callback(ctx);
252 }
253 for (auto& [callback, condition] : m_conditional_callbacks) {
254 if (condition(ctx)) {
255 callback(ctx);
256 }
257 }
258}
259
261{
262 if (m_gpu_compatible) {
263 return m_context_gpu;
264 }
265 return m_context;
266}
267
269{
270 m_callbacks.emplace_back([callback](NodeContext& ctx) {
271 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast)
272 callback(static_cast<FilterContext&>(ctx));
273 });
274}
275
276void Filter::on_tick_if(const NodeCondition& condition, const TypedHook<FilterContext>& callback)
277{
278 m_conditional_callbacks.emplace_back([callback](NodeContext& ctx) {
279 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast)
280 callback(static_cast<FilterContext&>(ctx));
281 },
282 condition);
283}
284
285std::vector<std::pair<ModulatorRole, std::shared_ptr<Node>>>
287{
288 if (m_input_node)
290 return {};
291}
292
293}
#define MF_WARN(comp, ctx,...)
Digital filter coefficient design for MayaFlux::Kinesis.
Core::GlobalInputConfig input
Definition Config.cpp:38
double frequency
vk::PhysicalDeviceType type
Definition VKDevice.cpp:146
bool available
Definition VKDevice.cpp:52
uint32_t index
Definition VKDevice.cpp:142
float value
std::shared_ptr< Core::VKImage > output
const std::vector< double > & input_history
Current input history buffer.
Definition Filter.hpp:72
Specialized context for filter node callbacks.
Definition Filter.hpp:38
void add_coef_internal(uint64_t index, double value, std::vector< double > &buffer)
Modifies a specific coefficient in a coefficient buffer.
Definition Filter.cpp:113
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:9
void add_coef(int index, double value, coefficients type=coefficients::ALL)
Modifies a specific coefficient.
Definition Filter.cpp:138
void on_tick(const TypedHook< FilterContext > &callback)
Registers a callback to be called on each tick with the filter context.
Definition Filter.cpp:268
std::vector< double > process_batch(unsigned int num_samples) override
Calculates the phase response at a specific frequency.
Definition Filter.cpp:220
virtual void reset()
Resets the filter's internal state.
Definition Filter.cpp:159
void build_input_history(double current_sample)
Builds input history from external context or internal accumulation.
Definition Filter.cpp:35
std::vector< double > m_coef_b
Feedforward (numerator) coefficients.
Definition Filter.hpp:652
std::vector< double > m_output_history
Buffer storing previous output samples.
Definition Filter.hpp:627
double max_pole_magnitude() const
Largest pole magnitude of the current denominator.
Definition Filter.cpp:154
virtual void update_outputs(double current_sample)
Updates the output history buffer with a new sample.
Definition Filter.cpp:59
std::vector< std::pair< ModulatorRole, std::shared_ptr< Node > > > get_modulators() const override
Retrieves the current modulators connected to this node.
Definition Filter.cpp:286
std::shared_ptr< Node > m_input_node
The most recent sample value generated by this oscillator.
Definition Filter.hpp:611
void update_coef_from_input(int length, coefficients type=coefficients::ALL)
Updates coefficients from the filter's own input.
Definition Filter.cpp:103
std::complex< double > get_frequency_response(double frequency, double sample_rate) const
Calculates the complex frequency response at a specific frequency.
Definition Filter.cpp:192
void set_coefs(const std::vector< double > &new_coefs, coefficients type=coefficients::ALL)
Updates filter coefficients.
Definition Filter.cpp:23
void normalize_coefficients(coefficients type=coefficients::ALL)
Normalizes filter coefficients.
Definition Filter.cpp:165
void setACoefficients(const std::vector< double > &new_coefs)
Updates the feedback (denominator) coefficients.
Definition Filter.cpp:67
std::vector< double > m_input_history
Buffer storing previous input samples.
Definition Filter.hpp:619
void setBCoefficients(const std::vector< double > &new_coefs)
Updates the feedforward (numerator) coefficients.
Definition Filter.cpp:86
virtual void update_inputs(double current_sample)
Updates the input history buffer with a new sample.
Definition Filter.cpp:51
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:97
void notify_tick(double value) override
Notifies all registered callbacks with the current filter context.
Definition Filter.cpp:245
FilterContextGpu m_context_gpu
Definition Filter.hpp:676
std::span< double > m_external_input_context
External input context for input history.
Definition Filter.hpp:636
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:276
double get_phase_response(double frequency, double sample_rate) const
Calculates the phase response at a specific frequency.
Definition Filter.cpp:215
void update_context(double value) override
Updates filter-specific context object.
Definition Filter.cpp:229
std::vector< double > m_coef_a
Feedback (denominator) coefficients.
Definition Filter.hpp:644
double get_magnitude_response(double frequency, double sample_rate) const
Calculates the magnitude response at a specific frequency.
Definition Filter.cpp:210
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:260
Base class for computational signal transformers implementing difference equations.
Definition Filter.hpp:148
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:454
std::vector< std::pair< NodeHook, NodeCondition > > m_conditional_callbacks
Collection of conditional callback functions with their predicates.
Definition Node.hpp:464
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:434
@ Configuration
Configuration and parameter updates.
@ NodeProcessing
Node graph processing (Nodes::NodeGraphManager)
@ Nodes
DSP Generator and Filter Nodes, graph pipeline, node management.
double max_pole_magnitude(std::span< const double > a)
Largest pole magnitude of a denominator polynomial.
constexpr double k_min_leading_coef
Minimum permissible magnitude for the leading denominator coefficient.
Definition Filter.hpp:20
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