Skip to content

05_05_05_topology_templates - Reusable DSP Building Blocks

Purpose

Library of parameterized topology templates for common DSP algorithms. Instead of rebuilding filters/oscillators/effects from scratch, instantiate proven templates with custom parameters. Ensures correctness, accelerates development, and promotes consistency.

Key Concepts

Template vs Instance

Template: Parameterized blueprint - biquad_filter(type, fc, Q) - Defines structure, not values - Reusable across projects

Instance: Concrete topology - biquad_filter(lowpass, 1000Hz, 0.707) - Specific parameters filled in - Ready to execute

Template Categories

Category Examples Use Cases
Filters Biquad, SVF, Ladder, One-Pole EQ, tone shaping, synthesis
Oscillators Sine, Saw, Square, Triangle Synthesis, LFOs, test tones
Effects Delay, Chorus, Flanger, Phaser Time-based effects
Dynamics Compressor, Limiter Level control
Utilities Gain, Mix, Pan, DC Block Signal routing

API Overview

Basic Usage

#include "template_library.hpp"

// Initialize built-in templates
templates::registerBuiltInTemplates();

auto& registry = templates::TemplateRegistry::instance();

// Instantiate a template
templates::TemplateParams params;
params["type"] = std::string("lowpass");
params["fc"] = 1000.0f;
params["Q"] = 0.707f;

auto topology = registry.instantiate("biquad_filter", params);

// Topology is ready to use!

List Available Templates

auto& registry = templates::TemplateRegistry::instance();

// All templates
auto all = registry.listTemplates();
for (const auto& name : all) {
    std::cout << "- " << name << "\n";
}

// By category
auto filters = registry.listByCategory("filter");
auto oscillators = registry.listByCategory("oscillator");
auto effects = registry.listByCategory("effect");

Get Template Info

auto* info = registry.getTemplateInfo("biquad_filter");
if (info) {
    std::cout << "Name: " << info->name << "\n";
    std::cout << "Category: " << info->category << "\n";
    std::cout << "Description: " << info->description << "\n";
    std::cout << "Required params: ";
    for (const auto& p : info->required_params) {
        std::cout << p << " ";
    }
    std::cout << "\nOptional params: ";
    for (const auto& p : info->optional_params) {
        std::cout << p << " ";
    }
}

Built-In Templates

Filters

1. Biquad Filter

params["type"] = "lowpass";  // or "highpass", "bandpass", "notch", etc.
params["fc"] = 1000.0f;      // Cutoff frequency (Hz)
params["Q"] = 0.707f;        // Resonance
params["gain_db"] = 0.0f;    // Gain for peaking/shelving (optional)
params["sample_rate"] = 48000.0f;

auto biquad = registry.instantiate("biquad_filter", params);

Supported types: - lowpass - Low-pass filter - highpass - High-pass filter - bandpass - Band-pass filter - notch - Notch/band-reject filter - allpass - All-pass filter - peaking - Peaking EQ - lowshelf - Low shelf - highshelf - High shelf

2. DC Blocker

params["fc"] = 20.0f;  // Cutoff (default: 20Hz)

auto dc_block = registry.instantiate("dc_blocker", params);

3. One-Pole Filter

params["type"] = "lowpass";  // or "highpass"
params["fc"] = 1000.0f;

auto one_pole = registry.instantiate("one_pole_filter", params);

Oscillators

1. Sine Oscillator

params["frequency"] = 440.0f;   // Hz
params["amplitude"] = 1.0f;     // 0.0 to 1.0
params["phase"] = 0.0f;         // 0.0 to 1.0

auto sine_osc = registry.instantiate("sine_oscillator", params);

Effects

1. Delay Line

params["delay_time_ms"] = 250.0f;  // Delay time in ms
params["feedback"] = 0.3f;         // Feedback amount (0.0 to 1.0)
params["mix"] = 0.5f;              // Dry/wet mix

auto delay = registry.instantiate("delay_line", params);

Utilities

1. Gain/Amplifier

params["gain_db"] = -6.0f;  // Gain in dB

auto gain = registry.instantiate("gain", params);

Template Implementation

