diff --git a/AudioEffectCompressor2_F32.cpp b/AudioEffectCompressor2_F32.cpp new file mode 100644 index 0000000..711dd11 --- /dev/null +++ b/AudioEffectCompressor2_F32.cpp @@ -0,0 +1,148 @@ +/* AudioEffectCompressor2_F32.cpp + * + * Bob Larkin W7PUA 22 January 2021 + * See AudioEffectCompressor2_F32.h for details + * + * MIT License. Use at your own risk. + */ +#include "AudioEffectCompressor2_F32.h" + + /* See https://github.com/Tympan/Tympan_Library/blob/master/src/AudioCalcGainWDRC_F32.h + * Dr Paul Beckmann + * https://community.arm.com/tools/f/discussions/4292/cmsis-dsp-new-functionality-proposal/22621#22621 + * Fast approximation to the log2() function. It uses a two step + * process. First, it decomposes the floating-point number into + * a fractional component F and an exponent E. The fraction component + * is used in a polynomial approximation and then the exponent added + * to the result. A 3rd order polynomial is used and the result + * when computing db20() is accurate to 7.984884e-003 dB. Y is log2(X) + */ + float v2DB_Approx(float volts) { + float Y, F; + int E; + // This is the approximation to log2() + F = frexpf(volts, &E); // first separate power of 2; + // Y = C[0]*F*F*F + C[1]*F*F + C[2]*F + C[3] + E; + Y = 1.23149591; //C[0] + Y *= F; + Y += -4.11852516f; //C[1] + Y *= F; + Y += 6.02197014f; //C[2] + Y *= F; + Y += -3.13396450f; //C[3] + Y += E; + // Convert to dB = 20 Log10(volts) + return 6.020599f * Y; // (20.0f/log2(10.0))*Y; + } + + // Accelerate the powf(10.0,x) function (from Chip's single slope compressor) + float pow10f(float x) { + //return powf(10.0f,x) //standard, but slower + return expf(2.30258509f*x); //faster: exp(log(10.0f)*x) + } + +// begin() +void AudioEffectCompressor2_F32::begin(void) { + for(int kk =0; kk<5; kk++) // Keeps division out of update() + slope[kk] = 1.0f/curve0.compressionRatio[kk]; + + outKneeDB[0] = curve0.marginDB; // Start at top + for(int kk=1; kk<5; kk++) { + outKneeDB[kk] = outKneeDB[kk-1] - (curve0.kneeDB[kk-1] - + curve0.kneeDB[kk])/curve0.compressionRatio[kk-1]; + } + + firstIndex = 4; // Start at bottom + for(int kk=4; kk>0; kk--) { + if(curve0.kneeDB[kk] >= -500.0) { + firstIndex = kk; + break; + } + } + /* for(int kk=0; kk<5; kk++) { + Serial.print(kk); + Serial.print(" <--k outKneeDB[k]--> "); + Serial.println(outKneeDB[kk]); + } + Serial.print("firstIndex--> "); + Serial.println(firstIndex); + */ + } + + // update() + void AudioEffectCompressor2_F32::update(void) { + float vAbs, vPeak; + float vInDB = 0.0f; + float vOutDB = 0.0f; + float targetGain; + + // Receive the input audio data + audio_block_f32_t *block = AudioStream_F32::receiveWritable_f32(); + if (!block) return; + // Allocate memory for the output + audio_block_f32_t *out_block = AudioStream_F32::allocate_f32(); + if (!out_block) { + release(block); + return; + } + + // Find the smoothed envelope, target gain and compressed output + vPeak = vPeakSave; + for (int k=0; klength; k++) { + vAbs = (block->data[k] >= 0.0f) ? block->data[k] : -block->data[k]; + if (vAbs >= vPeak) { // Attack (rising level) + vPeak = alpha * vPeak + (oneMinusAlpha) * vAbs; + } else { // Release (decay for falling level) + vPeak = beta * vPeak; + } + // Convert to dB + // At all levels and quite frequency flat, this under estimates by about 1.05 dB + vInDB = v2DB_Approx(vPeak) + 1.05f; + if(vInDB > vInMaxDB) vInMaxDB = vInDB; // For reporting back + + // Find gain point. Don't look below first segment firstIndex. + for(int kk=firstIndex; kk>=0; kk--) { + if( vInDB<=curve0.kneeDB[kk] || kk==0 ) { + vOutDB = outKneeDB[kk] + slope[kk]*(vInDB - curve0.kneeDB[kk]); + break; + } + } + // Convert the needed gain back to a voltage ratio 10^(db/20) + targetGain = pow10f(0.05f*(vOutDB - vInDB)); + // And apply target gain to signal stream from the delayed data. The + // delay buffer is circular because of delayBufferMask and length 2^m m<=8. + out_block->data[k] = targetGain * delayData[(k + in_index) & delayBufferMask]; + + if(printIO) { + Serial.print(block->data[k],6); + Serial.print("," ); + Serial.print(delayData[(k + in_index) & delayBufferMask],6); + Serial.print("," ); + Serial.println(targetGain); + } + + // Put the new data into the delay line, delaySize positions ahead. + // If delaySize==256, this will be the same location as we just got data from. + delayData[(k + in_index + delaySize) & delayBufferMask] = block->data[k]; + } + vPeakSave = vPeak; // save last vPeak for next time + sampleInputDB = vInDB; // Last values for get...() functions + sampleGainDB = vOutDB - vInDB; + // transmit the block and release memory + AudioStream_F32::release(block); + AudioStream_F32::transmit(out_block); // send the FIR output + AudioStream_F32::release(out_block); + // Update pointer in_index to delay line for next 128 update + in_index = (in_index + block->length) & delayBufferMask; + } // End update() + + // Sets a new compression curve by transferring structure + void AudioEffectCompressor2_F32::setCompressionCurve(struct compressionCurve *_cCurve) { + curve0.marginDB = _cCurve->marginDB; + curve0.offsetDB = _cCurve->offsetDB; + for(int kk=0; kk<5; kk++) { + // Also, adjust the input levels for offsetDB value + curve0.kneeDB[kk] = _cCurve->kneeDB[kk] - curve0.offsetDB; + curve0.compressionRatio[kk] = _cCurve->compressionRatio[kk]; + } + } diff --git a/AudioEffectCompressor2_F32.h b/AudioEffectCompressor2_F32.h new file mode 100644 index 0000000..56dd608 --- /dev/null +++ b/AudioEffectCompressor2_F32.h @@ -0,0 +1,225 @@ +/* + * AudioEffectCompressor2_F32.h + * + * Bob Larkin W7PUA 11 December 2020 + * + * This is a general purpose audio compressor block (C++ class). It works by determining + * the average input of the input signal, and based on a pre-determined curve, + * changes the gain going through the block. + * A good discussion is the Wikipedia page: + * https://en.wikipedia.org/wiki/Dynamic_range_compression + * This compressor includes up to 5 dB/dB line segments allowing for most of the + * features listed. These include + * Multi segment compression curves, up to 5 + * Limiting + * Approximation to "soft knees" + * Expansion for suppressing low-level artifacts + * Anticipation + * Scale offset for use such as hearing-aid audiology + * This is derived from the WDRC compressor. Chip Audette (OpenAudio) Feb 2017 + * Which was derived From: WDRC_circuit from CHAPRO from BTNRC: https://github.com/BTNRH/chapro + * As of Feb 2017, CHAPRO license is listed as "Creative Commons?" + * + * MIT License. Use at your own risk. + */ +/* Compressor #2. Amplifies input signals by varying amoounts depending + * on the signal level. This is controlled by up to 5 line segments specified + * by a point at the highest input level for the line along with a compression ratio + * (1/slope) for the line segment. This can provide limiting at the highest inputs + * by setting compressionRatio[0]=1000.0 (i.e., some big value). Expansion at the + * lw levels provides a squelch action, using a very small compression + * ratio like 0.01. + * + * A special case is the [0] segment, that continues at the same slope for inputs up + * to any level. For this the kneeDB[0] can be the expected 0.0 or other values + * on that line. The output level for kneeDB[0] is the input variable, marginDB. + * This allows gain control near clipping but below. marginDB is typically 1 or 2 dB. + * + Vout dB + | + | + 0.0 + kneeDB[0] + marg + kneeDB[1] @******************** + | @************ 1:compressionRatio[0] + | **** + | *** + | *** + | *** 1:compressionRatio[1] + | kneeDB[2] *** + | @*** + | * + | * + | * + | * + | * 1:compressionRatio[2] + | * + | * === Vout in dB vs. Vin in dB === + |* Three segment example + * Knees (breakpoints) are shown with '@' + *| compressionRatio[] are ratio of: input change (in dB):output change in dB + * | + * |________|___________________|____________________________|________ vIn dB + k1 k2 0.0 + + * The graph shows the changes in gain on a log or dB scale. A compressionRatio + * of 1 represents a constant gain with level. When the compressionRatio is greater + * than 1, say 2.0, the voltage gain is decreasing as the input level increases. + * + * vInDB refers to the time averaged envelope voltage. + * The zero reference is the full ADC range output. This is ±1.0 + * peak or 0.707 RMS in F32 terminology. + * + * The curve is for gainOffsetDB = 0.0. This parameter raises and lowers the + * input scale for the kneeDB[] parameter. + * + * Timing: For 44.1 kHz sample rate and 256 samples per update, the update( ) time + * runs 240 to 270 icroseconds using Teensy 3.6. + */ + +#ifndef _AUDIO_EFFECT_COMPRESSOR2_F32_H +#define _AUDIO_EFFECT_COMPRESSOR2_F32_H + +#include +#include + +// The following 3 defines are simplified implementations for common uses. +// They replace the begin() function that is otherwise required. +// See testCompressor2.ino example for how to use these defines. +// None of these support offsetting the input scale as done in Tympan. + +/* limiterBegin(pointerObject, float marDB, float linearInDB) has 2 segments. + * It is linear up to an input of linearInDB (typically -15.0f) and + * then virtually limits for higher input levels. The output level at + * this point is marDB, the margin to prevent clipping, like -2 dB. + * This is not a clipper with waveform distortion, but rather decreases + * the gain, dB for dB, as the input increases in the limiter region. + * pobject is a pointer to the INO AudioEffectCompressor2_F32 object. + * This function replaces begin() for the AudioEffectCompressor2_F32 object. + */ +#define limiterBegin(pobject, marDB, linearInDB) struct compressionCurve _curv={marDB,0.0f,{0.0,linearInDB,-1000.0f,-1000.0f,-1000.0f},{100.0,1.0f,1.0f,1.0f,1.0f}}; pobject->setCompressionCurve(&_curv); pobject->begin(); + +/* basicCompressorBegin has a 3 segments. It is linear up to an input linearInDB + * and then decreases gain according to compressionRatioDB up to an input -10 dB where it + * is almost limited, with an increase of output level of 1 dB for a 10 dB increase + * in input level. The output level at full input is 1 dB below full output. + * This function replaces begin() for the AudioEffectCompressor2_F32 object. + */ +#define basicCompressorBegin(pobject, linearInDB, compressionRatio) struct compressionCurve _curv={-1.0,0.0f,{0.0,-10.0f,linearInDB,-1000.0f,-1000.0f},{10.0f,compressionRatio,1.0f,1.0f,1.0f}}; pobject->setCompressionCurve(&_curv); pobject->begin(); + +/* squelchCompressorBegin is similar to basicCompression above, except that there is + * an expansion region for low levels. So, the call defines the four regions in + * terms of the input levels. squelchInDB sets the lowest input level + * before the squelching effect starts. */ +#define squelchCompressorBegin(pobject, squelchInDB, linearInDB, compressionInDB, compressionRatio) struct compressionCurve _curv={-1.0,0.0f,{0.0,compressionInDB,linearInDB,squelchInDB,-1000.0f},{10.0,compressionRatio,1.0f,0.1f,1.0f}}; pobject->setCompressionCurve(&_curv); pobject->begin(); + +// Basic definition of compression curve: +struct compressionCurve { + float marginDB; + float offsetDB; + float kneeDB[5]; + float compressionRatio[5]; +}; + +// --------------------------------------------------------------------------- + +class AudioEffectCompressor2_F32 : public AudioStream_F32 +{ +//GUI: inputs:1, outputs:1 //this line used for automatic generation of GUI node +//GUI: shortName: Compressor2 + public: + AudioEffectCompressor2_F32(void): AudioStream_F32(1, inputQueueArray) { + setAttackReleaseSec(0.005f, 0.100f); + } + + AudioEffectCompressor2_F32(const AudioSettings_F32 &settings): AudioStream_F32(1, inputQueueArray) { + //setSampleRate_Hz(settings.sample_rate_Hz); + setAttackReleaseSec(0.005f, 0.100f); + } + + virtual void update(void); + void begin(void); + void setCompressionCurve(struct compressionCurve*); + // A delay of 256 samples is 256/44100 = 0.0058 sec = 5.8 mSec + void setDelayBufferSize(int16_t _delaySize) { // Any power of 2, i.e., 256, 128, 64, etc. + delaySize = _delaySize; + delayBufferMask = _delaySize - 1; + in_index = 0; + } + void printOn(bool _printIO) { printIO = _printIO; } // Diagnostics ONLY. Not for general INO + float getCurrentInputDB(void) { return sampleInputDB; } + float getCurrentGainDB(void) { return sampleGainDB; } + float getvInMaxDB(void) { + float vRet = vInMaxDB; + vInMaxDB = -1000.0f; // Reset for next max measure + return vRet; + } + //convert time constants from seconds to unitless parameters, from CHAPRO, agc_prepare.c + void setAttackReleaseSec(const float atk_sec, const float rel_sec) { + // convert ANSI attack & release times to filter time constants + float ansi_atk = atk_sec * sample_rate_Hz / 2.425f; + float ansi_rel = rel_sec * sample_rate_Hz / 1.782f; + alpha = (float) (ansi_atk / (1.0f + ansi_atk)); + oneMinusAlpha = 1.0f - alpha; + beta = (float) (ansi_rel / (1.0f + ansi_rel)); + } + + private: + audio_block_f32_t *inputQueueArray[1]; + float delayData[256]; // The circular delay line for the signal + uint16_t in_index = 0; // Pointer to next block update entry + // And a mask to make the circular buffer limit to a power of 2 + uint16_t delayBufferMask = 0X00FF; + uint16_t delaySize = 256; + + float sample_rate_Hz = 44100; + float attackSec = 0.005f; // Q: Can this be reduced with the delay line added to the signal path?? + float releaseSec = 0.100f; + // This alpha, beta for 5 ms attack, 100ms release, about 0.07 dB max ripple at 1000 Hz + float alpha = 0.98912216f; + float oneMinusAlpha = 0.01087784f; + float beta = 0.9995961f; + + /* Definition of the compression curve. + * Input and output are normally referenced to the full scale range (-1.0 to 1.0 in float). + * The full scale point is called 0 dB. + * There are 5 slopes, specified by the top input level for each line segment (kneeDB[]) + * and the Compression Ratio (1/slope) of the segment (compressionRatio[]). + * These are numbered from the highest to the lowest allowing + * the number of segments to be adjusted, i.e., there is always a segment 0 + * but there may not be a segment 3 and 4, for instance. This becomes very flexible, + * as there can be, say, compression for top levels and expansion for very low levels. + * + * A special case is segment 0. On a plot of output dB vs input db, an input + * level of kneeDB[0] produces an output of marginDB. A typical marginDB might + * be -1.0 or -2.0. This margin allows controlling the gain without clipping in the DAC. + * + * This structure can have multiple curve structures like cCurve that can be used by + * cmpr1.setCompressionCurve(&cCurve); + * cmpr1.begin(); + * + * Unused segments can have knees like kneeDB[4] at, say -1000.0f. + * Values below -500 will slightly speed up the update() function. if the bottom + * two segments are not used, kneeDB[3] would also be set to -1000.0f amd so forth. + * + * Finally, there is a variable offsetDB that allows for a shift in the input scale. + * It simply shifts the definition input levels, in dB. + * This allows converting from DSP scales that have 0dB at full DSP scale + * to auditory scales such as are used in the Tympan library. An offsetDB=119.0 + * would allow all inputs to be in "SPL" units with a maximum input value of 119. + */ + struct compressionCurve curve0 = { -2.0f, 0.0f, // margin, offset + {0.0f, -10.0f, -20.0f, -30.0f, -1000.0f}, // kneeDB[] + { 100.0f, 2.0f, 1.5f, 1.0f, 1.0f} }; // compressionRatio + // VoutDB at each knee, needed to find gain; determined at begin(): + float outKneeDB[5]={-2.0f, -3.0f, -8.0f -978.0f, -978.0f}; + // slopes are 1/compressionRatio determined at begin() to save update{} time + float slope[5] = {0.01f, 0.5f, 0.666667f, 1.0f, 1.0f}; + + // Save time in update() by ignoring unused (low level) segments: + uint16_t firstIndex = 3; + float vPeakSave = 0.0f; + float vInMaxDB = -1000.0f; // Only for reporting + bool printIO = false; // Diagnostics Only + float sampleInputDB, sampleGainDB; +}; +#endif diff --git a/OpenAudio_ArduinoLibrary.h b/OpenAudio_ArduinoLibrary.h index 6c066d0..bfa29a5 100644 --- a/OpenAudio_ArduinoLibrary.h +++ b/OpenAudio_ArduinoLibrary.h @@ -8,6 +8,7 @@ #include "AudioControlTester.h" #include "AudioConvert_F32.h" #include "AudioEffectCompressor_F32.h" +#include "AudioEffectCompressor2_F32.h" #include "AudioEffectCompWDRC_F32.h" #include "AudioEffectEmpty_F32.h" #include "AudioEffectGain_F32.h" diff --git a/examples/testCompressor2/OutInDefault.gif b/examples/testCompressor2/OutInDefault.gif new file mode 100644 index 0000000..584b552 Binary files /dev/null and b/examples/testCompressor2/OutInDefault.gif differ diff --git a/examples/testCompressor2/OutInSquelch.gif b/examples/testCompressor2/OutInSquelch.gif new file mode 100644 index 0000000..79739b7 Binary files /dev/null and b/examples/testCompressor2/OutInSquelch.gif differ diff --git a/examples/testCompressor2/testCompressor2.ino b/examples/testCompressor2/testCompressor2.ino new file mode 100644 index 0000000..65eeda8 --- /dev/null +++ b/examples/testCompressor2/testCompressor2.ino @@ -0,0 +1,173 @@ +/* TestCompressor2.ino Bob Larkin 23 January 2021 + * + * Test of AudioEffectCompressor2_F32 + * See AudioEffectCompressor2_F32.h for much detail and explanation. + * Choice of test signals is a single sine wave, a random sequence + * of sine waves of varying frequency and amplitude, a power + * sweep or a pulse of sine wave to see transient behavior. + * + * This version is for the Chip Audette OpenAudio_F32 Library. and + * thus has that interface structure. + * + * NOTE: As of 23 January 2021, the compressor AudioEffectWDRC2_F32.h + * was not finalized and could change in detail. Use here with + * this in mind. + */ + +#include "Audio.h" +#include "OpenAudio_ArduinoLibrary.h" + +#define RANDOM 1 +#define POWER_SWEEP 2 +#define PULSE 3 +#define ALTERNATE 4 +// Edit in one of the last four, here: +#define SIGNAL_SOURCE RANDOM + +AudioSynthWaveformSine_F32 sine1; // Test signal +AudioEffectCompressor2_F32 compressor1; // Audio Compressor +AudioEffectGain_F32 gain0; // Sets volume sent to output +AudioEffectGain_F32 gain1; // Sets the same +AudioOutputI2S_F32 i2sOut; +AudioConnection_F32 patchCord1(sine1, 0, compressor1, 0); +AudioConnection_F32 patchCord2(compressor1, 0, gain0, 0); +AudioConnection_F32 patchCord3(compressor1, 0, gain1, 0); +AudioConnection_F32 patchCord4(gain0, 0, i2sOut, 0); +AudioConnection_F32 patchCord5(gain1, 0, i2sOut, 1); +AudioControlSGTL5000 sgtl5000_1; + +uint16_t count17, count27; +float level = 0.05f; + +void setup(void) { + AudioMemory(50); + AudioMemory_F32(100); + Serial.begin(300); delay(1000); + Serial.println("*** Test Compressor2 Gain Compressor **"); + + count17 = 0; + count27 = 0; + sine1.frequency(1000.0f); + sine1.amplitude(0.05f); + // CAUTION - If using ears on the output, adjust the following two carefully + gain0.setGain_dB(-25.0f); // Consider (-50.0f); + gain1.setGain_dB(-25.0f); // Consider (-50.0f); + sgtl5000_1.enable(); + + int16_t delaySize = 256; // Any power of 2, i.e., 256, 128, 64, etc. + compressor1.setDelayBufferSize(delaySize); + + // *** Here are four sample compressor curves. *** + // Select by number here, and re-compile. +#define COMPRESSOR_CURVE 1 + +#if COMPRESSOR_CURVE == 1 + // Specify arbitrary 5-segment compression curve. An example of specifying + // compressionCurve. See AudioEffectCompressor2_F32.h for more details. + struct compressionCurve crv = { -2.0f, 0.0f, // margin, offset + {0.0f, -10.0f, -20.0f, -30.0f, -1000.0f}, // kneeDB[] + { 100.0f, 2.5f, 1.5f, 1.0f, 1.0f} }; // compressionRatio + compressor1.setCompressionCurve(&crv); + compressor1.begin(); +#endif + + AudioEffectCompressor2_F32 *pc1 = &compressor1; // Needed for any *macro* defined curve. + // Defines pointer to compressor1, called pc1 + +#if COMPRESSOR_CURVE == 2 + // Sample of a limiter at -3 dB out for highest 15 dB, limiter macro + limiterBegin(pc1, -3.0f, -15.0f); // pc1 is a pointer to compressor1 object + +#elif COMPRESSOR_CURVE == 3 + // Another one, a basic compressor curve: + // (pobject, linearInDB, compressionRatioDB) <- macro parameters + basicCompressorBegin(pc1, -25.0f, 2.0); + +#elif COMPRESSOR_CURVE == 4 + // And one using a high gain region as a squelch (but no clicks or pops) + // (squelchIndB, linearInDB, compressionIndB, compressionRatioDB) <- macro parameters + squelchCompressorBegin(pc1, -40.0f, -25.0f, -10.0f, 1.5f); +#endif +} + +void loop(void) + { + static uint16_t kk; + +#if SIGNAL_SOURCE == RANDOM + /* To give an audio signal with interest, we alter the frequency + * every 17 blocks (49 msec) and alter the level every 27 b;ocks + * (78.4 msec) The pattern keeps changing to be more interesting + * Janet thinks it is aliens. */ + + delay(3); + // Serial.print(" CurInDB= "); Serial.print( compressor1.getCurrentInputDB(), 3); + // Serial.print(" CurrentGainDB= "); Serial.println( compressor1.getCurrentGainDB(), 3); + // Serial.print("Maximum Input = "); Serial.println(compressor1.getvInMaxDB()); + if(count17++ == 17) + { + // Put a delay in, like between words + if(randUniform() < 0.03) + delay( (int)(1500.0*randUniform()) ); + count17 = 0; + float ff = 350.0f + 700.0f*sqrtf( randUniform() ); + sine1.frequency(ff); //Serial.println(ff); + } + if(count27++ == 27) + { + count27 = 0; + level = 1.0f*powf( randUniform(), 2 ); // 0 to 1, emphasizing 0 end + sine1.amplitude(level); + } + +#elif SIGNAL_SOURCE == POWER_SWEEP + if(count17++ == 17) + { + count17 = 0; + level *= 1.0592537f; // 0.5 dB + delay(200); + if(level > 1.0f) + { + level=0.001f; + delay(500); + } + Serial.print( compressor1.getCurrentInputDB(), 3); + Serial.print(","); Serial.println( compressor1.getCurrentGainDB(), 3); + sine1.amplitude(level); + } + +#elif SIGNAL_SOURCE == PULSE + // A pulse, repeats every 3 minutes or so + delay(3); + if(count17 == 5) sine1.amplitude(0.0f); // Settling + else if(count17 == 498) compressor1.printOn(true); //record it + else if(count17 == 500) sine1.amplitude(0.03f); + else if(count17 == 510) sine1.amplitude(0.0f); + else if(count17 == 700) compressor1.printOn(false); + // or build your own transient test pulse here + count17++; +#endif + } + +/* randUniform() - Returns random number, uniform on (0, 1) + * The "Even Quicker" uniform random sample generator from D. E. Knuth and + * H. W. Lewis and described in Chapter 7 of "Numerical Receipes in C", + * 2nd ed, with the comment "this is about as good as any 32-bit linear + * congruential generator, entirely adequate for many uses." + */ +#define FL_ONE 0X3F800000 +#define FL_MASK 0X007FFFFF +float randUniform(void) + { + static uint32_t idum = 12345; + union { + uint32_t i32; + float f32; + } uinf; + + idum = (uint32_t)1664525 * idum + (uint32_t)1013904223; + // return (*(float *)&it); // Cute convert to float but gets compiler warning + uinf.i32 = FL_ONE | (FL_MASK & idum); // So do the same thing with a union + + return uinf.f32 - 1.0f; + }