pocketstation 1.0.0

Source-aware desktop audio Session SDK
use std::sync::Arc;

use crate::frame::SampleFormat;
use crate::graph::compile::{Compiler, RuntimePlanner};
use crate::graph::{
    AudioCaps, BinaryFormat, ChannelLayout, ConfigError, ExecutionPartition, MediaCaps,
    Multiplicity, NodeConfig, NodeDefinition, NodeDescriptor, NodeRegistry, NodeTypeId, Pipeline,
    PortDirection, PortSpec, SafetyContract, SignalSpec,
};

struct Definition {
    descriptor: NodeDescriptor,
}

impl NodeDefinition for Definition {
    fn descriptor(&self) -> NodeDescriptor {
        self.descriptor.clone()
    }

    fn validate_config(&self, _config: &NodeConfig) -> Result<(), ConfigError> {
        Ok(())
    }
}

fn audio_port(name: &str, direction: PortDirection) -> PortSpec {
    PortSpec {
        name: name.to_owned(),
        direction,
        signal: SignalSpec::audio(),
        media: MediaCaps::Audio(AudioCaps {
            sample_rate_hz: Some(48_000),
            frame_samples: Some(960),
            channel_layout: ChannelLayout::Mono,
            format: SampleFormat::F32Interleaved,
        }),
        multiplicity: Multiplicity::Many,
        required: true,
    }
}

fn typed_port(name: &str, direction: PortDirection) -> PortSpec {
    PortSpec {
        name: name.to_owned(),
        direction,
        signal: SignalSpec::custom("org.example.signal-a.v1")
            .with_schema("urn:example:signal-a:v1"),
        media: MediaCaps::Binary(BinaryFormat::Raw),
        multiplicity: Multiplicity::Many,
        required: true,
    }
}

#[test]
fn given_audio_and_typed_root_outputs_when_planned_then_each_branch_has_bounded_authority() {
    let mut registry = NodeRegistry::new();
    registry
        .register_definition(Arc::new(Definition {
            descriptor: NodeDescriptor {
                type_id: NodeTypeId::from("org.example.source-a.v1"),
                display_name: "test source root",
                inputs: Vec::new(),
                outputs: vec![
                    audio_port("audio", PortDirection::Output),
                    typed_port("signal", PortDirection::Output),
                ],
                execution: ExecutionPartition::BlockingWorker,
                safety: SafetyContract::AllocationAllowed,
                stateful: true,
            },
        }))
        .unwrap();
    registry
        .register_definition(Arc::new(Definition {
            descriptor: NodeDescriptor {
                type_id: NodeTypeId::from("org.example.audio-endpoint.v1"),
                display_name: "test audio endpoint",
                inputs: vec![audio_port("audio", PortDirection::Input)],
                outputs: Vec::new(),
                execution: ExecutionPartition::External,
                safety: SafetyContract::ExternalService,
                stateful: true,
            },
        }))
        .unwrap();
    registry
        .register_definition(Arc::new(Definition {
            descriptor: NodeDescriptor {
                type_id: NodeTypeId::from("org.example.typed-endpoint.v1"),
                display_name: "test typed endpoint",
                inputs: vec![typed_port("signal", PortDirection::Input)],
                outputs: Vec::new(),
                execution: ExecutionPartition::External,
                safety: SafetyContract::ExternalService,
                stateful: true,
            },
        }))
        .unwrap();

    let mut pipeline = Pipeline::new();
    let source = pipeline.add_node("org.example.source-a.v1", NodeConfig::new());
    let audio = pipeline.add_node("org.example.audio-endpoint.v1", NodeConfig::new());
    let typed = pipeline.add_node("org.example.typed-endpoint.v1", NodeConfig::new());
    let audio_edge = pipeline.connect(source.out("audio"), audio.in_("audio"));
    let typed_edge = pipeline.connect(source.out("signal"), typed.in_("signal"));
    let graph = Compiler::new()
        .compile(pipeline.into_spec(), &registry)
        .unwrap();
    let plan = RuntimePlanner::new().plan(&graph).unwrap();

    assert_eq!(plan.source_outputs.len(), 2);
    assert!(plan
        .source_outputs
        .iter()
        .all(|output| output.branch_edges.len() == 1));
    assert!(plan
        .memory_plan
        .edge_buffer(audio_edge)
        .is_some_and(|edge| edge.capacity_frames > 0));
    assert!(plan
        .typed_edge(typed_edge)
        .is_some_and(|edge| edge.capacity_signals > 0));
}