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
input.hpp
Go to the documentation of this file.
1/**
2 * @file input.hpp
3 * @brief CV Input component — smoothed ADC reading with su output (FDP-072)
4 * @ingroup components
5 *
6 * CvInput is a source adapter (L1/L2): it takes raw ADC counts, applies EWMA
7 * smoothing in the raw domain, and emits normalized signal units at its
8 * output — the transport contract of ADR-011 D2. The jack's musical role
9 * (unipolar / bipolar / V/Oct) is a structural parameter with a sensible
10 * default; conditioning constants live in one code-owned struct with named
11 * presets. Per-mode runtime remapping is a first-class pattern.
12 *
13 * Acquisition is owned by the component: feed values with update(), or read
14 * straight from a DMA scan buffer with update_dma() (ISR-safe).
15 *
16 * Usage:
17 * sbl::components::cv::CvInput jack{sbl::components::cv::Smoothing::Light};
18 * jack.set_conditioning(CvConditioning::from(sbl::hw::electrical::cv_5)); // Board::init() does this for you
19 * jack.set_mapping(CvMapping::VOct);
20 * jack.update_dma(adc_buf, 4);
21 * float pitch_offset = jack.value(); // su
22 */
23
24#ifndef SBL_HW_COMPONENTS_CV_INPUT_HPP_
25#define SBL_HW_COMPONENTS_CV_INPUT_HPP_
26
27#include <cstdint>
28#include <sbl/hw/util/ewma.hpp>
30
31namespace sbl {
32namespace components {
33namespace cv {
34
35/**
36 * @brief Smoothing presets for the raw-domain EWMA filter
37 *
38 * Values are the EWMA shift exponent (alpha = 1/2^shift).
39 */
40enum class Smoothing : uint8_t {
41 None = 0, ///< Raw passthrough (no filtering)
42 Light = 1, ///< alpha=1/2, settles in ~4 samples (pitch, fast CV)
43 Medium = 3, ///< alpha=1/8, settles in ~24 samples (pots, knobs)
44 Heavy = 5, ///< alpha=1/32, settles in ~96 samples (noisy sources)
45};
46
47/**
48 * @brief Musical interpretation of the jack — a structural parameter
49 *
50 * Runtime-settable; remapping per mode is supported. Mapping applies at the
51 * output stage, so remaps are glitch-free with respect to filter state.
52 * su-domain state derived from the old mapping (pickup targets, stored
53 * positions) must be re-armed by the app on remap.
54 */
55enum class CvMapping : uint8_t {
56 Unipolar, ///< [0, 1] — full ADC span (default)
57 Bipolar, ///< [-1, 1] — around the conditioned midpoint
58 VOct, ///< su pitch offset — 1 V/oct, 8 octaves per 1.0 su, unclamped
59};
60
61/**
62 * @brief Conditioning constants — one code-owned home (ADR-011 D2)
63 *
64 * Describes the analog front end between the jack and the ADC. Defaults
65 * describe a plain 16-bit unipolar input; use a named preset (or set your
66 * own values once at init) when your hardware differs. This replaces the
67 * scattered app-side magic numbers that drifted apart (RPT-026).
68 */
70 float counts_max = 65535.0f; ///< Full-scale ADC count
71 float midpoint = 32768.0f; ///< Count at 0 V (bipolar conditioning)
72 bool inverted = false; ///< Conditioning op-amp inverts (+V → lower count)
73 float counts_per_volt = 6553.5f; ///< Counts per volt (16-bit over ±5 V)
74
75 /// Derive from a port's electrical description (generated by sloth into
76 /// sbl::hw::electrical from the hardware manifest — the one owner of these facts).
77 static constexpr CvConditioning from(const sbl::hal::adc::Electrical& e) {
78 return {e.counts_max(), static_cast<float>(e.midpoint()), e.inverted, e.counts_per_volt()};
79 }
80};
81
85
86/**
87 * @brief Smoothed CV input emitting normalized signal units
88 *
89 * @note update()/update_dma() are ISR-safe (pure state machine, atomic
90 * 32-bit EWMA state on Cortex-M). Call update from exactly one
91 * context; getters are safe from another.
92 */
93class CvInput {
94public:
95 /// Pitch-su convention: 1.0 su spans 8 octaves (MIDI 24–120), 1 V/oct.
96 static constexpr float kSuPerVolt = 0.125f;
97
98 explicit CvInput(const CvInputConfig& config = {})
99 : filter_(static_cast<uint8_t>(config.smoothing)), raw_(0) {}
100
101 explicit CvInput(Smoothing smoothing)
102 : filter_(static_cast<uint8_t>(smoothing)), raw_(0) {}
103
104 /** @brief Feed a new raw ADC sample */
105 void update(uint16_t raw) {
106 raw_ = raw;
107 filter_.update(raw);
108 }
109
110 /**
111 * @brief Read one channel from a DMA scan buffer and update
112 *
113 * Volatile cast forces a real memory read — DMA updates the buffer
114 * asynchronously. ISR-safe, non-blocking.
115 */
116 void update_dma(const uint16_t* buffer, uint8_t channel_index) {
117 update(static_cast<const volatile uint16_t*>(buffer)[channel_index]);
118 }
119
120 /** @brief Last raw (unfiltered) ADC count — for calibration routines */
121 uint16_t raw() const { return raw_; }
122
123 /** @brief Smoothed output in signal units, per the configured mapping */
124 float value() const { return map(filter_.value()); }
125
126 /** @brief Set the jack's musical role (structural; runtime-remappable) */
127 void set_mapping(CvMapping mapping) { mapping_ = mapping; }
128
129 CvMapping mapping() const { return mapping_; }
130
131 /** @brief Set the analog front-end description (once at init, typically) */
132 void set_conditioning(const CvConditioning& cond) { cond_ = cond; }
133
134 const CvConditioning& conditioning() const { return cond_; }
135
136 /** @brief Change smoothing level without resetting filter state */
137 void set_smoothing(Smoothing smoothing) {
138 filter_.set_shift(static_cast<uint8_t>(smoothing));
139 }
140
141 /** @brief Reset filter state */
142 void reset() {
143 filter_.reset();
144 raw_ = 0;
145 }
146
147private:
148 float map(uint16_t counts) const {
149 float c = static_cast<float>(counts);
150 switch (mapping_) {
151 case CvMapping::Unipolar: {
152 float t = c / cond_.counts_max;
153 if (cond_.inverted) t = 1.0f - t;
154 return (t < 0.0f) ? 0.0f : (t > 1.0f) ? 1.0f : t;
155 }
156 case CvMapping::Bipolar: {
157 float b = (c - cond_.midpoint) / cond_.midpoint;
158 if (cond_.inverted) b = -b;
159 return (b < -1.0f) ? -1.0f : (b > 1.0f) ? 1.0f : b;
160 }
161 case CvMapping::VOct: {
162 // Offset semantics: unclamped — the receiving port clamps.
163 float volts = (c - cond_.midpoint) / cond_.counts_per_volt;
164 if (cond_.inverted) volts = -volts;
165 return volts * kSuPerVolt;
166 }
167 }
168 return 0.0f;
169 }
170
171 primitives::math::Ewma filter_;
172 uint16_t raw_;
174 CvConditioning cond_{};
175};
176
177} // namespace cv
178} // namespace components
179} // namespace sbl
180
181#endif // SBL_HW_COMPONENTS_CV_INPUT_HPP_
Smoothed CV input emitting normalized signal units.
Definition input.hpp:93
void update_dma(const uint16_t *buffer, uint8_t channel_index)
Read one channel from a DMA scan buffer and update.
Definition input.hpp:116
CvInput(Smoothing smoothing)
Definition input.hpp:101
uint16_t raw() const
Last raw (unfiltered) ADC count — for calibration routines.
Definition input.hpp:121
void set_mapping(CvMapping mapping)
Set the jack's musical role (structural; runtime-remappable)
Definition input.hpp:127
void set_smoothing(Smoothing smoothing)
Change smoothing level without resetting filter state.
Definition input.hpp:137
CvMapping mapping() const
Definition input.hpp:129
float value() const
Smoothed output in signal units, per the configured mapping.
Definition input.hpp:124
void set_conditioning(const CvConditioning &cond)
Set the analog front-end description (once at init, typically)
Definition input.hpp:132
static constexpr float kSuPerVolt
Pitch-su convention: 1.0 su spans 8 octaves (MIDI 24–120), 1 V/oct.
Definition input.hpp:96
CvInput(const CvInputConfig &config={})
Definition input.hpp:98
void update(uint16_t raw)
Feed a new raw ADC sample.
Definition input.hpp:105
const CvConditioning & conditioning() const
Definition input.hpp:134
void reset()
Reset filter state.
Definition input.hpp:142
uint16_t update(uint16_t raw)
Feed a new sample and return the smoothed value.
Definition ewma.hpp:46
uint16_t value() const
Return current smoothed value without updating.
Definition ewma.hpp:63
void set_shift(uint8_t shift)
Change smoothing exponent without resetting filter state.
Definition ewma.hpp:68
void reset()
Reset filter state (next update will re-seed)
Definition ewma.hpp:71
Electrical description of an analog port — the jack-to-converter circuit.
Exponentially Weighted Moving Average filter.
Smoothing
Smoothing presets for the raw-domain EWMA filter.
Definition input.hpp:40
@ None
Raw passthrough (no filtering)
@ Medium
alpha=1/8, settles in ~24 samples (pots, knobs)
@ Light
alpha=1/2, settles in ~4 samples (pitch, fast CV)
@ Heavy
alpha=1/32, settles in ~96 samples (noisy sources)
CvMapping
Musical interpretation of the jack — a structural parameter.
Definition input.hpp:55
@ Unipolar
[0, 1] — full ADC span (default)
@ Bipolar
[-1, 1] — around the conditioned midpoint
@ VOct
su pitch offset — 1 V/oct, 8 octaves per 1.0 su, unclamped
Root namespace for all Sound Byte Libs code.
Definition assert.hpp:51
Conditioning constants — one code-owned home (ADR-011 D2)
Definition input.hpp:69
float counts_per_volt
Counts per volt (16-bit over ±5 V)
Definition input.hpp:73
static constexpr CvConditioning from(const sbl::hal::adc::Electrical &e)
Definition input.hpp:77
float midpoint
Count at 0 V (bipolar conditioning)
Definition input.hpp:71
bool inverted
Conditioning op-amp inverts (+V → lower count)
Definition input.hpp:72
float counts_max
Full-scale ADC count.
Definition input.hpp:70
constexpr float counts_max() const
constexpr uint16_t midpoint() const
Code that reads as 0 V — the bipolar midpoint. Clamps if 0 V is out of range.
bool inverted
+V at the jack reads as a lower code
constexpr float counts_per_volt() const