I'm coding a synthesis external for MaxMSP in C. It's based on Peter Blasser's bounds and bounce concept ( http://petermopar.blogspot.co.uk/2014_04_01_archive.html )- basically triangle waves with constant cursor rate (rather than frequency) and modulated bounds - eg instead of moving between -1 and 1, the wave moves between lo and hi, with lo and hi modulated.
This morning I've been thinking of adding waveshape morphing between triangle and sine (or at least sinusoidal since the moving bounds will deform the sine).
I can think of a fairly simple way to do this:
// Assuming classic triangle moving between bounds -1 to 1
// and defining position of triangle cursor @ n = pos
// shape_amt between circa 0.1 (Tri) and 1.0 (sine)
out = sin(pos * shape_amt * 0.5* pi ) / sin(shape_amt *0.5 * pi())
ie - basic waveshaping technique - scale the portion of the lookup table that's being scanned (in this case some segment of half a sine wave) to vary the shape.
For my specific case all I'll need to do from there is build in scaling from relative position between to bounds to relative position between -1 and 1.
My question really is- is this a horribly inefficient way of achieving what I'm after? I'm new to DSP coding and to coding in C so I'm sure the way that's most obvious to me isn't the best.
The obvious performance improvement that occurs to me is to create a wavetable of the sin function and look up rather than calculate those values. Obviously that way I lose resolution on the "straighter" portions of the wave, but either a high res lookup or decent interpolation feels like it ought to deal with that fairly well. Mainly I'm considering this for LFO applications so quality needn't be utterly stellar (though it'll be nice nice if it's useable into the audio range without too much fuzzy aliasing).
But beyond that, is there just a completely different approach that would be more efficient? And if this is the best approach, are there any things I should think of when pursuing it?
Edit - Thanks for great answers below. I've ended up doing something fairly simple (and possibly crude) for the moment, which suits my purposes pretty well. I'll definitely be investigating both suggestions as improvements to this.
#define sign(a) ( ( (a) < 0 ) ? -1 : ( (a) > 0 ) )
#define LKTBL_LNGTH 2048
void setup_lktables (t_object* x)
{ // create lookup for 1/4 sine
int i;
for(i=0, i< LKTBL_LNGTH, i++){
x->sin[i] = sin(pi * i * 0.5 / LKTBL_LNGTH) ;
}
}
double lookup_shape (t_double pos, t_double shape, t_double lo, t_double hi)
// acting on triangle wave (phase + (2*gradient*f/sr) per sample, wraps & changes dir at <lo and >hi)
// shape comes in as restricted to (-1...-0.05, 0.05 ...1), defines the portion of lookup to use
{
t_double midpoint, halfwidth, ph, fracph, shaped;
t_int maxph, intph, sign;
// get relative position between bounds for waveshaping lookup
midpoint = lo + 0.5f * (hi - lo);
halfwidth = midpoint - lo;
// prepare phase values for lookups
maxph = shape * LKTBL_LNGTH - 1;
ph = (pos - midpoint) * maxph / halfwidth;
sign = sign(ph);
ph = ph * sign;
intph = (int)ph;
fracph = ph - intph;
// lookup, scale & lerp
shaped = sign * (x->sin[intph] * (1.f - fracph) + x->sin[intph+1] * fracph) / x->sin[maxph];
//now return waveshaping output scaled to actual bounds
return midpoint + shaped * halfwidth
}