36#ifndef SBL_DSP_PM_MODAL_RESONATOR_HPP_
37#define SBL_DSP_PM_MODAL_RESONATOR_HPP_
57template<u
int8_t MaxModes = 8>
59 static_assert(MaxModes >= 1,
"a resonator needs at least one mode");
67 for (uint8_t i = 0; i < MaxModes; ++i) {
75 void set_mode(uint8_t index,
float hz,
float q,
float match,
77 if (index >= MaxModes)
return;
87 for (uint8_t i = 0; i < count_; ++i) {
88 modes_[i].
set_resonance(modes[i].hz, modes[i].q, modes[i].match);
100 uint8_t
count()
const {
return count_; }
103 float weight(uint8_t i)
const {
return weight_[i]; }
104 float radiate(uint8_t i)
const {
return radiate_[i]; }
112 for (uint8_t i = 0; i < count_; ++i) h += weight_[i] * modes_[i].
history();
120 for (uint8_t i = 0; i < count_; ++i) {
121 const float vi = modes_[i].
commit(weight_[i] * force);
122 v += weight_[i] * vi;
123 heard += radiate_[i] * vi;
135 for (uint8_t i = 0; i < count_; ++i) e += modes_[i].
energy();
140 for (uint8_t i = 0; i < MaxModes; ++i) modes_[i].
reset();
149 for (uint8_t i = 0; i < count_; ++i) {
164 for (uint8_t i = 0; i < count_; ++i) g_ += weight_[i] * weight_[i] * modes_[i].
admittance();
168 float weight_[MaxModes];
169 float radiate_[MaxModes];
172 float radiated_ = 0.0f;
175static_assert(is_load_v<ModalResonator<8>> && is_described_v<ModalResonator<8>>,
"ModalResonator is a Load and describes itself (physical-modeling.md §4.1, §7)");
Clamping (Cross-cutting — Math)
uint8_t add_group(const char *name, uint8_t parent=NO_GROUP)
Open a group (a component); returns its id. Nodes added with it belong to it.
void add_edge(uint8_t from, uint8_t to, Wave wave)
uint8_t add_node(Kind kind, const char *name, uint8_t group, float value=0.0f, float value2=0.0f)
diagram::Ports describe(diagram::Graph &g, uint8_t group, const char *name="mode") const
One Load node: frequency and Q. Never called from audio code (AP-037).
float commit(float force)
Apply this sample's force and advance: returns the velocity.
void set_resonance(float hz, float q, float match)
The musical parametrisation: where, how wide, how much it takes.
const pm::MassSpringDamper & mode(uint8_t i) const
float weight(uint8_t i) const
static constexpr uint8_t MAX_MODES
void set_mode(uint8_t index, float hz, float q, float match, float weight=1.0f, float radiate=1.0f)
Set one mode. Modes at or beyond count() are kept but silent.
float energy() const
Stored energy across the live modes, for passivity tests.
void set_modes(const Mode *modes, uint8_t count)
Install a whole set; count is clamped to the bank's size.
diagram::Ports describe(diagram::Graph &g, uint8_t parent, const char *name="resonator") const
One Load node per live mode, wired in parallel to a shared point. Never called from audio code (AP-03...
float radiate(uint8_t i) const
float commit(float force)
Apply this sample's force to every live mode; return the point velocity.
float radiated() const
What the body sounds like this sample: each mode's velocity by its radiation weight.
void set_count(uint8_t count)
How many of the modes are live.
What a load, an exciter, a termination and a friction law provide (Physical modeling — cross-cutting)
A model's wiring, as data (AP-037)
A lumped resonator that can be a load (Physical modeling — primitive)
@ Junction
where waves scatter or a source meets the string
constexpr float clamp01(float x)
x held to [0, 1]; NaN → 0.
Physical modeling: laws, bows, junctions, loads, resonators, strings (docs/conventions/physical-model...
The ports a component exposes after describing itself, so an owner can wire them.
uint8_t in
where a wave enters (the junction, the load)
uint8_t out
where a wave leaves (the pickup, the reflection)
One resonance as a string feels it, its shape at the bridge, and its shape where it is heard.
float weight
coupling to the string at the bridge
float radiate
presence in radiated()