use crate::{
LegatoApp, LegatoFrontend, LegatoMsg,
config::Config,
dsl::{
ir::{DSLParams, NodeId, Port, Value},
parse::legato_parser,
pipeline::Pipeline,
},
graph::{Connection, ConnectionEntry},
midi::{MidiRuntimeFrontend, MidiStore},
node::LegatoNode,
nodes::audio::{
delay::DelayLine,
mixer::{MonoFanOut, TrackMixer},
},
params::{ParamKey, ParamMeta},
pipes::{Pipe, PipeRegistry},
ports::Ports,
registry::{
NodeRegistry, audio_registry_factory, control_registry_factory, midi_registry_factory,
},
resources::{DelayLineKey, ResourceBuilder, SampleKey},
runtime::{NodeKey, Runtime, RuntimeFrontend, build_runtime},
sample::{AudioSampleFrontend, AudioSampleHandle},
spec::NodeSpec,
};
use arc_swap::ArcSwapOption;
use std::{
collections::HashMap,
marker::PhantomData,
sync::{Arc, atomic::AtomicU64},
};
#[derive(Clone, PartialEq, Debug)]
pub enum ValidationError {
ParseError(String),
NodeNotFound(String),
NamespaceNotFound(String),
InvalidParameter(String),
MissingRequiredParameters(String),
MissingRequiredParameter(String),
ResourceNotFound(String),
PipeNotFound(String),
}
pub struct Unconfigured;
pub struct Configured;
pub struct ContainsNodes;
pub struct Connected;
pub struct ReadyToBuild;
pub trait CanRegister {}
pub trait CanAddNode {}
pub trait CanConnect {}
pub trait CanApplyPipe {}
pub trait CanSetSink {}
pub trait CanBuild {}
pub trait CanAddMidiRuntime {}
impl CanRegister for Unconfigured {}
impl CanRegister for Configured {}
impl CanRegister for ContainsNodes {}
impl CanAddMidiRuntime for Configured {}
impl CanAddMidiRuntime for ContainsNodes {}
impl CanAddMidiRuntime for Connected {}
impl CanAddMidiRuntime for ReadyToBuild {}
impl CanAddNode for Configured {}
impl CanAddNode for ContainsNodes {}
impl CanApplyPipe for ContainsNodes {}
impl CanConnect for ContainsNodes {}
impl CanConnect for Connected {}
impl CanSetSink for ContainsNodes {}
impl CanSetSink for Connected {}
impl CanBuild for ReadyToBuild {}
pub struct DslBuilding;
impl CanRegister for DslBuilding {}
impl CanAddNode for DslBuilding {}
impl CanConnect for DslBuilding {}
impl CanApplyPipe for DslBuilding {}
impl CanSetSink for DslBuilding {}
impl CanBuild for DslBuilding {}
impl<S> LegatoBuilder<S> {
#[inline]
fn into_state<T>(self) -> LegatoBuilder<T> {
LegatoBuilder {
runtime: self.runtime,
namespaces: self.namespaces,
working_name_lookup: self.working_name_lookup,
delay_name_to_key: self.delay_name_to_key,
resource_builder: self.resource_builder,
sample_frontends: self.sample_frontends,
sample_name_to_key: self.sample_name_to_key,
pipe_lookup: self.pipe_lookup,
last_selection: self.last_selection,
midi_runtime_frontend: self.midi_runtime_frontend,
_state: PhantomData,
}
}
}
pub struct LegatoBuilder<State> {
runtime: Runtime,
namespaces: HashMap<String, NodeRegistry>,
working_name_lookup: HashMap<String, NodeKey>,
pipe_lookup: PipeRegistry,
resource_builder: ResourceBuilder,
sample_name_to_key: HashMap<String, SampleKey>,
delay_name_to_key: HashMap<String, DelayLineKey>,
sample_frontends: HashMap<String, AudioSampleFrontend>,
last_selection: Option<SelectionKind>,
midi_runtime_frontend: Option<MidiRuntimeFrontend>,
_state: PhantomData<State>,
}
impl LegatoBuilder<Unconfigured> {
pub fn new(config: Config, ports: Ports) -> LegatoBuilder<Configured> {
let mut namespaces = HashMap::new();
let audio_registry = audio_registry_factory();
let control_registry = control_registry_factory();
let midi_registry = midi_registry_factory();
namespaces.insert("audio".into(), audio_registry);
namespaces.insert("control".into(), control_registry);
namespaces.insert("midi".into(), midi_registry);
namespaces.insert("user".into(), NodeRegistry::new());
let runtime = build_runtime(config, ports);
LegatoBuilder::<Configured> {
runtime,
resource_builder: ResourceBuilder::default(),
sample_name_to_key: HashMap::new(),
delay_name_to_key: HashMap::new(),
sample_frontends: HashMap::new(),
namespaces,
working_name_lookup: HashMap::new(),
pipe_lookup: PipeRegistry::default(),
last_selection: None,
midi_runtime_frontend: None,
_state: std::marker::PhantomData,
}
}
}
impl LegatoBuilder<Configured> {
pub fn build_dsl(self, graph: &str) -> (LegatoApp, LegatoFrontend) {
let can_build = self.into_state::<DslBuilding>();
can_build._build_dsl(graph)
}
}
impl<S> LegatoBuilder<S>
where
S: CanRegister,
{
pub fn add_node_registry(mut self, name: &'static str, registry: NodeRegistry) -> Self {
self.namespaces.insert(name.into(), registry);
self
}
pub fn register_node(mut self, namespace: &'static str, spec: NodeSpec) -> Self {
match self.namespaces.get_mut(namespace) {
Some(ns) => ns.declare_node(spec),
None => panic!("Cannot find namespace {}", namespace),
}
self
}
pub fn register_pipe(mut self, name: &'static str, pipe: Box<dyn Pipe>) -> Self {
self.pipe_lookup.insert(name.into(), pipe);
self
}
}
impl<S> LegatoBuilder<S>
where
S: CanAddMidiRuntime,
{
pub fn set_midi_runtime(mut self, rt: MidiRuntimeFrontend) -> Self {
self.midi_runtime_frontend = Some(rt);
self
}
}
impl<S> LegatoBuilder<S>
where
S: CanAddNode,
{
fn _add_node_ref_self(
&mut self,
namespace: &String,
node_kind: &String,
alias: &String,
params: &DSLParams,
) {
let ns = self
.namespaces
.get(namespace)
.unwrap_or_else(|| panic!("Could not find namespace {}", namespace));
let mut resource_builder_view = ResourceBuilderView {
config: &self.runtime.get_config(),
resource_builder: &mut self.resource_builder,
sample_keys: &mut self.sample_name_to_key,
delay_keys: &mut self.delay_name_to_key,
sample_frontends: &mut self.sample_frontends,
};
let node = ns
.get_node(&mut resource_builder_view, node_kind, params)
.unwrap_or_else(|_| panic!("Could not find node {}", node_kind));
let legato_node = LegatoNode::new(alias.into(), node_kind.into(), node);
let key = self.runtime.add_node(legato_node);
self.working_name_lookup.insert(alias.clone(), key);
self.last_selection = Some(SelectionKind::Single(key));
}
pub fn add_node(
mut self,
namespace: &String,
node_kind: &String,
alias: &String,
params: &DSLParams,
) -> LegatoBuilder<ContainsNodes> {
self._add_node_ref_self(namespace, node_kind, alias, params);
self.into_state()
}
pub fn add_node_raw(mut self, node: LegatoNode, alias: &str) -> LegatoBuilder<ContainsNodes> {
let key = self.runtime.add_node(node);
self.last_selection = Some(SelectionKind::Single(key));
self.working_name_lookup.insert(alias.into(), key);
self.into_state()
}
}
impl<S> LegatoBuilder<S>
where
S: CanConnect,
{
fn _connect_ref_self(&mut self, connection: AddConnectionProps) {
let source_indicies: Vec<usize> = match connection.source_kind {
Port::None => {
let ports = self.runtime.get_node_ports(&connection.source);
ports.audio_out.iter().enumerate().map(|(i, _)| i).collect()
}
Port::Index(port) => vec![port],
Port::Named(ref port) => {
let ports = self.runtime.get_node_ports(&connection.source);
let index = ports
.audio_out
.iter()
.find(|x| x.name == port)
.unwrap_or_else(|| panic!("Could not find index for named port {}", port))
.index;
vec![index]
}
Port::Slice(start, end) => {
if end < start {
panic!("End slice cannot be less than start!");
}
(start..end).collect::<Vec<_>>()
}
Port::Stride { start, end, stride } => {
if end < start {
panic!("End slice cannot be less than start!");
}
(start..end).step_by(stride).collect()
}
};
let sink_indicies: Vec<usize> = match connection.sink_kind {
Port::None => {
let ports = self.runtime.get_node_ports(&connection.sink);
ports.audio_in.iter().enumerate().map(|(i, _)| i).collect()
}
Port::Index(port) => vec![port],
Port::Named(ref port) => {
let ports = self.runtime.get_node_ports(&connection.sink);
let index = ports
.audio_in
.iter()
.find(|x| x.name == port)
.unwrap_or_else(|| panic!("Could not find index for named port {}", port))
.index;
vec![index]
}
Port::Slice(start, end) => {
if end < start {
panic!("End slice cannot be less than start!");
}
(start..end).collect::<Vec<_>>()
}
Port::Stride { start, end, stride } => {
if end < start {
panic!("End slice cannot be less than start!");
}
(start..end).step_by(stride).collect()
}
};
let source_arity = source_indicies.len();
let sink_arity = sink_indicies.len();
match (source_arity, sink_arity) {
(1, 1) => one_to_one(
&mut self.runtime,
connection,
source_indicies[0],
sink_indicies[0],
),
(1, n) if n >= 1 => one_to_n(
&mut self.runtime,
connection,
source_indicies[0],
sink_indicies.as_slice(),
),
(n, 1) if n >= 1 => n_to_one(
&mut self.runtime,
connection,
source_indicies.as_slice(),
sink_indicies[0],
),
(n, m) if n == m => n_to_n(
&mut self.runtime,
connection,
&source_indicies,
&sink_indicies,
),
(n, m) => unimplemented!("Cannot match request arity {}:{}", n, m),
}
}
pub fn connect(mut self, connection: AddConnectionProps) -> LegatoBuilder<Connected> {
self._connect_ref_self(connection);
self.into_state()
}
}
impl<S> LegatoBuilder<S>
where
S: CanSetSink,
{
pub fn set_sink(mut self, key: NodeKey) -> LegatoBuilder<ReadyToBuild> {
self.runtime.set_sink_key(key).expect("Sink key not found");
self.into_state()
}
pub fn set_source(mut self, key: NodeKey) -> LegatoBuilder<ReadyToBuild> {
self.runtime.set_sink_key(key).expect("Sink key not found");
self.into_state()
}
}
impl<S> LegatoBuilder<S>
where
S: CanApplyPipe,
{
pub fn pipe(&mut self, pipe_name: &str, props: Option<Value>) {
match self.last_selection {
Some(_) => {
if let Ok(pipe) = self.pipe_lookup.get(pipe_name) {
if let Some(last_selection) = &self.last_selection {
let mut view = SelectionView {
runtime: &mut self.runtime,
working_name_lookup: &mut self.working_name_lookup,
selection: last_selection.clone(),
};
pipe.pipe(&mut view, props);
self.last_selection = Some(view.get_selection_owned());
} else {
panic!(
"Cannot apply pipe when there is no last_selection! Please add a node first and apply a pipe directly after."
)
}
} else {
panic!("Pipe not found {}", pipe_name);
}
}
None => panic!("Cannot apply pipe to non-existent node!"),
}
}
}
impl<S> LegatoBuilder<S>
where
S: CanBuild,
{
pub fn build(self) -> (LegatoApp, LegatoFrontend) {
let mut runtime = self.runtime;
let (resources, param_store_frontend) = self.resource_builder.build();
runtime.set_resources(resources);
runtime.prepare();
if self.midi_runtime_frontend.is_some() {
let ctx = runtime.get_context_mut();
ctx.set_midi_store(MidiStore::new(256));
}
let queue = Box::leak(Box::new(heapless::spsc::Queue::<LegatoMsg, 512>::new()));
let (producer, consumer) = queue.split();
let mut app = LegatoApp::new(runtime, consumer);
if let Some(midi_rt) = self.midi_runtime_frontend {
app.set_midi_runtime(midi_rt);
}
let rt_frontend = RuntimeFrontend::new(self.sample_frontends);
let frontend = LegatoFrontend::new(rt_frontend, param_store_frontend, producer);
(app, frontend)
}
}
impl LegatoBuilder<DslBuilding> {
fn _build_dsl(mut self, content: &str) -> (LegatoApp, LegatoFrontend) {
let ast = legato_parser(content).unwrap();
let ir = Pipeline::default().run_from_ast(ast);
println!("{}", &ir);
debug_assert!(
!ir.has_unresolved_macros(),
"_build_dsl: unresolved MacroRef nodes remain after pipeline"
);
let mut ir_to_runtime: HashMap<NodeId, NodeKey> = HashMap::new();
for node_id in ir.topological_sort() {
let node = ir.get_node(node_id).unwrap();
let dsl_params = DSLParams::new(&node.params);
self._add_node_ref_self(&node.namespace, &node.node_type, &node.alias, &dsl_params);
let runtime_key = *self
.working_name_lookup
.get(&node.alias)
.expect("alias must be in lookup immediately after _add_node_ref_self");
ir_to_runtime.insert(node_id, runtime_key);
for pipe in &node.pipes {
self.pipe(&pipe.name, pipe.params.clone());
}
}
for edge in ir.edges() {
self._connect_ref_self(AddConnectionProps {
source: ir_to_runtime[&edge.source],
source_kind: edge.source_port.clone(),
sink: ir_to_runtime[&edge.sink],
sink_kind: edge.sink_port.clone(),
});
}
let sink_id = ir
.sink
.expect("IRGraph has no sink — check the DSL for a `sink:` declaration");
self.runtime
.set_sink_key(ir_to_runtime[&sink_id])
.expect("Could not set sink");
if let Some(source_id) = ir.source {
self.runtime
.set_source_key(ir_to_runtime[&source_id])
.expect("Could not set runtime source");
}
self.build()
}
}
#[derive(Clone, Debug)]
pub enum NodeViewKind {
Single(LegatoNode),
Multiple(Vec<LegatoNode>),
}
#[derive(Clone, PartialEq, Debug)]
pub enum SelectionKind {
Single(NodeKey),
Multiple(Vec<NodeKey>),
}
#[derive(Debug)]
pub struct SelectionView<'a> {
runtime: &'a mut Runtime,
working_name_lookup: &'a mut HashMap<String, NodeKey>,
selection: SelectionKind,
}
impl<'a> SelectionView<'a> {
pub fn new(
runtime: &'a mut Runtime,
working_name_lookup: &'a mut HashMap<String, NodeKey>,
selection: SelectionKind,
) -> Self {
Self {
runtime,
working_name_lookup,
selection,
}
}
pub fn insert(&mut self, node: LegatoNode) {
let working_name = node.name.clone();
let key = self.runtime.add_node(node);
self.working_name_lookup.insert(working_name, key);
match &mut self.selection {
SelectionKind::Single(old_node) => {
self.selection = SelectionKind::Multiple(vec![*old_node, key])
}
SelectionKind::Multiple(nodes) => nodes.push(key),
}
}
pub fn selection(&self) -> &SelectionKind {
&self.selection
}
pub fn config(&self) -> Config {
self.runtime.get_config()
}
pub fn replace(&mut self, key: NodeKey, node: LegatoNode) {
let working_name = node.name.clone();
self.runtime.replace_node(key, node);
if let Some((old_key, _)) = self.working_name_lookup.iter().find(|(_, nk)| **nk == key) {
self.working_name_lookup.remove(&old_key.clone());
self.working_name_lookup.insert(working_name, key);
}
}
pub fn get_node_mut(&mut self, key: &NodeKey) -> Option<&mut LegatoNode> {
self.runtime.get_node_mut(key)
}
pub fn get_node(&self, key: &NodeKey) -> Option<&LegatoNode> {
self.runtime.get_node(key)
}
pub fn get_key(&mut self, name: &'static str) -> Option<NodeKey> {
self.working_name_lookup.get(name).copied()
}
pub fn delete(&mut self, key: NodeKey) {
self.runtime.remove_node(key);
if let Some((old_key, _)) = self.working_name_lookup.iter().find(|(_, nk)| **nk == key) {
self.working_name_lookup.remove(&old_key.clone());
}
}
pub fn clone_node(&mut self, key: NodeKey) -> Option<LegatoNode> {
self.runtime.get_node(&key).cloned()
}
pub fn get_selection_owned(self) -> SelectionKind {
self.selection
}
}
pub struct ResourceBuilderView<'a> {
pub config: &'a Config,
pub resource_builder: &'a mut ResourceBuilder,
pub sample_keys: &'a mut HashMap<String, SampleKey>,
pub delay_keys: &'a mut HashMap<String, DelayLineKey>,
pub sample_frontends: &'a mut HashMap<String, AudioSampleFrontend>,
}
impl<'a> ResourceBuilderView<'a> {
pub fn add_delay_line(&mut self, name: &str, delay_line: DelayLine) -> DelayLineKey {
let key = self.resource_builder.add_delay_line(delay_line);
self.delay_keys.insert(name.to_string(), key);
key
}
pub fn replace_delay_line(&mut self, key: DelayLineKey, delay_line: DelayLine) {
self.resource_builder.replace_delay_line(key, delay_line);
}
pub fn get_delay_line_key(&self, name: &String) -> Option<DelayLineKey> {
self.delay_keys.get(name).cloned()
}
pub fn add_sampler(&mut self, name: &String) -> SampleKey {
if let Some(&key) = self.sample_keys.get(name) {
key
} else {
let data = ArcSwapOption::new(None);
let handle = Arc::new(AudioSampleHandle {
sample: data,
sample_version: AtomicU64::new(0),
});
let frontend = AudioSampleFrontend::new(handle.clone());
self.sample_frontends.insert(name.clone(), frontend);
self.resource_builder.add_sample_resource(handle)
}
}
pub fn add_param(&mut self, unique_name: String, meta: ParamMeta) -> ParamKey {
self.resource_builder.add_param(unique_name, meta)
}
pub fn get_sampler_key(&self, name: &String) -> Result<SampleKey, ValidationError> {
self.sample_keys.get(name).cloned().ok_or_else(|| {
ValidationError::ResourceNotFound(format!("Could not find sample key {}", name))
})
}
pub fn get_config(&self) -> &Config {
self.config
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct AddConnectionProps {
pub source: NodeKey,
pub source_kind: Port,
pub sink: NodeKey,
pub sink_kind: Port,
}
pub enum AddConnectionKind {
Index(usize),
Named(&'static str),
Auto,
}
fn one_to_one(
runtime: &mut Runtime,
props: AddConnectionProps,
source_index: usize,
sink_index: usize,
) {
runtime
.add_edge(Connection {
source: ConnectionEntry {
node_key: props.source,
port_index: source_index,
},
sink: ConnectionEntry {
node_key: props.sink,
port_index: sink_index,
},
})
.expect("Could not add edge");
}
fn one_to_n(
runtime: &mut Runtime,
props: AddConnectionProps,
source_index: usize,
sink_indicies: &[usize],
) {
let n = sink_indicies.len();
let mixer = runtime.add_node(LegatoNode::new(
format!("MonoFanOut{:?}{:?}", props.source, props.sink),
"MonoFanOut".into(),
Box::new(MonoFanOut::new(n)),
));
runtime
.add_edge(Connection {
source: ConnectionEntry {
node_key: props.source,
port_index: source_index,
},
sink: ConnectionEntry {
node_key: mixer,
port_index: 0,
},
})
.expect("Could not add edge");
for sink_index in sink_indicies.iter() {
runtime
.add_edge(Connection {
source: ConnectionEntry {
node_key: mixer,
port_index: 0,
},
sink: ConnectionEntry {
node_key: props.sink,
port_index: *sink_index,
},
})
.expect("Could not add edge");
}
}
fn n_to_one(
runtime: &mut Runtime,
props: AddConnectionProps,
source_indicies: &[usize],
sink_index: usize,
) {
let n = source_indicies.len();
let mixer = runtime.add_node(LegatoNode::new(
format!("TrackMixer{:?}{:?}", props.source, props.sink),
"TrackMixer".into(),
Box::new(TrackMixer::new(1, n, vec![1.0 / f32::sqrt(n as f32); n])),
));
for (i, source_index) in source_indicies.iter().enumerate() {
runtime
.add_edge(Connection {
source: ConnectionEntry {
node_key: props.source,
port_index: *source_index,
},
sink: ConnectionEntry {
node_key: mixer,
port_index: i,
},
})
.expect("Could not add edge");
}
runtime
.add_edge(Connection {
source: ConnectionEntry {
node_key: mixer,
port_index: 0,
},
sink: ConnectionEntry {
node_key: props.sink,
port_index: sink_index,
},
})
.expect("Could not add edge");
}
fn n_to_n(
runtime: &mut Runtime,
props: AddConnectionProps,
source_indicies: &[usize],
sink_indicies: &[usize],
) {
assert!(source_indicies.len() == sink_indicies.len());
source_indicies
.iter()
.zip(sink_indicies)
.for_each(|(source, sink)| {
runtime
.add_edge(Connection {
source: ConnectionEntry {
node_key: props.source,
port_index: *source,
},
sink: ConnectionEntry {
node_key: props.sink,
port_index: *sink,
},
})
.expect("Could not add edge");
});
}