44#ifndef SBL_DSP_PM_STRING_BASE_HPP_
45#define SBL_DSP_PM_STRING_BASE_HPP_
49#include <initializer_list>
74template<
typename Exciter,
typename Load, u
int16_t MaxPeriod = 1536>
76 static_assert(MaxPeriod >= 64,
"a string needs room to ring");
77 static_assert(is_exciter_v<Exciter>,
"the string is played by an Exciter (physical-modeling.md §4.2)");
78 static_assert(is_load_v<Load>,
"end b terminates on a Load (physical-modeling.md §4.1)");
152 for (uint16_t i = 0; i < n; ++i) {
155 const float radiated =
load_.radiated();
156 out[i] = radiated + mix * (at_b - radiated);
160 for (uint16_t i = 0; i < n; ++i) {
170 for (uint16_t i = 0; i < n; ++i) out[i] = 0.0f;
207 const uint8_t second =
exciter.find(
"table B");
214 const uint8_t tap = g.
add_node(Kind::Tap,
"out: load ↔ bare string", group);
218 const uint8_t tap = g.
add_node(Kind::Tap,
"out: end b velocity", group);
244 const float longest =
static_cast<float>(MaxPeriod) -
PERIOD_MARGIN;
245 if (period > longest) period = longest;
Did a non-finite sample get into this block?
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)
The scattering junction between a waveguide and a lumped load.
float buf_a_[MaxPeriod]
the loop's segment to end a
float buf_b_[MaxPeriod]
and to end b
void set_pickup_su(float su)
What is heard: 0 the load, 1 the bare string, a crossfade between.
void set_position_su(float su)
Where the exciter sits along the string.
void apply_position(float position_su)
StringBase(Exciter &exciter, Load &load, const char *name="String")
Exciter & exciter()
The parts, for whoever owns them and for tools.
void apply_damping(float damping_su)
static constexpr float RETUNE_THRESHOLD
samples of period change worth a retune
static constexpr float MIN_JUNCTION
static constexpr float MAX_JUNCTION
void set_damping_su(float su)
Loss filter cutoff: how fast the high partials give up. Smoothed.
float energy() const
Energy in the loop, the load and the exciter. Never called from audio code.
static constexpr float PERIOD_MARGIN
headroom under MaxPeriod for the split
primitives::SmoothedPort pickup_
LoadJunction< Load > end_b_
primitives::SmoothedPort damping_
primitives::SmoothedPort pitch_
static constexpr float DEFAULT_PITCH_SU
void process(float *out, uint16_t n)
static constexpr float DEFAULT_PICKUP_SU
all load
primitives::BlockGuard guard_
primitives::CrossfadeRamp mix_
void describe(diagram::Graph &g) const
The whole string as wired: exciter, loop, end b, pickup (AP-037)
void use_load(bool on)
End b terminates on the load (true) or on a wall (false). The junction starts at rest.
void reset()
The string at rest, its ports already at their targets rather than gliding to them.
float period_samples() const
The loop period the string is currently tuned to, in samples.
void set_pitch_su(float su)
Pitch across C1..C9 (math::note_of_su). Smoothed.
primitives::SmoothedPort position_
void update_period(float pitch_su)
static constexpr float DEFAULT_DAMPING_SU
const StringLoop & loop() const
static constexpr float LOOP_GAIN
energy lost at end b each trip
friend struct StringTestAccess
const LoadJunction< Load > & end_b() const
static constexpr float DEFAULT_POSITION_SU
junction 0.3
static constexpr float SMOOTH_MS
void set_damping_su(float su)
Loss filter cutoff: how fast the high partials give up. Signal units.
float tick(Excite &&excite, EndB &&end_b)
Advance one sample.
void set_junction(float beta)
Excitation point along the string, 0 at end a, 1 at end b (a string: nut and bridge).
void init(float *a_buffer, uint32_t a_max, float *b_buffer, uint32_t b_max)
Install the two segments' buffers.
float energy() const
Energy in the two segments, in wave units: the sum of squares of every sample in flight (physical-mod...
void set_loop_gain(float gain)
Round-trip gain, the passivity margin.
void set_period_samples(float period)
Loop period in samples — the pitch. Sample rate over frequency.
diagram::Ports describe(diagram::Graph &g, uint8_t parent=diagram::NO_GROUP, bool external_end_b=false) const
The loop's wiring as data (AP-037). Never called from audio code.
float period_samples() const
bool tripped() const
True if anything added since begin() was non-finite.
void end()
Close the block exactly on its target, so rounding never drifts.
void begin(float target, uint16_t n)
Start a block that ends at target after n steps.
float next()
The value for the next sample.
void set(float target)
The port's setter stores here; nothing moves until advance().
float advance(uint16_t n)
Advance by a block: the smoothed value to apply for this block.
What a load, an exciter, a termination and a friction law provide (Physical modeling — cross-cutting)
A gain ramped linearly across one block.
Fixed-point constants and audio sample types.
A model's wiring, as data (AP-037)
Where the string meets its bridge (Physical modeling — composition)
Wave
What travels along an edge.
constexpr uint8_t NO_NODE
Kind
What a node is, in the paper's vocabulary.
constexpr float clamp01(float x)
x held to [0, 1]; NaN → 0.
constexpr float note_of_su(float su)
float note_to_frequency(float midi_note)
MIDI note to frequency in Hz. A4 = 440 Hz.
Physical modeling: laws, bows, junctions, loads, resonators, strings (docs/conventions/physical-model...
Semitone-to-frequency-ratio conversion.
A port's target, its smoother and its last value.
A string as two waveguide segments (Physical modeling — composition)
The ports a component exposes after describing itself, so an owner can wire them.