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
cc_mapper.hpp
Go to the documentation of this file.
1// sbl/control/cc_mapper.hpp — Declarative MIDI CC routing (FDP-075)
2//
3// A constexpr table replaces the per-CC switch: each row routes one
4// controller on one channel to an app-defined target through a curve. Curves
5// are LUTE cam tables (the same vocabulary as knob macros) or linear.
6//
7// Dispatch is event-driven, inside the control tick's drain loop, after the
8// CC store (MidiConverter) has taken the event — converter before mapper. The
9// store stays the only CC state: a Paired14 row reads the other half of its
10// pair from it. Values are absolute; every matching row fires, so a CC with
11// two rows fans out to two targets.
12//
13// The sink is the app's write into the merge layer (FDP-074), e.g.
14// pickups[param].hold(value).
15//
16// Usage:
17// enum class Param : uint8_t { Pitch, Timbre };
18// constexpr sbl::control::CcEntry<Param> kCcMap[] = {
19// { 0, 86, Param::Pitch, sbl::control::kCcLinear, sbl::control::CcFlags::None },
20// { 0, 87, Param::Timbre, {cam::timbre_256, 256}, sbl::control::CcFlags::None },
21// };
22// sbl::control::CcMapper<Param> mapper{kCcMap, write_param, nullptr};
23//
24// // drain loop, per ControlChange event:
25// converter.cc(e.cc_number(), e.cc_value());
26// mapper.on_cc(e.channel, e.cc_number(), e.cc_value(), converter);
27
28#ifndef SBL_CONTROL_CC_MAPPER_HPP_
29#define SBL_CONTROL_CC_MAPPER_HPP_
30
31#include <cstddef>
32#include <cstdint>
33
34#include <sbl/control/cam.hpp>
35
36namespace sbl::control {
37
38/// A LUTE cam table (size + 1 entries, guard point included), or linear when table is null.
39struct CcCurve {
40 const float* table = nullptr;
41 uint16_t size = 0;
42};
43
44inline constexpr CcCurve kCcLinear{};
45
46enum class CcFlags : uint8_t {
47 None = 0,
48 Paired14 = 1 << 0, ///< 14-bit: `cc` is the MSB (0–31), cc + 32 the LSB
49};
50
51/// Row channel that matches every channel.
52inline constexpr uint8_t kAnyChannel = 0xFF;
53
54template<typename Target>
55struct CcEntry {
56 uint8_t channel; ///< 0–15, or kAnyChannel
57 uint8_t cc; ///< controller number (the MSB for Paired14)
58 Target target;
61};
62
63template<typename Target>
64class CcMapper {
65public:
66 /// The app's write into the merge layer. value_su is the curve output.
67 using Sink = void (*)(void* context, Target target, float value_su);
68
69 template<size_t N>
70 CcMapper(const CcEntry<Target> (&table)[N], Sink sink, void* context)
71 : table_(table), count_(N), sink_(sink), context_(context) {}
72
73 /**
74 * @brief Route one ControlChange event
75 *
76 * Call after `store` has taken the event. `store` provides
77 * `float cc(uint8_t number) const` in [0, 1] and supplies the other half
78 * of a 14-bit pair. Returns the number of rows dispatched.
79 */
80 template<typename Store>
81 uint8_t on_cc(uint8_t channel, uint8_t cc, uint8_t value, const Store& store) const {
82 uint8_t hits = 0;
83 for (size_t i = 0; i < count_; ++i) {
84 const CcEntry<Target>& e = table_[i];
85 if (e.channel != kAnyChannel && e.channel != channel) continue;
86
87 float t;
88 if (e.flags == CcFlags::Paired14) {
89 const uint8_t lsb_cc = static_cast<uint8_t>(e.cc + 32);
90 if (cc != e.cc && cc != lsb_cc) continue;
91 const uint8_t msb = (cc == e.cc) ? value : to7(store.cc(e.cc));
92 const uint8_t lsb = (cc == lsb_cc) ? value : to7(store.cc(lsb_cc));
93 t = static_cast<float>((static_cast<uint16_t>(msb) << 7) | lsb) *
94 (1.0f / 16383.0f);
95 } else {
96 if (cc != e.cc) continue;
97 t = static_cast<float>(value) * (1.0f / 127.0f);
98 }
99
100 const float v = e.curve.table ? cam_evaluate(e.curve.table, e.curve.size, t) : t;
101 sink_(context_, e.target, v);
102 ++hits;
103 }
104 return hits;
105 }
106
107 size_t size() const { return count_; }
108
109private:
110 static uint8_t to7(float su) {
111 const float c = su * 127.0f + 0.5f;
112 return c <= 0.0f ? 0 : (c >= 127.0f ? 127 : static_cast<uint8_t>(c));
113 }
114
115 const CcEntry<Target>* table_;
116 size_t count_;
117 Sink sink_;
118 void* context_;
119};
120
121} // namespace sbl::control
122
123#endif // SBL_CONTROL_CC_MAPPER_HPP_
Cam profile evaluation.
size_t size() const
CcMapper(const CcEntry< Target >(&table)[N], Sink sink, void *context)
Definition cc_mapper.hpp:70
void(*)(void *context, Target target, float value_su) Sink
The app's write into the merge layer. value_su is the curve output.
Definition cc_mapper.hpp:67
uint8_t on_cc(uint8_t channel, uint8_t cc, uint8_t value, const Store &store) const
Route one ControlChange event.
Definition cc_mapper.hpp:81
Control Stack v2: scheduler, event queue, merge, CC mapping, calibration, presets.
constexpr CcCurve kCcLinear
Definition cc_mapper.hpp:44
float cam_evaluate(const float *table, uint16_t size, float input)
Definition cam.hpp:27
constexpr uint8_t kAnyChannel
Row channel that matches every channel.
Definition cc_mapper.hpp:52
@ Paired14
14-bit: cc is the MSB (0–31), cc + 32 the LSB
A LUTE cam table (size + 1 entries, guard point included), or linear when table is null.
Definition cc_mapper.hpp:39
const float * table
Definition cc_mapper.hpp:40
uint8_t channel
0–15, or kAnyChannel
Definition cc_mapper.hpp:56
uint8_t cc
controller number (the MSB for Paired14)
Definition cc_mapper.hpp:57