Creating Custom Templates

// 1. Define template function
Topology createMyCustomFilter(const TemplateParams& params) {
    float cutoff = getParam<float>(params, "cutoff", 1000.0f);
    float resonance = getParam<float>(params, "resonance", 1.0f);

    return TopologyBuilder()
        .setName("my_custom_filter")
        .addMetadataField("template", "custom_filter")
        // ... build topology ...
        .build();
}

// 2. Register template
TemplateInfo info("my_custom_filter", "filter", "My custom filter design");
info.required_params = {"cutoff"};
info.optional_params = {"resonance"};

auto& registry = TemplateRegistry::instance();
registry.registerTemplate("my_custom_filter", info, createMyCustomFilter);

// 3. Use template
TemplateParams params;
params["cutoff"] = 2000.0f;
auto filter = registry.instantiate("my_custom_filter", params);

Template Best Practices

1. Provide Sensible Defaults

// ✅ Good
float fc = getParam<float>(params, "fc", 1000.0f);  // Default: 1kHz

// ❌ Bad
float fc = getParam<float>(params, "fc", 0.0f);  // Invalid default

2. Validate Parameters

float Q = getParam<float>(params, "Q", 0.707f);
Q = std::clamp(Q, 0.1f, 100.0f);  // Prevent extreme values

3. Document in Metadata

auto topology = TopologyBuilder()
    .setName("biquad_lowpass")
    .addMetadataField("template", "biquad_filter")
    .addMetadataField("type", "lowpass")
    .addMetadataField("fc", std::to_string(fc))
    .addMetadataField("Q", std::to_string(Q))
    // ...
    .build();

4. Use Meaningful Node Names

// ✅ Good
.addNode("delay_1_feedforward", "delay_1sample", NodeType::Processing)
.addNode("mul_b0", "multiply_scalar", NodeType::Processing)

// ❌ Bad
.addNode("node1", "delay_1sample", NodeType::Processing)
.addNode("node2", "multiply_scalar", NodeType::Processing)

Examples

Example 1: 3-Band EQ

templates::registerBuiltInTemplates();
auto& registry = templates::TemplateRegistry::instance();

// Low band (low shelf @ 100Hz)
templates::TemplateParams low;
low["type"] = std::string("lowshelf");
low["fc"] = 100.0f;
low["gain_db"] = 3.0f;
auto low_band = registry.instantiate("biquad_filter", low);

// Mid band (peaking @ 1kHz)
templates::TemplateParams mid;
mid["type"] = std::string("peaking");
mid["fc"] = 1000.0f;
mid["Q"] = 1.0f;
mid["gain_db"] = -2.0f;
auto mid_band = registry.instantiate("biquad_filter", mid);

// High band (high shelf @ 8kHz)
templates::TemplateParams high;
high["type"] = std::string("highshelf");
high["fc"] = 8000.0f;
high["gain_db"] = 1.5f;
auto high_band = registry.instantiate("biquad_filter", high);

// Chain them together (would need composition, see hierarchical_composition)

Example 2: Echo Effect

// 250ms echo with 30% feedback and 50% mix
templates::TemplateParams echo_params;
echo_params["delay_time_ms"] = 250.0f;
echo_params["feedback"] = 0.3f;
echo_params["mix"] = 0.5f;

auto echo = registry.instantiate("delay_line", echo_params);

Example 3: Test Tone Generator

// 1kHz sine at -6dB
templates::TemplateParams osc_params;
osc_params["frequency"] = 1000.0f;
osc_params["amplitude"] = 0.5f;  // -6dB ≈ 0.5

auto test_tone = registry.instantiate("sine_oscillator", osc_params);

Example 4: Template with Parameter Manager

// Create filter from template
templates::TemplateParams filter_params;
filter_params["type"] = std::string("lowpass");
filter_params["fc"] = 1000.0f;
filter_params["Q"] = 0.707f;

auto topology = registry.instantiate("biquad_filter", filter_params);

// Add parameter control
ParameterManager params(topology);

ExternalParameter cutoff("cutoff", 1000.0f);
cutoff.min_value = 20.0f;
cutoff.max_value = 20000.0f;
cutoff.unit = "Hz";

