MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
NodeGraphManager.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "NodeSpec.hpp"
4#include "RootNode.hpp"
5
6namespace MayaFlux::Nodes {
7
8using TokenChannelProcessor = std::function<std::vector<double>(RootNode*, uint32_t)>;
9using TokenSampleProcessor = std::function<double(RootNode*, uint32_t)>;
10
11namespace Network {
12 class NodeNetwork;
13} // namespace Network
14
15/**
16 * @class NodeGraphManager
17 * @brief Central manager for the computational processing node graph
18 *
19 * The NodeGraphManager is the primary interface for creating, connecting, and managing
20 * processing nodes in the MayaFlux engine. It serves as a registry for all nodes
21 * and maintains the root nodes for each processing channel and processing domain (token).
22 *
23 * Features:
24 * - Multi-modal (token-based) processing: Supports multiple independent processing domains
25 * (e.g., AUDIO_RATE, VISUAL_RATE, CUSTOM_RATE), each with its own set of channels and root nodes.
26 * - Per-channel root nodes: Each processing domain can have multiple channels, each with its own RootNode.
27 * - Node registry: Tracks all registered nodes for lifecycle management and deduplication.
28 * - Flexible connection: Nodes are connected by shared_ptr reference via the >> and + operators.
29 * - Subsystem token processors: Allows registration of custom processing functions for each token, enabling efficient backend-specific processing.
30 *
31 * This class provides methods to:
32 * - Create nodes of any type with automatic registration
33 * - Connect nodes to form processing chains
34 * - Add nodes to channel root nodes for output
35 * - Look up nodes by their string identifiers
36 *
37 * The NodeGraphManager maintains separate root nodes for each processing channel.
38 * Channels are local to a specific processing domain.
39 * This allows for independent transformation chains per channel while sharing nodes
40 * between channels when needed.
41 */
42class MAYAFLUX_API NodeGraphManager {
43public:
44 /**
45 * @brief Creates a new NodeGraphManager
46 * @param sample_rate Sample rate for audio processing (default: 48000 Hz)
47 * @param block_size Block size for audio processing (default: 512 samples)
48 * @param frame_rate Frame rate for visual processing (default: 60 FPS)
49 *
50 * Initializes the manager with a root node for channel 0 (the default channel)
51 * in the AUDIO_RATE domain. Additional root nodes for other tokens and channels
52 * are created on demand when accessed.
53 */
54 NodeGraphManager(uint32_t sample_rate = 48000, uint32_t block_size = 512, uint32_t frame_rate = 60);
55
56 /**
57 * @brief Destroys the NodeGraphManager
58 *
59 * Cleans up all registered nodes and networks.
60 */
62
63 // NodeGraphManager is non-copyable and non-moveable due to internal atomic state
68
69 /**
70 * @brief Add node to specific processing token and channel
71 * @param node Node to add
72 * @param token Processing domain (AUDIO_RATE, VISUAL_RATE, etc.)
73 * @param channel Channel within that domain
74 *
75 * Registers the node with the specified processing domain and channel.
76 * The node's output will contribute to that token/channel's output.
77 * If the node is not already globally registered, it will be registered automatically.
78 */
79 void add_to_root(const std::shared_ptr<Node>& node, ProcessingToken token, unsigned int channel = 0);
80
81 /**
82 * @brief Remove node from a specific processing token and channel
83 * @param node Node to remove
84 * @param token Processing domain (AUDIO_RATE, VISUAL_RATE, etc.)
85 * @param channel Channel within that domain
86 *
87 * Removes the specified node from the root node of the given processing domain and channel.
88 * If the node is not found in that root, no action is taken.
89 */
90 void remove_from_root(const std::shared_ptr<Node>& node, ProcessingToken token, unsigned int channel = 0);
91
92 /**
93 * @brief Get all nodes from their respective root nodes for a specific token and/or channel
94 * @param token Processing domain (AUDIO_RATE, VISUAL_RATE, etc.)
95 * @param channel Channel within that domain
96 * @return Vector of shared pointers to the nodes for that token and channel root
97 */
98 [[nodiscard]] const std::vector<std::shared_ptr<Node>>&
99 get_nodes(ProcessingToken token, uint32_t channel = 0) const;
100
101 /**
102 * @brief Register subsystem processor for a specific token
103 * @param token Processing domain to handle (e.g., AUDIO_RATE, VISUAL_RATE)
104 * @param processor Function that receives a span of root nodes for that token
105 *
106 * Registers a custom processing function for a given processing domain (token).
107 * When process_token() is called for that token, the registered processor will
108 * be invoked with a span of all root nodes for that domain, enabling efficient
109 * backend-specific or multi-channel processing.
110 */
111 void register_token_processor(ProcessingToken token,
112 std::function<void(std::span<RootNode*>)> processor);
113
114 /**
115 * @brief Gets all channel root nodes for the AUDIO_RATE domain
116 * @param token Processing domain to get the root nodes for (default is AUDIO_RATE)
117 * @return Constant reference to the map of channel indices to root nodes
118 *
119 * This provides access to all channel root nodes of the specified domain that have been created.
120 * Useful for processing all channels or inspecting the node graph structure.
121 * For multi-modal access, use get_token_roots().
122 */
123 const std::unordered_map<unsigned int, std::shared_ptr<RootNode>>& get_all_channel_root_nodes(ProcessingToken token = ProcessingToken::AUDIO_RATE) const;
124
125 /**
126 * @brief Checks if a node is registered with this manager
127 * @param node Node to check
128 * @return true if the node is registered, false otherwise
129 *
130 * A node is considered registered if it exists in the node registry
131 * with any identifier.
132 */
133 bool is_node_registered(const std::shared_ptr<Node>& node);
134
135 /**
136 * @brief Process all nodes in a specific token domain
137 * Calls registered processor if available, otherwise calls process() on each root
138 * @param token Processing domain to process
139 * @param num_samples Number of samples/frames to process
140 *
141 * Processes all root nodes for the specified processing domain (token).
142 * If a custom processor is registered for the token, it is called with all root nodes.
143 * Otherwise, process() is called on each root node individually.
144 */
145 void process_token(ProcessingToken token, unsigned int num_samples = 1);
146
147 /**
148 * @brief Register per-channel processor for a specific token
149 * @param token Processing domain to handle (e.g., AUDIO_RATE, VISUAL_RATE)
150 * @param processor Function that receives a single root node and returns processed data
151 *
152 * Registers a per-channel processing function that processes one root node at a time
153 * and returns the processed data. This enables coordination with buffer management
154 * on a per-channel basis.
155 */
156 void register_token_channel_processor(ProcessingToken token,
157 TokenChannelProcessor processor);
158
159 /**
160 * @brief Register per-sample processor for a specific token
161 * @param token Processing domain to handle (e.g., AUDIO_RATE, VISUAL_RATE)
162 * @param processor Function that processes a single sample and returns the processed value
163 *
164 * Registers a per-sample processing function that processes one sample at a time
165 * and returns the processed value. This is useful for low-level sample manipulation.
166 */
167 void register_token_sample_processor(ProcessingToken token,
168 TokenSampleProcessor processor);
169
170 /**
171 * @brief Process a specific channel within a token domain
172 * @param token Processing domain
173 * @param channel Channel index within that domain
174 * @param num_samples Number of samples/frames to process
175 * @return Processed data from the channel's root node
176 *
177 * Processes a single channel's root node and returns the processed data.
178 * If a custom per-channel processor is registered, it is used; otherwise,
179 * the default root node processing is performed.
180 */
181 std::vector<double> process_channel(ProcessingToken token, unsigned int channel, unsigned int num_samples);
182
183 /**
184 * @brief Process a single sample for a specific channel
185 * @param token Processing domain
186 * @param channel Channel index within that domain
187 * @return Processed sample value from the channel's root node
188 *
189 * Processes a single sample for the specified channel and returns the processed value.
190 * If a custom per-sample processor is registered, it is used; otherwise, the default
191 * root node processing is performed.
192 * As node graph manager feeds into hardware audio output, the value returned is normalized
193 */
194 double process_sample(ProcessingToken token, uint32_t channel);
195
196 /**
197 * @brief Process all channels for a token and return channel-separated data
198 * @param token Processing domain
199 * @param num_samples Number of samples/frames to process
200 * @return Map of channel index to processed data
201 *
202 * Processes all channels for a token and returns a map where each channel
203 * index maps to its processed data. This enables bulk processing while
204 * maintaining per-channel data separation.
205 */
206 std::unordered_map<unsigned int, std::vector<double>> process_token_with_channel_data(
207 ProcessingToken token, unsigned int num_samples);
208
209 /**
210 * @brief Get the number of active channels for a specific token
211 * @param token Processing domain
212 * @return Number of channels that have active root nodes
213 */
214 unsigned int get_channel_count(ProcessingToken token) const;
215
216 /**
217 * @brief Get spans of root nodes for a token (for custom processing)
218 * @param token Processing domain
219 * @return Vector of RootNode pointers for that domain
220 *
221 * Returns a vector of pointers to all root nodes for the specified processing domain.
222 * Useful for custom processing, introspection, or multi-channel operations.
223 */
224 std::vector<RootNode*> get_all_root_nodes(ProcessingToken token);
225
226 /**
227 * @brief Gets or creates the root node for a specific token and channel
228 * @param token Processing domain
229 * @param channel Channel index
230 * @return Reference to the root node for the given token and channel
231 *
232 * If the root node does not exist, it is created and registered.
233 */
234 RootNode& get_root_node(ProcessingToken token, unsigned int channel);
235
236 /**
237 * @brief Process all active tokens sequentially
238 * @param num_samples Number of samples/frames to process
239 *
240 * Iterates over all processing domains (tokens) that have active root nodes,
241 * and processes each one in turn. This enables multi-modal, multi-channel
242 * processing in a single call.
243 */
244 void process_all_tokens(unsigned int num_samples = 1);
245
246 /**
247 * @brief Gets all currently active processing tokens (domains)
248 * @return Vector of active ProcessingToken values
249 *
250 * Returns a list of all processing domains that have at least one root node.
251 * Useful for introspection and for iterating over all active domains.
252 */
253 std::vector<ProcessingToken> get_active_tokens() const;
254
255 /**
256 * @brief Gets all channel indices for a given processing token
257 * @param token Processing domain
258 * @return Vector of channel indices that have root nodes for this token
259 */
260 std::vector<unsigned int> get_all_channels(ProcessingToken token) const;
261
262 /**
263 * @brief Gets the total number of nodes registered under a given token
264 * @param token Processing domain
265 * @return Total number of nodes across all channels for this token
266 */
267 size_t get_node_count(ProcessingToken token) const;
268
269 //-------------------------------------------------------------------------
270 // NodeNetwork Management
271 //-------------------------------------------------------------------------
272
273 /**
274 * @brief Add a network to a processing token
275 * @param network Network to add
276 * @param token Processing domain (AUDIO_RATE, VISUAL_RATE, etc.)
277 *
278 * Networks are processed parallel to RootNodes, managing their own
279 * internal node coordination and processing.
280 */
281 void add_network(const std::shared_ptr<Network::NodeNetwork>& network, ProcessingToken token);
282
283 /**
284 * @brief Remove a network from a processing token
285 * @param network Network to remove
286 * @param token Processing domain
287 * @param channel Channel index within that domain, optional
288 */
289 void remove_network(const std::shared_ptr<Network::NodeNetwork>& network, ProcessingToken token);
290
291 /**
292 * @brief Get all networks for a specific token
293 * @param token Processing domain
294 * @return Vector of networks registered to this token
295 */
296 [[nodiscard]] std::vector<std::shared_ptr<Network::NodeNetwork>> get_networks(ProcessingToken token, uint32_t channel = 0) const;
297
298 /**
299 * @brief Get count of networks for a token
300 */
301 [[nodiscard]] size_t get_network_count(ProcessingToken token) const;
302
303 /**
304 * @brief Clear all networks from a token
305 */
306 void clear_networks(ProcessingToken token);
307
308 /**
309 * @brief Register network globally (like nodes)
310 */
311 void register_network_global(const std::shared_ptr<Network::NodeNetwork>& network);
312
313 /**
314 * @brief Unregister network globally
315 */
316 void unregister_network_global(const std::shared_ptr<Network::NodeNetwork>& network);
317
318 /**
319 * @brief Process audio networks for a specific channel
320 * @param token Processing domain (should be AUDIO_RATE)
321 * @param num_samples Number of samples/frames to process
322 * @param channel Channel index within that domain
323 * @return Vector of processed audio data from all networks for that channel
324 *
325 * Processes all audio-sink networks registered to the specified channel
326 * and returns their combined output data.
327 */
328 std::vector<std::vector<double>> process_audio_networks(ProcessingToken token, uint32_t num_samples, uint32_t channel = 0);
329
330 /**
331 * @brief Terminates all active processing across all tokens and channels
332 *
333 * This method stops all active processing contexts in all root nodes
334 * and networks, ensuring a clean shutdown of processing activities.
335 */
336 void terminate_active_processing();
337
338 /**
339 * @brief Routes a node's output to specific channels within a token domain
340 * @param node Node to route
341 * @param target_channels Vector of channel indices to route the node's output to
342 * @param fade_cycles Number of cycles to fade in the routing (optional)
343 * @param token Processing domain to route within
344 *
345 * This method adds the specified node to the root nodes of the target channels
346 * within the given processing domain. If fade_cycles is greater than 0, the routing
347 * will be smoothly faded in over that many processing cycles.
348 */
349 void route_node_to_channels(
350 const std::shared_ptr<Node>& node,
351 const std::vector<uint32_t>& target_channels,
352 uint32_t fade_cycles,
353 ProcessingToken token);
354
355 /**
356 * @brief Routes a network's output to specific channels within a token domain
357 * @param network Network to route (must be an audio sink)
358 * @param target_channels Vector of channel indices to route the network's output to
359 * @param fade_cycles Number of cycles to fade in the routing (optional)
360 * @param token Processing domain to route within
361 *
362 * This method registers the network and adds it to the specified channels' root nodes
363 * within the given processing domain. If fade_cycles is greater than 0, the routing
364 * will be smoothly faded in over that many processing cycles.
365 */
366 void route_network_to_channels(
367 const std::shared_ptr<Network::NodeNetwork>& network,
368 const std::vector<uint32_t>& target_channels,
369 uint32_t fade_cycles,
370 ProcessingToken token);
371
372 /**
373 * @brief Updates routing states for all nodes and networks for a given token
374 * @param token Processing domain to update routing states for
375 *
376 * This method should be called at the end of each processing cycle to update the
377 * routing states of all nodes and networks that are currently undergoing routing changes.
378 * It handles the fade-in/out logic and transitions routing states as needed.
379 */
380 void update_routing_states_for_cycle(ProcessingToken token);
381
382 /**
383 * @brief Cleans up completed routing transitions for a given token
384 * @param token Processing domain to clean up routing for
385 *
386 * This method should be called after routing states have been updated to remove any nodes
387 * or networks that have completed their fade-out transitions and are no longer contributing
388 * to the output of their previous channels.
389 */
390 void cleanup_completed_routing(ProcessingToken token);
391
392 /**
393 * @brief Sets the node configuration for this manager
394 * @param config The NodeConfig to set
395 */
396 void set_node_config(const NodeConfig& config) { m_node_config = config; }
397
398 /**
399 * @brief Gets the current node configuration
400 * @return Reference to the current NodeConfig
401 */
402 NodeConfig& get_node_config() { return m_node_config; }
403 const NodeConfig& get_node_config() const { return m_node_config; }
404
405private:
406 /**
407 * @brief Registry of all nodes by their string identifiers
408 */
409 std::unordered_set<std::shared_ptr<Node>> m_node_registry;
410
411 /**
412 * @brief Multi-modal map of processing tokens to their channel root nodes
413 *
414 * Each processing domain (token) can have multiple channels, each with its own RootNode.
415 * Enables support for audio, visual, and custom processing domains.
416 */
417 std::unordered_map<ProcessingToken,
418 std::unordered_map<unsigned int, std::shared_ptr<RootNode>>>
420
421 /**
422 * @brief Registered custom processors for each processing token
423 *
424 * Maps each processing domain (token) to a custom processing function that
425 * receives a span of all root nodes for that domain. Enables efficient
426 * backend-specific or multi-channel processing.
427 */
428 std::unordered_map<ProcessingToken,
429 std::function<void(std::span<RootNode*>)>>
431
432 /**
433 * @brief Per-channel processors for each processing token
434 *
435 * Maps each processing domain to a per-channel processing function that
436 * processes a single root node and returns processed data. This enables
437 * fine-grained processing with data extraction capabilities.
438 */
439 std::unordered_map<ProcessingToken, TokenChannelProcessor> m_token_channel_processors;
440
441 /**
442 * @brief Per-sample processors for each processing token
443 *
444 * Maps each processing domain to a per-sample processing function that
445 * processes a single sample and returns the processed value. This is useful
446 * for low-level sample manipulation and custom processing.
447 */
448 std::unordered_map<ProcessingToken, TokenSampleProcessor> m_token_sample_processors;
449
450 /**
451 * @brief Global network registry (like m_Node_registry)
452 */
453 std::unordered_set<std::shared_ptr<Network::NodeNetwork>> m_network_registry;
454
455 /**
456 * @brief Audio-sink networks
457 * Only populated for networks with OutputMode::AUDIO_SINK
458 */
459 std::unordered_map<ProcessingToken,
460 std::vector<std::shared_ptr<Network::NodeNetwork>>>
462
463 /**
464 * @brief Non-audio networks (token-level processing)
465 * For NONE, GRAPHICS_BIND, CUSTOM output modes
466 */
467 std::unordered_map<ProcessingToken, std::vector<std::shared_ptr<Network::NodeNetwork>>>
469
470 /**
471 * @brief Processing flags for each token's networks
472 *
473 * Used to prevent re-entrant processing of networks within the same cycle.
474 */
475 std::unordered_map<ProcessingToken, std::unique_ptr<std::atomic<bool>>> m_token_network_processing;
476
477 std::atomic<bool> m_terminate_requested { false }; ///< Global termination flag
478
479 uint32_t m_registered_sample_rate { 48000 }; ///< Sample rate for audio processing, used for normalization
480
481 uint32_t m_registered_frame_rate { 60 }; ///< Frame rate for visual processing, used for timing and normalization
482
483 uint32_t m_registered_block_size { 512 }; ///< Block size for audio processing, used for normalizationbuffer
484
485 NodeConfig m_node_config; ///< Configuration for node creation and management
486
487 /**
488 * @brief Ensures a root node exists for the given token and channel
489 * @param token Processing domain
490 * @param channel Channel index
491 *
492 * Creates and registers a new root node if one does not already exist.
493 */
494 void ensure_root_exists(ProcessingToken token, unsigned int channel);
495
496 /**
497 * @brief Ensures that a processing token entry exists
498 * @param token Processing domain
499 * @param num_channels Number of channels to initialize (default: 1)
500 *
501 * Creates the necessary data structures for the given processing token
502 * if they do not already exist.
503 */
504 void ensure_token_exists(ProcessingToken token, uint32_t num_channels = 1);
505
506 /**
507 * @brief Registers a node globally if not already registered
508 * @param node Node to register
509 *
510 * Assigns a generated identifier if needed and adds the node to the registry.
511 */
512 void register_global(const std::shared_ptr<Node>& node);
513
514 /**
515 * @brief Adds the specified channel mask to a node's global registration
516 * @param node Node to modify
517 * @param channel_id Channel mask to set
518 */
519 void set_channel_mask(const std::shared_ptr<Node>& node, uint32_t channel_id);
520
521 /**
522 * @brief Unsets the specified channel mask from a node's global registration
523 * @param node Node to modify
524 * @param channel_id Channel mask to unset
525 *
526 * Removes the specified channel mask from the node's global registration.
527 */
528 void unset_channel_mask(const std::shared_ptr<Node>& node, uint32_t channel_id);
529
530 /**
531 * @brief Unregisters a node globally
532 * @param node Node to unregister
533 *
534 * Removes the node from the global registry and cleans up any references.
535 */
536 void unregister_global(const std::shared_ptr<Node>& node);
537
538 /**
539 * @brief Normalizes a sample to the range [-1, 1] based on the number of nodes
540 * @param sample Reference to the sample value to normalize
541 * @param num_nodes Number of nodes in the processing chain
542 *
543 * Ensures that the sample value is within the valid range for audio processing.
544 */
545 void normalize_sample(double& sample, uint32_t num_nodes);
546
547 /**
548 * @brief Check if network is registered globally
549 */
550 bool is_network_registered(const std::shared_ptr<Network::NodeNetwork>& network);
551
552 /**
553 * @brief Get all networks for a specific token across all channels
554 * @param token Processing domain
555 * @return Vector of networks registered to this token
556 */
557 [[nodiscard]] std::vector<std::shared_ptr<Network::NodeNetwork>> get_all_networks(ProcessingToken token) const;
558
559 /**
560 * @brief Resets the processing state of audio networks for a token and channel
561 * @param token Processing domain
562 * @param channel Channel index
563 */
564 void reset_audio_network_state(ProcessingToken token, uint32_t channel = 0);
565
566 /**
567 * @brief Preprocess networks for a specific token
568 * @param token Processing domain
569 * @return true if preprocessing succeeded, false otherwise
570 */
571 bool preprocess_networks(ProcessingToken token);
572
573 /**
574 * @brief Postprocess networks for a specific token and channel
575 * @param token Processing domain
576 * @param channel Channel index
577 */
578 void postprocess_networks(ProcessingToken token, std::optional<uint32_t> channel);
579};
580
581}
Core::GlobalNetworkConfig network
Definition Config.cpp:37
void set_node_config(const NodeConfig &config)
Sets the node configuration for this manager.
NodeGraphManager(NodeGraphManager &&)=delete
std::unordered_map< ProcessingToken, std::vector< std::shared_ptr< Network::NodeNetwork > > > m_token_networks
Non-audio networks (token-level processing) For NONE, GRAPHICS_BIND, CUSTOM output modes.
std::unordered_map< ProcessingToken, std::function< void(std::span< RootNode * >)> > m_token_processors
Registered custom processors for each processing token.
const NodeConfig & get_node_config() const
NodeGraphManager & operator=(const NodeGraphManager &)=delete
std::unordered_map< ProcessingToken, TokenSampleProcessor > m_token_sample_processors
Per-sample processors for each processing token.
std::unordered_set< std::shared_ptr< Node > > m_node_registry
Registry of all nodes by their string identifiers.
NodeGraphManager(const NodeGraphManager &)=delete
std::unordered_set< std::shared_ptr< Network::NodeNetwork > > m_network_registry
Global network registry (like m_Node_registry)
std::unordered_map< ProcessingToken, std::unordered_map< unsigned int, std::shared_ptr< RootNode > > > m_token_roots
Multi-modal map of processing tokens to their channel root nodes.
std::unordered_map< ProcessingToken, std::vector< std::shared_ptr< Network::NodeNetwork > > > m_audio_networks
Audio-sink networks Only populated for networks with OutputMode::AUDIO_SINK.
NodeGraphManager & operator=(NodeGraphManager &&)=delete
NodeConfig m_node_config
Configuration for node creation and management.
std::unordered_map< ProcessingToken, TokenChannelProcessor > m_token_channel_processors
Per-channel processors for each processing token.
std::unordered_map< ProcessingToken, std::unique_ptr< std::atomic< bool > > > m_token_network_processing
Processing flags for each token's networks.
NodeConfig & get_node_config()
Gets the current node configuration.
Central manager for the computational processing node graph.
Container for top-level nodes in a processing channel with multi-modal support.
Definition RootNode.hpp:29
ProcessingToken
Enumerates the different processing domains for nodes.
std::function< double(RootNode *, uint32_t)> TokenSampleProcessor
std::function< std::vector< double >(RootNode *, uint32_t)> TokenChannelProcessor
Contains the node-based computational processing system components.
Definition Chronie.hpp:14
Configuration settings for individual audio nodes.
Definition NodeSpec.hpp:29