MayaFlux 1.0.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"
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 * @enum ModulatorRole
19 * @brief Describes the role a modulator node plays relative to its owner.
20 */
21enum class ModulatorRole : uint8_t {
25 Lhs,
26 Rhs,
28};
29
30/**
31 * @struct ModulatorTree
32 * @brief Recursive tree node describing a modulator and all of its own modulators.
33 */
36 std::shared_ptr<Node> node;
37 std::vector<ModulatorTree> modulators;
38};
39
40/**
41 * @class NodeContext
42 * @brief Base context class for node callbacks
43 *
44 * Provides basic context information for callbacks and can be
45 * extended by specific node types to include additional context.
46 * The NodeContext serves as a container for node state information
47 * that is passed to callback functions, allowing callbacks to
48 * access relevant node data during execution.
49 *
50 * Node implementations can extend this class to provide type-specific
51 * context information, which can be safely accessed using the as<T>() method.
52 */
53class MAYAFLUX_API NodeContext {
54public:
55 virtual ~NodeContext() = default;
56
57 /**
58 * @brief Current sample value
59 *
60 * The most recent output value produced by the node.
61 * This is the primary data point that callbacks will typically use.
62 */
63 double value;
64
65 /**
66 * @brief Safely cast to a derived context type
67 * @tparam T The derived context type to cast to
68 * @return Pointer to the derived context or nullptr if types don't match
69 *
70 * Provides type-safe access to derived context classes. If the requested
71 * type matches the actual type of this context, returns a properly cast
72 * pointer. Otherwise, returns nullptr to prevent unsafe access.
73 *
74 * Example:
75 * ```cpp
76 * if (auto filter_ctx = ctx.as<FilterContext>()) {
77 * // Access filter-specific context members
78 * double gain = filter_ctx->gain;
79 * }
80 * ```
81 */
82 template <typename T>
83 T* as()
84 {
85 return dynamic_cast<T*>(this);
86 }
87
88 template <typename T>
89 const T* as() const
90 {
91 return dynamic_cast<const T*>(this);
92 }
93
94protected:
95 /**
96 * @brief Protected constructor for NodeContext
97 * @param value The current sample value
98 * @param type String identifier for the context type
99 *
100 * This constructor is protected to ensure that only derived classes
101 * can create context objects, with proper type identification.
102 */
104 : value(value)
105 {
106 }
107};
108
109/**
110 * @class Node
111 * @brief Base interface for all computational processing nodes
112 *
113 * The Node class defines the fundamental interface for all processing components
114 * in the MayaFlux engine. Nodes are the basic building blocks of transformation chains
115 * and can be connected together to create complex computational graphs.
116 *
117 * Each node processes data on a sample-by-sample basis, allowing for flexible
118 * real-time processing. Nodes can be:
119 * - Connected in series (output of one feeding into input of another)
120 * - Combined in parallel (outputs mixed together)
121 * - Multiplied (outputs multiplied together)
122 *
123 * The node system supports both single-sample processing for real-time applications
124 * and batch processing for more efficient offline processing.
125 */
126class MAYAFLUX_API Node {
127
128public:
129 /**
130 * @brief Virtual destructor for proper cleanup of derived classes
131 */
132 virtual ~Node() = default;
133
134 /**
135 * @brief Processes a single data sample
136 * @param input The input sample value
137 * @return The processed output sample value
138 *
139 * This is the core processing method that all nodes must implement.
140 * It takes a single input value, applies the node's transformation algorithm,
141 * and returns the resulting output value.
142 *
143 * For generator nodes that don't require input (like oscillators or stochastic generators),
144 * the input parameter may be ignored.
145 * Note: This method does NOT mark the node as processed. That responsibility
146 * belongs to the caller, typically a chained parent node or the root node.
147 */
148 virtual double process_sample(double input = 0.) = 0;
149
150 /**
151 * @brief Processes multiple samples at once
152 * @param num_samples Number of samples to process
153 * @return Vector containing the processed samples
154 *
155 * This method provides batch processing capability for more efficient
156 * processing of multiple samples. The default implementation typically
157 * calls process_sample() for each sample, but specialized nodes can
158 * override this with more optimized batch processing algorithms.
159 */
160 virtual std::vector<double> process_batch(unsigned int num_samples) = 0;
161
162 /**
163 * @brief Registers a callback to be called on each tick
164 * @param callback Function to call with the current node context
165 *
166 * Registers a callback function that will be called each time the node
167 * produces a new output value. The callback receives a NodeContext object
168 * containing information about the node's current state.
169 *
170 * This mechanism enables external components to monitor and react to
171 * the node's activity without interrupting the processing flow.
172 *
173 * Example:
174 * ```cpp
175 * node->on_tick([](NodeContext& ctx) {
176 * std::cout << "Node produced value: " << ctx.value << std::endl;
177 * });
178 * ```
179 *
180 * @return Id of the registration, usable with unhook(). A callback equivalent to one
181 * already registered is not added again and the existing id is returned.
182 */
183 virtual HookId on_tick(const NodeHook& callback);
184
185 /**
186 * @brief Registers a conditional callback
187 * @param condition Predicate that determines when callback should be triggered
188 * @param callback Function to call when condition is met
189 *
190 * Registers a callback function that will be called only when the specified
191 * condition is met. The condition is evaluated each time the node produces
192 * a new output value, and the callback is triggered only if the condition
193 * returns true.
194 *
195 * This mechanism enables selective monitoring and reaction to specific
196 * node states or events, such as threshold crossings or pattern detection.
197 *
198 * Example:
199 * ```cpp
200 * node->on_tick_if(
201 * [](NodeContext& ctx) { return ctx.value > 0.8; },
202 * [](NodeContext& ctx) { std::cout << "Threshold exceeded!" << std::endl; }
203 * );
204 * ```
205 */
206 virtual HookId on_tick_if(const NodeCondition& condition, const NodeHook& callback);
207
208 /**
209 * @brief Removes a previously registered callback
210 * @param callback The callback to remove
211 * @return True if the callback was found and removed, false otherwise
212 *
213 * Unregisters a callback that was previously registered with on_tick().
214 * After removal, the callback will no longer be triggered when the node
215 * produces new output values.
216 *
217 * This method is useful for cleaning up callbacks when they are no longer
218 * needed, preventing memory leaks and unnecessary processing.
219 */
220 virtual bool remove_hook(const NodeHook& callback);
221
222 /**
223 * @brief Removes a previously registered conditional callback
224 * @param callback The callback part of the conditional callback to remove
225 * @return True if the callback was found and removed, false otherwise
226 *
227 * Unregisters a conditional callback that was previously registered with
228 * on_tick_if(). After removal, the callback will no longer be triggered
229 * even when its condition is met.
230 *
231 * This method is useful for cleaning up conditional callbacks when they
232 * are no longer needed, preventing memory leaks and unnecessary processing.
233 */
234 virtual bool remove_conditional_hook(const NodeCondition& callback);
235
236 /**
237 * @brief Removes all registered callbacks
238 *
239 * Unregisters all callbacks that were previously registered with on_tick()
240 * and on_tick_if(). After calling this method, no callbacks will be triggered
241 * when the node produces new output values.
242 *
243 * This method is useful for completely resetting the node's callback system,
244 * such as when repurposing a node or preparing for cleanup.
245 */
246 virtual void remove_all_hooks();
247
248 /**
249 * @brief Removes the one registration with this id, whichever hook list holds it
250 * @param id Id returned by an on_* registration of this node
251 * @return True if the registration existed and was removed
252 */
253 virtual bool unhook(HookId id);
254
255 /**
256 * @brief Resets the processed state of the node and any attached input nodes
257 *
258 * This method is used by the processing system to reset the processed state
259 * of the node at the end of each processing cycle. This ensures that
260 * all nodes are marked as unprocessed before the cycle next begins, allowing
261 * the system to correctly identify which nodes need to be processed.
262 */
263 virtual void reset_processed_state();
264
265 /**
266 * @brief Retrieves the most recent output value produced by the node
267 * @return The last output sample value
268 *
269 * This method provides access to the node's most recent output without
270 * triggering additional processing. It's useful for monitoring node state,
271 * debugging, and for implementing feedback loops where a node needs to
272 * access its previous output.
273 *
274 * The returned value represents the last sample that was produced by
275 * the node's process_sample() method.
276 */
277 inline virtual double get_last_output() { return m_last_output; }
278
279 /**
280 * @brief Exclude this node's output from the graph it is registered in.
281 * @param excluded True to process the node without adding its output to the graph, false to restore.
282 *
283 * The node is processed every cycle as usual: its state advances, its hooks fire and
284 * get_last_output() stays valid for anything that reads it. Only its contribution to the
285 * sum of the root it is registered on is left out. Calls to process_sample() or
286 * process_batch() are not affected.
287 */
288 virtual void exclude_output_from_graph(bool excluded = true);
289
290 /** @brief True if the node's output is excluded from the graph. */
291 [[nodiscard]] virtual bool is_output_excluded_from_graph() const;
292
293 /** @brief Old name of exclude_output_from_graph(). */
294 [[deprecated("use exclude_output_from_graph")]] void enable_mock_process(bool mock_process)
295 {
296 exclude_output_from_graph(mock_process);
297 }
298
299 /** @brief Old name of is_output_excluded_from_graph(). */
300 [[deprecated("use is_output_excluded_from_graph")]] [[nodiscard]] bool should_mock_process() const
301 {
302 return is_output_excluded_from_graph();
303 }
304
305 /**
306 * @brief Mark the specificed channel as a processor/user
307 * @param channel_id The ID of the channel to register
308 *
309 * This method uses a bitmask to track which channels are currently using this node.
310 * It allows the node to manage its state based on channel usage, which is important
311 * for the audio engine's processing lifecycle. When a channel registers usage,
312 * the node can adjust its processing state accordingly, such as preventing state resets
313 * within the same cycle, or use the same output for multiple channels
314 */
315 void register_channel_usage(uint32_t channel_id);
316
317 /**
318 * @brief Removes the specified channel from the usage tracking
319 * @param channel_id The ID of the channel to unregister
320 */
321 void unregister_channel_usage(uint32_t channel_id);
322
323 /**
324 * @brief Checks if the node is currently used by a specific channel
325 * @param channel_id The ID of the channel to check
326 */
327 [[nodiscard]] bool is_used_by_channel(uint32_t channel_id) const;
328
329 /**
330 * @brief Requests a reset of the processed state from a specific channel
331 * @param channel_id The ID of the channel requesting the reset
332 *
333 * This method is called by channels to signal that they have completed their
334 * processing and that the node's processed state should be reset. It uses a bitmask
335 * to track pending resets and ensures that all channels have completed before
336 * actually resetting the node's state.
337 */
338 void request_reset_from_channel(uint32_t channel_id);
339
340 /**
341 * @brief Retrieves the current bitmask of active channels using this node
342 * @return Bitmask where each bit represents an active channel
343 *
344 * This method returns the current bitmask that tracks which channels
345 * are actively using this node. Each bit in the mask corresponds to a
346 * specific channel ID, allowing the node to manage its state based on
347 * channel usage.
348 */
349 [[nodiscard]] const inline std::atomic<uint32_t>& get_channel_mask() const { return m_active_channels_mask; }
350
351 /**
352 * @brief Updates the context object with the current node state
353 * @param value The current sample value
354 *
355 * This method is responsible for updating the NodeContext object
356 * with the latest state information from the node. It is called
357 * internally whenever a new output value is produced, ensuring that
358 * the context reflects the current state of the node for use in callbacks.
359 */
360 virtual void update_context(double value) = 0;
361
362 /**
363 * @brief Retrieves the last created context object
364 * @return Reference to the last NodeContext object
365 *
366 * This method provides access to the most recent NodeContext object
367 * created by the node. This context contains information about the
368 * node's state at the time of the last output generation.
369 */
371
372 /**
373 * @brief Sets whether the node is compatible with GPU processing
374 * @param compatible True if the node supports GPU processing, false otherwise
375 */
376 virtual void set_gpu_compatible(bool compatible)
377 {
378 m_gpu_compatible = compatible;
379 if (compatible) {
380 m_timing_rate = m_frame_rate; // Use frame rate for timing calculations if GPU compatible
381 } else {
382 m_timing_rate = m_sample_rate; // Use sample rate for timing calculations if not GPU compatible
383 }
384 }
385
386 /**
387 * @brief Checks if the node supports GPU processing
388 * @return True if the node is GPU compatible, false otherwise
389 */
390 [[nodiscard]] bool is_gpu_compatible() const { return m_gpu_compatible; }
391
392 /**
393 * @brief Provides access to the GPU data buffer
394 * @return Span of floats representing the GPU data buffer
395 * This method returns a span of floats that represents the GPU data buffer
396 * associated with this node. The buffer contains data that can be uploaded to the GPU
397 * for processing, enabling efficient execution in GPU-accelerated pipelines.
398 */
399 [[nodiscard]] std::span<const float> get_gpu_data_buffer() const;
400
401 void set_sample_rate(uint32_t sample_rate) { m_sample_rate = sample_rate; }
402 [[nodiscard]] uint32_t get_sample_rate() const { return m_sample_rate; }
403
404 void set_frame_rate(uint32_t frame_rate) { m_frame_rate = frame_rate; }
405 [[nodiscard]] uint32_t get_frame_rate() const { return m_frame_rate; }
406
407protected:
408 /**
409 * @brief Notifies all registered callbacks with the current context
410 * @param value The current sample value
411 *
412 * This method is called by the node implementation when a new output value
413 * is produced. It creates a context object using create_context(), then
414 * calls all registered callbacks with that context.
415 *
416 * For unconditional callbacks (registered with on_tick()), the callback
417 * is always called. For conditional callbacks (registered with on_tick_if()),
418 * the callback is called only if its condition returns true.
419 *
420 * Node implementations should call this method at appropriate points in their
421 * processing flow to trigger callbacks.
422 */
423 virtual void notify_tick(double value) = 0;
424
425 /**
426 * @brief Resets the processed state of the node directly
427 *
428 * Unlike reset_processed_state(), this method is called internally
429 * and does not perform any checks or state transitions.
430 */
431 virtual void reset_processed_state_internal();
432
433 /**
434 * @brief The most recent sample value generated by this oscillator
435 *
436 * This value is updated each time process_sample() is called and can be
437 * accessed via get_last_output() without triggering additional processing.
438 * It's useful for monitoring the oscillator's state and for implementing
439 * feedback loops.
440 */
441 double m_last_output { 0 };
442
443 /**
444 * @brief Flag indicating if the node supports GPU processing
445 * This flag is set by derived classes to indicate whether
446 * the node can be processed on the GPU. Nodes that support GPU
447 * processing can provide GPU-compatible context data for
448 * efficient execution in GPU-accelerated pipelines.
449 */
450 bool m_gpu_compatible {};
451
452 /**
453 * @brief GPU data buffer for context objects
454 *
455 * This buffer is used to store float data that can be uploaded
456 * to the GPU for nodes that support GPU processing. It provides
457 * a contiguous array of floats that can be bound to GPU descriptors,
458 * enabling efficient data transfer and processing on the GPU.
459 */
460 std::vector<float> m_gpu_data_buffer;
461
462private:
463 friend class HookList;
464
465 void register_hook_list(HookList& list) { m_hook_lists.push_back(&list); }
466
467 HookId next_hook_id() { return m_next_hook_id.fetch_add(1); }
468
469 std::vector<HookList*> m_hook_lists;
470 std::atomic<HookId> m_next_hook_id { 1 };
471
472protected:
473 /**
474 * @brief Hooks fired on every tick
475 *
476 * Registered with this node, so remove_all_hooks clears it.
477 */
478 HookList m_callbacks { *this };
479
480 /**
481 * @brief Hooks fired on a tick only while their condition holds
482 *
483 * Registered with this node, so remove_all_hooks clears it.
484 */
485 HookList m_conditional_callbacks { *this };
486
487 /**
488 * @brief Flag indicating if the node is part of a NodeNetwork
489 * This flag is used to disable event firing when the node is
490 * managed within a NodeNetwork, preventing redundant or conflicting
491 * event notifications.
492 */
493 bool m_networked_node {};
494
495 /**
496 @brief tracks if the node's state has been saved by a snapshot operation
497 */
498 bool m_state_saved {};
499
500 uint32_t m_sample_rate { 48000 }; ///< Sample rate for audio processing, used for normalization
501
502 uint32_t m_frame_rate { 60 }; ///< Frame rate for time-based processing, used for normalization
503
504 uint32_t m_timing_rate { m_sample_rate }; ///< Current timing rate for the node, used for timing calculations (can be sample rate or frame rate)
505
506 uint8_t m_node_capability { NodeCapability::SCALAR }; ///< Bitmask of capabilities declared by this node
507
508public:
509 /**
510 * @brief Saves the node's current state for later restoration
511 * Recursively cascades through all connected modulator nodes
512 * Protected - only NodeSourceProcessor and NodeBuffer can call
513 */
514 virtual void save_state() = 0;
515
516 /**
517 * @brief Restores the node's state from the last save
518 * Recursively cascades through all connected modulator nodes
519 * Protected - only NodeSourceProcessor and NodeBuffer can call
520 */
521 virtual void restore_state() = 0;
522
523 /**
524 * @brief Internal flag controlling whether notify_tick fires during state snapshots
525 * Default: false (events don't fire during isolated buffer processing)
526 * Can be exposed in future if needed via concrete implementation in parent
527 */
528 bool m_fire_events_during_snapshot = false;
529
530 /**
531 * @brief Atomic state flag tracking the node's processing status
532 *
533 * This atomic state variable tracks the node's current operational status using
534 * bit flags defined in NodeState. It indicates whether the node is:
535 * - ACTIVE: Currently part of the processing graph
536 * - PROCESSED: Has been processed in the current cycle
537 * - PENDING_REMOVAL: Marked for removal from the processing graph
538 * - OUTPUT_EXCLUDED: Processed as usual but its output is left out of the graph
539 *
540 * The atomic nature ensures thread-safe state transitions, allowing the audio
541 * engine to safely coordinate processing across multiple threads without data races.
542 */
543 std::atomic<NodeState> m_state { NodeState::INACTIVE };
544
545 /**
546 * @brief Counter tracking how many other nodes are using this node as a modulator
547 *
548 * This counter is incremented when another node begins using this node as a
549 * modulation source, and decremented when that relationship ends. It's critical
550 * for the node lifecycle management system, as it prevents premature state resets
551 * when a node's output is still needed by downstream nodes.
552 *
553 * When this counter is non-zero, the node's processed state will not be reset
554 * automatically after processing, ensuring that all dependent nodes can access
555 * its output before it's cleared.
556 */
557 std::atomic<uint32_t> m_modulator_count { 0 };
558
559 /**
560 * @brief Attempt to claim snapshot context for this processing cycle
561 * @param context_id Unique context identifier for this buffer processing
562 * @return true if this caller claimed the context (should call save_state)
563 *
564 * This method enables multiple NodeBuffers referencing the same node to
565 * coordinate save/restore state operations. Only the first caller per
566 * processing context will claim the snapshot, preventing nested state saves.
567 */
568 bool try_claim_snapshot_context(uint64_t context_id);
569
570 /**
571 * @brief Check if currently in a snapshot context
572 * @param context_id Context to check
573 * @return true if this context is active
574 *
575 * Used by secondary callers to detect when the primary snapshot holder
576 * has completed processing and released the context.
577 */
578 [[nodiscard]] bool is_in_snapshot_context(uint64_t context_id) const;
579
580 /**
581 * @brief Release snapshot context
582 * @param context_id Context to release
583 *
584 * Called by the snapshot owner after restore_state() completes,
585 * allowing other buffers to proceed with their own snapshots.
586 */
587 void release_snapshot_context(uint64_t context_id);
588
589 /**
590 * @brief Check if node is currently being snapshotted by any context
591 * @return true if a snapshot is in progress
592 */
593 [[nodiscard]] bool has_active_snapshot() const;
594
595 /**
596 * @brief Get the active snapshot context ID
597 * @return The current active snapshot context ID, or 0 if none
598 */
599 [[nodiscard]] inline uint64_t get_active_snapshot_context() const
600 {
601 return m_snapshot_context_id.load(std::memory_order_acquire);
602 }
603
604 /**
605 * @brief Increments the buffer reference count
606 * This method is called when a new buffer starts using this node
607 * to ensure proper lifecycle management.
608 */
609 void add_buffer_reference();
610
611 /**
612 * @brief Decrements the buffer reference count
613 * This method is called when a buffer stops using this node
614 * to ensure proper lifecycle management.
615 */
616 void remove_buffer_reference();
617
618 /**
619 * @brief Marks the node as having been processed by a buffer
620 * @return true if the buffer was successfully marked as processed
621 *
622 * This method checks if the node can be marked as processed based on
623 * the current buffer count and node state. If conditions are met,
624 * it updates the processed flag and increments the reset counter.
625 */
626 bool mark_buffer_processed();
627
628 /**
629 * @brief Requests a reset of the buffer state
630 *
631 * This method is called to signal that the buffer's processed state
632 * should be reset. It increments the reset counter, which is used to
633 * determine when it's safe to clear the processed state.
634 */
635 void request_buffer_reset();
636
637 /**
638 * @brief Checks if the buffer has been processed
639 * @return true if the buffer is marked as processed
640 */
641 [[nodiscard]] inline bool is_buffer_processed() const
642 {
643 return m_buffer_processed.load(std::memory_order_acquire);
644 }
645
646 /**
647 * @brief Sets whether the node is part of a NodeNetwork
648 * @param networked True if the node is managed within a NodeNetwork
649 *
650 * This method sets a flag indicating whether the node is part of a
651 * NodeNetwork. When set, certain behaviors such as event firing
652 * may be disabled to prevent redundant or conflicting notifications.
653 */
654 [[nodiscard]] inline bool is_in_network() const { return m_networked_node; }
655
656 /**
657 * @brief Marks the node as being part of a NodeNetwork
658 * @param networked True if the node is managed within a NodeNetwork
659 *
660 * This method sets a flag indicating whether the node is part of a
661 * NodeNetwork. When set, certain behaviors such as event firing
662 * may be disabled to prevent redundant or conflicting notifications.
663 */
664 void set_in_network(bool networked) { m_networked_node = networked; }
665
666 /**
667 * @brief Retrieves the current routing state of the network
668 * @return Reference to the current RoutingState structure
669 *
670 * This method provides access to the network's current routing state, which
671 * includes information about fade-in/out (Active) phases, channel counts, and elapsed cycles.
672 * The routing state is used to manage smooth transitions when routing changes occur,
673 * ensuring seamless audio output during dynamic reconfigurations of the processing graph.
674 */
675 [[nodiscard]] const RoutingState& get_routing_state() const { return m_routing_state; }
676
677 /**
678 * @brief Retrieves the current routing state of the network (non-const)
679 * @return Reference to the current RoutingState structure
680 */
681 RoutingState& get_routing_state() { return m_routing_state; }
682
683 /**
684 * @brief Checks if the network is currently in a routing transition phase
685 * @return true if the network is in a fade-in or fade-out (Active) phase
686 *
687 * This method checks the network's routing state to determine if it is currently
688 * undergoing a routing transition, such as fading in or out. This information
689 * can be used by processing algorithms to adjust their behavior during transitions,
690 * ensuring smooth audio output without artifacts.
691 */
692 [[nodiscard]] bool needs_channel_routing() const
693 {
694 return m_routing_state.phase & (RoutingState::ACTIVE | RoutingState::COMPLETED);
695 }
696
697 /**
698 * @brief Declare which data shapes this node's context can produce.
699 *
700 * Override to advertise capabilities beyond SCALAR. The default reflects
701 * the Node interface guarantee: every node produces a scalar.
702 * Combine flags with bitwise OR for nodes whose context implements
703 * multiple GpuContext mixins.
704 *
705 * @return Bitmask of NodeCapability flags.
706 */
707 [[nodiscard]] virtual uint8_t node_capabilities() const { return m_node_capability; }
708
709 /**
710 * @brief Query a single capability.
711 * @param cap Capability flag to test.
712 * @return true if this node supports the requested data shape.
713 */
714 [[nodiscard]] bool has_capability(NodeCapability cap) const
715 {
716 return (m_node_capability & cap) != 0U;
717 }
718
719 /**
720 * @brief Returns direct modulator nodes and their roles.
721 *
722 * Each entry pairs the role the modulator plays in this node's processing
723 * with the modulator node itself. Default returns empty. Concrete nodes
724 * override to expose their held modulator references.
725 */
726 [[nodiscard]] virtual std::vector<std::pair<ModulatorRole, std::shared_ptr<Node>>>
727 get_modulators() const { return {}; }
728
729 /**
730 * @brief Returns the full modulator tree rooted at this node.
731 *
732 * Calls get_modulators() on this node, then recursively on each returned
733 * node, building the complete tree. The caller receives the entire
734 * hierarchy in one call.
735 */
736 [[nodiscard]] std::vector<ModulatorTree> get_modulator_tree() const
737 {
738 std::vector<ModulatorTree> result;
739 for (auto& [role, node] : get_modulators()) {
740 ModulatorTree entry;
741 entry.role = role;
742 entry.node = node;
743 entry.modulators = node->get_modulator_tree();
744 result.push_back(std::move(entry));
745 }
746 return result;
747 }
748
749private:
750 /**
751 * @brief Bitmask tracking which channels are currently using this node
752 */
753 std::atomic<uint32_t> m_active_channels_mask { 0 };
754
755 /**
756 * @brief Bitmask tracking which channels have requested a reset
757 *
758 * This mask is used to track which channels have requested the node's processed
759 * state to be reset. When all channels that are currently using the node have
760 * requested a reset, the node can safely clear its processed state.
761 */
762 std::atomic<uint32_t> m_pending_reset_mask { 0 };
763
764 /**
765 * @brief Unique identifier for the current snapshot context
766 *
767 * This atomic variable holds the unique identifier of the current
768 * snapshot context that has claimed ownership of this node's state.
769 * It ensures that only one processing context can perform save/restore
770 * operations at a time, preventing nested snapshots and ensuring
771 * consistent state management.
772 */
773 std::atomic<uint64_t> m_snapshot_context_id { 0 };
774
775 /**
776 * @brief Counter tracking how many buffers are using this node
777 * This counter is incremented when a buffer starts using this node
778 * and decremented when the buffer stops using it. It helps manage
779 * the node's lifecycle in relation to buffer usage.
780 */
781 std::atomic<uint32_t> m_buffer_count { 0 };
782
783 /**
784 * @brief Flag indicating whether the buffer has been processed
785 * This atomic flag is set when the buffer has been successfully
786 * processed and is used to prevent redundant processing.
787 */
788 std::atomic<bool> m_buffer_processed { false };
789
790 /**
791 * @brief Counter tracking how many buffers have requested a reset
792 *
793 * When all buffers using this node have requested a reset, the node's
794 * processed state can be safely cleared. This counter helps coordinate
795 * that process.
796 */
797 std::atomic<uint32_t> m_buffer_reset_count { 0 };
798
799 /**
800 * @brief Internal state tracking for routing transitions
801 *
802 * This structure tracks the state of routing transitions,
803 * such as fade-in and fade-out phases, channel counts, and elapsed cycles.
804 * It's used to manage smooth transitions when routing changes occur, ensuring
805 * that audio output remains seamless during dynamic reconfigurations of the processing graph.
806 */
808};
809}
Core::GlobalInputConfig input
Definition Config.cpp:42
PickCallback callback
Definition Picker.cpp:22
glm::vec3 value
Stores, fires and clears the hooks of one node channel.
Definition NodeUtils.hpp:93
NodeContext(double value)
Protected constructor for NodeContext.
Definition Node.hpp:103
virtual ~NodeContext()=default
double value
Current sample value.
Definition Node.hpp:63
T * as()
Safely cast to a derived context type.
Definition Node.hpp:83
const T * as() const
Definition Node.hpp:89
Base context class for node callbacks.
Definition Node.hpp:53
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:277
virtual std::vector< double > process_batch(unsigned int num_samples)=0
Processes multiple samples at once.
bool is_buffer_processed() const
Checks if the buffer has been processed.
Definition Node.hpp:641
virtual void save_state()=0
Saves the node's current state for later restoration Recursively cascades through all connected modul...
bool is_in_network() const
Sets whether the node is part of a NodeNetwork.
Definition Node.hpp:654
uint32_t get_frame_rate() const
Definition Node.hpp:405
void set_in_network(bool networked)
Marks the node as being part of a NodeNetwork.
Definition Node.hpp:664
virtual void restore_state()=0
Restores the node's state from the last save Recursively cascades through all connected modulator nod...
uint64_t get_active_snapshot_context() const
Get the active snapshot context ID.
Definition Node.hpp:599
const std::atomic< uint32_t > & get_channel_mask() const
Retrieves the current bitmask of active channels using this node.
Definition Node.hpp:349
bool needs_channel_routing() const
Checks if the network is currently in a routing transition phase.
Definition Node.hpp:692
void enable_mock_process(bool mock_process)
Old name of exclude_output_from_graph().
Definition Node.hpp:294
virtual NodeContext & get_last_context()=0
Retrieves the last created context object.
virtual void notify_tick(double value)=0
Notifies all registered callbacks with the current context.
virtual void update_context(double value)=0
Updates the context object with the current node state.
bool should_mock_process() const
Old name of is_output_excluded_from_graph().
Definition Node.hpp:300
virtual void set_gpu_compatible(bool compatible)
Sets whether the node is compatible with GPU processing.
Definition Node.hpp:376
void set_sample_rate(uint32_t sample_rate)
Definition Node.hpp:401
bool is_gpu_compatible() const
Checks if the node supports GPU processing.
Definition Node.hpp:390
virtual ~Node()=default
Virtual destructor for proper cleanup of derived classes.
uint32_t get_sample_rate() const
Definition Node.hpp:402
RoutingState & get_routing_state()
Retrieves the current routing state of the network (non-const)
Definition Node.hpp:681
HookId next_hook_id()
Definition Node.hpp:467
bool has_capability(NodeCapability cap) const
Query a single capability.
Definition Node.hpp:714
std::vector< ModulatorTree > get_modulator_tree() const
Returns the full modulator tree rooted at this node.
Definition Node.hpp:736
void set_frame_rate(uint32_t frame_rate)
Definition Node.hpp:404
virtual std::vector< std::pair< ModulatorRole, std::shared_ptr< Node > > > get_modulators() const
Returns direct modulator nodes and their roles.
Definition Node.hpp:727
void register_hook_list(HookList &list)
Definition Node.hpp:465
std::vector< float > m_gpu_data_buffer
GPU data buffer for context objects.
Definition Node.hpp:460
RoutingState m_routing_state
Internal state tracking for routing transitions.
Definition Node.hpp:807
std::vector< HookList * > m_hook_lists
Definition Node.hpp:469
virtual uint8_t node_capabilities() const
Declare which data shapes this node's context can produce.
Definition Node.hpp:707
const RoutingState & get_routing_state() const
Retrieves the current routing state of the network.
Definition Node.hpp:675
Base interface for all computational processing nodes.
Definition Node.hpp:126
TypedHook<> NodeHook
Alias for TypedHook<NodeContext>.
Definition NodeUtils.hpp:38
ModulatorRole
Describes the role a modulator node plays relative to its owner.
Definition Node.hpp:21
uint64_t HookId
Identifies one registration inside a HookList.
Definition NodeUtils.hpp:60
NodeCapability
Bitmask flags declaring what data shapes a node's context can produce.
Definition NodeSpec.hpp:109
std::function< bool(NodeContext &)> NodeCondition
Predicate function type for conditional callbacks.
Definition NodeUtils.hpp:54
Contains the node-based computational processing system components.
Definition Chronie.hpp:14
std::vector< ModulatorTree > modulators
Definition Node.hpp:37
std::shared_ptr< Node > node
Definition Node.hpp:36
Recursive tree node describing a modulator and all of its own modulators.
Definition Node.hpp:34
Represents the state of routing transitions for a node.
Definition NodeSpec.hpp:69