More efficient implementation of fluid_ct2hz() (#569)

This provides a less branchy and therefore more instruction-cache-friendly version of fluid_ct2hz(), which also significantly reduces the number of floating-point comparisons.
This commit is contained in:
Tom M 2019-10-22 13:09:42 +02:00 committed by GitHub
parent 41e77afe84
commit fa7354a336
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 47 additions and 52 deletions

View file

@ -22,7 +22,10 @@ static void fluid_conversion_config(void)
for(i = 0; i < FLUID_CENTS_HZ_SIZE; i++)
{
fluid_ct2hz_tab[i] = powl(2.0, (double) i / 1200.0);
// 6,875 is just a factor that we already multiply into the lookup table to save
// that multiplication in fluid_ct2hz_real()
// 6.875 Hz because 440Hz / 2^6
fluid_ct2hz_tab[i] = 6.875 * powl(2.0, (double) i / 1200.0);
}
/* centibels to amplitude conversion

View file

@ -23,66 +23,55 @@
#include "fluid_conv_tables.c"
/*
* fluid_ct2hz
* Converts absolute cents to Hertz
*
* As per sfspec section 9.3:
*
* ABSOLUTE CENTS - An absolute logarithmic measure of frequency based on a
* reference of MIDI key number scaled by 100.
* A cent is 1/1200 of an octave [which is the twelve hundredth root of two],
* and value 6900 is 440 Hz (A-440).
*
* Implemented below basically is the following:
* 440 * 2^((cents-6900)/1200)
* = 440 * 2^((int)((cents-6900)/1200)) * 2^(((int)cents-6900)%1200))
* = 2^((int)((cents-6900)/1200)) * (440 * 2^(((int)cents-6900)%1200)))
* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* This second factor is stored in the lookup table.
*
* The first factor can be implemented with a fast shift when the exponent
* is always an int. This is the case when using 440/2^6 Hz rather than 440Hz
* reference.
*/
fluid_real_t
fluid_ct2hz_real(fluid_real_t cents)
{
if(cents < 0)
if(FLUID_UNLIKELY(cents < 0))
{
return (fluid_real_t) 1.0;
}
else if(cents < 900)
{
return (fluid_real_t) 6.875 * fluid_ct2hz_tab[(int)(cents + 300)];
}
else if(cents < 2100)
{
return (fluid_real_t) 13.75 * fluid_ct2hz_tab[(int)(cents - 900)];
}
else if(cents < 3300)
{
return (fluid_real_t) 27.5 * fluid_ct2hz_tab[(int)(cents - 2100)];
}
else if(cents < 4500)
{
return (fluid_real_t) 55.0 * fluid_ct2hz_tab[(int)(cents - 3300)];
}
else if(cents < 5700)
{
return (fluid_real_t) 110.0 * fluid_ct2hz_tab[(int)(cents - 4500)];
}
else if(cents < 6900)
{
return (fluid_real_t) 220.0 * fluid_ct2hz_tab[(int)(cents - 5700)];
}
else if(cents < 8100)
{
return (fluid_real_t) 440.0 * fluid_ct2hz_tab[(int)(cents - 6900)];
}
else if(cents < 9300)
{
return (fluid_real_t) 880.0 * fluid_ct2hz_tab[(int)(cents - 8100)];
}
else if(cents < 10500)
{
return (fluid_real_t) 1760.0 * fluid_ct2hz_tab[(int)(cents - 9300)];
}
else if(cents < 11700)
{
return (fluid_real_t) 3520.0 * fluid_ct2hz_tab[(int)(cents - 10500)];
}
else if(cents < 12900)
{
return (fluid_real_t) 7040.0 * fluid_ct2hz_tab[(int)(cents - 11700)];
}
else if(cents < 14100)
{
return (fluid_real_t) 14080.0 * fluid_ct2hz_tab[(int)(cents - 12900)];
}
else
{
return (fluid_real_t) 1.0; /* some loony trying to make you deaf */
unsigned int mult, fac, rem;
unsigned int icents = (unsigned int)cents;
icents += 300u;
// don't use stdlib div() here, it turned out have poor performance
fac = icents / 1200u;
rem = icents % 1200u;
// Think of "mult" as the factor that we multiply (440/2^6)Hz with,
// or in other words mult is the "first factor" of the above
// functions comment.
//
// Assuming sizeof(uint)==4 this will give us a maximum range of
// 32 * 1200cents - 300cents == 38100 cents == 29,527,900,160 Hz
// which is much more than ever needed. For bigger values, just
// safely wrap around.
mult = 1u << (fac & (sizeof(mult)*8u - 1u));
// don't use ldexp() either (poor performance)
return mult * fluid_ct2hz_tab[rem];
}
}

View file

@ -16,6 +16,9 @@ int main(void)
{
// 440 * 2^((x-6900)/1200) where x is the cent value given to ct2hz()
TEST_ASSERT(float_eq(fluid_ct2hz_real(38099), 2.9510849101059895e10));
TEST_ASSERT(float_eq(fluid_ct2hz_real(13500), 19912.12696));
TEST_ASSERT(float_eq(fluid_ct2hz_real(12900), 14080));