Graph Framework

Real-time graph-based media processing

Graph Framework

Graph-based processing infrastructure for real-time audio and video. Provides directed-graph topology, automatic scheduling, and zero-allocation execution. Originally developed in libbahn (music tracker), generalized into libmediacore for all media consumers.


Location

src/libs/libmediacore/mediacore/graph/
  Graph.hpp / .cpp
  GraphNode.hpp / .cpp
  Connection.hpp / .cpp
  Buffer.hpp / .cpp
  BufferPool.hpp / .cpp
  ProcessorCharacter.hpp / .cpp
  Schedule.hpp / .cpp
  ScheduleFactory.hpp / .cpp
  Job.hpp / .cpp

Architecture

The framework has three layers:

+---------------------------------------------------------------------+
|                              GRAPH LAYER                            |
|  Graph -> GraphNode -> Connection                                   |
|  - DFS-based cycle detection                                        |
|  - Topology change tracking                                         |
|  - Spatial queries (lasso selection)                                |
+---------------------------------------------------------------------+
                                |
                                v (on topology change)
+---------------------------------------------------------------------+
|                          COMPILATION LAYER                           |
|  ScheduleFactory -> Schedule -> Job[]                               |
|  - BFS traversal for job ordering                                   |
|  - Buffer allocation via "Dowry" pattern                            |
|  - Zero-copy buffer inheritance                                     |
+---------------------------------------------------------------------+
                                |
                                v (during playback)
+---------------------------------------------------------------------+
|                          EXECUTION LAYER                             |
|  Engine.process() -> Schedule.process() -> Job.execute()            |
|  - Single contiguous memory block                                   |
|  - 32-byte aligned buffers (SIMD-ready)                             |
|  - No allocation during processing                                  |
+---------------------------------------------------------------------+

Graph layer

Graph

Container for nodes and connections. Tracks topology changes and provides queries.

Method Description
addNode(GraphNode*) Add a processing node
removeNode(GraphNode*) Remove a processing node
connect(output, input) Create a connection between nodes
disconnect(Connection*) Remove a connection
topologyChanged() Returns true if graph was modified since last check
nodesInRect(QRectF) Spatial query for UI lasso selection

GraphNode

Base class for all processing nodes. Each node declares its input/output character via ProcessorCharacter.

Method Description
character() Returns input/output capabilities
inputConnections() List of incoming connections
outputConnections() List of outgoing connections
position() Node position for graph editor UI

ProcessorCharacter

Declares a node's role in the graph.

Property Description
takesInput Node accepts input connections
givesOutput Node produces output connections

Derived convenience methods:

Method Meaning
isSource() Output only (no input)
isSink() Input only (no output)
isProcessor() Both input and output
isIsolated() Neither input nor output

Connection

Directed edge between two nodes. The graph layer validates that connections don't create cycles using DFS traversal.


Compilation layer

ScheduleFactory

Compiles a Graph into an executable Schedule by:

  1. BFS traversal from source nodes to determine processing order
  2. Buffer allocation using the "Dowry" pattern -- each node receives pre-allocated buffer slots
  3. Zero-copy buffer inheritance where possible (output buffer of one node reused as input of the next)

Schedule

An ordered list of Job entries backed by a single contiguous memory block.

Method Description
process() Execute all jobs in order
resizeMemory(count, alignment) Allocate the contiguous buffer
jobCount() Number of jobs in the schedule

Job

A single processing step: a node reference plus its buffer assignment.

Field Description
node The GraphNode to process
type Source, processor, or sink
buffer Pointer into the contiguous memory block

Execution layer

Memory layout

All buffers are allocated as a single contiguous block with 32-byte alignment:

static const size_t BUFFER_ALIGNMENT_BYTES{32};

32-byte alignment enables AVX/AVX2 SIMD operations. Misaligned access causes 2-10x slowdown on x86.

Zero-allocation guarantee

During processing, no memory allocation occurs:

  • All buffers are pre-allocated at compilation time
  • Job list is a flat array
  • Node processing writes directly into pre-assigned buffer slots

This is critical for real-time audio where allocation can cause glitches.

Buffer

SIMD-aligned buffer with external memory ownership (the Schedule owns the memory, Buffer provides a typed view).

Method Description
data() Pointer to buffer samples
size() Number of samples
clear() Zero the buffer

Extension: libbahn

The music tracker (libbahn) extends this framework with audio-specific concepts:

libmediacore libbahn extension Added functionality
GraphNode AudioNode process(AudioContext&)
AudioNode Processor Tracker state, parameters, serialization
- ProcessorContext Transport state, sequencer position
- ProcessorFactory Registry of available processor types

Applications that need the graph engine but not the tracker (e.g., singularity's jingle system) can use AudioNode directly without importing tracker-specific code.


Usage example

// Build a simple graph
Graph graph;
auto* source = new OscNode(440.0);  // 440 Hz sine
auto* gain = new GainNode(0.5);     // 50% volume
auto* output = new OutputNode();

graph.addNode(source);
graph.addNode(gain);
graph.addNode(output);
graph.connect(source, gain);
graph.connect(gain, output);

// Compile to schedule
ScheduleFactory factory;
Schedule schedule = factory.compile(graph);

// Process one buffer
schedule.process();

See Also

In this section
Topics
reference media graph processing real-time
See also