## Efficient Expo Converter

Reaktor, MAX/MSP, VST/AU, etc. A place for all things soft....

Moderators: Joe., lisa, luketeaford, Kent

jorg
Veteran Wiggler
Posts: 506
Joined: Fri Apr 03, 2015 9:38 am
Location: East Coast USA

### Efficient Expo Converter

I've put together an expo converter (and log converter) which I believe are much more efficient than the standard C library implementations (at a slight cost in accuracy which is irrelevant for musical purposes). They are based on a hardware floating point trick - the normalize and de-normalize functions which every FP-capable CPU uses to load and retrieve floating point numbers, are pretty close to log-base-2 and expo-base-2 functions (thanks to the magic of binary math). Add in a mild polynomial to curve it a bit and voila! These are scaled to unit-per-octave (i.e. a difference of 1.0 on the expo input corresponds to one octave of frequency change on the output) - pretty much the same thing as volt-per-octave in the analog world. Error is a repeating pattern in each octave and there is no cumulative error across octaves; peak error is 0.01 cents for expo, and 0.3 cents for log.

EDIT: I've found a way to remove another multiply from the log and sin functions.

#include <math.h>
#include <stdio.h>
#include <stdlib.h>

#define LOG2_TO_LN 0.693147180559945f
#define LOG2_TO_LOG10 0.301029995663981f
#define EXP2_TO_EXP 1.442695040888963f
#define EXP2_TO_EXP10 3.321928094887362f

inline float log2f (float arg)
// frexp is a macro, supported in FP hardware, which basically takes a "binary logarithm" of a floating point number.
// Use MS Excel to compute a trendline approximation (4th order polynomial) then manually tilt it so the ends have zero error (at 0.5 and 1.0).
// Manual tilt increases peak error but makes the output seamless and monotonic.
// return (-1.3063652f*x*x*x*x + 5.1500062f*x*x*x - 8.456181f*x*x + 8.1212785f*x - 3.508697f + (float)exp); // Peak Error ~ 0.00025
// Use https://www.wolframalpha.com/calculator ... alculator/ to reduce the number of multiplies by factoring the above polynomial:
// Math is reduced from 11 multiplies and 5 adds to 4 multiplies and 5 adds.
// For a fair comparison, standard "CSE" compiler optimization would give 9 multiplies and 5 adds.
{
float x;
int exp;
x = frexpf(arg, &exp);
return (-1.30637f*(x - 1.81165f)*(x - 0.999971f)*(x*(x - 1.13062f) + 1.48259f)) + (float)exp;
}

inline float exp2f (float arg)
// ldexp is a macro, supported in FP hardware, which basically takes a "binary exponentiation" of a floating point number.
// Use MS Excel to compute a trendline approximation (4th order polynomial) then manually tilt it so the ends have zero error (at 0.0 and 1.0).
// Manual tilt increases peak error but makes the output seamless and monotonic.
// y = 0.0068396f*x*x*x*x + 0.0531991f*x*x*x + 0.2394017f*x*x + 0.6930421f*x + 0.9999999f; // Error ~ 0.0009%
// Use https://www.wolframalpha.com/calculator ... alculator/ to reduce the number of multiplies by factoring the above polynomial:
// Math is reduced from 11 multiplies and 5 adds to 4 multiplies and 4 adds.
// For a fair comparison, standard "CSE" compiler optimization would give 9 multiplies and 5 adds.
{
float x,y,result;
int exp;
exp = (int)(arg)+(arg>0.0f);
x = arg-exp;
y = 0.0068396f*(x*(x + 1.76767f) + 13.726f)*(x*(x + 6.01044f) + 10.6519f);
return ldexpf(y,exp);
}

inline float logfq (float arg)
{
return LOG2_TO_LN * log2f (arg);
}

inline float log10fq (float arg)
{
return LOG2_TO_LOG10 * log2f (arg);
}

inline float expfq (float arg)
{
return exp2f (EXP2_TO_EXP * arg);
}

inline float exp10fq (float arg)
{
return exp2f (EXP2_TO_EXP10 * arg);
}

int main ()
{
float x, y, z, ygood, p=69, f=440, _l2;
int exp;

printf ("z, exp2(z), experr\n");
for(z=-2.002; z<=3.0; z+=0.002)
{
y=exp2f(z), ygood=pow(2.0,z);
printf ("%.6f, %.6f, %.6g\n",z,y,(y-ygood)/ygood);
}

printf ("x, log2(x), err, z, exp\n");
_l2=1.0/log(2.0);
for(x=0.24; x<=16.2; x*=1.05)
{
y=log2f(x), ygood=log(x)*_l2;
z = frexpf(x, &exp);
printf ("%.6f, %.6f, %.6g, %.6g, %d\n",x,y, y-ygood, z, exp);
}
return 0;
}