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
envelope_follower.hpp
Go to the documentation of this file.
1// sbl/dsp/comp/envelope_follower.hpp — Amplitude envelope extraction
2//
3// Rectifies and smooths an audio signal to extract its amplitude envelope.
4// Full-wave rectification with asymmetric one-pole smoothing (fast attack,
5// slow release). Output is a slowly-varying positive float representing
6// "how loud the input is."
7//
8// Domain: Amplitude — makes the amplitude contour of a signal explicit.
9// See docs/design-philosophy.md for the domain manipulation framework.
10//
11// Usage:
12// sbl::dsp::comp::EnvelopeFollower ef;
13// ef.set_times(1.0f, 100.0f, 48000); // 1ms attack, 100ms release
14// float level = ef.process(sample); // Single sample
15// ef.process(in, out, frames); // Block: in → envelope out
16
17#ifndef SBL_DSP_COMP_ENVELOPE_FOLLOWER_HPP_
18#define SBL_DSP_COMP_ENVELOPE_FOLLOWER_HPP_
19
20#include <cstdint>
21
22namespace sbl::dsp::comp {
23
25public:
26 /// @note All public methods are ISR-safe — bounded computation, no I/O.
27
28 /**
29 * @brief Set attack and release times in milliseconds
30 *
31 * Computes one-pole coefficients from time constants.
32 * Attack: how fast the follower rises to meet a transient.
33 * Release: how fast it falls after the signal drops.
34 *
35 * @param attack_ms Attack time in milliseconds (typically 0.1–10)
36 * @param release_ms Release time in milliseconds (typically 50–500)
37 * @param sample_rate Audio sample rate (e.g. 48000)
38 */
39 void set_times(float attack_ms, float release_ms, uint32_t sample_rate) {
40 float sr = static_cast<float>(sample_rate);
41 // Time constant → coefficient: a = 1 - exp(-1 / (t * sr))
42 // For t in seconds: t = ms / 1000
43 // Approximation: 1 - exp(-x) ≈ x / (1 + x) for reasonable accuracy
44 float attack_samples = (attack_ms / 1000.0f) * sr;
45 float release_samples = (release_ms / 1000.0f) * sr;
46
47 if (attack_samples > 0.0f) {
48 float x = 1.0f / attack_samples;
49 attack_coeff_ = x / (1.0f + x);
50 } else {
51 attack_coeff_ = 1.0f; // Instant attack
52 }
53
54 if (release_samples > 0.0f) {
55 float x = 1.0f / release_samples;
56 release_coeff_ = x / (1.0f + x);
57 } else {
58 release_coeff_ = 1.0f; // Instant release
59 }
60 }
61
62 /**
63 * @brief Set attack and release coefficients directly
64 *
65 * Coefficient range [0.0, 1.0). Higher = faster response.
66 * 0.0 = no change (hold forever), approaching 1.0 = instant.
67 *
68 * @param attack Attack coefficient
69 * @param release Release coefficient
70 */
71 void set_coefficients(float attack, float release) {
72 attack_coeff_ = attack;
73 release_coeff_ = release;
74 }
75
76 /**
77 * @brief Process a single sample, return envelope value
78 *
79 * Full-wave rectifies the input, then applies asymmetric smoothing:
80 * if |input| > state: use attack coefficient (rise fast)
81 * if |input| < state: use release coefficient (fall slow)
82 *
83 * @param sample Input audio sample
84 * @return Current envelope value (always >= 0)
85 */
86 float process(float sample) {
87 // Full-wave rectification
88 float rectified = sample < 0.0f ? -sample : sample;
89
90 // Asymmetric one-pole: choose coefficient based on direction
91 float coeff = rectified > envelope_ ? attack_coeff_ : release_coeff_;
92 envelope_ += coeff * (rectified - envelope_);
93
94 return envelope_;
95 }
96
97 /**
98 * @brief Process a block, write envelope to output buffer
99 *
100 * Input and output can be the same buffer (in-place).
101 *
102 * @param in Input audio buffer
103 * @param out Output envelope buffer (always >= 0)
104 * @param frames Number of samples
105 */
106 void process(const float* in, float* out, uint16_t frames) {
107 for (uint16_t i = 0; i < frames; ++i) {
108 out[i] = process(in[i]);
109 }
110 }
111
112 /** @brief Current envelope value (can be read without processing) */
113 float value() const { return envelope_; }
114
115 /** @brief Reset envelope state to zero */
116 void reset() { envelope_ = 0.0f; }
117
118 /** @brief Reset envelope state to a specific value */
119 void reset(float initial) { envelope_ = initial; }
120
121private:
122 float envelope_ = 0.0f;
123 float attack_coeff_ = 0.0f;
124 float release_coeff_ = 0.0f;
125};
126
127} // namespace sbl::dsp::comp
128
129#endif // SBL_DSP_COMP_ENVELOPE_FOLLOWER_HPP_
void process(const float *in, float *out, uint16_t frames)
Process a block, write envelope to output buffer.
void reset()
Reset envelope state to zero.
void set_coefficients(float attack, float release)
Set attack and release coefficients directly.
void set_times(float attack_ms, float release_ms, uint32_t sample_rate)
Set attack and release times in milliseconds.
float process(float sample)
Process a single sample, return envelope value.
float value() const
Current envelope value (can be read without processing)
void reset(float initial)
Reset envelope state to a specific value.
Compositions: subcircuits of primitives.