///|
pub(all) suberror GraphTemplateDocError {
LengthMismatch(Int, Int)
DuplicateNodeId(String)
MissingNodeId(String)
RetiredNodeId(String)
Topology(GraphTopologyEditError)
} derive(Debug)
///|
pub impl Show for GraphTemplateDocError with output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
/// Identity-bearing authoring wrapper for graph templates.
///
/// Runtime graph compilation remains index-based. `GraphTemplateDoc` owns the
/// stable authoring IDs and exposes `GraphIndexMap` at the boundary where
/// controls, bindings, and topology edits are translated to existing indices.
pub struct GraphTemplateDoc {
priv revision : @identity.Revision
priv ids : Array[@identity.GraphNodeId]
priv nodes : Array[DspNode]
priv retired_ids : Map[String, Bool]
}
///|
pub struct GraphIndexMap {
priv revision : @identity.Revision
priv ids : Array[@identity.GraphNodeId]
priv indices : Map[String, Int]
}
///|
fn copy_retired_graph_ids(source : Map[String, Bool]) -> Map[String, Bool] {
let result : Map[String, Bool] = {}
source.each(fn(id, retired) { result[id] = retired })
result
}
///|
fn find_graph_id_index(
ids : Array[@identity.GraphNodeId],
id : @identity.GraphNodeId,
) -> Int? {
for index in 0.. Unit raise GraphTemplateDocError {
if ids.length() != expected_length {
raise LengthMismatch(ids.length(), expected_length)
}
let seen : Map[String, Bool] = {}
for id in ids {
let key = id.value()
if seen.contains(key) {
raise DuplicateNodeId(key)
}
seen[key] = true
}
}
///|
fn validate_new_graph_id(
current_ids : Array[@identity.GraphNodeId],
retired_ids : Map[String, Bool],
id : @identity.GraphNodeId,
) -> Unit raise GraphTemplateDocError {
let key = id.value()
if find_graph_id_index(current_ids, id) is Some(_) {
raise DuplicateNodeId(key)
}
if retired_ids.contains(key) {
raise RetiredNodeId(key)
}
}
///|
fn validate_new_graph_ids(
current_ids : Array[@identity.GraphNodeId],
retired_ids : Map[String, Bool],
ids : Array[@identity.GraphNodeId],
) -> Unit raise GraphTemplateDocError {
let seen : Map[String, Bool] = {}
for id in ids {
let key = id.value()
if seen.contains(key) {
raise DuplicateNodeId(key)
}
seen[key] = true
validate_new_graph_id(current_ids, retired_ids, id)
}
}
///|
fn graph_id_index_or_raise(
ids : Array[@identity.GraphNodeId],
id : @identity.GraphNodeId,
) -> Int raise GraphTemplateDocError {
match find_graph_id_index(ids, id) {
Some(index) => index
None => raise MissingNodeId(id.value())
}
}
///|
fn apply_graph_template_edit(
nodes : Array[DspNode],
edit : GraphTopologyEdit,
) -> Unit raise GraphTemplateDocError {
match apply_topology_edit_result(nodes, edit) {
Ok(_) => ()
Err(error) => raise Topology(error)
}
}
///|
fn compact_graph_ids_after_delete(
ids : Array[@identity.GraphNodeId],
start_index : Int,
end_index : Int,
) -> Array[@identity.GraphNodeId] {
let result : Array[@identity.GraphNodeId] = []
for index in 0.. end_index {
result.push(ids[index])
}
}
result
}
///|
fn retire_graph_id_range(
retired_ids : Map[String, Bool],
ids : Array[@identity.GraphNodeId],
start_index : Int,
end_index : Int,
) -> Unit {
for index = start_index; index <= end_index; index = index + 1 {
retired_ids[ids[index].value()] = true
}
}
///|
pub fn GraphTemplateDoc::from_nodes(
ids~ : Array[@identity.GraphNodeId],
nodes~ : Array[DspNode],
) -> GraphTemplateDoc raise GraphTemplateDocError {
validate_graph_template_ids(ids, nodes.length())
{
revision: @identity.Revision::zero(),
ids: ids.copy(),
nodes: nodes.copy(),
retired_ids: {},
}
}
///|
pub fn GraphTemplateDoc::revision(
self : GraphTemplateDoc,
) -> @identity.Revision {
self.revision
}
///|
pub fn GraphTemplateDoc::length(self : GraphTemplateDoc) -> Int {
self.nodes.length()
}
///|
pub fn GraphTemplateDoc::nodes(self : GraphTemplateDoc) -> Array[DspNode] {
self.nodes.copy()
}
///|
pub fn GraphTemplateDoc::node_index(
self : GraphTemplateDoc,
id : @identity.GraphNodeId,
) -> Int? {
find_graph_id_index(self.ids, id)
}
///|
pub fn GraphTemplateDoc::node_id_at(
self : GraphTemplateDoc,
index : Int,
) -> @identity.GraphNodeId? {
if index < 0 || index >= self.ids.length() {
None
} else {
Some(self.ids[index])
}
}
///|
pub fn GraphTemplateDoc::node_at(
self : GraphTemplateDoc,
id : @identity.GraphNodeId,
) -> DspNode? {
match self.node_index(id) {
Some(index) => Some(self.nodes[index])
None => None
}
}
///|
pub fn GraphTemplateDoc::contains_node(
self : GraphTemplateDoc,
id : @identity.GraphNodeId,
) -> Bool {
self.node_index(id) is Some(_)
}
///|
pub fn GraphTemplateDoc::contains_retired_node(
self : GraphTemplateDoc,
id : @identity.GraphNodeId,
) -> Bool {
self.retired_ids.contains(id.value())
}
///|
pub fn GraphTemplateDoc::index_map(self : GraphTemplateDoc) -> GraphIndexMap {
let indices : Map[String, Int] = {}
for index in 0.. CompiledTemplate {
CompiledTemplate::analyze(self.nodes)
}
///|
pub fn GraphTemplateDoc::compile(
self : GraphTemplateDoc,
context : DspContext,
) -> CompiledDsp? {
CompiledDsp::compile(self.analyze(), context)
}
///|
pub fn GraphTemplateDoc::compile_stereo(
self : GraphTemplateDoc,
context : DspContext,
) -> CompiledStereoDsp? {
CompiledStereoDsp::compile(self.analyze(), context)
}
///|
pub fn GraphTemplateDoc::replace_node(
self : GraphTemplateDoc,
id : @identity.GraphNodeId,
replacement : DspNode,
) -> GraphTemplateDoc raise GraphTemplateDocError {
let index = graph_id_index_or_raise(self.ids, id)
let nodes = self.nodes.copy()
nodes[index] = replacement
{
revision: self.revision.next(),
ids: self.ids.copy(),
nodes,
retired_ids: copy_retired_graph_ids(self.retired_ids),
}
}
///|
pub fn GraphTemplateDoc::rewire_input(
self : GraphTemplateDoc,
id : @identity.GraphNodeId,
input_slot : GraphTopologyInputSlot,
source_id : @identity.GraphNodeId,
) -> GraphTemplateDoc raise GraphTemplateDocError {
let index = graph_id_index_or_raise(self.ids, id)
let source_index = graph_id_index_or_raise(self.ids, source_id)
let nodes = self.nodes.copy()
apply_graph_template_edit(
nodes,
GraphTopologyEdit::rewire_input(index, input_slot, source_index),
)
{
revision: self.revision.next(),
ids: self.ids.copy(),
nodes,
retired_ids: copy_retired_graph_ids(self.retired_ids),
}
}
///|
pub fn GraphTemplateDoc::insert_node(
self : GraphTemplateDoc,
inserted_id : @identity.GraphNodeId,
retarget_id : @identity.GraphNodeId,
input_slot : GraphTopologyInputSlot,
inserted_node : DspNode,
) -> GraphTemplateDoc raise GraphTemplateDocError {
validate_new_graph_id(self.ids, self.retired_ids, inserted_id)
let retarget_index = graph_id_index_or_raise(self.ids, retarget_id)
let nodes = self.nodes.copy()
apply_graph_template_edit(
nodes,
GraphTopologyEdit::insert_node(retarget_index, input_slot, inserted_node),
)
let ids = self.ids.copy()
ids.push(inserted_id)
{
revision: self.revision.next(),
ids,
nodes,
retired_ids: copy_retired_graph_ids(self.retired_ids),
}
}
///|
pub fn GraphTemplateDoc::insert_chain(
self : GraphTemplateDoc,
inserted_ids : Array[@identity.GraphNodeId],
retarget_id : @identity.GraphNodeId,
input_slot : GraphTopologyInputSlot,
chain_nodes : Array[DspNode],
) -> GraphTemplateDoc raise GraphTemplateDocError {
validate_new_graph_ids(self.ids, self.retired_ids, inserted_ids)
if inserted_ids.length() != chain_nodes.length() {
raise LengthMismatch(inserted_ids.length(), chain_nodes.length())
}
let retarget_index = graph_id_index_or_raise(self.ids, retarget_id)
let nodes = self.nodes.copy()
apply_graph_template_edit(
nodes,
GraphTopologyEdit::insert_chain(retarget_index, input_slot, chain_nodes),
)
let ids = self.ids.copy()
for id in inserted_ids {
ids.push(id)
}
{
revision: self.revision.next(),
ids,
nodes,
retired_ids: copy_retired_graph_ids(self.retired_ids),
}
}
///|
pub fn GraphTemplateDoc::delete_node(
self : GraphTemplateDoc,
delete_id : @identity.GraphNodeId,
retarget_id : @identity.GraphNodeId,
input_slot : GraphTopologyInputSlot,
replacement_source_id : @identity.GraphNodeId,
) -> GraphTemplateDoc raise GraphTemplateDocError {
let delete_index = graph_id_index_or_raise(self.ids, delete_id)
let retarget_index = graph_id_index_or_raise(self.ids, retarget_id)
let replacement_source_index = graph_id_index_or_raise(
self.ids,
replacement_source_id,
)
let nodes = self.nodes.copy()
apply_graph_template_edit(
nodes,
GraphTopologyEdit::delete_node(
delete_index, retarget_index, input_slot, replacement_source_index,
),
)
let retired_ids = copy_retired_graph_ids(self.retired_ids)
retire_graph_id_range(retired_ids, self.ids, delete_index, delete_index)
{
revision: self.revision.next(),
ids: compact_graph_ids_after_delete(self.ids, delete_index, delete_index),
nodes,
retired_ids,
}
}
///|
pub fn GraphTemplateDoc::delete_chain(
self : GraphTemplateDoc,
start_id : @identity.GraphNodeId,
end_id : @identity.GraphNodeId,
retarget_id : @identity.GraphNodeId,
input_slot : GraphTopologyInputSlot,
replacement_source_id : @identity.GraphNodeId,
) -> GraphTemplateDoc raise GraphTemplateDocError {
let start_index = graph_id_index_or_raise(self.ids, start_id)
let end_index = graph_id_index_or_raise(self.ids, end_id)
let retarget_index = graph_id_index_or_raise(self.ids, retarget_id)
let replacement_source_index = graph_id_index_or_raise(
self.ids,
replacement_source_id,
)
let nodes = self.nodes.copy()
apply_graph_template_edit(
nodes,
GraphTopologyEdit::delete_chain(
start_index, end_index, retarget_index, input_slot, replacement_source_index,
),
)
let retired_ids = copy_retired_graph_ids(self.retired_ids)
retire_graph_id_range(retired_ids, self.ids, start_index, end_index)
{
revision: self.revision.next(),
ids: compact_graph_ids_after_delete(self.ids, start_index, end_index),
nodes,
retired_ids,
}
}
///|
pub fn GraphIndexMap::revision(self : GraphIndexMap) -> @identity.Revision {
self.revision
}
///|
pub fn GraphIndexMap::length(self : GraphIndexMap) -> Int {
self.ids.length()
}
///|
pub fn GraphIndexMap::node_index(
self : GraphIndexMap,
id : @identity.GraphNodeId,
) -> Int? {
self.indices.get(id.value())
}
///|
pub fn GraphIndexMap::node_id_at(
self : GraphIndexMap,
index : Int,
) -> @identity.GraphNodeId? {
if index < 0 || index >= self.ids.length() {
None
} else {
Some(self.ids[index])
}
}
///|
pub fn GraphIndexMap::gate_on(
self : GraphIndexMap,
id : @identity.GraphNodeId,
) -> GraphControl? {
self.node_index(id).map(fn(index) { GraphControl::gate_on(index) })
}
///|
pub fn GraphIndexMap::gate_off(
self : GraphIndexMap,
id : @identity.GraphNodeId,
) -> GraphControl? {
self.node_index(id).map(fn(index) { GraphControl::gate_off(index) })
}
///|
pub fn GraphIndexMap::set_param(
self : GraphIndexMap,
id : @identity.GraphNodeId,
slot : GraphParamSlot,
value : Double,
) -> GraphControl? {
self
.node_index(id)
.map(fn(index) { GraphControl::set_param(index, slot, value) })
}
///|
pub fn GraphIndexMap::bind_control(
self : GraphIndexMap,
builder : ControlBindingBuilder,
key : String,
id : @identity.GraphNodeId,
slot : GraphParamSlot,
) -> ControlBindingBuilder? {
self
.node_index(id)
.map(fn(index) { builder.bind(key~, node_index=index, slot~) })
}
///|
pub fn GraphIndexMap::replace_node(
self : GraphIndexMap,
id : @identity.GraphNodeId,
replacement : DspNode,
) -> GraphTopologyEdit? {
self
.node_index(id)
.map(fn(index) { GraphTopologyEdit::replace_node(index, replacement) })
}
///|
pub fn GraphIndexMap::rewire_input(
self : GraphIndexMap,
id : @identity.GraphNodeId,
input_slot : GraphTopologyInputSlot,
source_id : @identity.GraphNodeId,
) -> GraphTopologyEdit? {
match (self.node_index(id), self.node_index(source_id)) {
(Some(index), Some(source_index)) =>
Some(GraphTopologyEdit::rewire_input(index, input_slot, source_index))
_ => None
}
}
///|
pub fn GraphIndexMap::insert_node(
self : GraphIndexMap,
retarget_id : @identity.GraphNodeId,
input_slot : GraphTopologyInputSlot,
inserted_node : DspNode,
) -> GraphTopologyEdit? {
self
.node_index(retarget_id)
.map(fn(index) {
GraphTopologyEdit::insert_node(index, input_slot, inserted_node)
})
}
///|
pub fn GraphIndexMap::delete_node(
self : GraphIndexMap,
delete_id : @identity.GraphNodeId,
retarget_id : @identity.GraphNodeId,
input_slot : GraphTopologyInputSlot,
replacement_source_id : @identity.GraphNodeId,
) -> GraphTopologyEdit? {
match
(
self.node_index(delete_id),
self.node_index(retarget_id),
self.node_index(replacement_source_id),
) {
(Some(delete_index), Some(retarget_index), Some(replacement_source_index)) =>
Some(
GraphTopologyEdit::delete_node(
delete_index, retarget_index, input_slot, replacement_source_index,
),
)
_ => None
}
}
///|
pub fn GraphIndexMap::insert_chain(
self : GraphIndexMap,
retarget_id : @identity.GraphNodeId,
input_slot : GraphTopologyInputSlot,
chain_nodes : Array[DspNode],
) -> GraphTopologyEdit? {
self
.node_index(retarget_id)
.map(fn(index) {
GraphTopologyEdit::insert_chain(index, input_slot, chain_nodes)
})
}
///|
pub fn GraphIndexMap::delete_chain(
self : GraphIndexMap,
start_id : @identity.GraphNodeId,
end_id : @identity.GraphNodeId,
retarget_id : @identity.GraphNodeId,
input_slot : GraphTopologyInputSlot,
replacement_source_id : @identity.GraphNodeId,
) -> GraphTopologyEdit? {
match
(
self.node_index(start_id),
self.node_index(end_id),
self.node_index(retarget_id),
self.node_index(replacement_source_id),
) {
(
Some(start_index),
Some(end_index),
Some(retarget_index),
Some(replacement_source_index),
) =>
Some(
GraphTopologyEdit::delete_chain(
start_index, end_index, retarget_index, input_slot, replacement_source_index,
),
)
_ => None
}
}