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