11#ifndef SBL_DSP_DIAGRAM_MERMAID_HPP_
12#define SBL_DSP_DIAGRAM_MERMAID_HPP_
36 template<
typename... Args>
37 void line(
const char* fmt, Args... args) {
40#pragma GCC diagnostic push
41#pragma GCC diagnostic ignored "-Wformat-security"
43 const int n = snprintf(
buf +
pos,
cap -
pos, fmt, args...);
45#pragma GCC diagnostic pop
48 pos +=
static_cast<size_t>(n) <
cap -
pos ?
static_cast<size_t>(n) :
cap -
pos - 1;
58 case Kind::Loss: snprintf(out, cap,
"%s<br/>LP %.0f Hz", n.
name,
static_cast<double>(n.
value));
break;
64 case Kind::Bandpass: snprintf(out, cap,
"%s<br/>BP %.0f Hz, Q %.1f", n.
name,
static_cast<double>(n.
value),
static_cast<double>(n.
value2));
break;
65 case Kind::Gain: snprintf(out, cap,
"%s<br/>× %.2f", n.
name,
static_cast<double>(n.
value));
break;
66 default: snprintf(out, cap,
"%s", n.
name);
break;
71inline void node_decl(
const Node& n, uint8_t
id,
char* out,
size_t cap) {
75 case Kind::DelayLine: snprintf(out, cap,
"n%u[\"%s\"]",
id, label);
break;
78 case Kind::DcBlock: snprintf(out, cap,
"n%u(\"%s\")",
id, label);
break;
79 case Kind::Reflection: snprintf(out, cap,
"n%u{{\"%s\"}}",
id, label);
break;
80 case Kind::Junction: snprintf(out, cap,
"n%u((\"%s\"))",
id, label);
break;
81 case Kind::Source: snprintf(out, cap,
"n%u>\"%s\"]",
id, label);
break;
83 case Kind::Resonator: snprintf(out, cap,
"n%u[(\"%s\")]",
id, label);
break;
85 case Kind::Gain: snprintf(out, cap,
"n%u(\"%s\")",
id, label);
break;
86 case Kind::Sum: snprintf(out, cap,
"n%u((\"%s\"))",
id, label);
break;
87 case Kind::Tap: snprintf(out, cap,
"n%u[/\"%s\"/]",
id, label);
break;
88 case Kind::Component: snprintf(out, cap,
"n%u[[\"%s\"]]",
id, label);
break;
101inline void edge_decl(uint8_t from, uint8_t to,
Wave w,
char* out,
size_t cap) {
103 if (label[0]) snprintf(out, cap,
"n%u -->|%s| n%u", from, label, to);
104 else snprintf(out, cap,
"n%u --> n%u", from, to);
114 const char* pad = indent ?
" " :
"";
115 o.
line(
"%ssubgraph g%u[\"%s\"]", pad, gid, g.
group(gid).
name);
126 for (uint8_t i = 0; i < g.
node_count(); ++i) {
130 o.
line(
"%s %s", pad, decl);
132 o.
line(
"%send", pad);
142 if (cap == 0)
return 0;
145 o.
line(
"flowchart LR");
147 for (uint8_t gid = 0; gid < g.
group_count(); ++gid) {
151 for (uint8_t i = 0; i < g.
node_count(); ++i) {
163 for (uint8_t i = 0; i < g.
edge_count(); ++i) {
165 uint8_t from = e.
from, to = e.
to;
169 if (gf !=
NO_GROUP) from =
static_cast<uint8_t
>(200 + gf);
170 if (gt !=
NO_GROUP) to =
static_cast<uint8_t
>(200 + gt);
171 if (from == to)
continue;
173 for (uint8_t k = 0; k < seen; ++k) {
174 if (seen_from[k] == from && seen_to[k] == to) { dup =
true;
break; }
177 if (seen <
Graph::MAX_EDGES) { seen_from[seen] = from; seen_to[seen] = to; ++seen; }
uint8_t node_count() const
static constexpr uint8_t MAX_EDGES
uint8_t group_count() const
const Group & group(uint8_t i) const
const Edge & edge(uint8_t i) const
uint8_t edge_count() const
const Node & node(uint8_t i) const
A model's wiring, as data (AP-037)
uint8_t fold_target(const Graph &g, uint8_t group)
The group a node renders in at Components depth: nested groups fold to their id.
void edge_decl(uint8_t from, uint8_t to, Wave w, char *out, size_t cap)
void emit_group(const Graph &g, uint8_t gid, Depth depth, Out &o, int indent)
void node_label(const Node &n, char *out, size_t cap)
Label with the paper symbol and the cheap live parameter.
const char * wave_name(Wave w)
void node_decl(const Node &n, uint8_t id, char *out, size_t cap)
Shape by kind: the reader should tell a delay from a filter at a glance.
A model's wiring as data, for pictures.
Wave
What travels along an edge.
size_t render_mermaid(const Graph &g, char *buf, size_t cap, Depth depth=Depth::Full)
Render the graph as a Mermaid flowchart into a buffer.
@ Loss
value: cutoff in Hz
@ Bandpass
a banded-waveguide band; value: Hz, value2: Q
@ Component
a folded group, made by the renderer
@ DcBlock
value: corner in Hz
@ Junction
where waves scatter or a source meets the string
@ Resonator
a modal source; value: Hz, value2: Q
@ Reflection
value: coefficient (−1 wall, +1 free end)
@ DelayLine
value: length in samples
@ Load
a lumped body; value: Hz, value2: Q
@ Source
a driving signal (bow velocity, pluck)
@ Allpass
value: delay in samples
@ Components
nested groups folded to one box each
@ Full
every node, as the components described them
constexpr uint8_t NO_GROUP
const char * name
short label; string literals only, the graph does not own it
void line(const char *fmt, Args... args)