Răsfoiți Sursa

stb_hexwave added, stretchy_buffer.h deprecated

Sean Barrett 4 ani în urmă
părinte
comite
559d759c2c

+ 7 - 6
tests/stretch_test.c → deprecated/stretch_test.c

@@ -2,7 +2,8 @@
 #define STB_TRUETYPE_IMPLEMENTATION
 #include "stb_truetype.h"
 
-#include "stretchy_buffer.h"
+#define STB_DS_IMPLEMENTATION
+#include "stb_ds.h"
 #include <assert.h>
 
 int main(int arg, char **argv)
@@ -11,18 +12,18 @@ int main(int arg, char **argv)
    int *arr = NULL;
 
    for (i=0; i < 1000000; ++i)
-      sb_push(arr, i);
+      arrput(arr, i);
 
-   assert(sb_count(arr) == 1000000);
+   assert(arrlen(arr) == 1000000);
    for (i=0; i < 1000000; ++i)
       assert(arr[i] == i);
 
-   sb_free(arr);
+   arrfree(arr);
    arr = NULL;
 
    for (i=0; i < 1000; ++i)
-      sb_add(arr, 1000);
-   assert(sb_count(arr) == 1000000);
+      arrput(arr, 1000);
+   assert(arrlen(arr) == 1000000);
 
    return 0;
 }

+ 0 - 0
stretchy_buffer.h → deprecated/stretchy_buffer.h


+ 634 - 0
stb_hexwave.h

@@ -0,0 +1,634 @@
+// 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/CPP 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.
+//
+// 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

+ 1 - 9
tests/stb.dsp

@@ -66,7 +66,7 @@ LINK32=link.exe
 # PROP Ignore_Export_Lib 0
 # PROP Target_Dir ""
 # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c
-# ADD CPP /nologo /MTd /W3 /GX /Zi /Od /I ".." /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /D "DS_TEST" /FR /FD /GZ /c
+# ADD CPP /nologo /MTd /W3 /GX /Zi /Od /I ".." /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /D "TT_TEST" /FR /FD /GZ /c
 # SUBTRACT CPP /YX
 # ADD BASE RSC /l 0x409 /d "_DEBUG"
 # ADD RSC /l 0x409 /d "_DEBUG"
@@ -194,14 +194,6 @@ SOURCE=..\stb_voxel_render.h
 # End Source File
 # Begin Source File
 
-SOURCE=..\stretchy_buffer.h
-# End Source File
-# Begin Source File
-
-SOURCE=.\stretchy_buffer_test.c
-# End Source File
-# Begin Source File
-
 SOURCE=.\test_c_compilation.c
 # End Source File
 # Begin Source File

+ 0 - 15
tests/stb.dsw

@@ -90,9 +90,6 @@ Package=<4>
     Project_Dep_Name image_test
     End Project Dependency
     Begin Project Dependency
-    Project_Dep_Name stretch_test
-    End Project Dependency
-    Begin Project Dependency
     Project_Dep_Name c_lexer_test
     End Project Dependency
 }}}
@@ -123,18 +120,6 @@ Package=<4>
 
 ###############################################################################
 
-Project: "stretch_test"=.\stretch_test.dsp - Package Owner=<4>
-
-Package=<5>
-{{{
-}}}
-
-Package=<4>
-{{{
-}}}
-
-###############################################################################
-
 Project: "unicode"=..\tools\unicode\unicode.dsp - Package Owner=<4>
 
 Package=<5>

+ 0 - 7
tests/stretchy_buffer_test.c

@@ -1,7 +0,0 @@
-#include "stretchy_buffer.h"
-
-void test_sb(void)
-{
-   char *x = NULL;
-   sb_push(x, 'x');
-}

+ 17 - 2
tests/test_cpp_compilation.cpp

@@ -1,9 +1,22 @@
+#define STB_IMAGE_WRITE_STATIC
+#define STBIWDEF static inline
+
+#include "stb_image.h"
+#include "stb_rect_pack.h"
+#include "stb_truetype.h"
+#include "stb_image_write.h"
+#include "stb_perlin.h"
+#include "stb_dxt.h"
+#include "stb_c_lexer.h"
+#include "stb_divide.h"
+#include "stb_herringbone_wang_tile.h"
+#include "stb_ds.h"
+#include "stb_hexwave.h"
+
 #include "stb_sprintf.h"
 #define STB_SPRINTF_IMPLEMENTATION
 #include "stb_sprintf.h"
 
-#define STB_IMAGE_WRITE_STATIC
-#define STBIWDEF static inline
 
 #define STB_IMAGE_WRITE_IMPLEMENTATION
 #define STB_TRUETYPE_IMPLEMENTATION
@@ -16,6 +29,7 @@
 #define STB_RECT_PACK_IMPLEMENTATION
 #define STB_VOXEL_RENDER_IMPLEMENTATION
 #define STB_CONNECTED_COMPONENTS_IMPLEMENTATION
+#define STB_HEXWAVE_IMPLEMENTATION
 #define STB_DS_IMPLEMENTATION
 #define STBDS_UNIT_TESTS
 
@@ -37,6 +51,7 @@ void my_free(void *) { }
 #include "stb_divide.h"
 #include "stb_herringbone_wang_tile.h"
 #include "stb_ds.h"
+#include "stb_hexwave.h"
 
 #define STBCC_GRID_COUNT_X_LOG2  10
 #define STBCC_GRID_COUNT_Y_LOG2  10