05_05_09 - Hierarchical Composition¶
📋 Descripción¶
Sistema de composición jerárquica que permite crear sub-topologías (topologías anidadas) como nodos reutilizables. Facilita la modularización, encapsulación y reutilización de componentes DSP complejos.
🎯 Objetivos¶
- Sub-topologías: Encapsular grafos completos como nodos compuestos
- Interfaz externa: Exponer puertos y parámetros selectivamente
- Parameter forwarding: Propagar parámetros a través de la jerarquía
- Flattening: Convertir jerarquía a topología plana para ejecución
- Module library: Biblioteca de componentes reutilizables
🏗️ Arquitectura¶
Componentes Principales¶
hierarchical_topology.hpp/cpp # Sistema jerárquico
├── SubTopology # Topología como módulo reutilizable
├── HierarchicalTopology # Topología con sub-topologías
├── TopologyFlattener # Flatten jerarquía → topología plana
├── HierarchicalBuilder # Fluent API para construcción
├── SubTopologyFactory # Factory de módulos built-in
├── HierarchyAnalyzer # Análisis de estructura jerárquica
├── ParameterForwarder # Forwarding de parámetros
└── ModuleLibrary # Registro de módulos reutilizables
Flujo de Trabajo¶
Build Hierarchy
↓
[SubTopology] → Encapsulate internal graph
↓
[HierarchicalTopology] → Compose with sub-topologies
↓
[Flattener] → Flatten to single-level topology
↓
Execute (using existing pipeline)
📦 Sub-Topology (Módulo)¶
Concepto¶
Una SubTopology es una topología completa que se puede usar como un nodo dentro de otra topología. Define:
- Topología interna: Grafo de nodos y conexiones
- Interfaz externa: Puertos y parámetros expuestos
- Mapeos: Relación entre interfaz externa ↔ nodos internos
Crear Sub-Topology Manualmente¶
auto sub = std::make_shared<SubTopology>("my_filter");
// 1. Construir topología interna
auto& internal = sub->internal_topology();
Node input_gain{"gain_in", "multiply_scalar", NodeType::Processing};
Node filter{"lpf", "biquad_lowpass", NodeType::Processing};
Node output_gain{"gain_out", "multiply_scalar", NodeType::Processing};
internal.addNode(input_gain);
internal.addNode(filter);
internal.addNode(output_gain);
// Conexiones internas
internal.addEdge({"gain_in", "out", "lpf", "in"});
internal.addEdge({"lpf", "out", "gain_out", "in"});
// 2. Definir interfaz externa
Port input{"in", PortDirection::Input, 512};
Port output{"out", PortDirection::Output, 512};
// Mapear puertos externos → nodos internos
sub->add_external_input(input, "gain_in", "in");
sub->add_external_output(output, "gain_out", "out");
// 3. Exponer parámetros
Parameter cutoff{"cutoff", 1000.0f};
Parameter gain{"gain", 1.0f};
sub->add_external_parameter(cutoff, "lpf", "fc");
sub->add_external_parameter(gain, "gain_out", "scalar");
Usar Sub-Topology Factory¶
// Módulos built-in
auto biquad = SubTopologyFactory::create_biquad_filter();
auto delay = SubTopologyFactory::create_stereo_delay();
auto compressor = SubTopologyFactory::create_compressor();
auto reverb = SubTopologyFactory::create_reverb();
auto eq_band = SubTopologyFactory::create_eq_band();
🏗️ Hierarchical Topology¶
Crear Topología Jerárquica¶
HierarchicalBuilder builder;
// Topología top-level
builder.setName("channel_strip")
.addNode("input", "external_input", NodeType::Source)
.addNode("output", "external_output", NodeType::Sink);
// Agregar nodos compuestos (sub-topologies)
auto eq = SubTopologyFactory::create_eq_band();
auto compressor = SubTopologyFactory::create_compressor();
builder.addCompoundNode("eq", eq)
.addCompoundNode("comp", compressor);
// Conexiones (igual que topología normal)
builder.connect("input", "out", "eq", "in")
.connect("eq", "out", "comp", "in")
.connect("comp", "out", "output", "in");
// Parámetros en nodos compuestos
builder.setParameter("eq", "cutoff", 5000.0f)
.setParameter("eq", "Q", 1.0f)
.setParameter("comp", "threshold", -12.0f);
auto hierarchical = builder.build();
Inspeccionar Jerarquía¶
std::cout << "Compound nodes: " << hierarchical.get_compound_nodes().size() << "\n";
std::cout << "Depth: " << hierarchical.get_depth() << "\n";
for (const auto& node_id : hierarchical.get_compound_nodes()) {
auto sub = hierarchical.get_sub_topology(node_id);
std::cout << " - " << node_id << ": " << sub->name() << "\n";
std::cout << " Internal nodes: "
<< sub->internal_topology().nodes().size() << "\n";
}
🔄 Flattening (Aplanamiento)¶
Convertir a Topología Plana¶
El flattening convierte la jerarquía multinivel en una topología plana de un solo nivel, lista para ejecución:
// Topología jerárquica
HierarchicalTopology hierarchical = builder.build();
// Flatten a topología ejecutable
FlatteningOptions options;
options.preserve_hierarchy_metadata = true;
options.inline_single_node_subs = true;
options.id_separator = "_";
Topology flat = TopologyFlattener::flatten(hierarchical, options);
std::cout << "Original nodes (top-level): "
<< hierarchical.top_level().nodes().size() << "\n";
std::cout << "Flattened nodes (total): "
<< flat.nodes().size() << "\n";
Proceso de Flattening¶
- Expandir nodos compuestos: Reemplazar cada nodo compuesto por sus nodos internos
- Generar IDs jerárquicos:
eq_lpf,eq_gain_out(evita colisiones) - Redirigir conexiones:
- Conexiones a puerto externo → puerto interno correspondiente
- Conexiones desde puerto externo → puerto interno correspondiente
- Eliminar nodos compuestos: Quedan solo nodos atómicos
Ejemplo de IDs Jerárquicos¶
Original (jerárquico):
- eq (compound)
- gain_in
- lpf
- gain_out
Flattened (plano):
- eq_gain_in
- eq_lpf
- eq_gain_out
🎛️ Parameter Forwarding¶
Propagación de Parámetros¶
// Cambiar parámetro en nodo compuesto
ParameterForwarder::forward_parameter(
hierarchical,
"eq", // Nodo compuesto
"cutoff", // Parámetro externo
2000.0f // Nuevo valor
);
// Esto se propaga automáticamente a:
// eq.lpf.fc = 2000.0f (según el mapeo definido)
Rutas de Parámetros¶
// Obtener todas las rutas de un parámetro
auto paths = ParameterForwarder::get_parameter_paths(hierarchical, "cutoff");
for (const auto& path : paths) {
std::cout << path << "\n";
}
// Output:
// eq.lpf.fc
// comp.filter.fc
Resolver Parámetro por Ruta¶
auto value = ParameterForwarder::resolve_parameter(
hierarchical,
"eq.lpf.fc"
);
if (value.has_value()) {
std::cout << "Value: " << value.value() << "\n";
}
📊 Análisis de Jerarquía¶
Estadísticas¶
auto stats = HierarchyAnalyzer::analyze(hierarchical);
std::cout << "Hierarchy Statistics:\n";
std::cout << " Total nodes: " << stats.total_nodes << "\n";
std::cout << " Top-level nodes: " << stats.top_level_nodes << "\n";
std::cout << " Compound nodes: " << stats.compound_nodes << "\n";
std::cout << " Leaf nodes: " << stats.leaf_nodes << "\n";
std::cout << " Max depth: " << stats.max_depth << "\n";
std::cout << " Avg children per compound: "
<< stats.average_children_per_compound << "\n";
std::cout << " Hierarchy ratio: " << stats.hierarchy_ratio * 100 << "%\n";
Árbol Jerárquico¶
auto tree = HierarchyAnalyzer::get_hierarchy_tree(hierarchical);
for (const auto& [parent, children] : tree) {
std::cout << parent << ":\n";
for (const auto& child : children) {
std::cout << " - " << child << "\n";
}
}
Nodos por Nivel¶
// Nivel 0 (top-level)
auto level0 = HierarchyAnalyzer::get_nodes_at_depth(hierarchical, 0);
// Nivel 1 (dentro de sub-topologies)
auto level1 = HierarchyAnalyzer::get_nodes_at_depth(hierarchical, 1);
std::cout << "Level 0: " << level0.size() << " nodes\n";
std::cout << "Level 1: " << level1.size() << " nodes\n";
📚 Module Library¶
Registro de Módulos¶
// Registrar módulos built-in
ModuleLibrary::register_builtin_modules();
// Listar módulos disponibles
auto& lib = ModuleLibrary::instance();
auto modules = lib.list_modules();
std::cout << "Available modules:\n";
for (const auto& name : modules) {
std::cout << " - " << name << "\n";
}
// Output:
// - biquad_filter
// - stereo_delay
// - compressor
// - reverb
// - eq_band
Instanciar Módulos¶
// Obtener módulo (shared)
auto eq_module = lib.get_module("biquad_filter");
// Instanciar (copia independiente)
auto eq1 = lib.instantiate("biquad_filter");
auto eq2 = lib.instantiate("biquad_filter");
// Cada instancia es independiente
Registrar Módulos Personalizados¶
// Crear módulo personalizado
auto custom = std::make_shared<SubTopology>("my_distortion");
// ... build internal topology ...
// Registrar en biblioteca
ModuleLibrary::instance().register_module("distortion", custom);
// Usar en construcción
auto dist = ModuleLibrary::instance().instantiate("distortion");
builder.addCompoundNode("dist", dist);
📝 Ejemplo Completo: EQ de 3 Bandas¶
#include "hierarchical_topology.hpp"
using namespace audiolab::topology::hierarchical;
int main() {
// 1. Registrar módulos built-in
ModuleLibrary::register_builtin_modules();
// 2. Construir topología jerárquica
HierarchicalBuilder builder;
builder.setName("three_band_eq")
.addNode("input", "external_input", NodeType::Source)
.addNode("output", "external_output", NodeType::Sink);
// Instanciar 3 bandas de EQ
auto low_band = ModuleLibrary::instance().instantiate("eq_band");
auto mid_band = ModuleLibrary::instance().instantiate("eq_band");
auto high_band = ModuleLibrary::instance().instantiate("eq_band");
builder.addCompoundNode("low", low_band)
.addCompoundNode("mid", mid_band)
.addCompoundNode("high", high_band);
// Conexiones en serie
builder.connect("input", "out", "low", "in")
.connect("low", "out", "mid", "in")
.connect("mid", "out", "high", "in")
.connect("high", "out", "output", "in");
// Configurar cada banda
builder.setParameter("low", "fc", 200.0f) // Low shelf @ 200Hz
.setParameter("low", "Q", 0.707f)
.setParameter("low", "gain", 0.0f)
.setParameter("mid", "fc", 1000.0f) // Peaking @ 1kHz
.setParameter("mid", "Q", 1.0f)
.setParameter("mid", "gain", 0.0f)
.setParameter("high", "fc", 8000.0f) // High shelf @ 8kHz
.setParameter("high", "Q", 0.707f)
.setParameter("high", "gain", 0.0f);
auto hierarchical = builder.build();
// 3. Analizar jerarquía
std::cout << "=== Hierarchical Topology ===\n";
auto stats = HierarchyAnalyzer::analyze(hierarchical);
std::cout << "Total nodes: " << stats.total_nodes << "\n";
std::cout << "Compound nodes: " << stats.compound_nodes << "\n";
std::cout << "Depth: " << stats.max_depth << "\n\n";
// 4. Flatten para ejecución
FlatteningOptions flatten_opts;
flatten_opts.id_separator = "_";
Topology flat = TopologyFlattener::flatten(hierarchical, flatten_opts);
std::cout << "=== Flattened Topology ===\n";
std::cout << "Total nodes: " << flat.nodes().size() << "\n";
std::cout << "Total edges: " << flat.edges().size() << "\n\n";
std::cout << "Flattened node IDs:\n";
for (const auto& [node_id, node] : flat.nodes()) {
std::cout << " - " << node_id << " (" << node.type << ")\n";
}
// 5. Ahora se puede usar con el resto del pipeline
// (causality validation, dependency analysis, buffer management, code gen)
return 0;
}
Salida Esperada¶
=== Hierarchical Topology ===
Total nodes: 35 (2 source/sink + 3 compound + ~30 internal)
Compound nodes: 3
Depth: 2
=== Flattened Topology ===
Total nodes: 33 (2 source/sink + 31 expanded internal nodes)
Total edges: 42
Flattened node IDs:
- input (external_input)
- output (external_output)
- low_z1 (delay_1sample)
- low_z2 (delay_1sample)
- low_mul_b0 (multiply_scalar)
- low_mul_b1 (multiply_scalar)
- low_mul_b2 (multiply_scalar)
- low_mul_a1 (multiply_scalar)
- low_mul_a2 (multiply_scalar)
- low_add1 (add)
- low_add2 (add)
- low_add3 (add)
- low_add4 (add)
- mid_z1 (delay_1sample)
- mid_z2 (delay_1sample)
... (similar para mid y high)
🔗 Integración con Pipeline Existente¶
Workflow Completo¶
// 1. Construir jerarquía
HierarchicalBuilder builder;
// ... build hierarchical topology ...
auto hierarchical = builder.build();
// 2. Flatten
Topology topology = TopologyFlattener::flatten(hierarchical);
// 3. Validar (usa pipeline existente)
auto validation = CausalityValidator::validate(topology);
if (!validation.is_causal) {
std::cerr << "Causality validation failed\n";
return;
}
// 4. Analizar dependencias
auto analysis = DependencyAnalyzer::analyze(topology);
// 5. Optimizar memoria
auto buffer_plan = BufferManager::createPlan(topology, analysis.execution_order);
// 6. Generar código
auto code = CodeGenerator::generate(topology, analysis, buffer_plan, options);
code.saveToFiles("./generated/");
Ventajas de Jerarquía + Pipeline¶
| Característica | Beneficio |
|---|---|
| Modularidad | Componentes reutilizables (EQ band, compressor) |
| Abstracción | Oculta complejidad interna |
| Composición | Combina módulos fácilmente |
| Flattening | Compatible con pipeline existente |
| Debugging | Jerarquía preserva estructura lógica |
🎯 Casos de Uso¶
1. Channel Strip¶
auto eq = ModuleLibrary::instance().instantiate("eq_band");
auto comp = ModuleLibrary::instance().instantiate("compressor");
auto gate = ModuleLibrary::instance().instantiate("noise_gate");
builder.addCompoundNode("eq", eq)
.addCompoundNode("comp", comp)
.addCompoundNode("gate", gate)
.connect("input", "out", "gate", "in")
.connect("gate", "out", "eq", "in")
.connect("eq", "out", "comp", "in")
.connect("comp", "out", "output", "in");
2. Reverb con Pre-Delay¶
auto delay = SubTopologyFactory::create_stereo_delay();
auto reverb = SubTopologyFactory::create_reverb();
builder.addCompoundNode("pre_delay", delay)
.addCompoundNode("reverb_engine", reverb)
.connect("input", "out", "pre_delay", "in")
.connect("pre_delay", "out", "reverb_engine", "in")
.connect("reverb_engine", "out", "output", "in");
3. Multi-Band Processor¶
// Crear sub-topology para 1 banda
auto create_band = []() {
auto band = std::make_shared<SubTopology>("mb_band");
// Filter + Compressor por banda
auto lpf = SubTopologyFactory::create_biquad_filter();
auto hpf = SubTopologyFactory::create_biquad_filter();
auto comp = SubTopologyFactory::create_compressor();
// ... build internal topology with crossover + comp ...
return band;
};
// Instanciar 4 bandas
for (int i = 0; i < 4; i++) {
auto band = create_band();
builder.addCompoundNode("band_" + std::to_string(i), band);
}
// Configurar crossover frequencies, etc.
📈 Métricas de Rendimiento¶
Overhead de Flattening¶
| Operación | Complejidad | Tiempo Típico |
|---|---|---|
| Flatten | O(V + E) | <1ms para 100 nodos |
| ID remapping | O(V) | Negligible |
| Edge redirect | O(E) | Negligible |
El flattening es una operación offline (compile-time), por lo que no afecta al rendimiento en tiempo real.
Comparación: Manual vs Jerárquico¶
| Aspecto | Manual (Flat) | Jerárquico |
|---|---|---|
| LOC | ~200 líneas | ~50 líneas |
| Reutilización | Copiar/pegar | Instanciar módulo |
| Mantenimiento | Difícil | Fácil (cambio en módulo) |
| Debugging | IDs planos | IDs jerárquicos informativos |
| Performance | Igual | Igual (después de flatten) |
🚀 Estado del Sistema¶
- ✅ SubTopology: Encapsulación completa de topologías internas
- ✅ HierarchicalTopology: Composición con sub-topologías
- ✅ TopologyFlattener: Flatten con IDs jerárquicos y remap
- ✅ HierarchicalBuilder: Fluent API para construcción
- ✅ SubTopologyFactory: 5 módulos built-in
- ✅ HierarchyAnalyzer: Estadísticas y análisis de estructura
- ✅ ParameterForwarder: Propagación de parámetros
- ✅ ModuleLibrary: Registro y instanciación de módulos
📚 Referencias¶
- Hierarchical Systems: Abelson, H. "Structure and Interpretation of Computer Programs"
- Component-Based Design: Szyperski, C. "Component Software"
- Graph Flattening: Cormen et al. "Introduction to Algorithms" (Graph algorithms)
- DSP Architecture: Puckette, M. "The Theory and Technique of Electronic Music"
Subsistema: 05_MODULES → 05_05_TOPOLOGY_DESIGN → 05_05_09_hierarchical_composition Autor: AudioLab Development Team Versión: 1.0.0 Última actualización: 2025-10-10