Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
spectrogram.hpp
Go to the documentation of this file.
1#pragma once
2
5
6#include <vector>
7#include <cmath>
8#include <algorithm>
9#include <array>
10#include <cstddef>
11#include <cstdint>
12
13namespace pulp::signal {
14
17 uint8_t r = 0, g = 0, b = 0, a = 255;
18};
19
22
28public:
30
32 SpectrogramColor map(float t) const {
33 // std::clamp(NaN, …) returns NaN, which the ramps then cast to
34 // uint8_t. Sanitize NaN to 0 first; ±Inf clamps safely to 1.0 / 0.0
35 // below.
36 if (std::isnan(t)) t = 0.0f;
37 t = std::clamp(t, 0.0f, 1.0f);
38
39 switch (ramp_) {
40 case ColorRamp::inferno: return map_inferno(t);
41 case ColorRamp::viridis: return map_viridis(t);
42 case ColorRamp::grayscale: return map_grayscale(t);
43 case ColorRamp::heat: return map_heat(t);
44 }
45 return map_grayscale(t);
46 }
47
48 void set_ramp(ColorRamp ramp) { ramp_ = ramp; }
49 ColorRamp ramp() const { return ramp_; }
50
51private:
52 ColorRamp ramp_;
53
54 struct CP { float t; uint8_t r, g, b; };
55
56 static SpectrogramColor interpolate(const CP* pts, int count, float t) {
57 if (t <= pts[0].t) return {pts[0].r, pts[0].g, pts[0].b, 255};
58 if (t >= pts[count - 1].t) return {pts[count-1].r, pts[count-1].g, pts[count-1].b, 255};
59
60 for (int i = 0; i < count - 1; ++i) {
61 if (t >= pts[i].t && t <= pts[i + 1].t) {
62 float f = (t - pts[i].t) / (pts[i + 1].t - pts[i].t);
63 return {
64 static_cast<uint8_t>(pts[i].r + f * (pts[i + 1].r - pts[i].r)),
65 static_cast<uint8_t>(pts[i].g + f * (pts[i + 1].g - pts[i].g)),
66 static_cast<uint8_t>(pts[i].b + f * (pts[i + 1].b - pts[i].b)),
67 255
68 };
69 }
70 }
71 return {0, 0, 0, 255};
72 }
73
74 static SpectrogramColor map_inferno(float t) {
75 static constexpr CP pts[] = {
76 {0.0f, 0, 0, 4},
77 {0.25f, 87, 16, 110},
78 {0.5f, 188, 55, 84},
79 {0.75f, 249, 142, 9},
80 {1.0f, 252, 255, 164},
81 };
82 return interpolate(pts, 5, t);
83 }
84
85 static SpectrogramColor map_viridis(float t) {
86 static constexpr CP pts[] = {
87 {0.0f, 68, 1, 84},
88 {0.25f, 59, 82, 139},
89 {0.5f, 33, 145, 140},
90 {0.75f, 94, 201, 98},
91 {1.0f, 253, 231, 37},
92 };
93 return interpolate(pts, 5, t);
94 }
95
96 static SpectrogramColor map_grayscale(float t) {
97 auto v = static_cast<uint8_t>(t * 255);
98 return {v, v, v, 255};
99 }
100
101 static SpectrogramColor map_heat(float t) {
102 static constexpr CP pts[] = {
103 {0.0f, 0, 0, 0},
104 {0.33f, 180, 0, 0},
105 {0.66f, 255, 200, 0},
106 {1.0f, 255, 255, 255},
107 };
108 return interpolate(pts, 4, t);
109 }
110};
111
114
119public:
121 void configure(int fft_size, float sample_rate, FrequencyScale scale = FrequencyScale::logarithmic) {
122 fft_size_ = fft_size;
123 sample_rate_ = sample_rate;
124 scale_ = scale;
125 num_bins_ = fft_size / 2 + 1;
126 nyquist_ = sample_rate / 2.0f;
127 }
128
130 float bin_to_display(int bin) const {
131 float freq = bin_to_hz(bin);
132 return hz_to_display(freq);
133 }
134
136 int display_to_bin(float display_pos) const {
137 float freq = display_to_hz(display_pos);
138 return hz_to_bin(freq);
139 }
140
142 float bin_to_hz(int bin) const {
143 return static_cast<float>(bin) * sample_rate_ / fft_size_;
144 }
145
147 int hz_to_bin(float hz) const {
148 int bin = static_cast<int>(std::round(hz * fft_size_ / sample_rate_));
149 return std::clamp(bin, 0, num_bins_ - 1);
150 }
151
153 float hz_to_display(float hz) const {
154 hz = std::clamp(hz, 1.0f, nyquist_);
155 switch (scale_) {
157 return hz / nyquist_;
159 return std::log2(hz) / std::log2(nyquist_);
161 return hz_to_mel(hz) / hz_to_mel(nyquist_);
162 }
163 return hz / nyquist_;
164 }
165
167 float display_to_hz(float pos) const {
168 pos = std::clamp(pos, 0.0f, 1.0f);
169 switch (scale_) {
171 return pos * nyquist_;
173 return std::pow(2.0f, pos * std::log2(nyquist_));
175 return mel_to_hz(pos * hz_to_mel(nyquist_));
176 }
177 return pos * nyquist_;
178 }
179
180 int num_bins() const { return num_bins_; }
181 float nyquist() const { return nyquist_; }
182 FrequencyScale scale() const { return scale_; }
183
184private:
185 int fft_size_ = 1024;
186 float sample_rate_ = 44100.0f;
188 int num_bins_ = 513;
189 float nyquist_ = 22050.0f;
190
191 static float hz_to_mel(float hz) {
192 return 2595.0f * std::log10(1.0f + hz / 700.0f);
193 }
194
195 static float mel_to_hz(float mel) {
196 return 700.0f * (std::pow(10.0f, mel / 2595.0f) - 1.0f);
197 }
198};
199
206public:
210 void configure(int width, int height) {
211 width_ = std::max(0, width);
212 height_ = std::max(0, height);
213 pixels_.resize(static_cast<std::size_t>(width_) * static_cast<std::size_t>(height_));
214 write_col_ = 0;
215 frames_written_ = 0;
216 }
217
221 template <typename SampleType = float>
222 void push_column(const SampleType* magnitudes_db, int num_bins,
223 const ColorMapper& mapper,
224 float min_db = -80.0f, float max_db = 0.0f) {
225 if (width_ <= 0 || height_ <= 0) return;
226
227 float range = max_db - min_db;
228 if (range <= 0) range = 1.0f;
229
230 for (int row = 0; row < height_; ++row) {
231 // Map row to bin (simple linear mapping; for log/mel, caller
232 // should pre-resample the magnitudes)
233 int bin = (num_bins > 0 && height_ > 0)
234 ? std::clamp(row * num_bins / height_, 0, num_bins - 1)
235 : 0;
236
237 float magnitude_db = magnitudes_db != nullptr && num_bins > 0
238 ? static_cast<float>(magnitudes_db[bin])
239 : min_db;
240 float normalized = (magnitude_db - min_db) / range;
241 pixels_[row * width_ + write_col_] = mapper.map(normalized);
242 }
243
244 write_col_ = (write_col_ + 1) % width_;
245 ++frames_written_;
246 }
247
248 void push_column(std::nullptr_t magnitudes_db, int num_bins,
249 const ColorMapper& mapper,
250 float min_db = -80.0f, float max_db = 0.0f) {
251 push_column<float>(magnitudes_db, num_bins, mapper, min_db, max_db);
252 }
253
257 const SpectrogramColor* pixels() const { return pixels_.data(); }
258
260 int write_column() const { return write_col_; }
261
262 int width() const { return width_; }
263 int height() const { return height_; }
264 int frames_written() const { return frames_written_; }
265
266private:
267 int width_ = 0;
268 int height_ = 0;
269 int write_col_ = 0;
270 int frames_written_ = 0;
271 std::vector<SpectrogramColor> pixels_;
272};
273
274} // namespace pulp::signal
Maps a normalized value [0,1] to a color using a predefined ramp.
Definition spectrogram.hpp:27
ColorMapper(ColorRamp ramp=ColorRamp::inferno)
Definition spectrogram.hpp:29
ColorRamp ramp() const
Definition spectrogram.hpp:49
SpectrogramColor map(float t) const
Map a normalized value (0 = minimum, 1 = maximum) to a color.
Definition spectrogram.hpp:32
void set_ramp(ColorRamp ramp)
Definition spectrogram.hpp:48
Utilities for mapping FFT bins to display coordinates.
Definition spectrogram.hpp:118
void configure(int fft_size, float sample_rate, FrequencyScale scale=FrequencyScale::logarithmic)
Configure the axis for a given FFT size and sample rate.
Definition spectrogram.hpp:121
float bin_to_hz(int bin) const
Convert bin index to frequency in Hz.
Definition spectrogram.hpp:142
float nyquist() const
Definition spectrogram.hpp:181
FrequencyScale scale() const
Definition spectrogram.hpp:182
int hz_to_bin(float hz) const
Convert frequency in Hz to nearest bin index.
Definition spectrogram.hpp:147
float bin_to_display(int bin) const
Map a frequency bin index to a normalized display position [0, 1].
Definition spectrogram.hpp:130
float hz_to_display(float hz) const
Map Hz to normalized display position [0, 1].
Definition spectrogram.hpp:153
float display_to_hz(float pos) const
Map normalized display position [0, 1] back to Hz.
Definition spectrogram.hpp:167
int num_bins() const
Definition spectrogram.hpp:180
int display_to_bin(float display_pos) const
Map a normalized display position [0, 1] to the nearest bin index.
Definition spectrogram.hpp:136
Scrolling spectrogram buffer that accumulates STFT frames as rows.
Definition spectrogram.hpp:205
void push_column(const SampleType *magnitudes_db, int num_bins, const ColorMapper &mapper, float min_db=-80.0f, float max_db=0.0f)
Push a new STFT frame as a column of colors.
Definition spectrogram.hpp:222
void push_column(std::nullptr_t magnitudes_db, int num_bins, const ColorMapper &mapper, float min_db=-80.0f, float max_db=0.0f)
Definition spectrogram.hpp:248
int height() const
Definition spectrogram.hpp:263
int write_column() const
Get the column index where the next frame will be written.
Definition spectrogram.hpp:260
int frames_written() const
Definition spectrogram.hpp:264
int width() const
Definition spectrogram.hpp:262
const SpectrogramColor * pixels() const
Access the pixel buffer for rendering.
Definition spectrogram.hpp:257
void configure(int width, int height)
Configure the buffer.
Definition spectrogram.hpp:210
@ count
sentinel: number of real capabilities (keep LAST)
Definition canvas_capability.hpp:38
@ f
Definition pitch.hpp:25
Definition additive_bank.hpp:218
float magnitude_db(const BiquadT< SampleType > &filter, double freq_hz, double sample_rate)
Ask a live filter what it looks like: the magnitude, in dB, that this BiquadT is applying at freq_hz ...
Definition frequency_response.hpp:130
@ logarithmic
Definition modulation_curve.hpp:29
FrequencyScale
Frequency axis scaling modes.
Definition spectrogram.hpp:113
@ mel
Definition spectrogram.hpp:113
@ logarithmic
Definition spectrogram.hpp:113
@ linear
Definition spectrogram.hpp:113
@ i
Definition formant_filter_bank.hpp:20
@ linear
2-point. Cheapest; audible imaging when pitching up.
Definition granular.hpp:133
ColorRamp
Predefined color ramp types for spectrogram rendering.
Definition spectrogram.hpp:21
@ viridis
Definition spectrogram.hpp:21
@ grayscale
Definition spectrogram.hpp:21
@ heat
Definition spectrogram.hpp:21
@ inferno
Definition spectrogram.hpp:21
@ v
Definition input_events.hpp:41
@ t
Definition input_events.hpp:41
RGBA color for spectrogram pixels.
Definition spectrogram.hpp:16
uint8_t a
Definition spectrogram.hpp:17
uint8_t g
Definition spectrogram.hpp:17
uint8_t b
Definition spectrogram.hpp:17
uint8_t r
Definition spectrogram.hpp:17