FastLED 3.9.15
Loading...
Searching...
No Matches
stb_hexwave.cpp.hpp
Go to the documentation of this file.
1// stb_hexwave.cpp.hpp - Hexwave audio oscillator implementation
2//
3// This file contains the implementation. For the API declarations,
4// include "third_party/stb/hexwave/stb_hexwave.h" instead.
5//
6// Original: https://github.com/nothings/stb/blob/master/stb_hexwave.h
7// Author: Sean Barrett - http://nothings.org/stb_vorbis/
8// License: MIT or Public Domain
9//
10// stb_hexwave - v0.5 - public domain, initial release 2021-04-01
11//
12// A flexible anti-aliased (bandlimited) digital audio oscillator.
13//
14// This library generates waveforms of a variety of shapes made of
15// line segments. It does not do envelopes, LFO effects, etc.; it
16// merely tries to solve the problem of generating an artifact-free
17// morphable digital waveform with a variety of spectra, and leaves
18// it to the user to rescale the waveform and mix multiple voices, etc.
19//
20// LICENSE
21//
22// See end of file for license information.
23
25//
26// Include the standalone header for API declarations
27//
29
31//
32// IMPLEMENTATION BEGINS HERE
33//
35
36#ifndef FL_STB_HEXWAVE_HEADER_ONLY
37
38// FastLED stl wrappers instead of standard library headers
39#include "fl/stl/malloc.h" // For fl::malloc, fl::free
40#include "fl/stl/cstring.h" // For fl::memset, fl::memcpy, fl::memmove
41#include "fl/math/math.h" // For fl::sin, fl::cos, fl::fabs
42
43namespace fl {
44namespace third_party {
45namespace hexwave {
46
47// Import fl:: functions for use within this namespace
48using fl::malloc;
49using fl::free;
50using fl::memset;
51using fl::memcpy;
52using fl::memmove;
53using fl::sin;
54using fl::cos;
55using fl::fabs;
56
57#define hexwave_clamp(v,a,b) ((v) < (a) ? (a) : (v) > (b) ? (b) : (v))
58
59void hexwave_change(HexWave *hex, int32_t reflect, float peak_time, float half_height, float zero_wait) FL_NOEXCEPT
60{
61 hex->pending.reflect = reflect;
62 hex->pending.peak_time = hexwave_clamp(peak_time,0.0f,1.0f);
63 hex->pending.half_height = half_height;
64 hex->pending.zero_wait = hexwave_clamp(zero_wait,0.0f,1.0f);
65 // put a barrier here to allow changing from a different thread than the generator
66 hex->have_pending = 1;
67}
68
69void hexwave_create(HexWave *hex, HexWaveEngine *engine, int32_t reflect, float peak_time, float half_height, float zero_wait) FL_NOEXCEPT
70{
71 memset(hex, 0, sizeof(*hex));
72 hex->engine = engine;
73 hexwave_change(hex, reflect, peak_time, half_height, zero_wait);
74 hex->current = hex->pending;
75 hex->have_pending = 0;
76 hex->t = 0;
77 hex->prev_dt = 0;
78}
79
80// Global engine for legacy API - lazily initialized
81static HexWaveEngine* sGlobalEngine = nullptr;
82
83void hexwave_create_legacy(HexWave *hex, int32_t reflect, float peak_time, float half_height, float zero_wait) FL_NOEXCEPT
84{
85 hexwave_create(hex, sGlobalEngine, reflect, peak_time, half_height, zero_wait);
86}
87
88static void hex_add_oversampled_bleplike(float *output, float time_since_transition, float scale, float *data, HexWaveEngine *engine) FL_NOEXCEPT
89{
90 float *d1,*d2;
91 float lerpweight;
92 int32_t i, bw = engine->width;
93
94 int32_t slot = (int32_t) (time_since_transition * engine->oversample);
95 if (slot >= engine->oversample)
96 slot = engine->oversample-1; // clamp in case the floats overshoot
97
98 d1 = &data[ slot *bw];
99 d2 = &data[(slot+1)*bw];
100
101 lerpweight = time_since_transition * engine->oversample - slot;
102 for (i=0; i < bw; ++i)
103 output[i] += scale * (d1[i] + (d2[i]-d1[i])*lerpweight);
104}
105
106static void hex_blep (float *output, float time_since_transition, float scale, HexWaveEngine *engine) FL_NOEXCEPT
107{
108 hex_add_oversampled_bleplike(output, time_since_transition, scale, engine->blep, engine);
109}
110
111static void hex_blamp(float *output, float time_since_transition, float scale, HexWaveEngine *engine) FL_NOEXCEPT
112{
113 hex_add_oversampled_bleplike(output, time_since_transition, scale, engine->blamp, engine);
114}
115
116typedef struct
117{
118 float t,v,s; // time, value, slope
119} hexvert;
120
121// each half of the waveform needs 4 vertices to represent 3 line
122// segments, plus 1 more for wraparound
124{
125 int32_t j;
126 float min_len = dt / 256.0f;
127
128 vert[0].t = 0;
129 vert[0].v = 0;
130 vert[1].t = hex->current.zero_wait*0.5f;
131 vert[1].v = 0;
132 vert[2].t = 0.5f*hex->current.peak_time + vert[1].t*(1-hex->current.peak_time);
133 vert[2].v = 1;
134 vert[3].t = 0.5f;
135 vert[3].v = hex->current.half_height;
136
137 if (hex->current.reflect) {
138 for (j=4; j <= 7; ++j) {
139 vert[j].t = 1 - vert[7-j].t;
140 vert[j].v = - vert[7-j].v;
141 }
142 } else {
143 for (j=4; j <= 7; ++j) {
144 vert[j].t = 0.5f + vert[j-4].t;
145 vert[j].v = - vert[j-4].v;
146 }
147 }
148 vert[8].t = 1;
149 vert[8].v = 0;
150
151 for (j=0; j < 8; ++j) {
152 if (vert[j+1].t <= vert[j].t + min_len) {
153 // if change takes place over less than a fraction of a sample treat as discontinuity
154 //
155 // otherwise the slope computation can blow up to arbitrarily large and we
156 // try to generate a huge BLAMP and the result is wrong.
157 //
158 // why does this happen if the math is right? i believe if done perfectly,
159 // the two BLAMPs on either side of the slope would cancel out, but our
160 // BLAMPs have only limited sub-sample precision and limited integration
161 // accuracy. or maybe it's just the math blowing up w/ floating point precision
162 // limits as we try to make x * (1/x) cancel out
163 //
164 // min_len verified artifact-free even near nyquist with only oversample=4
165 vert[j+1].t = vert[j].t;
166 }
167 }
168
169 if (vert[8].t != 1.0f) {
170 // if the above fixup moved the endpoint away from 1.0, move it back,
171 // along with any other vertices that got moved to the same time
172 float t = vert[8].t;
173 for (j=5; j <= 8; ++j)
174 if (vert[j].t == t)
175 vert[j].t = 1.0f;
176 }
177
178 // compute the exact slopes from the final fixed-up positions
179 for (j=0; j < 8; ++j)
180 if (vert[j+1].t == vert[j].t)
181 vert[j].s = 0;
182 else
183 vert[j].s = (vert[j+1].v - vert[j].v) / (vert[j+1].t - vert[j].t);
184
185 // wraparound at end
186 vert[8].t = 1;
187 vert[8].v = vert[0].v;
188 vert[8].s = vert[0].s;
189}
190
191void hexwave_generate_samples(float *output, int32_t num_samples, HexWave *hex, float freq) FL_NOEXCEPT
192{
193 HexWaveEngine *engine = hex->engine;
194 hexvert vert[9];
195 int32_t pass,i,j;
196 float t = hex->t;
197 float temp_output[2*FL_STB_HEXWAVE_MAX_BLEP_LENGTH];
198 int32_t buffered_length = static_cast<int32_t>(sizeof(float)*engine->width);
199 float dt = (float) fabs(freq);
200 float recip_dt = (dt == 0.0f) ? 0.0f : 1.0f / dt;
201
202 int32_t halfw = engine->width/2;
203 // all sample times are biased by halfw to leave room for BLEP/BLAMP to go back in time
204
205 if (num_samples <= 0)
206 return;
207
208 // convert parameters to times and slopes
210
211 if (hex->prev_dt != dt) {
212 // if frequency changes, add a fixup at the derivative discontinuity starting at now
213 float slope;
214 for (j=1; j < 6; ++j)
215 if (t < vert[j].t)
216 break;
217 slope = vert[j].s;
218 if (slope != 0)
219 hex_blamp(output, 0, (dt - hex->prev_dt)*slope, engine);
220 hex->prev_dt = dt;
221 }
222
223 // copy the buffered data from last call and clear the rest of the output array
224 memset(output, 0, sizeof(float)*num_samples);
225 memset(temp_output, 0, 2*engine->width*sizeof(float));
226
227 if (num_samples >= engine->width) {
228 memcpy(output, hex->buffer, buffered_length);
229 } else {
230 // if the output is shorter than engine->width, we do all synthesis to temp_output
231 memcpy(temp_output, hex->buffer, buffered_length);
232 }
233
234 for (pass=0; pass < 2; ++pass) {
235 int32_t i0,i1;
236 float *out;
237
238 // we want to simulate having one buffer that is num_output + engine->width
239 // samples long, without putting that requirement on the user, and without
240 // allocating a temp buffer that's as long as the whole thing. so we use two
241 // overlapping buffers, one the user's buffer and one a fixed-length temp
242 // buffer.
243
244 if (pass == 0) {
245 if (num_samples < engine->width)
246 continue;
247 // run as far as we can without overwriting the end of the user's buffer
248 out = output;
249 i0 = 0;
250 i1 = num_samples - engine->width;
251 } else {
252 // generate the rest into a temp buffer
253 out = temp_output;
254 i0 = 0;
255 if (num_samples >= engine->width)
256 i1 = engine->width;
257 else
258 i1 = num_samples;
259 }
260
261 // determine current segment
262 for (j=0; j < 8; ++j)
263 if (t < vert[j+1].t)
264 break;
265
266 i = i0;
267 for(;;) {
268 while (t < vert[j+1].t) {
269 if (i == i1)
270 goto done;
271 out[i+halfw] += vert[j].v + vert[j].s*(t - vert[j].t);
272 t += dt;
273 ++i;
274 }
275 // transition from lineseg starting at j to lineseg starting at j+1
276
277 if (vert[j].t == vert[j+1].t)
278 hex_blep(out+i, recip_dt*(t-vert[j+1].t), (vert[j+1].v - vert[j].v), engine);
279 hex_blamp(out+i, recip_dt*(t-vert[j+1].t), dt*(vert[j+1].s - vert[j].s), engine);
280 ++j;
281
282 if (j == 8) {
283 // change to different waveform if there's a change pending
284 j = 0;
285 t -= 1.0f; // t was >= 1.f if j==8
286 if (hex->have_pending) {
287 float prev_s0 = vert[j].s;
288 float prev_v0 = vert[j].v;
289 hex->current = hex->pending;
290 hex->have_pending = 0;
292 // the following never occurs with this oscillator, but it makes
293 // the code work in more general cases
294 if (vert[j].v != prev_v0)
295 hex_blep (out+i, recip_dt*t, (vert[j].v - prev_v0), engine);
296 if (vert[j].s != prev_s0)
297 hex_blamp(out+i, recip_dt*t, dt*(vert[j].s - prev_s0), engine);
298 }
299 }
300 }
301 done:
302 ;
303 }
304
305 // at this point, we've written output[] and temp_output[]
306 if (num_samples >= engine->width) {
307 // the first half of temp[] overlaps the end of output, the second half will be the new start overlap
308 for (i=0; i < engine->width; ++i)
309 output[num_samples-engine->width + i] += temp_output[i];
310 memcpy(hex->buffer, temp_output+engine->width, buffered_length);
311 } else {
312 for (i=0; i < num_samples; ++i)
313 output[i] = temp_output[i];
314 memcpy(hex->buffer, temp_output+num_samples, buffered_length);
315 }
316
317 hex->t = t;
318}
319
321{
322 if (engine == nullptr)
323 return;
324
325 #ifndef FL_STB_HEXWAVE_NO_ALLOCATION
326 if (engine->ownsBuffers) {
327 free(engine->blep);
328 free(engine->blamp);
329 }
330 free(engine);
331 #endif
332}
333
334// buffer should be NULL or must be 4*(width*(oversample+1)*2 +
336{
339
340 int32_t halfwidth = width/2;
341 int32_t half = halfwidth*oversample;
342 int32_t blep_buffer_count = width*(oversample+1);
343 int32_t n = 2*half+1;
344
345 #ifdef FL_STB_HEXWAVE_NO_ALLOCATION
346 (void)blep_buffer_count;
347 if (user_buffer == nullptr)
348 return nullptr; // Cannot allocate in no-allocation mode
349 float *buffers = user_buffer;
350 #else
351 float *buffers = user_buffer ? user_buffer : (float *) malloc(sizeof(float) * n * 2);
352 #endif
353
354 float *step = buffers+0*n;
355 float *ramp = buffers+1*n;
356 float *blep_buffer, *blamp_buffer;
357 double integrate_impulse=0, integrate_step=0;
358 int32_t i,j;
359
360 bool ownsBuffers = false;
361 if (user_buffer == 0) {
362 #ifndef FL_STB_HEXWAVE_NO_ALLOCATION
363 blep_buffer = (float *) malloc(sizeof(float)*blep_buffer_count);
364 blamp_buffer = (float *) malloc(sizeof(float)*blep_buffer_count);
365 ownsBuffers = true;
366 #else
367 blep_buffer = nullptr;
368 blamp_buffer = nullptr;
369 #endif
370 } else {
371 blep_buffer = ramp+n;
372 blamp_buffer = blep_buffer + blep_buffer_count;
373 }
374
375 // compute BLEP and BLAMP by integrating windowed sinc
376 for (i=0; i < n; ++i) {
377 for (j=0; j < 16; ++j) {
378 float sinc_t = 3.141592f* (i-half) / oversample;
379 float sinc = (i==half) ? 1.0f : (float) sin(static_cast<double>(sinc_t)) / (sinc_t);
380 float wt = 2.0f*3.1415926f * i / (n-1);
381 float window = (float) (0.355768 - 0.487396*cos(static_cast<double>(wt)) + 0.144232*cos(2*static_cast<double>(wt)) - 0.012604*cos(3*static_cast<double>(wt))); // Nuttall
382 double value = window * sinc;
383 integrate_impulse += value/16;
384 integrate_step += integrate_impulse/16;
385 }
386 step[i] = (float) integrate_impulse;
387 ramp[i] = (float) integrate_step;
388 }
389
390 // renormalize
391 for (i=0; i < n; ++i) {
392 step[i] = step[i] * (float) (1.0 / step[n-1]); // step needs to reach to 1.0
393 ramp[i] = ramp[i] * (float) (halfwidth / ramp[n-1]); // ramp needs to become a slope of 1.0 after oversampling
394 }
395
396 // deinterleave to allow efficient interpolation e.g. w/SIMD
397 for (j=0; j <= oversample; ++j) {
398 for (i=0; i < width; ++i) {
399 blep_buffer [j*width+i] = step[j+i*oversample];
400 blamp_buffer[j*width+i] = ramp[j+i*oversample];
401 }
402 }
403
404 // subtract out the naive waveform; note we can't do this to the raw data
405 // above, because we want the discontinuity to be in a different locations
406 // for j=0 and j=oversample (which exists to provide something to interpolate against)
407 for (j=0; j <= oversample; ++j) {
408 // subtract step
409 for (i=halfwidth; i < width; ++i)
410 blep_buffer [j*width+i] -= 1.0f;
411 // subtract ramp
412 for (i=halfwidth; i < width; ++i)
413 blamp_buffer[j*width+i] -= (j+i*oversample-half)*(1.0f/oversample);
414 }
415
416 // Allocate and initialize engine
417 #ifndef FL_STB_HEXWAVE_NO_ALLOCATION
418 HexWaveEngine *engine = (HexWaveEngine *) malloc(sizeof(HexWaveEngine));
419 engine->blep = blep_buffer;
420 engine->blamp = blamp_buffer;
421 engine->width = width;
422 engine->oversample = oversample;
423 engine->ownsBuffers = ownsBuffers;
424
425 if (user_buffer == 0)
426 free(buffers);
427
428 return engine;
429 #else
430 (void)ownsBuffers;
431 return nullptr;
432 #endif
433}
434
436//
437// LEGACY API IMPLEMENTATION
438//
440
441void hexwave_shutdown(float *user_buffer) FL_NOEXCEPT
442{
443 (void)user_buffer;
444 if (sGlobalEngine != nullptr) {
446 sGlobalEngine = nullptr;
447 }
448}
449
450void hexwave_init(int32_t width, int32_t oversample, float *user_buffer) FL_NOEXCEPT
451{
452 if (sGlobalEngine != nullptr) {
454 }
455 sGlobalEngine = hexwave_engine_create(width, oversample, user_buffer);
456}
457
458#undef hexwave_clamp
459
460} // namespace hexwave
461} // namespace third_party
462} // namespace fl
463
464#endif // FL_STB_HEXWAVE_HEADER_ONLY
465
466/*
467------------------------------------------------------------------------------
468This software is available under 2 licenses -- choose whichever you prefer.
469------------------------------------------------------------------------------
470ALTERNATIVE A - MIT License
471Copyright (c) 2017 Sean Barrett
472Permission is hereby granted, free of charge, to any person obtaining a copy of
473this software and associated documentation files (the "Software"), to deal in
474the Software without restriction, including without limitation the rights to
475use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
476of the Software, and to permit persons to whom the Software is furnished to do
477so, subject to the following conditions:
478The above copyright notice and this permission notice shall be included in all
479copies or substantial portions of the Software.
480THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
481IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
482FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
483AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
484LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
485OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
486SOFTWARE.
487------------------------------------------------------------------------------
488ALTERNATIVE B - Public Domain (www.unlicense.org)
489This is free and unencumbered software released into the public domain.
490Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
491software, either in source code form or as a compiled binary, for any purpose,
492commercial or non-commercial, and by any means.
493In jurisdictions that recognize copyright laws, the author or authors of this
494software dedicate any and all copyright interest in the software to the public
495domain. We make this dedication for the benefit of the public at large and to
496the detriment of our heirs and successors. We intend this dedication to be an
497overt act of relinquishment in perpetuity of all present and future rights to
498this software under copyright law.
499THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
500IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
501FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
502AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
503OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
504SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
505------------------------------------------------------------------------------
506*/
bool done
fl::UISlider scale("Scale", 4,.1, 4,.1)
void hexwave_engine_destroy(HexWaveEngine *engine) FL_NOEXCEPT
Destroy a HexWaveEngine and free its resources.
void * memcpy(void *dest, const void *src, size_t n) FL_NOEXCEPT
static HexWaveEngine * sGlobalEngine
double fabs(double value) FL_NOEXCEPT
Definition math.h:509
static void hex_blep(float *output, float time_since_transition, float scale, HexWaveEngine *engine) FL_NOEXCEPT
void * memset(void *s, int c, size_t n) FL_NOEXCEPT
static void hex_add_oversampled_bleplike(float *output, float time_since_transition, float scale, float *data, HexWaveEngine *engine) FL_NOEXCEPT
void hexwave_create(HexWave *hex, HexWaveEngine *engine, int32_t reflect, float peak_time, float half_height, float zero_wait) FL_NOEXCEPT
Create a new oscillator with the given waveform parameters.
void hexwave_change(HexWave *hex, int32_t reflect, float peak_time, float half_height, float zero_wait) FL_NOEXCEPT
Change oscillator waveform parameters (takes effect at next cycle boundary)
void * malloc(size_t size)
Definition malloc.cpp.hpp:9
void hexwave_create_legacy(HexWave *hex, int32_t reflect, float peak_time, float half_height, float zero_wait) FL_NOEXCEPT
Create oscillator using global engine (DEPRECATED)
enable_if< is_fixed_point< T >::value, T >::type cos(T angle) FL_NOEXCEPT
void hexwave_init(int32_t width, int32_t oversample, float *user_buffer) FL_NOEXCEPT
Initialize the hexwave library (DEPRECATED - use hexwave_engine_create)
enable_if< is_fixed_point< T >::value, T >::type sin(T angle) FL_NOEXCEPT
void hexwave_shutdown(float *user_buffer) FL_NOEXCEPT
Shutdown the hexwave library (DEPRECATED - use hexwave_engine_destroy)
HexWaveEngine * hexwave_engine_create(int32_t width, int32_t oversample, float *user_buffer) FL_NOEXCEPT
Create and initialize a new HexWaveEngine.
static void hexwave_generate_linesegs(hexvert vert[9], HexWave *hex, float dt) FL_NOEXCEPT
void hexwave_generate_samples(float *output, int32_t num_samples, HexWave *hex, float freq) FL_NOEXCEPT
Generate audio samples.
static void hex_blamp(float *output, float time_since_transition, float scale, HexWaveEngine *engine) FL_NOEXCEPT
float * blep
Band-limited step table.
bool ownsBuffers
True if engine allocated blep/blamp (vs user-provided)
float * blamp
Band-limited ramp table.
int32_t oversample
Number of oversampled versions.
int32_t width
Width of fixup in samples (4..64)
Definition stb_hexwave.h:99
Engine state holding BLEP/BLAMP tables.
Definition stb_hexwave.h:98
fl::i32 int32_t
Definition coder.h:220
void * memcpy(void *dest, const void *src, size_t n) FL_NOEXCEPT
double fabs(double value) FL_NOEXCEPT
Definition math.h:509
constexpr int type_rank< T >::value
void * memset(void *s, int c, size_t n) FL_NOEXCEPT
const hex_t hex
Definition ios.cpp.hpp:6
void * malloc(size_t size)
Definition malloc.cpp.hpp:9
u8 width
Definition blur.h:186
void * memmove(void *dest, const void *src, size_t n) FL_NOEXCEPT
void free(void *ptr)
enable_if< is_fixed_point< T >::value, T >::type cos(T angle) FL_NOEXCEPT
constexpr enable_if< is_fixed_point< T >::value, T >::type step(T edge, T x) FL_NOEXCEPT
enable_if< is_fixed_point< T >::value, T >::type sin(T angle) FL_NOEXCEPT
Base definition for an LED controller.
Definition crgb.hpp:179
#define FL_NOEXCEPT
#define hexwave_clamp(v, a, b)
#define FL_STB_HEXWAVE_MAX_BLEP_LENGTH
Definition stb_hexwave.h:67