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
bow_set.hpp
Go to the documentation of this file.
1// sbl/dsp/pm/bow_set.hpp — Several table laws behind one select, and the bow that plays them (Physical modeling — composition)
2//
3// TableSet is a friction law: a set of BowTables and a select position across
4// them. Two TableFrictions run — the one the select sits on and the next —
5// and the force is the crossfade between them. Both run every sample so the
6// one fading in carries current stick/slip state rather than a stale one.
7// With one table the set is that law and nothing is crossfaded.
8//
9// BowSet is a Bow over a TableSet with the select as a third port, smoothed
10// like the other two.
11
12#ifndef SBL_DSP_PM_BOW_SET_HPP_
13#define SBL_DSP_PM_BOW_SET_HPP_
14
15#include <cstdint>
16
19#include <sbl/dsp/pm/bow.hpp>
22
23namespace sbl::dsp::pm {
24
25class TableSet {
26public:
27 /// @note All public methods are ISR-safe — bounded computation, no I/O.
28
29 static constexpr uint8_t MAX_TABLES = 8;
30
31 /// The tables, in the order the select sweeps them. The array must outlive the set.
32 void set_tables(const BowTable* tables, uint8_t count) {
33 tables_ = tables;
34 count_ = count > MAX_TABLES ? MAX_TABLES : count;
35 a_index_ = NO_TABLE;
36 b_index_ = NO_TABLE;
37 apply_select(select_su_);
38 }
39
40 uint8_t count() const { return count_; }
41 const BowTable* tables() const { return tables_; }
42
43 /// Which table: 0 the first, 1 the last, a crossfade between neighbours. Applied at once.
44 void set_select_su(float su) {
45 select_su_ = math::clamp01(su);
46 apply_select(select_su_);
47 }
48
49 void set_force_su(float su) {
50 a_.set_force_su(su);
51 b_.set_force_su(su);
52 }
53
54 /// One sample: relative velocity in, the crossfaded force out (wave units).
55 float process(float dv_free) {
56 if (count_ == 0) return 0.0f;
57 float force = a_.process(dv_free);
58 if (count_ > 1) {
59 const float other = b_.process(dv_free);
60 force += blend_ * (other - force);
61 }
62 return force;
63 }
64
65 /// The table you hear — the one with the larger crossfade weight — holds the string.
66 bool sticking() const { return blend_ > HALF_BLEND ? b_.sticking() : a_.sticking(); }
67 float limit() const { return blend_ > HALF_BLEND ? b_.limit() : a_.limit(); }
68
69 void reset() {
70 a_.reset();
71 b_.reset();
72 }
73
74 /// One junction per running table. `in`/`out` is the first; the second is the named port "table B".
75 diagram::Ports describe(diagram::Graph& g, uint8_t group, const char* name = "friction") const {
76 diagram::Ports p = a_.describe(g, group, count_ > 1 ? "friction A" : name);
77 if (count_ > 1) {
78 const diagram::Ports other = b_.describe(g, group, "friction B");
79 p.add("table B", other.in);
80 }
81 return p;
82 }
83
84private:
85 static constexpr float HALF_BLEND = 0.5f;
86 static constexpr uint8_t NO_TABLE = 0xFF;
87
88 void apply_select(float select_su) {
89 if (tables_ == nullptr || count_ == 0) return;
90
91 const float pos = select_su * static_cast<float>(count_ - 1);
92 uint8_t a = static_cast<uint8_t>(pos);
93 if (count_ > 1 && a >= count_ - 1) a = static_cast<uint8_t>(count_ - 2);
94 blend_ = count_ > 1 ? pos - static_cast<float>(a) : 0.0f;
95
96 // A law that changes table is at the far end of the crossfade, so its
97 // stick state is inaudible; starting it stuck is the clean state.
98 if (a != a_index_) {
99 a_.set_bow(tables_[a]);
100 a_.reset();
101 a_index_ = a;
102 }
103 const uint8_t b = count_ > 1 ? static_cast<uint8_t>(a + 1) : a;
104 if (b != b_index_) {
105 b_.set_bow(tables_[b]);
106 b_.reset();
107 b_index_ = b;
108 }
109 }
110
111 TableFriction a_;
112 TableFriction b_;
113 const BowTable* tables_ = nullptr;
114 uint8_t count_ = 0;
115 uint8_t a_index_ = NO_TABLE;
116 uint8_t b_index_ = NO_TABLE;
117 float select_su_ = 0.0f;
118 float blend_ = 0.0f;
119};
120
121static_assert(is_friction_law_v<TableSet>, "TableSet is a friction law (physical-modeling.md §4.5)");
122
123/// A bow over a set of tables: velocity, pressure and select, all smoothed.
124class BowSet : public Bow<TableSet> {
125public:
126 static constexpr float DEFAULT_SELECT_SU = 0.0f;
127
129 select_.set(DEFAULT_SELECT_SU);
131 select_.reset();
132 }
133
134 /// The tables this bow can be played with (TableSet::set_tables).
135 void set_tables(const BowTable* tables, uint8_t count) { law().set_tables(tables, count); }
136
137 /// Which table: crossfades across the set. Smoothed.
138 void set_select_su(float su) { select_.set(math::clamp01(su)); }
139
140 void advance(uint16_t n) {
141 law().set_select_su(select_.advance(n));
143 }
144
145 void reset() {
147 select_.reset();
148 law().set_select_su(select_.value());
150 }
151
152private:
154};
155
156static_assert(is_exciter_v<BowSet>, "BowSet is an Exciter (physical-modeling.md §4.2)");
157
158} // namespace sbl::dsp::pm
159
160#endif // SBL_DSP_PM_BOW_SET_HPP_
A bow: a speed, a weight, and a friction law at a string's junction (Physical modeling — composition)
Clamping (Cross-cutting — Math)
A bow over a set of tables: velocity, pressure and select, all smoothed.
Definition bow_set.hpp:124
void advance(uint16_t n)
Definition bow_set.hpp:140
static constexpr float DEFAULT_SELECT_SU
Definition bow_set.hpp:126
void set_tables(const BowTable *tables, uint8_t count)
The tables this bow can be played with (TableSet::set_tables).
Definition bow_set.hpp:135
void set_select_su(float su)
Which table: crossfades across the set. Smoothed.
Definition bow_set.hpp:138
void reset()
Definition bow.hpp:87
static constexpr float SMOOTH_MS
Definition bow.hpp:43
void advance(uint16_t n)
A block's worth of port movement.
Definition bow.hpp:69
TableSet & law()
The law, for its own parameters (a table, a rosin).
Definition bow.hpp:55
float process(float dv_free)
One sample: relative velocity in, injection force out.
float limit() const
Static-friction limit at the current bow force — where the string releases.
void set_bow(const float *family, const float *limits, uint8_t slices, uint16_t size, uint16_t stride, float dv_max=1.0f)
Install a bow: a lute-solved family and its per-slice limits.
bool sticking() const
True while the string travels with the bow.
void set_force_su(float su)
Bow force across the family [0, 1]; crossfades adjacent slices.
diagram::Ports describe(diagram::Graph &g, uint8_t group, const char *name="bow friction") const
One Junction node: the friction nonlinearity. Never called from audio code (AP-037).
float limit() const
Definition bow_set.hpp:67
bool sticking() const
The table you hear — the one with the larger crossfade weight — holds the string.
Definition bow_set.hpp:66
void set_tables(const BowTable *tables, uint8_t count)
The tables, in the order the select sweeps them. The array must outlive the set.
Definition bow_set.hpp:32
void set_select_su(float su)
Which table: 0 the first, 1 the last, a crossfade between neighbours. Applied at once.
Definition bow_set.hpp:44
float process(float dv_free)
One sample: relative velocity in, the crossfaded force out (wave units).
Definition bow_set.hpp:55
const BowTable * tables() const
Definition bow_set.hpp:41
static constexpr uint8_t MAX_TABLES
Definition bow_set.hpp:29
void set_force_su(float su)
Definition bow_set.hpp:49
diagram::Ports describe(diagram::Graph &g, uint8_t group, const char *name="friction") const
One junction per running table. in/out is the first; the second is the named port "table B".
Definition bow_set.hpp:75
uint8_t count() const
Definition bow_set.hpp:40
void set(float target)
The port's setter stores here; nothing moves until advance().
void set_time_ms(float ms, float rate_hz)
Settling time, at the rate the smoother is advanced at (per sample here).
void reset()
Snap to the target: what reset() does, so ports start where they were set.
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 model's wiring, as data (AP-037)
constexpr float clamp01(float x)
x held to [0, 1]; NaN → 0.
Definition clamp.hpp:18
Physical modeling: laws, bows, junctions, loads, resonators, strings (docs/conventions/physical-model...
Definition bow.hpp:31
float SAMPLE_RATE_F
Definition fixed.hpp:17
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
One bow: a lute-solved family and the shape of its arrays.
A bow: friction solved against the string (Physical modeling — primitive)