Sound Byte Libs 0.5.1-121-g3358a44
C++ firmware library for audio applications on 32-bit ARM Cortex-M processors
Loading...
Searching...
No Matches
load_junction.hpp
Go to the documentation of this file.
1// sbl/dsp/pm/load_junction.hpp — Where the string meets its bridge (Physical modeling — composition)
2//
3// A waveguide's end is a rigid wall until something sits there. This is the
4// something: a lumped load with a driving-point admittance, and the
5// scattering of the string's velocity wave against it. Velocity at the
6// junction is incoming plus reflected; the force the string exerts is their
7// difference times the wave impedance, which the loop normalises to 1:
8//
9// v_b = v_in + v_ref F = v_in − v_ref = 2 v_in − v_b
10//
11// The load answers v_b = g F + h (see MassSpringDamper), so the junction is
12// one algebraic solve — no delay-free loop, no iteration:
13//
14// v_b = (2 Z g v_in + h) / (1 + Z g) F = Z (2 v_in − v_b) v_ref = v_b − v_in
15//
16// with Z the guide's wave impedance (1 unless set), so a load's g and h stay
17// physical whatever guide it sits on.
18//
19// The limits are the checks: a load too heavy to move (g → 0, h → 0) gives
20// v_ref = −v_in, the rigid wall the loop had before; a load too light to
21// resist (g → ∞) gives v_b = 2 v_in and v_ref = +v_in, a free end. In
22// between, where the body is easy to move the string's energy leaves into
23// it — that partial decays faster — and where it resists, energy comes back
24// with a phase shift that pulls the string's pitch. This is the mechanism a
25// body adds; the load decides how much and where.
26//
27// Energy: the junction itself is lossless — what does not come back went
28// into the load — and the load is passive, so the string cannot gain here
29// (RPT-029 Part 4 rule 1).
30//
31// Usage:
32// sbl::dsp::pm::MassSpringDamper body;
33// sbl::dsp::pm::LoadJunction<sbl::dsp::pm::MassSpringDamper> bridge(body);
34// float out = loop.tick(bow, [&](float arriving) { return bridge.reflect(arriving); });
35// float radiated = bridge.load_velocity();
36
37#ifndef SBL_DSP_PM_LOAD_JUNCTION_HPP_
38#define SBL_DSP_PM_LOAD_JUNCTION_HPP_
39
40#include <cstdint>
41
44
45namespace sbl::dsp::pm {
46
47/**
48 * @brief The scattering junction between a waveguide and a lumped load
49 *
50 * @tparam Load anything with admittance(), history() and commit(force):
51 * the velocity it produces for a force, split into an instantaneous
52 * part and a history part.
53 *
54 * @note All public methods are ISR-safe — bounded computation, no I/O.
55 */
56template<typename Load>
58 static_assert(is_load_v<Load>, "LoadJunction needs a Load (physical-modeling.md §4.1)");
59
60public:
61 explicit LoadJunction(Load& load) : load_(load) {}
62
63 /// The wave impedance of the guide this junction terminates: the load's g
64 /// and h are physical, the waves are the guide's.
65 void set_impedance(float z) { impedance_ = z > 0.0f ? z : 1.0f; }
66 float impedance() const { return impedance_; }
67
68 /**
69 * @brief Scatter one sample: the wave arriving at the bridge in, the reflection out
70 *
71 * The force found here is committed to the load, so load_velocity() and
72 * force() read this sample's values afterwards.
73 */
74 float reflect(float incoming) {
75 const float g = load_.admittance();
76 const float h = load_.history();
77 const float zg = impedance_ * g;
78 const float v_load = (TWO * zg * incoming + h) / (1.0f + zg);
79 force_ = impedance_ * (TWO * incoming - v_load);
80 v_load_ = load_.commit(force_);
81 return v_load_ - incoming;
82 }
83
84 /// The body's velocity at the junction this sample — what it radiates, to first order.
85 float load_velocity() const { return v_load_; }
86
87 /// The force the string put into the body this sample.
88 float force() const { return force_; }
89
90 Load& load() { return load_; }
91 const Load& load() const { return load_; }
92
93 /// The junction stores nothing itself; what is behind it does.
94 float energy() const { return load_.energy(); }
95
96 void reset() {
97 load_.reset();
98 v_load_ = 0.0f;
99 force_ = 0.0f;
100 }
101
102 /**
103 * @brief The junction and its load, wired: force into the load, velocity back
104 *
105 * `in`/`out` are the junction (the guide side); the named port "load" is
106 * the load, for a tap on what it radiates. Never called from audio code (AP-037).
107 */
108 diagram::Ports describe(diagram::Graph& g, uint8_t parent) const {
110 p.group = g.add_group("LoadJunction", parent);
111 p.in = p.out = g.add_node(diagram::Kind::Junction, "load junction", p.group);
112 const diagram::Ports load = load_.describe(g, p.group);
115 p.add("load", load.out);
116 return p;
117 }
118
119private:
120 static constexpr float TWO = 2.0f;
121
122 Load& load_;
123 float impedance_ = 1.0f;
124 float v_load_ = 0.0f;
125 float force_ = 0.0f;
126};
127
128} // namespace sbl::dsp::pm
129
130#endif // SBL_DSP_PM_LOAD_JUNCTION_HPP_
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.
Definition graph.hpp:75
void add_edge(uint8_t from, uint8_t to, Wave wave)
Definition graph.hpp:88
uint8_t add_node(Kind kind, const char *name, uint8_t group, float value=0.0f, float value2=0.0f)
Definition graph.hpp:81
The scattering junction between a waveguide and a lumped load.
float force() const
The force the string put into the body this sample.
float energy() const
The junction stores nothing itself; what is behind it does.
float reflect(float incoming)
Scatter one sample: the wave arriving at the bridge in, the reflection out.
float load_velocity() const
The body's velocity at the junction this sample — what it radiates, to first order.
diagram::Ports describe(diagram::Graph &g, uint8_t parent) const
The junction and its load, wired: force into the load, velocity back.
const Load & load() const
What a load, an exciter, a termination and a friction law provide (Physical modeling — cross-cutting)
A model's wiring, as data (AP-037)
@ Junction
where waves scatter or a source meets the string
Physical modeling: laws, bows, junctions, loads, resonators, strings (docs/conventions/physical-model...
Definition bow.hpp:31
The ports a component exposes after describing itself, so an owner can wire them.
Definition graph.hpp:130
void add(const char *name, uint8_t node)
Add a named port; a ninth is refused and find() answers NO_NODE for it.
Definition graph.hpp:147
uint8_t in
where a wave enters (the junction, the load)
Definition graph.hpp:133
uint8_t out
where a wave leaves (the pickup, the reflection)
Definition graph.hpp:134