MayaFlux 0.4.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
203 /**
204 * @brief Retrieves the current modulators connected to this node
205 * @return Vector of pairs containing the modulator role and the corresponding node
206 */
207 [[nodiscard]] std::vector<std::pair<ModulatorRole, std::shared_ptr<Node>>> get_modulators() const override;
208
209protected:
210 /**
211 * @brief Notifies all registered callbacks about a new output value
212 * @param value The newly combined output value
213 *
214 * This method is called internally whenever a new value is produced,
215 * creating the appropriate context with both input values and the output,
216 * and invoking all registered callbacks that should receive notification.
217 */
218 void notify_tick(double value) override;
219
220 /**
221 * @brief updates context object for callbacks
222 * @param value The current combined output value
223 *
224 * This method updates the specialized context object containing
225 * the combined output value and the individual values from both
226 * input nodes, providing callbacks with rich information about
227 * the operation's inputs and output.
228 */
229 void update_context(double value) override;
230
231 /**
232 * @brief Retrieves the last created context object
233 * @return Reference to the last BinaryOpContext object
234 *
235 * This method provides access to the most recent BinaryOpContext object
236 * created by the binary operation node. This context contains information
237 * about both input values and the combined output value.
238 */
239 NodeContext& get_last_context() override;
240
241private:
242 /**
243 * @brief The left-hand side node
244 */
245 std::shared_ptr<Node> m_lhs;
246
247 /**
248 * @brief The right-hand side node
249 */
250 std::shared_ptr<Node> m_rhs;
251
252 /**
253 * @brief The function used to combine the outputs of both nodes
254 */
256
257 /**
258 * @brief Reference to the node graph manager for registration and callback management
259 *
260 * This reference is used to register the binary operation node and its input nodes
261 * with the graph manager, allowing them to be included in processing cycles and
262 * for querying Config.
263 */
264 NodeGraphManager* m_manager {};
265
266 /**
267 * @brief The processing token indicating the domain in which this node operates
268 */
269 ProcessingToken m_token { ProcessingToken::AUDIO_RATE };
270
271 /**
272 * @brief The last output value from the left-hand side node
273 *
274 * This value is stored to provide context information to callbacks,
275 * allowing them to access not just the combined result but also
276 * the individual contributions from each input node.
277 */
278 double m_last_lhs_value {};
279
280 /**
281 * @brief The last output value from the right-hand side node
282 *
283 * This value is stored to provide context information to callbacks,
284 * allowing them to access not just the combined result but also
285 * the individual contributions from each input node.
286 */
287 double m_last_rhs_value {};
288
289 /**
290 * @brief Flag indicating whether the binary operator has been properly initialized
291 *
292 * This flag is set to true when both the lhs and rhs nodes have been
293 * registered for processing and the connector itself is registered. It's used
294 * to ensure that the operator func doesn't attempt to process signals before all
295 * components are ready, preventing potential null pointer issues or
296 * processing inconsistencies.
297 */
298 bool m_is_initialized {};
299
300 bool m_state_saved {};
301 double m_saved_last_lhs_value {};
302 double m_saved_last_rhs_value {};
303
306
307public:
308 bool is_initialized() const;
309};
310
311// =============================================================================
312// CompositeOpNode
313// =============================================================================
314
315namespace detail {
317 const std::vector<std::shared_ptr<Node>>& inputs,
318 NodeGraphManager& manager,
319 ProcessingToken token,
320 const std::shared_ptr<Node>& self);
321
323 const std::vector<std::shared_ptr<Node>>& inputs,
324 NodeGraphManager& manager,
325 ProcessingToken token);
326
327 void composite_validate(const std::vector<std::shared_ptr<Node>>& inputs, size_t N);
328}
329
330/**
331 * @class CompositeOpNode
332 * @brief Combines the outputs of N nodes using a composite operation
333 *
334 * All input nodes process the same input sample. Their outputs are
335 * collected and passed to a user-supplied combine function.
336 * Generalizes BinaryOpNode to arbitrary arity.
337 *
338 * No operator overloads are provided since there is no natural N-ary
339 * operator syntax in C++.
340 *
341 * @tparam N Optional compile-time arity. 0 (default) means runtime-determined.
342 */
343template <size_t N = 0>
344class MAYAFLUX_API CompositeOpNode : public Node, public std::enable_shared_from_this<CompositeOpNode<N>> {
345public:
346 using CombineFunc = std::function<double(std::span<const double>)>;
347
348 /**
349 * @brief Creates a composite operation node (unmanaged)
350 * @param inputs The input nodes whose outputs will be combined
351 * @param func Function that receives all node outputs and returns combined result
352 */
354 std::vector<std::shared_ptr<Node>> inputs,
355 CombineFunc func)
356 : m_inputs(std::move(inputs))
357 , m_func(std::move(func))
358 , m_input_values(m_inputs.size(), 0.0)
359 , m_saved_input_values(m_inputs.size(), 0.0)
360 , m_context(0.0, std::vector<double>(m_inputs.size(), 0.0))
361 , m_context_gpu(0.0, std::vector<double>(m_inputs.size(), 0.0), Node::get_gpu_data_buffer())
362 {
363 detail::composite_validate(m_inputs, N);
364 }
365
366 /**
367 * @brief Creates a composite operation node (managed)
368 * @param inputs The input nodes whose outputs will be combined
369 * @param func Function that receives all node outputs and returns combined result
370 * @param manager Graph manager for registration
371 * @param token Processing domain for registration (default AUDIO_RATE)
372 */
374 std::vector<std::shared_ptr<Node>> inputs,
375 CombineFunc func,
376 NodeGraphManager& manager,
377 ProcessingToken token = ProcessingToken::AUDIO_RATE)
378 : m_inputs(std::move(inputs))
379 , m_func(std::move(func))
380 , m_manager(&manager)
381 , m_token(token)
382 , m_input_values(m_inputs.size(), 0.0)
383 , m_saved_input_values(m_inputs.size(), 0.0)
384 , m_context(0.0, std::vector<double>(m_inputs.size(), 0.0))
385 , m_context_gpu(0.0, std::vector<double>(m_inputs.size(), 0.0), Node::get_gpu_data_buffer())
386 {
387 detail::composite_validate(m_inputs, N);
388 }
389
390 /**
391 * @brief Creates a composite operation node with derived node types (managed)
392 * @tparam DerivedNode A type derived from Node
393 * @param inputs The input nodes whose outputs will be combined
394 * @param func Function that receives all node outputs and returns combined result
395 * @param manager Graph manager for registration
396 * @param token Processing domain for registration (default AUDIO_RATE)
397 *
398 * This constructor allows for creating a CompositeOpNode using a vector of shared pointers to a type derived from Node.
399 * It performs an implicit conversion to the base Node type for internal storage, while still allowing the caller to use their specific node types.
400 */
401 template <typename DerivedNode>
402 requires std::derived_from<DerivedNode, Node>
404 std::vector<std::shared_ptr<DerivedNode>> inputs,
405 CombineFunc func,
406 NodeGraphManager& manager,
407 ProcessingToken token = ProcessingToken::AUDIO_RATE)
409 std::vector<std::shared_ptr<Node>>(inputs.begin(), inputs.end()),
410 std::move(func), manager, token)
411 {
412 }
413
415 {
416 if (!m_is_initialized) {
417 if (m_manager)
418 detail::composite_initialize(m_inputs, *m_manager, m_token, this->shared_from_this());
419 m_is_initialized = true;
420 }
421 if (m_manager)
422 detail::composite_apply_semantics(m_inputs, *m_manager, m_token);
423 }
424
425 double process_sample(double input = 0.) override
426 {
427 if (!is_initialized())
428 initialize();
429
430 for (auto& node : m_inputs) {
431 atomic_inc_modulator_count(node->m_modulator_count, 1);
432 }
433
434 for (size_t i = 0; i < m_inputs.size(); ++i) {
435 uint32_t state = m_inputs[i]->m_state.load();
436 if (state & NodeState::PROCESSED) {
437 m_input_values[i] = input + m_inputs[i]->get_last_output();
438 } else {
439 m_input_values[i] = m_inputs[i]->process_sample(input);
440 atomic_add_flag(m_inputs[i]->m_state, NodeState::PROCESSED);
441 }
442 }
443
444 m_last_output = m_func(std::span<const double>(m_input_values));
445
446 if (!m_state_saved || (m_state_saved && m_fire_events_during_snapshot))
447 notify_tick(m_last_output);
448
449 for (auto& node : m_inputs) {
450 atomic_dec_modulator_count(node->m_modulator_count, 1);
451 }
452
453 for (auto& node : m_inputs) {
455 }
456
457 return m_last_output;
458 }
459
460 std::vector<double> process_batch(unsigned int num_samples) override
461 {
462 std::vector<double> output(num_samples);
463 for (unsigned int i = 0; i < num_samples; ++i) {
464 output[i] = process_sample(0.0);
465 }
466 return output;
467 }
468
469 void reset_processed_state() override
470 {
471 atomic_remove_flag(m_state, NodeState::PROCESSED);
472 for (auto& node : m_inputs) {
473 if (node)
474 node->reset_processed_state();
475 }
476 }
477
478 void save_state() override
479 {
480 m_saved_input_values = m_input_values;
481 for (auto& node : m_inputs) {
482 if (node)
483 node->save_state();
484 }
485 m_state_saved = true;
486 }
487
488 void restore_state() override
489 {
490 m_input_values = m_saved_input_values;
491 for (auto& node : m_inputs) {
492 if (node)
493 node->restore_state();
494 }
495 m_state_saved = false;
496 }
497
498 [[nodiscard]] size_t input_count() const { return m_inputs.size(); }
499 [[nodiscard]] const std::vector<std::shared_ptr<Node>>& inputs() const { return m_inputs; }
500
501 [[nodiscard]] bool is_initialized() const
502 {
503 for (auto& node : m_inputs) {
504 auto state = node->m_state.load();
505 if (state & NodeState::ACTIVE)
506 return false;
507 }
508 return m_is_initialized;
509 }
510
511 /**
512 * @brief Retrieves the current modulators connected to this node
513 * @return Vector of pairs containing the modulator role and the corresponding node
514 */
515 [[nodiscard]] std::vector<std::pair<ModulatorRole, std::shared_ptr<Node>>>
516 get_modulators() const override
517 {
518 std::vector<std::pair<ModulatorRole, std::shared_ptr<Node>>> result;
519 result.reserve(m_inputs.size());
520 for (const auto& n : m_inputs)
521 result.emplace_back(ModulatorRole::SignalMod, n);
522 return result;
523 }
524
525protected:
526 void notify_tick(double value) override
527 {
528 update_context(value);
529 auto& ctx = get_last_context();
530
531 for (const auto& callback : m_callbacks) {
532 callback(ctx);
533 }
534
535 for (const auto& [callback, condition] : m_conditional_callbacks) {
536 if (condition(ctx)) {
537 callback(ctx);
538 }
539 }
540 }
541
542 void update_context(double) override
543 {
544 if (m_gpu_compatible) {
545 m_context_gpu.value = m_last_output;
546 m_context_gpu.input_values = m_input_values;
547 } else {
548 m_context.value = m_last_output;
549 m_context.input_values = m_input_values;
550 }
551 }
552
554 {
555 if (m_gpu_compatible)
556 return m_context_gpu;
557 return m_context;
558 }
559
560private:
561 std::vector<std::shared_ptr<Node>> m_inputs;
563 NodeGraphManager* m_manager {};
564 ProcessingToken m_token { ProcessingToken::AUDIO_RATE };
565 std::vector<double> m_input_values;
566 std::vector<double> m_saved_input_values;
567 bool m_is_initialized {};
568 bool m_state_saved {};
569
572};
573
574}
Core::GlobalInputConfig input
Definition Config.cpp:36
#define N(method_name, full_type_name)
Definition Creator.hpp:106
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...
std::vector< std::pair< ModulatorRole, std::shared_ptr< Node > > > get_modulators() const override
Retrieves the current modulators connected to this node.
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:53
Central manager for the computational processing node graph.
Base interface for all computational processing nodes.
Definition Node.hpp:126
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:60
void try_reset_processed_state(std::shared_ptr< Node > node)
Attempts to reset the processed state of a node.
Definition NodeUtils.cpp:97
void atomic_inc_modulator_count(std::atomic< uint32_t > &count, int amount)
Atomically increments the modulator count by a specified amount.
Definition NodeUtils.cpp:87
void atomic_remove_flag(std::atomic< NodeState > &state, NodeState flag)
Atomically removes a flag from a node state.
Definition NodeUtils.cpp:71
void atomic_dec_modulator_count(std::atomic< uint32_t > &count, int amount)
Atomically decrements the modulator count by a specified amount.
Definition NodeUtils.cpp:92
Contains the node-based computational processing system components.
Definition Chronie.hpp:14
bool is_initialized()
Checks if the default engine has been initialized.
Definition Core.cpp:52