knyst/graph/run_graph.rs
1//! Provides the [`RunGraph`] struct which safely wraps a running [`Graph`] and
2//! provides access to its outputs. Used internally by implementations of
3//! `AudioBackend`, but it can also be used directly for custom environments or
4//! for offline processing.
5
6use std::{
7 sync::{Arc, RwLock},
8 time::{Duration, Instant},
9};
10
11#[allow(unused)]
12use crate::controller::Controller;
13#[allow(unused)]
14use crate::controller::KnystCommands;
15#[allow(unused)]
16use crate::graph::Connection;
17use crate::resources::{ResourcesCommand, ResourcesResponse};
18use rtrb::RingBuffer;
19
20use crate::{scheduling::MusicalTimeMap, Resources};
21
22use super::{node::Node, Graph, NodeBufferRef, NodeId, Sample};
23
24/// Wrapper around a [`Graph`] `Node` with convenience methods to run the
25/// Graph, either from an audio thread or for non-real time purposes.
26pub struct RunGraph {
27 graph_node: Node,
28 graph_sample_rate: Sample,
29 resources: Resources,
30 input_buffer_ptr: *mut Sample,
31 input_buffer_length: usize,
32 input_node_buffer_ref: NodeBufferRef,
33 output_node_buffer_ref: NodeBufferRef,
34 resources_command_receiver: rtrb::Consumer<ResourcesCommand>,
35 resources_response_sender: rtrb::Producer<ResourcesResponse>,
36}
37
38impl RunGraph {
39 /// Prepare the necessary resources for running the graph. This will fail if
40 /// the Graph passed in has already been turned into a Node somewhere else,
41 /// for example if it has been pushed to a different Graph.
42 ///
43 /// Returns Self as well as ring buffer channels to apply changes to
44 /// Resources.
45 pub fn new(
46 graph: &mut Graph,
47 resources: Resources,
48 settings: RunGraphSettings,
49 ) -> Result<
50 (
51 Self,
52 rtrb::Producer<ResourcesCommand>,
53 rtrb::Consumer<ResourcesResponse>,
54 ),
55 RunGraphError,
56 > {
57 // Create a new dummy NodeId since it doesn't matter; we know
58 // that the Graph will not do anything with its NodeId in its init function
59 // and the top level graph doesn't have a NodeId anyway.
60 match graph.split_and_create_top_level_node(NodeId::new(u64::MAX)) {
61 Ok(graph_node) => {
62 let input_buffer_length = graph_node.num_inputs() * graph_node.block_size;
63 let input_buffer_ptr = if input_buffer_length != 0 {
64 let input_buffer = vec![0.0 as Sample; input_buffer_length].into_boxed_slice();
65 let input_buffer = Box::into_raw(input_buffer);
66 // Safety: we just created the slice of non-zero length
67 input_buffer.cast::<Sample>()
68 } else {
69 std::ptr::null_mut()
70 };
71 let input_node_buffer_ref = NodeBufferRef::new(
72 input_buffer_ptr,
73 graph_node.num_inputs(),
74 graph_node.block_size,
75 );
76 let graph_sample_rate = graph.sample_rate;
77 let musical_time_map = Arc::new(RwLock::new(MusicalTimeMap::new()));
78 // TODO: Start the scheduler of the Graph
79 // Safety: The Nodes get initiated and their buffers allocated
80 // when the Graph is split and the Node is created from it.
81 // Therefore, we can safely store a reference to a buffer in
82 // that Node here. We want to store it to be able to return a
83 // reference to it instead of an owned value which includes a
84 // raw pointer.
85 let output_node_buffer_ref = graph_node.output_buffers();
86
87 let scheduler_start_time_stamp = Instant::now();
88 graph.start_scheduler(
89 settings.scheduling_latency,
90 scheduler_start_time_stamp,
91 &None,
92 &musical_time_map,
93 );
94 // Run a first update to make sure any queued changes get sent to the GraphGen
95 graph.update();
96 // Create ring buffer channels for communicating with Resources
97 let (resources_command_sender, resources_command_receiver) = RingBuffer::new(50);
98 let (resources_response_sender, resources_response_receiver) = RingBuffer::new(50);
99 Ok((
100 Self {
101 graph_node,
102 graph_sample_rate,
103 resources,
104 input_buffer_length,
105 input_buffer_ptr,
106 input_node_buffer_ref,
107 output_node_buffer_ref,
108 resources_command_receiver,
109 resources_response_sender,
110 },
111 resources_command_sender,
112 resources_response_receiver,
113 ))
114 }
115 Err(e) => Err(RunGraphError::CouldNodeCreateNode(e)),
116 }
117 }
118 /// Returns the input buffer that will be read as the input of the main
119 /// graph. For example, you may want to fill this with the sound inputs of
120 /// the sound card. The buffer does not get emptied automatically. If you
121 /// don't change it between calls to [`RunGraph::process_block`], its content will be static.
122 #[inline]
123 pub fn graph_input_buffers(&mut self) -> &mut NodeBufferRef {
124 &mut self.input_node_buffer_ref
125 }
126 /// Receive and apply any commands to modify the [`Resources`]. Commands are
127 /// normally sent by a [`KnystCommands`] via a [`Controller`].
128 pub fn run_resources_communication(&mut self, max_commands_to_process: usize) {
129 let mut i = 0;
130 while let Ok(command) = self.resources_command_receiver.pop() {
131 if let Err(e) = self
132 .resources_response_sender
133 .push(self.resources.apply_command(command))
134 {
135 eprintln!(
136 "Warning: A ResourcesResponse could not be sent back from the RunGraph. This may lead to dropping on the audio thread. {e}"
137 );
138 }
139 i += 1;
140 if i >= max_commands_to_process {
141 break;
142 }
143 }
144 }
145 /// Run the Graph for one block using the inputs currently stored in the
146 /// input buffer. The results can be accessed through the output buffer
147 /// through [`RunGraph::graph_output_buffers`].
148 pub fn process_block(&mut self) {
149 self.graph_node.process(
150 &self.input_node_buffer_ref,
151 self.graph_sample_rate,
152 &mut self.resources,
153 );
154 }
155 /// Return a reference to the buffer holding the output of the [`Graph`].
156 /// Channels which have no [`Connection`]/graph edge to them will be 0.0.
157 pub fn graph_output_buffers(&self) -> &NodeBufferRef {
158 &self.output_node_buffer_ref
159 }
160 /// Return a reference to the buffer holding the output of the [`Graph`].
161 /// Channels which have no [`Connection`]/graph edge to them will be 0.0.
162 pub fn graph_output_buffers_mut(&mut self) -> &mut NodeBufferRef {
163 &mut self.output_node_buffer_ref
164 }
165 /// Returns the block size of the resulting output and input buffer(s) of
166 /// the top level [`Graph`].
167 pub fn block_size(&self) -> usize {
168 self.output_node_buffer_ref.block_size()
169 }
170}
171
172impl Drop for RunGraph {
173 fn drop(&mut self) {
174 // Safety: The slice is allocated when the RunGraph is created with the
175 // given size, unless that size is 0 in which case no allocation is
176 // made.
177 if !self.input_buffer_ptr.is_null() {
178 unsafe {
179 drop(Box::from_raw(std::slice::from_raw_parts_mut(
180 self.input_buffer_ptr,
181 self.input_buffer_length,
182 )));
183 }
184 }
185 }
186}
187unsafe impl Send for RunGraph {}
188
189#[allow(missing_docs)]
190#[derive(thiserror::Error, Debug, PartialEq)]
191pub enum RunGraphError {
192 #[error("Unable to create a node from the Graph: {0}")]
193 CouldNodeCreateNode(String),
194}
195
196/// Settings for a [`RunGraph`]
197pub struct RunGraphSettings {
198 /// How much time is added to every *relative* scheduling event to ensure the Change has time to travel to the GraphGen.
199 pub scheduling_latency: Duration,
200}
201impl Default for RunGraphSettings {
202 fn default() -> Self {
203 Self {
204 scheduling_latency: Duration::from_millis(50),
205 }
206 }
207}