params.addParameter(cutoff);

// Find the multiply nodes and bind (template-specific)
params.addBinding("cutoff", "mul_b0", "scalar", [](ParameterValue fc) {
    // Recalculate b0 coefficient when fc changes
    // (simplified - real implementation would recalculate all coeffs)
    return fc;
});

Template Internals: Biquad Example

Biquad Direct Form I Structure

Input ──[×b0]──→ [+] ──→ Output
  │              ↑
  ├─[z⁻¹]─[×b1]─┘
  │              ↑
  └─[z⁻²]─[×b2]─┘
Output ─[z⁻¹]─[×-a1]─┘
  │              ↑
  └─[z⁻²]─[×-a2]─┘

Coefficient Calculation

Lowpass example:

float omega0 = 2π × fc / fs
float alpha = sin(omega0) / (2 × Q)

b0 = (1 - cos(omega0)) / 2
b1 = 1 - cos(omega0)
b2 = (1 - cos(omega0)) / 2

a0 = 1 + alpha
a1 = -2 × cos(omega0)
a2 = 1 - alpha

// Normalize by a0
b0 /= a0
b1 /= a0
b2 /= a0
a1 /= a0
a2 /= a0

Topology Generation

// Build 11 nodes for biquad
- input (source)
- mul_b0, mul_b1, mul_b2 (feedforward multipliers)
- delay_1_ff, delay_2_ff (feedforward delays)
- mul_a1, mul_a2 (feedback multipliers, negated)
- delay_1_fb, delay_2_fb (feedback delays - CAUSAL!)
- sum (5-input adder)
- output (sink)

// 15 connections
- 6 feedforward connections
- 6 feedback connections (with delays for causality)
- 3 output connections

Template Validation

Automatic Validation

Templates are validated on instantiation:

  1. Required parameters - Must be present
  2. Causality - No instantaneous loops
  3. Type compatibility - Connections match types
  4. Range validity - Parameters within sensible ranges
try {
    auto topology = registry.instantiate("biquad_filter", params);
    // Success!
} catch (const std::runtime_error& e) {
    std::cerr << "Template error: " << e.what() << "\n";
    // Handle: missing params, invalid values, etc.
}

Template Testing

Each template should have:

// Unit test
TEST_CASE("Biquad template instantiation") {
    TemplateParams params;
    params["type"] = std::string("lowpass");
    params["fc"] = 1000.0f;
    params["Q"] = 0.707f;

    auto topology = createBiquadFilter(params);

    REQUIRE(topology.getNodeCount() == 11);
    REQUIRE(topology.getEdgeCount() == 15);

    // Validate causality
    auto validation = CausalityValidator::validate(topology);
    REQUIRE(validation.is_causal == true);
}

// Audio test
TEST_CASE("Biquad audio correctness") {
    auto topology = createBiquadFilter({...});

    // Generate impulse response
    // Compare against reference implementation
    // Verify frequency response
}

Performance

Operation Complexity Notes
Template instantiation O(N) N = nodes in template
Parameter lookup O(1) Hash map
Registry lookup O(1) Hash map
Listing templates O(T) T = total templates

Memory: Each template instance creates new topology (~5-20 KB typically)

Typical instantiation time: - Simple (Gain): <10 µs - Medium (Biquad): <100 µs - Complex (Reverb): <1 ms

Integration

Input Dependencies

  • Topology builder from 00_graph_representation
  • Parameter system from 04_parameter_system (optional, for dynamic control)

Output Consumers

  • Validation (01_causality_validation) validates templates
  • Code generation (06_code_generation) compiles template instances
  • Hierarchical composition (10_hierarchical_composition) uses templates as sub-topologies

Next Steps

After templates: 1. 06_code_generation - Compile template instances to C++ 2. 10_hierarchical_composition - Use templates as building blocks 3. Testing - Validate audio correctness of templates


Status: ✅ Core templates implemented (6 templates) Coverage: Filters, oscillators, effects, utilities Validation: 100% templates pass causality check Target: 30+ templates (80% DSP algorithm coverage)