MayaFlux 0.1.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
Node.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "GpuContext.hpp"
4#include "NodeOperators.hpp"
5
6/**
7 * @namespace MayaFlux::Nodes
8 * @brief Contains the node-based computational processing system components
9 *
10 * The Nodes namespace provides a flexible, composable processing architecture
11 * based on the concept of interconnected computational nodes. Each node represents a
12 * discrete transformation unit that can be connected to other nodes to form
13 * complex processing networks and computational graphs.
14 */
15namespace MayaFlux::Nodes {
16
17/**
18 * @class NodeContext
19 * @brief Base context class for node callbacks
20 *
21 * Provides basic context information for callbacks and can be
22 * extended by specific node types to include additional context.
23 * The NodeContext serves as a container for node state information
24 * that is passed to callback functions, allowing callbacks to
25 * access relevant node data during execution.
26 *
27 * Node implementations can extend this class to provide type-specific
28 * context information, which can be safely accessed using the as<T>() method.
29 */
30class MAYAFLUX_API NodeContext {
31public:
32 virtual ~NodeContext() = default;
33
34 /**
35 * @brief Current sample value
36 *
37 * The most recent output value produced by the node.
38 * This is the primary data point that callbacks will typically use.
39 */
40 double value;
41
42 /**
43 * @brief Type identifier for runtime type checking
44 *
45 * String identifier that represents the concrete type of the context.
46 * Used for safe type casting with the as<T>() method.
47 */
48 std::string type_id;
49
50 /**
51 * @brief Safely cast to a derived context type
52 * @tparam T The derived context type to cast to
53 * @return Pointer to the derived context or nullptr if types don't match
54 *
55 * Provides type-safe access to derived context classes. If the requested
56 * type matches the actual type of this context, returns a properly cast
57 * pointer. Otherwise, returns nullptr to prevent unsafe access.
58 *
59 * Example:
60 * ```cpp
61 * if (auto filter_ctx = ctx.as<FilterContext>()) {
62 * // Access filter-specific context members
63 * double gain = filter_ctx->gain;
64 * }
65 * ```
66 */
67 template <typename T>
68 T* as()
69 {
70 if (typeid(T).name() == type_id) {
71 return static_cast<T*>(this);
72 }
73 return nullptr;
74 }
75
76protected:
77 /**
78 * @brief Protected constructor for NodeContext
79 * @param value The current sample value
80 * @param type String identifier for the context type
81 *
82 * This constructor is protected to ensure that only derived classes
83 * can create context objects, with proper type identification.
84 */
85 NodeContext(double value, const std::string& type)
86 : value(value)
87 , type_id(type)
88 {
89 }
90};
91
92/**
93 * @class Node
94 * @brief Base interface for all computational processing nodes
95 *
96 * The Node class defines the fundamental interface for all processing components
97 * in the MayaFlux engine. Nodes are the basic building blocks of transformation chains
98 * and can be connected together to create complex computational graphs.
99 *
100 * Each node processes data on a sample-by-sample basis, allowing for flexible
101 * real-time processing. Nodes can be:
102 * - Connected in series (output of one feeding into input of another)
103 * - Combined in parallel (outputs mixed together)
104 * - Multiplied (outputs multiplied together)
105 *
106 * The node system supports both single-sample processing for real-time applications
107 * and batch processing for more efficient offline processing.
108 */
109class MAYAFLUX_API Node {
110
111public:
112 /**
113 * @brief Virtual destructor for proper cleanup of derived classes
114 */
115 virtual ~Node() = default;
116
117 /**
118 * @brief Processes a single data sample
119 * @param input The input sample value
120 * @return The processed output sample value
121 *
122 * This is the core processing method that all nodes must implement.
123 * It takes a single input value, applies the node's transformation algorithm,
124 * and returns the resulting output value.
125 *
126 * For generator nodes that don't require input (like oscillators or stochastic generators),
127 * the input parameter may be ignored.
128 * Note: This method does NOT mark the node as processed. That responsibility
129 * belongs to the caller, typically a chained parent node or the root node.
130 */
131 virtual double process_sample(double input = 0.) = 0;
132
133 /**
134 * @brief Processes multiple samples at once
135 * @param num_samples Number of samples to process
136 * @return Vector containing the processed samples
137 *
138 * This method provides batch processing capability for more efficient
139 * processing of multiple samples. The default implementation typically
140 * calls process_sample() for each sample, but specialized nodes can
141 * override this with more optimized batch processing algorithms.
142 */
143 virtual std::vector<double> process_batch(unsigned int num_samples) = 0;
144
145 /**
146 * @brief Registers a callback to be called on each tick
147 * @param callback Function to call with the current node context
148 *
149 * Registers a callback function that will be called each time the node
150 * produces a new output value. The callback receives a NodeContext object
151 * containing information about the node's current state.
152 *
153 * This mechanism enables external components to monitor and react to
154 * the node's activity without interrupting the processing flow.
155 *
156 * Example:
157 * ```cpp
158 * node->on_tick([](NodeContext& ctx) {
159 * std::cout << "Node produced value: " << ctx.value << std::endl;
160 * });
161 * ```
162 */
163 virtual void on_tick(const NodeHook& callback);
164
165 /**
166 * @brief Registers a conditional callback
167 * @param callback Function to call when condition is met
168 * @param condition Predicate that determines when callback should be triggered
169 *
170 * Registers a callback function that will be called only when the specified
171 * condition is met. The condition is evaluated each time the node produces
172 * a new output value, and the callback is triggered only if the condition
173 * returns true.
174 *
175 * This mechanism enables selective monitoring and reaction to specific
176 * node states or events, such as threshold crossings or pattern detection.
177 *
178 * Example:
179 * ```cpp
180 * node->on_tick_if(
181 * [](NodeContext& ctx) { std::cout << "Threshold exceeded!" << std::endl; },
182 * [](NodeContext& ctx) { return ctx.value > 0.8; }
183 * );
184 * ```
185 */
186 virtual void on_tick_if(const NodeHook& callback, const NodeCondition& condition);
187
188 /**
189 * @brief Removes a previously registered callback
190 * @param callback The callback to remove
191 * @return True if the callback was found and removed, false otherwise
192 *
193 * Unregisters a callback that was previously registered with on_tick().
194 * After removal, the callback will no longer be triggered when the node
195 * produces new output values.
196 *
197 * This method is useful for cleaning up callbacks when they are no longer
198 * needed, preventing memory leaks and unnecessary processing.
199 */
200 virtual bool remove_hook(const NodeHook& callback);
201
202 /**
203 * @brief Removes a previously registered conditional callback
204 * @param callback The callback part of the conditional callback to remove
205 * @return True if the callback was found and removed, false otherwise
206 *
207 * Unregisters a conditional callback that was previously registered with
208 * on_tick_if(). After removal, the callback will no longer be triggered
209 * even when its condition is met.
210 *
211 * This method is useful for cleaning up conditional callbacks when they
212 * are no longer needed, preventing memory leaks and unnecessary processing.
213 */
214 virtual bool remove_conditional_hook(const NodeCondition& callback);
215
216 /**
217 * @brief Removes all registered callbacks
218 *
219 * Unregisters all callbacks that were previously registered with on_tick()
220 * and on_tick_if(). After calling this method, no callbacks will be triggered
221 * when the node produces new output values.
222 *
223 * This method is useful for completely resetting the node's callback system,
224 * such as when repurposing a node or preparing for cleanup.
225 */
226 virtual void remove_all_hooks();
227
228 /**
229 * @brief Resets the processed state of the node and any attached input nodes
230 *
231 * This method is used by the processing system to reset the processed state
232 * of the node at the end of each processing cycle. This ensures that
233 * all nodes are marked as unprocessed before the cycle next begins, allowing
234 * the system to correctly identify which nodes need to be processed.
235 */
236 virtual void reset_processed_state();
237
238 /**
239 * @brief Retrieves the most recent output value produced by the node
240 * @return The last output sample value
241 *
242 * This method provides access to the node's most recent output without
243 * triggering additional processing. It's useful for monitoring node state,
244 * debugging, and for implementing feedback loops where a node needs to
245 * access its previous output.
246 *
247 * The returned value represents the last sample that was produced by
248 * the node's process_sample() method.
249 */
250 inline virtual double get_last_output() { return m_last_output; }
251
252 /**
253 * @brief Mark the specificed channel as a processor/user
254 * @param channel_id The ID of the channel to register
255 *
256 * This method uses a bitmask to track which channels are currently using this node.
257 * It allows the node to manage its state based on channel usage, which is important
258 * for the audio engine's processing lifecycle. When a channel registers usage,
259 * the node can adjust its processing state accordingly, such as preventing state resets
260 * within the same cycle, or use the same output for multiple channels
261 */
262 void register_channel_usage(uint32_t channel_id);
263
264 /**
265 * @brief Removes the specified channel from the usage tracking
266 * @param channel_id The ID of the channel to unregister
267 */
268 void unregister_channel_usage(uint32_t channel_id);
269
270 /**
271 * @brief Checks if the node is currently used by a specific channel
272 * @param channel_id The ID of the channel to check
273 */
274 bool is_used_by_channel(uint32_t channel_id) const;
275
276 /**
277 * @brief Requests a reset of the processed state from a specific channel
278 * @param channel_id The ID of the channel requesting the reset
279 *
280 * This method is called by channels to signal that they have completed their
281 * processing and that the node's processed state should be reset. It uses a bitmask
282 * to track pending resets and ensures that all channels have completed before
283 * actually resetting the node's state.
284 */
285 void request_reset_from_channel(uint32_t channel_id);
286
287 /**
288 * @brief Retrieves the current bitmask of active channels using this node
289 * @return Bitmask where each bit represents an active channel
290 *
291 * This method returns the current bitmask that tracks which channels
292 * are actively using this node. Each bit in the mask corresponds to a
293 * specific channel ID, allowing the node to manage its state based on
294 * channel usage.
295 */
296 [[nodiscard]] const inline std::atomic<uint32_t>& get_channel_mask() const { return m_active_channels_mask; }
297
298 /**
299 * @brief Retrieves the last created context object
300 * @return Reference to the last NodeContext object
301 *
302 * This method provides access to the most recent NodeContext object
303 * created by the node. This context contains information about the
304 * node's state at the time of the last output generation.
305 */
306 NodeContext& get_last_context() { return *m_last_context; }
307
308 /**
309 * @brief Sets whether the node is compatible with GPU processing
310 * @param compatible True if the node supports GPU processing, false otherwise
311 */
312 void set_gpu_compatible(bool compatible) { m_gpu_compatible = compatible; }
313
314 /**
315 * @brief Checks if the node supports GPU processing
316 * @return True if the node is GPU compatible, false otherwise
317 */
318 [[nodiscard]] bool is_gpu_compatible() const { return m_gpu_compatible; }
319
320 /**
321 * @brief Provides access to the GPU data buffer
322 * @return Span of floats representing the GPU data buffer
323 * This method returns a span of floats that represents the GPU data buffer
324 * associated with this node. The buffer contains data that can be uploaded to the GPU
325 * for processing, enabling efficient execution in GPU-accelerated pipelines.
326 */
327 [[nodiscard]] std::span<const float> get_gpu_data_buffer() const;
328
329protected:
330 /**
331 * @brief Creates an appropriate context object for this node type
332 * @param value The current sample value
333 * @return A context object containing node-specific information
334 *
335 * This method is responsible for creating a NodeContext object (or a derived
336 * class) that contains information about the node's current state. The context
337 * object is passed to callbacks and conditions to provide them with the
338 * information they need to execute properly.
339 *
340 * Node implementations should override this method to create a context object
341 * that includes all relevant node-specific information.
342 */
343 virtual std::unique_ptr<NodeContext> create_context(double value) = 0;
344
345 /**
346 * @brief Notifies all registered callbacks with the current context
347 * @param value The current sample value
348 *
349 * This method is called by the node implementation when a new output value
350 * is produced. It creates a context object using create_context(), then
351 * calls all registered callbacks with that context.
352 *
353 * For unconditional callbacks (registered with on_tick()), the callback
354 * is always called. For conditional callbacks (registered with on_tick_if()),
355 * the callback is called only if its condition returns true.
356 *
357 * Node implementations should call this method at appropriate points in their
358 * processing flow to trigger callbacks.
359 */
360 virtual void notify_tick(double value) = 0;
361
362 /**
363 * @brief Resets the processed state of the node directly
364 *
365 * Unlike reset_processed_state(), this method is called internally
366 * and does not perform any checks or state transitions.
367 */
368 virtual void reset_processed_state_internal();
369
370 /**
371 * @brief The most recent sample value generated by this oscillator
372 *
373 * This value is updated each time process_sample() is called and can be
374 * accessed via get_last_output() without triggering additional processing.
375 * It's useful for monitoring the oscillator's state and for implementing
376 * feedback loops.
377 */
378 double m_last_output { 0 };
379
380 /**
381 * @brief Flag indicating if the node supports GPU processing
382 * This flag is set by derived classes to indicate whether
383 * the node can be processed on the GPU. Nodes that support GPU
384 * processing can provide GPU-compatible context data for
385 * efficient execution in GPU-accelerated pipelines.
386 */
387 bool m_gpu_compatible {};
388
389 /**
390 * @brief The last context object created for callbacks
391 *
392 * This member stores the most recent NodeContext object created by
393 * create_context(). It can be reused for efficiency, avoiding
394 * unnecessary allocations when notifying callbacks.
395 */
396 std::unique_ptr<NodeContext> m_last_context;
397
398 /**
399 * @brief GPU data buffer for context objects
400 *
401 * This buffer is used to store float data that can be uploaded
402 * to the GPU for nodes that support GPU processing. It provides
403 * a contiguous array of floats that can be bound to GPU descriptors,
404 * enabling efficient data transfer and processing on the GPU.
405 */
406 std::vector<float> m_gpu_data_buffer;
407
408 /**
409 * @brief Collection of standard callback functions
410 *
411 * Stores the registered callback functions that will be notified
412 * whenever the binary operation produces a new output value. These callbacks
413 * enable external components to monitor and react to the combined output
414 * without interrupting the processing flow.
415 */
416 std::vector<NodeHook> m_callbacks;
417
418 /**
419 * @brief Collection of conditional callback functions with their predicates
420 *
421 * Stores pairs of callback functions and their associated condition predicates.
422 * These callbacks are only invoked when their condition evaluates to true
423 * for a combined output value, enabling selective monitoring of specific
424 * conditions or patterns in the combined signal.
425 */
426 std::vector<std::pair<NodeHook, NodeCondition>> m_conditional_callbacks;
427
428public:
429 /**
430 * @brief Saves the node's current state for later restoration
431 * Recursively cascades through all connected modulator nodes
432 * Protected - only NodeSourceProcessor and NodeBuffer can call
433 */
434 virtual void save_state() = 0;
435
436 /**
437 * @brief Restores the node's state from the last save
438 * Recursively cascades through all connected modulator nodes
439 * Protected - only NodeSourceProcessor and NodeBuffer can call
440 */
441 virtual void restore_state() = 0;
442
443 /**
444 * @brief Internal flag controlling whether notify_tick fires during state snapshots
445 * Default: false (events don't fire during isolated buffer processing)
446 * Can be exposed in future if needed via concrete implementation in parent
447 */
448 bool m_fire_events_during_snapshot = false;
449
450 /**
451 * @brief Atomic state flag tracking the node's processing status
452 *
453 * This atomic state variable tracks the node's current operational status using
454 * bit flags defined in Utils::NodeState. It indicates whether the node is:
455 * - ACTIVE: Currently part of the processing graph
456 * - PROCESSED: Has been processed in the current cycle
457 * - PENDING_REMOVAL: Marked for removal from the processing graph
458 * - MOCK_PROCESS: Should be processed but output ignored
459 *
460 * The atomic nature ensures thread-safe state transitions, allowing the audio
461 * engine to safely coordinate processing across multiple threads without data races.
462 */
463 std::atomic<Utils::NodeState> m_state { Utils::NodeState::INACTIVE };
464
465 /**
466 * @brief Counter tracking how many other nodes are using this node as a modulator
467 *
468 * This counter is incremented when another node begins using this node as a
469 * modulation source, and decremented when that relationship ends. It's critical
470 * for the node lifecycle management system, as it prevents premature state resets
471 * when a node's output is still needed by downstream nodes.
472 *
473 * When this counter is non-zero, the node's processed state will not be reset
474 * automatically after processing, ensuring that all dependent nodes can access
475 * its output before it's cleared.
476 */
477 std::atomic<uint32_t> m_modulator_count { 0 };
478
479private:
480 /**
481 * @brief Bitmask tracking which channels are currently using this node
482 */
483 std::atomic<uint32_t> m_active_channels_mask { 0 };
484
485 /**
486 * @brief Bitmask tracking which channels have requested a reset
487 *
488 * This mask is used to track which channels have requested the node's processed
489 * state to be reset. When all channels that are currently using the node have
490 * requested a reset, the node can safely clear its processed state.
491 */
492 std::atomic<uint32_t> m_pending_reset_mask { 0 };
493};
494}
std::string type_id
Type identifier for runtime type checking.
Definition Node.hpp:48
virtual ~NodeContext()=default
double value
Current sample value.
Definition Node.hpp:40
NodeContext(double value, const std::string &type)
Protected constructor for NodeContext.
Definition Node.hpp:85
T * as()
Safely cast to a derived context type.
Definition Node.hpp:68
Base context class for node callbacks.
Definition Node.hpp:30
virtual double process_sample(double input=0.)=0
Processes a single data sample.
virtual double get_last_output()
Retrieves the most recent output value produced by the node.
Definition Node.hpp:250
virtual std::vector< double > process_batch(unsigned int num_samples)=0
Processes multiple samples at once.
virtual void save_state()=0
Saves the node's current state for later restoration Recursively cascades through all connected modul...
void set_gpu_compatible(bool compatible)
Sets whether the node is compatible with GPU processing.
Definition Node.hpp:312
std::vector< NodeHook > m_callbacks
Collection of standard callback functions.
Definition Node.hpp:416
virtual void restore_state()=0
Restores the node's state from the last save Recursively cascades through all connected modulator nod...
const std::atomic< uint32_t > & get_channel_mask() const
Retrieves the current bitmask of active channels using this node.
Definition Node.hpp:296
NodeContext & get_last_context()
Retrieves the last created context object.
Definition Node.hpp:306
virtual void notify_tick(double value)=0
Notifies all registered callbacks with the current context.
bool is_gpu_compatible() const
Checks if the node supports GPU processing.
Definition Node.hpp:318
std::vector< std::pair< NodeHook, NodeCondition > > m_conditional_callbacks
Collection of conditional callback functions with their predicates.
Definition Node.hpp:426
virtual ~Node()=default
Virtual destructor for proper cleanup of derived classes.
virtual std::unique_ptr< NodeContext > create_context(double value)=0
Creates an appropriate context object for this node type.
std::unique_ptr< NodeContext > m_last_context
The last context object created for callbacks.
Definition Node.hpp:396
std::vector< float > m_gpu_data_buffer
GPU data buffer for context objects.
Definition Node.hpp:406
Base interface for all computational processing nodes.
Definition Node.hpp:109
std::function< void(NodeContext &)> NodeHook
Callback function type for node processing events.
Definition NodeUtils.hpp:25
std::function< bool(NodeContext &)> NodeCondition
Predicate function type for conditional callbacks.
Definition NodeUtils.hpp:43
Contains the node-based computational processing system components.
Definition Chronie.hpp:5