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}