You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and dots ('.'), can be up to 35 characters long. Letters must be lowercase.
680 lines
27 KiB
680 lines
27 KiB
// stb_hexwave - v0.5 - public domain, initial release 2021-04-01 |
|
// |
|
// A flexible anti-aliased (bandlimited) digital audio oscillator. |
|
// |
|
// This library generates waveforms of a variety of shapes made of |
|
// line segments. It does not do envelopes, LFO effects, etc.; it |
|
// merely tries to solve the problem of generating an artifact-free |
|
// morphable digital waveform with a variety of spectra, and leaves |
|
// it to the user to rescale the waveform and mix multiple voices, etc. |
|
// |
|
// Compiling: |
|
// |
|
// In one C/C++ file that #includes this file, do |
|
// |
|
// #define STB_HEXWAVE_IMPLEMENTATION |
|
// #include "stb_hexwave.h" |
|
// |
|
// Optionally, #define STB_HEXWAVE_STATIC before including |
|
// the header to cause the definitions to be private to the |
|
// implementation file (i.e. to be "static" instead of "extern"). |
|
// |
|
// Notes: |
|
// |
|
// Optionally performs memory allocation during initialization, |
|
// never allocates otherwise. |
|
// |
|
// License: |
|
// |
|
// See end of file for license information. |
|
// |
|
// Usage: |
|
// |
|
// Initialization: |
|
// |
|
// hexwave_init(32,16,NULL); // read "header section" for alternatives |
|
// |
|
// Create oscillator: |
|
// |
|
// HexWave *osc = malloc(sizeof(*osc)); // or "new HexWave", or declare globally or on stack |
|
// hexwave_create(osc, reflect_flag, peak_time, half_height, zero_wait); |
|
// see "Waveform shapes" below for the meaning of these parameters |
|
// |
|
// Generate audio: |
|
// |
|
// hexwave_generate_samples(output, number_of_samples, osc, oscillator_freq) |
|
// where: |
|
// output is a buffer where the library will store floating point audio samples |
|
// number_of_samples is the number of audio samples to generate |
|
// osc is a pointer to a Hexwave |
|
// oscillator_freq is the frequency of the oscillator divided by the sample rate |
|
// |
|
// The output samples will continue from where the samples generated by the |
|
// previous hexwave_generate_samples() on this oscillator ended. |
|
// |
|
// Change oscillator waveform: |
|
// |
|
// hexwave_change(osc, reflect_flag, peak_time, half_height, zero_wait); |
|
// can call in between calls to hexwave_generate_samples |
|
// |
|
// Waveform shapes: |
|
// |
|
// All waveforms generated by hexwave are constructed from six line segments |
|
// characterized by 3 parameters. |
|
// |
|
// See demonstration: https://www.youtube.com/watch?v=hsUCrAsDN-M |
|
// |
|
// reflect=0 reflect=1 |
|
// |
|
// 0-----P---1 0-----P---1 peak_time = P |
|
// . 1 . 1 |
|
// /\_ : /\_ : |
|
// / \_ : / \_ : |
|
// / \.H / \.H half_height = H |
|
// / | : / | : |
|
// _____/ |_:___ _____/ | : _____ |
|
// . : \ | . | : / |
|
// . : \ | . | : / |
|
// . : \ _/ . \_: / |
|
// . : \ _/ . :_ / |
|
// . -1 \/ . -1 \/ |
|
// 0 - Z - - - - 1 0 - Z - - - - 1 zero_wait = Z |
|
// |
|
// Classic waveforms: |
|
// peak half zero |
|
// reflect time height wait |
|
// Sawtooth 1 0 0 0 |
|
// Square 1 0 1 0 |
|
// Triangle 1 0.5 0 0 |
|
// |
|
// Some waveforms can be produced in multiple ways, which is useful when morphing |
|
// into other waveforms, and there are a few more notable shapes: |
|
// |
|
// peak half zero |
|
// reflect time height wait |
|
// Sawtooth 1 1 any 0 |
|
// Sawtooth (8va) 1 0 -1 0 |
|
// Triangle 1 0.5 0 0 |
|
// Square 1 0 1 0 |
|
// Square 0 0 1 0 |
|
// Triangle 0 0.5 0 0 |
|
// Triangle 0 0 -1 0 |
|
// AlternatingSaw 0 0 0 0 |
|
// AlternatingSaw 0 1 any 0 |
|
// Stairs 0 0 1 0.5 |
|
// |
|
// The "Sawtooth (8va)" waveform is identical to a sawtooth wave with 2x the |
|
// frequency, but when morphed with other values, it becomes an overtone of |
|
// the base frequency. |
|
// |
|
// Morphing waveforms: |
|
// |
|
// Sweeping peak_time morphs the waveform while producing various spectra. |
|
// Sweeping half_height effectively crossfades between two waveforms; useful, but less exciting. |
|
// Sweeping zero_wait produces a similar effect no matter the reset of the waveform, |
|
// a sort of high-pass/PWM effect where the wave becomes silent at zero_wait=1. |
|
// |
|
// You can trivially morph between any two waveforms from the above table |
|
// which only differ in one column. |
|
// |
|
// Crossfade between classic waveforms: |
|
// peak half zero |
|
// Start End reflect time height wait |
|
// ----- --- ------- ---- ------ ---- |
|
// Triangle Square 0 0 -1..1 0 |
|
// Saw Square 1 0 0..1 0 |
|
// Triangle Saw 1 0.5 0..2 0 |
|
// |
|
// The last morph uses uses half-height values larger than 1, which means it will |
|
// be louder and the output should be scaled down by half to compensate, or better |
|
// by dynamically tracking the morph: volume_scale = 1 - half_height/4 |
|
// |
|
// Non-crossfade morph between classic waveforms, most require changing |
|
// two parameters at the same time: |
|
// peak half zero |
|
// Start End reflect time height wait |
|
// ----- --- ------- ---- ------ ---- |
|
// Square Triangle any 0..0.5 1..0 0 |
|
// Square Saw 1 0..1 1..any 0 |
|
// Triangle Saw 1 0.5..1 0..-1 0 |
|
// |
|
// Other noteworthy morphs between simple shapes: |
|
// peak half zero |
|
// Start Halfway End reflect time height wait |
|
// ----- --------- --- ------- ---- ------ ---- |
|
// Saw (8va,neg) Saw (pos) 1 0..1 -1 0 |
|
// Saw (neg) Saw (pos) 1 0..1 0 0 |
|
// Triangle AlternatingSaw 0 0..1 -1 0 |
|
// AlternatingSaw Triangle AlternatingSaw 0 0..1 0 0 |
|
// Square AlternatingSaw 0 0..1 1 0 |
|
// Triangle Triangle AlternatingSaw 0 0..1 -1..1 0 |
|
// Square AlternatingSaw 0 0..1 1..0 0 |
|
// Saw (8va) Triangle Saw 1 0..1 -1..1 0 |
|
// Saw (neg) Saw (pos) 1 0..1 0..1 0 |
|
// AlternatingSaw AlternatingSaw 0 0..1 0..any 0 |
|
// |
|
// The last entry is noteworthy because the morph from the halfway point to either |
|
// endpoint sounds very different. For example, an LFO sweeping back and forth over |
|
// the whole range will morph between the middle timbre and the AlternatingSaw |
|
// timbre in two different ways, alternating. |
|
// |
|
// Entries with "any" for half_height are whole families of morphs, as you can pick |
|
// any value you want as the endpoint for half_height. |
|
// |
|
// You can always morph between any two waveforms with the same value of 'reflect' |
|
// by just sweeping the parameters simultaneously. There will never be artifacts |
|
// and the result will always be useful, if not necessarily what you want. |
|
// |
|
// You can vary the sound of two-parameter morphs by ramping them differently, |
|
// e.g. if the morph goes from t=0..1, then square-to-triangle looks like: |
|
// peak_time = lerp(t, 0, 0.5) |
|
// half_height = lerp(t, 1, 0 ) |
|
// but you can also do things like: |
|
// peak_time = lerp(smoothstep(t), 0, 0.5) |
|
// half_height = cos(PI/2 * t) |
|
// |
|
// How it works: |
|
// |
|
// hexwave use BLEP to bandlimit discontinuities and BLAMP |
|
// to bandlimit C1 discontinuities. This is not polyBLEP |
|
// (polynomial BLEP), it is table-driven BLEP. It is |
|
// also not minBLEP (minimum-phase BLEP), as that complicates |
|
// things for little benefit once BLAMP is involved. |
|
// |
|
// The previous oscillator frequency is remembered, and when |
|
// the frequency changes, a BLAMP is generated to remove the |
|
// C1 discontinuity, which reduces artifacts for sweeps/LFO. |
|
// |
|
// Changes to an oscillator timbre using hexwave_change() actually |
|
// wait until the oscillator finishes its current cycle. All |
|
// waveforms with non-zero "zero_wait" settings pass through 0 |
|
// and have 0-slope at the start of a cycle, which means changing |
|
// the settings is artifact free at that time. (If zero_wait is 0, |
|
// the code still treats it as passing through 0 with 0-slope; it'll |
|
// apply the necessary fixups to make it artifact free as if it does |
|
// transition to 0 with 0-slope vs. the waveform at the end of |
|
// the cycle, then adds the fixups for a non-0 and non-0 slope |
|
// at the start of the cycle, which cancels out if zero_wait is 0, |
|
// and still does the right thing if zero_wait is 0 when the |
|
// settings are updated.) |
|
// |
|
// BLEP/BLAMP normally requires overlapping buffers, but this |
|
// is hidden from the user by generating the waveform to a |
|
// temporary buffer and saving the overlap regions internally |
|
// between calls. (It is slightly more complicated; see code.) |
|
// |
|
// By design all shapes have 0 DC offset; this is one reason |
|
// hexwave uses zero_wait instead of standard PWM. |
|
// |
|
// The internals of hexwave could support any arbitrary shape |
|
// made of line segments, but I chose not to expose this |
|
// generality in favor of a simple, easy-to-use API. |
|
|
|
#ifndef STB_INCLUDE_STB_HEXWAVE_H |
|
#define STB_INCLUDE_STB_HEXWAVE_H |
|
|
|
#ifndef STB_HEXWAVE_MAX_BLEP_LENGTH |
|
#define STB_HEXWAVE_MAX_BLEP_LENGTH 64 // good enough for anybody |
|
#endif |
|
|
|
#ifdef STB_HEXWAVE_STATIC |
|
#define STB_HEXWAVE_DEF static |
|
#else |
|
#define STB_HEXWAVE_DEF extern |
|
#endif |
|
|
|
typedef struct HexWave HexWave; |
|
|
|
STB_HEXWAVE_DEF void hexwave_init(int width, int oversample, float *user_buffer); |
|
// width: size of BLEP, from 4..64, larger is slower & more memory but less aliasing |
|
// oversample: 2+, number of subsample positions, larger uses more memory but less noise |
|
// user_buffer: optional, if provided the library will perform no allocations. |
|
// 16*width*(oversample+1) bytes, must stay allocated as long as library is used |
|
// technically it only needs: 8*( width * (oversample + 1)) |
|
// + 8*((width * oversample) + 1) bytes |
|
// |
|
// width can be larger than 64 if you define STB_HEXWAVE_MAX_BLEP_LENGTH to a larger value |
|
|
|
STB_HEXWAVE_DEF void hexwave_shutdown(float *user_buffer); |
|
// user_buffer: pass in same parameter as passed to hexwave_init |
|
|
|
STB_HEXWAVE_DEF void hexwave_create(HexWave *hex, int reflect, float peak_time, float half_height, float zero_wait); |
|
// see docs above for description |
|
// |
|
// reflect is tested as 0 or non-zero |
|
// peak_time is clamped to 0..1 |
|
// half_height is not clamped |
|
// zero_wait is clamped to 0..1 |
|
|
|
STB_HEXWAVE_DEF void hexwave_change(HexWave *hex, int reflect, float peak_time, float half_height, float zero_wait); |
|
// see docs |
|
|
|
STB_HEXWAVE_DEF void hexwave_generate_samples(float *output, int num_samples, HexWave *hex, float freq); |
|
// output: buffer where the library will store generated floating point audio samples |
|
// number_of_samples: the number of audio samples to generate |
|
// osc: pointer to a Hexwave initialized with 'hexwave_create' |
|
// oscillator_freq: frequency of the oscillator divided by the sample rate |
|
|
|
// private: |
|
typedef struct |
|
{ |
|
int reflect; |
|
float peak_time; |
|
float zero_wait; |
|
float half_height; |
|
} HexWaveParameters; |
|
|
|
struct HexWave |
|
{ |
|
float t, prev_dt; |
|
HexWaveParameters current, pending; |
|
int have_pending; |
|
float buffer[STB_HEXWAVE_MAX_BLEP_LENGTH]; |
|
}; |
|
#endif |
|
|
|
#ifdef STB_HEXWAVE_IMPLEMENTATION |
|
|
|
#ifndef STB_HEXWAVE_NO_ALLOCATION |
|
#include <stdlib.h> // malloc,free |
|
#endif |
|
|
|
#include <string.h> // memset,memcpy,memmove |
|
#include <math.h> // sin,cos,fabs |
|
|
|
#define hexwave_clamp(v,a,b) ((v) < (a) ? (a) : (v) > (b) ? (b) : (v)) |
|
|
|
STB_HEXWAVE_DEF void hexwave_change(HexWave *hex, int reflect, float peak_time, float half_height, float zero_wait) |
|
{ |
|
hex->pending.reflect = reflect; |
|
hex->pending.peak_time = hexwave_clamp(peak_time,0,1); |
|
hex->pending.half_height = half_height; |
|
hex->pending.zero_wait = hexwave_clamp(zero_wait,0,1); |
|
// put a barrier here to allow changing from a different thread than the generator |
|
hex->have_pending = 1; |
|
} |
|
|
|
STB_HEXWAVE_DEF void hexwave_create(HexWave *hex, int reflect, float peak_time, float half_height, float zero_wait) |
|
{ |
|
memset(hex, 0, sizeof(*hex)); |
|
hexwave_change(hex, reflect, peak_time, half_height, zero_wait); |
|
hex->current = hex->pending; |
|
hex->have_pending = 0; |
|
hex->t = 0; |
|
hex->prev_dt = 0; |
|
} |
|
|
|
static struct |
|
{ |
|
int width; // width of fixup in samples |
|
int oversample; // number of oversampled versions (there's actually one more to allow lerpign) |
|
float *blep; |
|
float *blamp; |
|
} hexblep; |
|
|
|
static void hex_add_oversampled_bleplike(float *output, float time_since_transition, float scale, float *data) |
|
{ |
|
float *d1,*d2; |
|
float lerpweight; |
|
int i, bw = hexblep.width; |
|
|
|
int slot = (int) (time_since_transition * hexblep.oversample); |
|
if (slot >= hexblep.oversample) |
|
slot = hexblep.oversample-1; // clamp in case the floats overshoot |
|
|
|
d1 = &data[ slot *bw]; |
|
d2 = &data[(slot+1)*bw]; |
|
|
|
lerpweight = time_since_transition * hexblep.oversample - slot; |
|
for (i=0; i < bw; ++i) |
|
output[i] += scale * (d1[i] + (d2[i]-d1[i])*lerpweight); |
|
} |
|
|
|
static void hex_blep (float *output, float time_since_transition, float scale) |
|
{ |
|
hex_add_oversampled_bleplike(output, time_since_transition, scale, hexblep.blep); |
|
} |
|
|
|
static void hex_blamp(float *output, float time_since_transition, float scale) |
|
{ |
|
hex_add_oversampled_bleplike(output, time_since_transition, scale, hexblep.blamp); |
|
} |
|
|
|
typedef struct |
|
{ |
|
float t,v,s; // time, value, slope |
|
} hexvert; |
|
|
|
// each half of the waveform needs 4 vertices to represent 3 line |
|
// segments, plus 1 more for wraparound |
|
static void hexwave_generate_linesegs(hexvert vert[9], HexWave *hex, float dt) |
|
{ |
|
int j; |
|
float min_len = dt / 256.0f; |
|
|
|
vert[0].t = 0; |
|
vert[0].v = 0; |
|
vert[1].t = hex->current.zero_wait*0.5f; |
|
vert[1].v = 0; |
|
vert[2].t = 0.5f*hex->current.peak_time + vert[1].t*(1-hex->current.peak_time); |
|
vert[2].v = 1; |
|
vert[3].t = 0.5f; |
|
vert[3].v = hex->current.half_height; |
|
|
|
if (hex->current.reflect) { |
|
for (j=4; j <= 7; ++j) { |
|
vert[j].t = 1 - vert[7-j].t; |
|
vert[j].v = - vert[7-j].v; |
|
} |
|
} else { |
|
for (j=4; j <= 7; ++j) { |
|
vert[j].t = 0.5f + vert[j-4].t; |
|
vert[j].v = - vert[j-4].v; |
|
} |
|
} |
|
vert[8].t = 1; |
|
vert[8].v = 0; |
|
|
|
for (j=0; j < 8; ++j) { |
|
if (vert[j+1].t <= vert[j].t + min_len) { |
|
// if change takes place over less than a fraction of a sample treat as discontinuity |
|
// |
|
// otherwise the slope computation can blow up to arbitrarily large and we |
|
// try to generate a huge BLAMP and the result is wrong. |
|
// |
|
// why does this happen if the math is right? i believe if done perfectly, |
|
// the two BLAMPs on either side of the slope would cancel out, but our |
|
// BLAMPs have only limited sub-sample precision and limited integration |
|
// accuracy. or maybe it's just the math blowing up w/ floating point precision |
|
// limits as we try to make x * (1/x) cancel out |
|
// |
|
// min_len verified artifact-free even near nyquist with only oversample=4 |
|
vert[j+1].t = vert[j].t; |
|
} |
|
} |
|
|
|
if (vert[8].t != 1.0f) { |
|
// if the above fixup moved the endpoint away from 1.0, move it back, |
|
// along with any other vertices that got moved to the same time |
|
float t = vert[8].t; |
|
for (j=5; j <= 8; ++j) |
|
if (vert[j].t == t) |
|
vert[j].t = 1.0f; |
|
} |
|
|
|
// compute the exact slopes from the final fixed-up positions |
|
for (j=0; j < 8; ++j) |
|
if (vert[j+1].t == vert[j].t) |
|
vert[j].s = 0; |
|
else |
|
vert[j].s = (vert[j+1].v - vert[j].v) / (vert[j+1].t - vert[j].t); |
|
|
|
// wraparound at end |
|
vert[8].t = 1; |
|
vert[8].v = vert[0].v; |
|
vert[8].s = vert[0].s; |
|
} |
|
|
|
STB_HEXWAVE_DEF void hexwave_generate_samples(float *output, int num_samples, HexWave *hex, float freq) |
|
{ |
|
hexvert vert[9]; |
|
int pass,i,j; |
|
float t = hex->t; |
|
float temp_output[2*STB_HEXWAVE_MAX_BLEP_LENGTH]; |
|
int buffered_length = sizeof(float)*hexblep.width; |
|
float dt = (float) fabs(freq); |
|
float recip_dt = (dt == 0.0f) ? 0.0f : 1.0f / dt; |
|
|
|
int halfw = hexblep.width/2; |
|
// all sample times are biased by halfw to leave room for BLEP/BLAMP to go back in time |
|
|
|
if (num_samples <= 0) |
|
return; |
|
|
|
// convert parameters to times and slopes |
|
hexwave_generate_linesegs(vert, hex, dt); |
|
|
|
if (hex->prev_dt != dt) { |
|
// if frequency changes, add a fixup at the derivative discontinuity starting at now |
|
float slope; |
|
for (j=1; j < 6; ++j) |
|
if (t < vert[j].t) |
|
break; |
|
slope = vert[j].s; |
|
if (slope != 0) |
|
hex_blamp(output, 0, (dt - hex->prev_dt)*slope); |
|
hex->prev_dt = dt; |
|
} |
|
|
|
// copy the buffered data from last call and clear the rest of the output array |
|
memset(output, 0, sizeof(float)*num_samples); |
|
memset(temp_output, 0, 2*hexblep.width*sizeof(float)); |
|
|
|
if (num_samples >= hexblep.width) { |
|
memcpy(output, hex->buffer, buffered_length); |
|
} else { |
|
// if the output is shorter than hexblep.width, we do all synthesis to temp_output |
|
memcpy(temp_output, hex->buffer, buffered_length); |
|
} |
|
|
|
for (pass=0; pass < 2; ++pass) { |
|
int i0,i1; |
|
float *out; |
|
|
|
// we want to simulate having one buffer that is num_output + hexblep.width |
|
// samples long, without putting that requirement on the user, and without |
|
// allocating a temp buffer that's as long as the whole thing. so we use two |
|
// overlapping buffers, one the user's buffer and one a fixed-length temp |
|
// buffer. |
|
|
|
if (pass == 0) { |
|
if (num_samples < hexblep.width) |
|
continue; |
|
// run as far as we can without overwriting the end of the user's buffer |
|
out = output; |
|
i0 = 0; |
|
i1 = num_samples - hexblep.width; |
|
} else { |
|
// generate the rest into a temp buffer |
|
out = temp_output; |
|
i0 = 0; |
|
if (num_samples >= hexblep.width) |
|
i1 = hexblep.width; |
|
else |
|
i1 = num_samples; |
|
} |
|
|
|
// determine current segment |
|
for (j=0; j < 8; ++j) |
|
if (t < vert[j+1].t) |
|
break; |
|
|
|
i = i0; |
|
for(;;) { |
|
while (t < vert[j+1].t) { |
|
if (i == i1) |
|
goto done; |
|
out[i+halfw] += vert[j].v + vert[j].s*(t - vert[j].t); |
|
t += dt; |
|
++i; |
|
} |
|
// transition from lineseg starting at j to lineseg starting at j+1 |
|
|
|
if (vert[j].t == vert[j+1].t) |
|
hex_blep(out+i, recip_dt*(t-vert[j+1].t), (vert[j+1].v - vert[j].v)); |
|
hex_blamp(out+i, recip_dt*(t-vert[j+1].t), dt*(vert[j+1].s - vert[j].s)); |
|
++j; |
|
|
|
if (j == 8) { |
|
// change to different waveform if there's a change pending |
|
j = 0; |
|
t -= 1.0; // t was >= 1.f if j==8 |
|
if (hex->have_pending) { |
|
float prev_s0 = vert[j].s; |
|
float prev_v0 = vert[j].v; |
|
hex->current = hex->pending; |
|
hex->have_pending = 0; |
|
hexwave_generate_linesegs(vert, hex, dt); |
|
// the following never occurs with this oscillator, but it makes |
|
// the code work in more general cases |
|
if (vert[j].v != prev_v0) |
|
hex_blep (out+i, recip_dt*t, (vert[j].v - prev_v0)); |
|
if (vert[j].s != prev_s0) |
|
hex_blamp(out+i, recip_dt*t, dt*(vert[j].s - prev_s0)); |
|
} |
|
} |
|
} |
|
done: |
|
; |
|
} |
|
|
|
// at this point, we've written output[] and temp_output[] |
|
if (num_samples >= hexblep.width) { |
|
// the first half of temp[] overlaps the end of output, the second half will be the new start overlap |
|
for (i=0; i < hexblep.width; ++i) |
|
output[num_samples-hexblep.width + i] += temp_output[i]; |
|
memcpy(hex->buffer, temp_output+hexblep.width, buffered_length); |
|
} else { |
|
for (i=0; i < num_samples; ++i) |
|
output[i] = temp_output[i]; |
|
memcpy(hex->buffer, temp_output+num_samples, buffered_length); |
|
} |
|
|
|
hex->t = t; |
|
} |
|
|
|
STB_HEXWAVE_DEF void hexwave_shutdown(float *user_buffer) |
|
{ |
|
#ifndef STB_HEXWAVE_NO_ALLOCATION |
|
if (user_buffer != 0) { |
|
free(hexblep.blep); |
|
free(hexblep.blamp); |
|
} |
|
#endif |
|
} |
|
|
|
// buffer should be NULL or must be 4*(width*(oversample+1)*2 + |
|
STB_HEXWAVE_DEF void hexwave_init(int width, int oversample, float *user_buffer) |
|
{ |
|
int halfwidth = width/2; |
|
int half = halfwidth*oversample; |
|
int blep_buffer_count = width*(oversample+1); |
|
int n = 2*half+1; |
|
#ifdef STB_HEXWAVE_NO_ALLOCATION |
|
float *buffers = user_buffer; |
|
#else |
|
float *buffers = user_buffer ? user_buffer : (float *) malloc(sizeof(float) * n * 2); |
|
#endif |
|
float *step = buffers+0*n; |
|
float *ramp = buffers+1*n; |
|
float *blep_buffer, *blamp_buffer; |
|
double integrate_impulse=0, integrate_step=0; |
|
int i,j; |
|
|
|
if (width > STB_HEXWAVE_MAX_BLEP_LENGTH) |
|
width = STB_HEXWAVE_MAX_BLEP_LENGTH; |
|
|
|
if (user_buffer == 0) { |
|
#ifndef STB_HEXWAVE_NO_ALLOCATION |
|
blep_buffer = (float *) malloc(sizeof(float)*blep_buffer_count); |
|
blamp_buffer = (float *) malloc(sizeof(float)*blep_buffer_count); |
|
#endif |
|
} else { |
|
blep_buffer = ramp+n; |
|
blamp_buffer = blep_buffer + blep_buffer_count; |
|
} |
|
|
|
// compute BLEP and BLAMP by integerating windowed sinc |
|
for (i=0; i < n; ++i) { |
|
for (j=0; j < 16; ++j) { |
|
float sinc_t = 3.141592f* (i-half) / oversample; |
|
float sinc = (i==half) ? 1.0f : (float) sin(sinc_t) / (sinc_t); |
|
float wt = 2.0f*3.1415926f * i / (n-1); |
|
float window = (float) (0.355768 - 0.487396*cos(wt) + 0.144232*cos(2*wt) - 0.012604*cos(3*wt)); // Nuttall |
|
double value = window * sinc; |
|
integrate_impulse += value/16; |
|
integrate_step += integrate_impulse/16; |
|
} |
|
step[i] = (float) integrate_impulse; |
|
ramp[i] = (float) integrate_step; |
|
} |
|
|
|
// renormalize |
|
for (i=0; i < n; ++i) { |
|
step[i] = step[i] * (float) (1.0 / step[n-1]); // step needs to reach to 1.0 |
|
ramp[i] = ramp[i] * (float) (halfwidth / ramp[n-1]); // ramp needs to become a slope of 1.0 after oversampling |
|
} |
|
|
|
// deinterleave to allow efficient interpolation e.g. w/SIMD |
|
for (j=0; j <= oversample; ++j) { |
|
for (i=0; i < width; ++i) { |
|
blep_buffer [j*width+i] = step[j+i*oversample]; |
|
blamp_buffer[j*width+i] = ramp[j+i*oversample]; |
|
} |
|
} |
|
|
|
// subtract out the naive waveform; note we can't do this to the raw data |
|
// above, because we want the discontinuity to be in a different locations |
|
// for j=0 and j=oversample (which exists to provide something to interpolate against) |
|
for (j=0; j <= oversample; ++j) { |
|
// subtract step |
|
for (i=halfwidth; i < width; ++i) |
|
blep_buffer [j*width+i] -= 1.0f; |
|
// subtract ramp |
|
for (i=halfwidth; i < width; ++i) |
|
blamp_buffer[j*width+i] -= (j+i*oversample-half)*(1.0f/oversample); |
|
} |
|
|
|
hexblep.blep = blep_buffer; |
|
hexblep.blamp = blamp_buffer; |
|
hexblep.width = width; |
|
hexblep.oversample = oversample; |
|
|
|
#ifndef STB_HEXWAVE_NO_ALLOCATION |
|
if (user_buffer == 0) |
|
free(buffers); |
|
#endif |
|
} |
|
#endif // STB_HEXWAVE_IMPLEMENTATION |
|
|
|
/* |
|
------------------------------------------------------------------------------ |
|
This software is available under 2 licenses -- choose whichever you prefer. |
|
------------------------------------------------------------------------------ |
|
ALTERNATIVE A - MIT License |
|
Copyright (c) 2017 Sean Barrett |
|
Permission is hereby granted, free of charge, to any person obtaining a copy of |
|
this software and associated documentation files (the "Software"), to deal in |
|
the Software without restriction, including without limitation the rights to |
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies |
|
of the Software, and to permit persons to whom the Software is furnished to do |
|
so, subject to the following conditions: |
|
The above copyright notice and this permission notice shall be included in all |
|
copies or substantial portions of the Software. |
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|
SOFTWARE. |
|
------------------------------------------------------------------------------ |
|
ALTERNATIVE B - Public Domain (www.unlicense.org) |
|
This is free and unencumbered software released into the public domain. |
|
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this |
|
software, either in source code form or as a compiled binary, for any purpose, |
|
commercial or non-commercial, and by any means. |
|
In jurisdictions that recognize copyright laws, the author or authors of this |
|
software dedicate any and all copyright interest in the software to the public |
|
domain. We make this dedication for the benefit of the public at large and to |
|
the detriment of our heirs and successors. We intend this dedication to be an |
|
overt act of relinquishment in perpetuity of all present and future rights to |
|
this software under copyright law. |
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
|
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|
------------------------------------------------------------------------------ |
|
*/
|
|
|