1
+ #pragma once
2
+ #include " AudioLibs/AudioFFT.h"
3
+ #include < FastLED.h>
4
+
5
+ namespace audio_tools {
6
+ class LEDOutput ;
7
+ struct LEDOutputConfig ;
8
+
9
+ LEDOutput *selfLEDOutput = nullptr ;
10
+ // default callback function which implements led update
11
+ void fftLEDOutput (LEDOutputConfig *cfg, LEDOutput *matrix);
12
+ // led update for volume
13
+ void volumeLEDOutput (LEDOutputConfig *cfg, LEDOutput *matrix);
14
+ // default color
15
+ // CHSV getDefaultColor(int x, int y, int magnitude);
16
+ // fft mutex
17
+ Mutex fft_mux;
18
+
19
+ /* *
20
+ * LED Matrix Configuration. Provide the number of leds in x and y direction and
21
+ * the data pin.
22
+ * @author Phil Schatzmann
23
+ */
24
+ struct LEDOutputConfig {
25
+ // / Number of leds in x direction
26
+ int x = 0 ;
27
+ // / Number of leds in y direction
28
+ int y = 1 ;
29
+ // / optinal custom logic to provide CHSV color: Prividing a 'rainbow' color
30
+ // / with hue 0-255, saturating 0-255, and brightness (value) 0-255 (v2)
31
+ Color& (*color_callback)(int x, int y, int magnitude);// = getDefaultColor;
32
+ // / Custom callback logic to update the LEDs - by default we use
33
+ // / fftLEDOutput()
34
+ void (*update_callback)(LEDOutputConfig *cfg, LEDOutput *matrix) = nullptr ;
35
+ // / Update the leds only ever nth call
36
+ int update_frequency = 1 ; // update every call
37
+ bool is_serpentine_layout = true ;
38
+ bool is_matrix_vertical = true ;
39
+ // / start bin which is displayed
40
+ int fft_start_bin = 0 ;
41
+ // / group result by adding subsequent bins
42
+ int fft_group_bin = 1 ;
43
+ // / Influences the senitivity
44
+ int fft_max_magnitude = 700 ;
45
+ };
46
+
47
+ /* *
48
+ * @brief LEDOutput using the FastLED library. You write the data to the FFT Stream.
49
+ * This displays the result of the FFT to a LED matrix.
50
+ * @ingroup io
51
+ * @author Phil Schatzmann
52
+ */
53
+ template <class LED , class Color , LEDFunctions>
54
+ class LEDOutput {
55
+ public:
56
+ LEDOutput () = default ;
57
+
58
+ // / @brief Default Constructor
59
+ // / @param fft
60
+ LEDOutput (AudioFFTBase &fft) {
61
+ selfLEDOutput = this ;
62
+ p_fft = &fft;
63
+ cfg.update_callback = fftLEDOutput;
64
+ }
65
+
66
+ LEDOutput (VolumeOutput &vol) {
67
+ selfLEDOutput = this ;
68
+ p_vol = &vol;
69
+ cfg.update_callback = volumeLEDOutput;
70
+ }
71
+
72
+ // / Provides the default config object
73
+ LEDOutputConfig defaultConfig () { return cfg; }
74
+
75
+ // / Setup Led matrix
76
+ bool begin (LEDOutputConfig config) {
77
+ cfg = config;
78
+ // if (!*p_fft) {
79
+ // LOGE("fft not started");
80
+ // return false;
81
+ // }
82
+ if (ledCount () == 0 ) {
83
+ LOGE (" x or y == 0" );
84
+ return false ;
85
+ }
86
+
87
+ // allocate leds
88
+ leds.resize (ledCount ());
89
+ for (int j = 0 ; j < ledCount (); j++) {
90
+ led (j) = CRGB::Black;
91
+ }
92
+
93
+ // clear LED
94
+ FastLED.clear (); // clear all pixel data
95
+
96
+ if (p_fft != nullptr ) {
97
+ // assign fft callback
98
+ AudioFFTConfig &fft_cfg = p_fft->config ();
99
+ fft_cfg.callback = fftCallback;
100
+
101
+ // number of bins
102
+ magnitudes.resize (p_fft->size ());
103
+ for (int j = 0 ; j < p_fft->size (); j++) {
104
+ magnitudes[j] = 0 ;
105
+ }
106
+ }
107
+
108
+ return true ;
109
+ }
110
+
111
+ // / Provides the number of LEDs: call begin() first!
112
+ int ledCount () {
113
+ int num_leds = cfg.x * cfg.y ;
114
+ return num_leds;
115
+ }
116
+
117
+ // / Provides the address fo the CRGB array: call begin() first!
118
+ LED *ledData () {
119
+ if (ledCount () == 0 ) {
120
+ LOGE (" x or y == 0" );
121
+ return nullptr ;
122
+ }
123
+ // leds.resize(ledCount());
124
+ return leds.data ();
125
+ }
126
+
127
+ // / Updates the display: call this method in your loop
128
+ virtual void update () {
129
+ if (cfg.update_callback != nullptr && count++ % cfg.update_frequency == 0 ) {
130
+ // use custom update logic defined in config
131
+ cfg.update_callback (&cfg, this );
132
+ }
133
+ }
134
+
135
+ // / Determine the led with the help of the x and y pos
136
+ LED &ledXY (uint8_t x, uint8_t y) {
137
+ if (x > cfg.x )
138
+ x = cfg.x - 1 ;
139
+ if (x < 0 )
140
+ x = 0 ;
141
+ if (y > cfg.y )
142
+ y = cfg.y - 1 ;
143
+ if (y < 0 )
144
+ y = 0 ;
145
+ int index = xy (x, y);
146
+ return leds[index];
147
+ }
148
+
149
+ // / Determine the led with the help of the index pos
150
+ LED &led (uint8_t index) {
151
+ if (index > cfg.x * cfg.y )
152
+ return not_valid;
153
+ return leds[index];
154
+ }
155
+
156
+ // / Returns the magnitude for the indicated led x position. We might
157
+ // / need to combine values from the magnitudes array if this is much bigger.
158
+ virtual float getMagnitude (int x) {
159
+ // get magnitude from fft
160
+ float total = 0 ;
161
+ for (int j = 0 ; j < cfg.fft_group_bin ; j++) {
162
+ int idx = cfg.fft_start_bin + (x * cfg.fft_group_bin ) + j;
163
+ if (idx >= magnitudes.size ()) {
164
+ idx = magnitudes.size () - 1 ;
165
+ }
166
+ total += magnitudes[idx];
167
+ }
168
+ return total / cfg.fft_group_bin ;
169
+ }
170
+
171
+ // / @brief Provodes the max magnitude
172
+ virtual float getMaxMagnitude () {
173
+ // get magnitude from
174
+ if (p_vol != nullptr ) {
175
+ return p_vol->volume ();
176
+ }
177
+ float max = 0 ;
178
+ for (int j = 0 ; j < cfg.x ; j++) {
179
+ float value = getMagnitude (j);
180
+ if (value > max) {
181
+ max = value;
182
+ }
183
+ }
184
+ return max;
185
+ }
186
+
187
+ // / Update the indicated column with the indicated bar
188
+ void updateColumn (int x, int currY) {
189
+ // update vertical bar
190
+ for (uint8_t y = 0 ; y < currY; y++) {
191
+ // determine color
192
+ CHSV color = cfg.color_callback (x, y, currY);
193
+ // update LED
194
+ ledXY (x, y) = color;
195
+ }
196
+ for (uint8_t y = currY; y < cfg.y ; y++) {
197
+ ledXY (x, y) = CRGB::Black;
198
+ }
199
+ }
200
+
201
+ // / Update the last column with the indicated bar
202
+ void updateColumn (int currY) { updateColumn (cfg.x - 1 , currY); }
203
+
204
+ // / Adds an empty column to the end shifting the content to the left
205
+ void addEmptyColumn () {
206
+ for (int x = 1 ; x < cfg.x ; x++) {
207
+ for (int y = 0 ; y < cfg.y ; y++) {
208
+ ledXY (x - 1 , y) = ledXY (x, y);
209
+ }
210
+ }
211
+ for (int y = 0 ; y < cfg.y ; y++) {
212
+ ledXY (cfg.x - 1 , y) = CRGB::Black;
213
+ }
214
+ }
215
+
216
+ // / Provides access to the actual config object. E.g. to change the update
217
+ // / logic
218
+ LEDOutputConfig &config () { return cfg; }
219
+
220
+ protected:
221
+ friend class AudioFFTBase ;
222
+ LEDFunctions functions;
223
+ LED not_valid;
224
+ Vector<LED> leds{0 };
225
+ Vector<float > magnitudes{0 };
226
+ LEDOutputConfig cfg;
227
+ AudioFFTBase *p_fft = nullptr ;
228
+ VolumeOutput *p_vol = nullptr ;
229
+ uint64_t count = 0 ;
230
+
231
+ uint16_t xy (uint8_t x, uint8_t y) {
232
+ uint16_t i;
233
+
234
+ if (cfg.is_serpentine_layout == false ) {
235
+ if (cfg.is_matrix_vertical == false ) {
236
+ i = (y * cfg.x ) + x;
237
+ } else {
238
+ i = cfg.y * (cfg.x - (x + 1 )) + y;
239
+ }
240
+ }
241
+
242
+ if (cfg.is_serpentine_layout == true ) {
243
+ if (cfg.is_matrix_vertical == false ) {
244
+ if (y & 0x01 ) {
245
+ // Odd rows run backwards
246
+ uint8_t reverseX = (cfg.x - 1 ) - x;
247
+ i = (y * cfg.x ) + reverseX;
248
+ } else {
249
+ // Even rows run forwards
250
+ i = (y * cfg.x ) + x;
251
+ }
252
+ } else { // vertical positioning
253
+ if (x & 0x01 ) {
254
+ i = cfg.y * (cfg.x - (x + 1 )) + y;
255
+ } else {
256
+ i = cfg.y * (cfg.x - x) - (y + 1 );
257
+ }
258
+ }
259
+ }
260
+
261
+ return i;
262
+ }
263
+
264
+ // / callback method which provides updated data from fft
265
+ static void fftCallback (AudioFFTBase &fft) {
266
+ // just save magnitudes to be displayed
267
+ LockGuard guard (fft_mux);
268
+ for (int j = 0 ; j < fft.size (); j++) {
269
+ float value = fft.magnitude (j);
270
+ selfLEDOutput->magnitudes [j] = value;
271
+ }
272
+ };
273
+ };
274
+
275
+ // / Default update implementation which provides the fft result as "barchart"
276
+ void fftLEDOutput (LEDOutputConfig *cfg, LEDOutput *matrix) {
277
+ {
278
+ LockGuard guard (fft_mux);
279
+ // process horizontal
280
+ for (int x = 0 ; x < cfg->x ; x++) {
281
+ // max y determined by magnitude
282
+ int currY = mapFloat (matrix->getMagnitude (x), 0 , cfg->fft_max_magnitude ,
283
+ 0 .0f , static_cast <float >(cfg->y ));
284
+ LOGD (" x: %d, y: %d" , x, currY);
285
+ matrix->updateColumn (x, currY);
286
+ }
287
+ }
288
+ functions.show ();
289
+ }
290
+
291
+ // / Default update implementation which provides the fft result as "barchart"
292
+ void volumeLEDOutput (LEDOutputConfig *cfg, LEDOutput *matrix) {
293
+ float vol = matrix->getMaxMagnitude ();
294
+ int currY = mapFloat (matrix->getMagnitude (vol), 0 , cfg->fft_max_magnitude ,
295
+ 0 .0f , static_cast <float >(cfg->y ));
296
+ matrix->addEmptyColumn ();
297
+ matrix->updateColumn (currY);
298
+ functions.show ();
299
+ }
300
+
301
+
302
+ } // namespace audio_tools
0 commit comments