MayaFlux 0.2.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
NodeCombine.hpp
Go to the documentation of this file.
1#pragma once
2
4
6
7namespace MayaFlux::Nodes {
8
9class NodeGraphManager;
10
11/**
12 * @class BinaryOpContext
13 * @brief Specialized context for binary operation callbacks
14 *
15 * BinaryOpContext extends the base NodeContext to provide detailed information
16 * about a binary operation's inputs and output to callbacks. It includes the
17 * individual values from both the left and right nodes that were combined to
18 * produce the final output value.
19 *
20 * This rich context enables callbacks to perform sophisticated analysis and
21 * monitoring of signal combinations, such as:
22 * - Tracking the relative contributions of each input signal
23 * - Implementing adaptive responses based on input relationships
24 * - Detecting specific interaction patterns between signals
25 * - Creating visualizations that show both inputs and their combination
26 */
27class MAYAFLUX_API BinaryOpContext : public NodeContext {
28public:
29 /**
30 * @brief Constructs a BinaryOpContext with the current operation state
31 * @param value The combined output value
32 * @param lhs_value The value from the left-hand side node
33 * @param rhs_value The value from the right-hand side node
34 *
35 * Creates a context object that provides a complete snapshot of the
36 * binary operation's current state, including both input values and
37 * the resulting output value after combination.
38 */
39 BinaryOpContext(double value, double lhs_value, double rhs_value);
40
41 /**
42 * @brief The value from the left-hand side node
43 *
44 * This is the output value from the left node before combination.
45 * It allows callbacks to analyze the individual contribution of
46 * the left node to the final combined output.
47 */
48 double lhs_value;
49
50 /**
51 * @brief The value from the right-hand side node
52 *
53 * This is the output value from the right node before combination.
54 * It allows callbacks to analyze the individual contribution of
55 * the right node to the final combined output.
56 */
57 double rhs_value;
58};
59
60/**
61 * @class BinaryOpContextGpu
62 * @brief GPU-compatible context for binary operation callbacks
63 *
64 * BinaryOpContextGpu extends BinaryOpContext and implements GpuVectorData
65 * to provide GPU-compatible data for binary operation callbacks. It includes
66 * the individual values from both the left and right nodes, as well as a
67 * span of float data that can be uploaded to the GPU for efficient processing.
68 *
69 * This context enables GPU-accelerated callbacks to analyze and respond to
70 * binary operations in high-performance scenarios, such as:
71 * - Real-time audio processing on the GPU
72 * - Complex signal interactions in visual effects
73 * - High-throughput data transformations in compute shaders
74 */
75class MAYAFLUX_API BinaryOpContextGpu : public BinaryOpContext, public GpuVectorData {
76public:
77 BinaryOpContextGpu(double value, double lhs_value, double rhs_value, std::span<const float> gpu_data);
78};
79
80/**
81 * @class CompositeOpContext
82 * @brief Context for N-ary composite operation callbacks
83 *
84 * Provides the individual values from all input nodes alongside
85 * the combined output value.
86 */
87class MAYAFLUX_API CompositeOpContext : public NodeContext {
88public:
89 CompositeOpContext(double value, std::vector<double> input_values);
90
91 std::vector<double> input_values;
92};
93
94/**
95 * @class CompositeOpContextGpu
96 * @brief GPU-compatible context for composite operation callbacks
97 */
98class MAYAFLUX_API CompositeOpContextGpu : public CompositeOpContext, public GpuVectorData {
99public:
100 CompositeOpContextGpu(double value, std::vector<double> input_values, std::span<const float> gpu_data);
101};
102
103/**
104 * @class BinaryOpNode
105 * @brief Combines the outputs of two nodes using a binary operation
106 *
107 * The BinaryOpNode implements the Node interface and represents a combination
108 * of two nodes where both nodes process the same input, and their outputs
109 * are combined using a specified binary operation (like addition or multiplication).
110 * This is the implementation behind the '+' and '*' operators for nodes.
111 *
112 * When processed, the BinaryOpNode:
113 * 1. Passes the input to both the left and right nodes
114 * 2. Takes the outputs from both nodes
115 * 3. Combines the outputs using the specified function
116 * 4. Returns the combined result
117 */
118class MAYAFLUX_API BinaryOpNode : public Node, public std::enable_shared_from_this<BinaryOpNode> {
119public:
120 /**
121 * @typedef CombineFunc
122 * @brief Function type for combining two node outputs
123 *
124 * A function that takes two double values (the outputs from the left
125 * and right nodes) and returns a single double value (the combined result).
126 */
127 using CombineFunc = std::function<double(double, double)>;
128
129 /**
130 * @brief Creates a new binary operation node
131 * @param lhs The left-hand side node
132 * @param rhs The right-hand side node
133 * @param func The function to combine the outputs of both nodes
134 *
135 * Common combine functions include:
136 * - Addition: [](double a, double b) { return a + b; }
137 * - Multiplication: [](double a, double b) { return a * b; }
138 */
139 BinaryOpNode(const std::shared_ptr<Node>& lhs, const std::shared_ptr<Node>& rhs, CombineFunc func);
140
141 /**
142 * @brief Creates a new binary operation node (managed)
143 * @param lhs The left-hand side node
144 * @param rhs The right-hand side node
145 * @param func The function to combine the outputs of both nodes
146 * @param manager Graph manager for registration
147 * @param token Processing domain for registration (default AUDIO_RATE)
148 *
149 * Common combine functions include:
150 * - Addition: [](double a, double b) { return a + b; }
151 * - Multiplication: [](double a, double b) { return a * b; }
152 */
154 const std::shared_ptr<Node>& lhs,
155 const std::shared_ptr<Node>& rhs,
156 CombineFunc func,
157 NodeGraphManager& manager,
158 ProcessingToken token = ProcessingToken::AUDIO_RATE);
159
160 /**
161 * @brief Initializes the binary operation node
162 *
163 * This method performs any necessary setup for the binary operation node,
164 * such as ensuring both input nodes are properly initialized and registered.
165 * It should be called before the node is used for processing to ensure
166 * correct operation.
167 */
168 void initialize();
169
170 /**
171 * @brief Processes a single sample through both nodes and combines the results
172 * @param input The input sample
173 * @return The combined output after processing through both nodes
174 *
175 * The input is processed by both the left and right nodes, and their
176 * outputs are combined using the specified function.
177 */
178 double process_sample(double input = 0.) override;
179
180 /**
181 * @brief Processes multiple samples through both nodes and combines the results
182 * @param num_samples Number of samples to process
183 * @return Vector of combined processed samples
184 *
185 * Each sample is processed through both the left and right nodes, and
186 * their outputs are combined using the specified function.
187 */
188 std::vector<double> process_batch(unsigned int num_samples) override;
189
190 /**
191 * @brief Resets the processed state of the node and any attached input nodes
192 *
193 * This method is used by the processing system to reset the processed state
194 * of the node at the end of each processing cycle. This ensures that
195 * all nodes are marked as unprocessed before the cycle next begins, allowing
196 * the system to correctly identify which nodes need to be processed.
197 */
198 void reset_processed_state() override;
199
200 void save_state() override;
201 void restore_state() override;
202
203protected:
204 /**
205 * @brief Notifies all registered callbacks about a new output value
206 * @param value The newly combined output value
207 *
208 * This method is called internally whenever a new value is produced,
209 * creating the appropriate context with both input values and the output,
210 * and invoking all registered callbacks that should receive notification.
211 */
212 void notify_tick(double value) override;
213
214 /**
215 * @brief updates context object for callbacks
216 * @param value The current combined output value
217 *
218 * This method updates the specialized context object containing
219 * the combined output value and the individual values from both
220 * input nodes, providing callbacks with rich information about
221 * the operation's inputs and output.
222 */
223 void update_context(double value) override;
224
225 /**
226 * @brief Retrieves the last created context object
227 * @return Reference to the last BinaryOpContext object
228 *
229 * This method provides access to the most recent BinaryOpContext object
230 * created by the binary operation node. This context contains information
231 * about both input values and the combined output value.
232 */
233 NodeContext& get_last_context() override;
234
235private:
236 /**
237 * @brief The left-hand side node
238 */
239 std::shared_ptr<Node> m_lhs;
240
241 /**
242 * @brief The right-hand side node
243 */
244 std::shared_ptr<Node> m_rhs;
245
246 /**
247 * @brief The function used to combine the outputs of both nodes
248 */
250
251 /**
252 * @brief Reference to the node graph manager for registration and callback management
253 *
254 * This reference is used to register the binary operation node and its input nodes
255 * with the graph manager, allowing them to be included in processing cycles and
256 * for querying Config.
257 */
258 NodeGraphManager* m_manager {};
259
260 /**
261 * @brief The processing token indicating the domain in which this node operates
262 */
263 ProcessingToken m_token { ProcessingToken::AUDIO_RATE };
264
265 /**
266 * @brief The last output value from the left-hand side node
267 *
268 * This value is stored to provide context information to callbacks,
269 * allowing them to access not just the combined result but also
270 * the individual contributions from each input node.
271 */
272 double m_last_lhs_value {};
273
274 /**
275 * @brief The last output value from the right-hand side node
276 *
277 * This value is stored to provide context information to callbacks,
278 * allowing them to access not just the combined result but also
279 * the individual contributions from each input node.
280 */
281 double m_last_rhs_value {};
282
283 /**
284 * @brief Flag indicating whether the binary operator has been properly initialized
285 *
286 * This flag is set to true when both the lhs and rhs nodes have been
287 * registered for processing and the connector itself is registered. It's used
288 * to ensure that the operator func doesn't attempt to process signals before all
289 * components are ready, preventing potential null pointer issues or
290 * processing inconsistencies.
291 */
292 bool m_is_initialized {};
293
294 bool m_state_saved {};
295 double m_saved_last_lhs_value {};
296 double m_saved_last_rhs_value {};
297
300
301public:
302 bool is_initialized() const;
303};
304
305// =============================================================================
306// CompositeOpNode
307// =============================================================================
308
309namespace detail {
311 const std::vector<std::shared_ptr<Node>>& inputs,
312 NodeGraphManager& manager,
313 ProcessingToken token,
314 const std::shared_ptr<Node>& self);
315
317 const std::vector<std::shared_ptr<Node>>& inputs,
318 NodeGraphManager& manager,
319 ProcessingToken token);
320
321 void composite_validate(const std::vector<std::shared_ptr<Node>>& inputs, size_t N);
322}
323
324/**
325 * @class CompositeOpNode
326 * @brief Combines the outputs of N nodes using a composite operation
327 *
328 * All input nodes process the same input sample. Their outputs are
329 * collected and passed to a user-supplied combine function.
330 * Generalizes BinaryOpNode to arbitrary arity.
331 *
332 * No operator overloads are provided since there is no natural N-ary
333 * operator syntax in C++.
334 *
335 * @tparam N Optional compile-time arity. 0 (default) means runtime-determined.
336 */
337template <size_t N = 0>
338class MAYAFLUX_API CompositeOpNode : public Node, public std::enable_shared_from_this<CompositeOpNode<N>> {
339public:
340 using CombineFunc = std::function<double(std::span<const double>)>;
341
342 /**
343 * @brief Creates a composite operation node (unmanaged)
344 * @param inputs The input nodes whose outputs will be combined
345 * @param func Function that receives all node outputs and returns combined result
346 */
348 std::vector<std::shared_ptr<Node>> inputs,
349 CombineFunc func)
350 : m_inputs(std::move(inputs))
351 , m_func(std::move(func))
352 , m_input_values(m_inputs.size(), 0.0)
353 , m_saved_input_values(m_inputs.size(), 0.0)
354 , m_context(0.0, std::vector<double>(m_inputs.size(), 0.0))
355 , m_context_gpu(0.0, std::vector<double>(m_inputs.size(), 0.0), Node::get_gpu_data_buffer())
356 {
357 detail::composite_validate(m_inputs, N);
358 }
359
360 /**
361 * @brief Creates a composite operation node (managed)
362 * @param inputs The input nodes whose outputs will be combined
363 * @param func Function that receives all node outputs and returns combined result
364 * @param manager Graph manager for registration
365 * @param token Processing domain for registration (default AUDIO_RATE)
366 */
368 std::vector<std::shared_ptr<Node>> inputs,
369 CombineFunc func,
370 NodeGraphManager& manager,
371 ProcessingToken token = ProcessingToken::AUDIO_RATE)
372 : m_inputs(std::move(inputs))
373 , m_func(std::move(func))
374 , m_manager(&manager)
375 , m_token(token)
376 , m_input_values(m_inputs.size(), 0.0)
377 , m_saved_input_values(m_inputs.size(), 0.0)
378 , m_context(0.0, std::vector<double>(m_inputs.size(), 0.0))
379 , m_context_gpu(0.0, std::vector<double>(m_inputs.size(), 0.0), Node::get_gpu_data_buffer())
380 {
381 detail::composite_validate(m_inputs, N);
382 }
383
384 /**
385 * @brief Creates a composite operation node with derived node types (managed)
386 * @tparam DerivedNode A type derived from Node
387 * @param inputs The input nodes whose outputs will be combined
388 * @param func Function that receives all node outputs and returns combined result
389 * @param manager Graph manager for registration
390 * @param token Processing domain for registration (default AUDIO_RATE)
391 *
392 * This constructor allows for creating a CompositeOpNode using a vector of shared pointers to a type derived from Node.
393 * It performs an implicit conversion to the base Node type for internal storage, while still allowing the caller to use their specific node types.
394 */
395 template <typename DerivedNode>
396 requires std::derived_from<DerivedNode, Node>
398 std::vector<std::shared_ptr<DerivedNode>> inputs,
399 CombineFunc func,
400 NodeGraphManager& manager,
401 ProcessingToken token = ProcessingToken::AUDIO_RATE)
403 std::vector<std::shared_ptr<Node>>(inputs.begin(), inputs.end()),
404 std::move(func), manager, token)
405 {
406 }
407
409 {
410 if (!m_is_initialized) {
411 if (m_manager)
412 detail::composite_initialize(m_inputs, *m_manager, m_token, this->shared_from_this());
413 m_is_initialized = true;
414 }
415 if (m_manager)
416 detail::composite_apply_semantics(m_inputs, *m_manager, m_token);
417 }
418
419 double process_sample(double input = 0.) override
420 {
421 if (!is_initialized())
422 initialize();
423
424 for (auto& node : m_inputs) {
425 atomic_inc_modulator_count(node->m_modulator_count, 1);
426 }
427
428 for (size_t i = 0; i < m_inputs.size(); ++i) {
429 uint32_t state = m_inputs[i]->m_state.load();
430 if (state & NodeState::PROCESSED) {
431 m_input_values[i] = input + m_inputs[i]->get_last_output();
432 } else {
433 m_input_values[i] = m_inputs[i]->process_sample(input);
434 atomic_add_flag(m_inputs[i]->m_state, NodeState::PROCESSED);
435 }
436 }
437
438 m_last_output = m_func(std::span<const double>(m_input_values));
439
440 if (!m_state_saved || (m_state_saved && m_fire_events_during_snapshot))
441 notify_tick(m_last_output);
442
443 for (auto& node : m_inputs) {
444 atomic_dec_modulator_count(node->m_modulator_count, 1);
445 }
446
447 for (auto& node : m_inputs) {
449 }
450
451 return m_last_output;
452 }
453
454 std::vector<double> process_batch(unsigned int num_samples) override
455 {
456 std::vector<double> output(num_samples);
457 for (unsigned int i = 0; i < num_samples; ++i) {
458 output[i] = process_sample(0.0);
459 }
460 return output;
461 }
462
463 void reset_processed_state() override
464 {
465 atomic_remove_flag(m_state, NodeState::PROCESSED);
466 for (auto& node : m_inputs) {
467 if (node)
468 node->reset_processed_state();
469 }
470 }
471
472 void save_state() override
473 {
474 m_saved_input_values = m_input_values;
475 for (auto& node : m_inputs) {
476 if (node)
477 node->save_state();
478 }
479 m_state_saved = true;
480 }
481
482 void restore_state() override
483 {
484 m_input_values = m_saved_input_values;
485 for (auto& node : m_inputs) {
486 if (node)
487 node->restore_state();
488 }
489 m_state_saved = false;
490 }
491
492 [[nodiscard]] size_t input_count() const { return m_inputs.size(); }
493 [[nodiscard]] const std::vector<std::shared_ptr<Node>>& inputs() const { return m_inputs; }
494
495 [[nodiscard]] bool is_initialized() const
496 {
497 for (auto& node : m_inputs) {
498 auto state = node->m_state.load();
499 if (state & NodeState::ACTIVE)
500 return false;
501 }
502 return m_is_initialized;
503 }
504
505protected:
506 void notify_tick(double value) override
507 {
508 update_context(value);
509 auto& ctx = get_last_context();
510
511 for (const auto& callback : m_callbacks) {
512 callback(ctx);
513 }
514
515 for (const auto& [callback, condition] : m_conditional_callbacks) {
516 if (condition(ctx)) {
517 callback(ctx);
518 }
519 }
520 }
521
522 void update_context(double) override
523 {
524 if (m_gpu_compatible) {
525 m_context_gpu.value = m_last_output;
526 m_context_gpu.input_values = m_input_values;
527 } else {
528 m_context.value = m_last_output;
529 m_context.input_values = m_input_values;
530 }
531 }
532
534 {
535 if (m_gpu_compatible)
536 return m_context_gpu;
537 return m_context;
538 }
539
540private:
541 std::vector<std::shared_ptr<Node>> m_inputs;
543 NodeGraphManager* m_manager {};
544 ProcessingToken m_token { ProcessingToken::AUDIO_RATE };
545 std::vector<double> m_input_values;
546 std::vector<double> m_saved_input_values;
547 bool m_is_initialized {};
548 bool m_state_saved {};
549
552};
553
554}
#define N(method_name, full_type_name)
Definition Creator.hpp:183
GPU-compatible context for binary operation callbacks.
double rhs_value
The value from the right-hand side node.
double lhs_value
The value from the left-hand side node.
Specialized context for binary operation callbacks.
std::shared_ptr< Node > m_lhs
The left-hand side node.
std::function< double(double, double)> CombineFunc
Function type for combining two node outputs.
CombineFunc m_func
The function used to combine the outputs of both nodes.
std::shared_ptr< Node > m_rhs
The right-hand side node.
BinaryOpContextGpu m_context_gpu
Combines the outputs of two nodes using a binary operation.
GPU-compatible context for composite operation callbacks.
std::vector< double > input_values
Context for N-ary composite operation callbacks.
std::vector< double > m_input_values
CompositeOpNode(std::vector< std::shared_ptr< Node > > inputs, CombineFunc func)
Creates a composite operation node (unmanaged)
CompositeOpNode(std::vector< std::shared_ptr< DerivedNode > > inputs, CombineFunc func, NodeGraphManager &manager, ProcessingToken token=ProcessingToken::AUDIO_RATE)
Creates a composite operation node with derived node types (managed)
CompositeOpContextGpu m_context_gpu
void update_context(double) override
Updates the context object with the current node state.
void restore_state() override
Restores the node's state from the last save Recursively cascades through all connected modulator nod...
void notify_tick(double value) override
Notifies all registered callbacks with the current context.
NodeContext & get_last_context() override
Retrieves the last created context object.
CompositeOpNode(std::vector< std::shared_ptr< Node > > inputs, CombineFunc func, NodeGraphManager &manager, ProcessingToken token=ProcessingToken::AUDIO_RATE)
Creates a composite operation node (managed)
const std::vector< std::shared_ptr< Node > > & inputs() const
double process_sample(double input=0.) override
Processes a single data sample.
std::vector< double > process_batch(unsigned int num_samples) override
Processes multiple samples at once.
void save_state() override
Saves the node's current state for later restoration Recursively cascades through all connected modul...
std::vector< double > m_saved_input_values
std::vector< std::shared_ptr< Node > > m_inputs
std::function< double(std::span< const double >)> CombineFunc
void reset_processed_state() override
Resets the processed state of the node and any attached input nodes.
Combines the outputs of N nodes using a composite operation.
GPU-uploadable 1D array data interface.
Base context class for node callbacks.
Definition Node.hpp:30
Central manager for the computational processing node graph.
Base interface for all computational processing nodes.
Definition Node.hpp:109
void initialize()
Definition main.cpp:11
void composite_validate(const std::vector< std::shared_ptr< Node > > &inputs, size_t N)
void composite_initialize(const std::vector< std::shared_ptr< Node > > &inputs, NodeGraphManager &manager, ProcessingToken token, const std::shared_ptr< Node > &self)
void composite_apply_semantics(const std::vector< std::shared_ptr< Node > > &inputs, NodeGraphManager &manager, ProcessingToken token)
ProcessingToken
Enumerates the different processing domains for nodes.
void atomic_add_flag(std::atomic< NodeState > &state, NodeState flag)
Atomically adds a flag to a node state.
Definition NodeUtils.cpp:94
void try_reset_processed_state(std::shared_ptr< Node > node)
Attempts to reset the processed state of a node.
void atomic_inc_modulator_count(std::atomic< uint32_t > &count, int amount)
Atomically increments the modulator count by a specified amount.
void atomic_remove_flag(std::atomic< NodeState > &state, NodeState flag)
Atomically removes a flag from a node state.
void atomic_dec_modulator_count(std::atomic< uint32_t > &count, int amount)
Atomically decrements the modulator count by a specified amount.
Contains the node-based computational processing system components.
Definition Chronie.hpp:11
bool is_initialized()
Checks if the default engine has been initialized.
Definition Core.cpp:50