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¶
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:
- Required parameters - Must be present
- Causality - No instantaneous loops
- Type compatibility - Connections match types
- 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)