parent
94c5d375df
commit
be8f93b976
@ -0,0 +1,144 @@ |
|||||||
|
/* Audio Library for Teensy 3.X
|
||||||
|
* Dynamics Processor (Gate, Compressor & Limiter) |
||||||
|
* Copyright (c) 2017, Marc Paquette (marc@dacsystemes.com) |
||||||
|
* Based on analyse_rms & mixer objects by Paul Stoffregen |
||||||
|
* |
||||||
|
* Development of this audio library was funded by PJRC.COM, LLC by sales of |
||||||
|
* Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop |
||||||
|
* open source software by purchasing Teensy or other PJRC products. |
||||||
|
* |
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
* of this software and associated documentation files (the "Software"), to deal |
||||||
|
* in the Software without restriction, including without limitation the rights |
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
* copies of the Software, and to permit persons to whom the Software is |
||||||
|
* furnished to do so, subject to the following conditions: |
||||||
|
* |
||||||
|
* The above copyright notice, development funding notice, and this permission |
||||||
|
* notice shall be included in all copies or substantial portions of the Software. |
||||||
|
* |
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||||
|
* THE SOFTWARE. |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "effect_dynamics.h" |
||||||
|
#include "fast_log.h" |
||||||
|
#include "utility/dspinst.h" |
||||||
|
#include "utility/sqrt_integer.h" |
||||||
|
|
||||||
|
static float analyse_rms(int16_t *data) { |
||||||
|
|
||||||
|
uint32_t *p = (uint32_t *)data; |
||||||
|
const uint32_t *end = p + AUDIO_BLOCK_SAMPLES / 2; |
||||||
|
int64_t sum = 0; |
||||||
|
do { |
||||||
|
uint32_t n1 = *p++; |
||||||
|
uint32_t n2 = *p++; |
||||||
|
uint32_t n3 = *p++; |
||||||
|
uint32_t n4 = *p++; |
||||||
|
sum = multiply_accumulate_16tx16t_add_16bx16b(sum, n1, n1); |
||||||
|
sum = multiply_accumulate_16tx16t_add_16bx16b(sum, n2, n2); |
||||||
|
sum = multiply_accumulate_16tx16t_add_16bx16b(sum, n3, n3); |
||||||
|
sum = multiply_accumulate_16tx16t_add_16bx16b(sum, n4, n4); |
||||||
|
|
||||||
|
} while (p < end); |
||||||
|
|
||||||
|
int32_t meansq = sum / AUDIO_BLOCK_SAMPLES; |
||||||
|
return sqrt_uint32(meansq) / 32767.0f; |
||||||
|
} |
||||||
|
|
||||||
|
static void applyGain(int16_t *data, int32_t mult1, int32_t mult2) { |
||||||
|
|
||||||
|
uint32_t *p = (uint32_t *)data; |
||||||
|
const uint32_t *end = p + AUDIO_BLOCK_SAMPLES / 2; |
||||||
|
int32_t inc = (mult2 - mult1) / (AUDIO_BLOCK_SAMPLES / 2); |
||||||
|
|
||||||
|
do { |
||||||
|
uint32_t tmp32 = *p; // read 2 samples from *data
|
||||||
|
int32_t val1 = signed_multiply_32x16b(mult1, tmp32); |
||||||
|
mult1 += inc; |
||||||
|
int32_t val2 = signed_multiply_32x16t(mult1, tmp32); |
||||||
|
mult1 += inc; |
||||||
|
val1 = signed_saturate_rshift(val1, 16, 0); |
||||||
|
val2 = signed_saturate_rshift(val2, 16, 0); |
||||||
|
*p++ = pack_16b_16b(val2, val1); |
||||||
|
} while (p < end); |
||||||
|
} |
||||||
|
|
||||||
|
void AudioEffectDynamics::update(void) { |
||||||
|
|
||||||
|
audio_block_t *block; |
||||||
|
|
||||||
|
block = receiveWritable(0); |
||||||
|
|
||||||
|
if (!block) return; |
||||||
|
|
||||||
|
if (!gateEnabled && !compEnabled && !limiterEnabled) { |
||||||
|
|
||||||
|
//Transmit & release
|
||||||
|
transmit(block); |
||||||
|
release(block); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
//Analyze received block
|
||||||
|
float rms = analyse_rms(block->data); |
||||||
|
|
||||||
|
//Compute block RMS level in Db
|
||||||
|
float inputdb = MIN_DB; |
||||||
|
if (rms > 0) inputdb = unitToDb(rms); |
||||||
|
|
||||||
|
//Gate
|
||||||
|
if (gateEnabled) { |
||||||
|
if (inputdb >= gateThresholdOpen) gatedb = (aGateAttack * gatedb) + (aOneMinusGateAttack * MAX_DB); |
||||||
|
else if (inputdb < gateThresholdClose) gatedb = (aGateRelease * gatedb) + (aOneMinusGateRelease * MIN_DB); |
||||||
|
} |
||||||
|
else gatedb = MAX_DB; |
||||||
|
|
||||||
|
//Compressor
|
||||||
|
if (compEnabled) { |
||||||
|
float attdb = MAX_DB; //Below knee
|
||||||
|
if (inputdb >= aLowKnee) { |
||||||
|
if(inputdb <= aHighKnee) { |
||||||
|
//Knee transition
|
||||||
|
float knee = inputdb - aLowKnee; |
||||||
|
attdb = aKneeRatio * knee * knee * aTwoKneeWidth; |
||||||
|
} |
||||||
|
else { |
||||||
|
//Above knee
|
||||||
|
attdb = compThreshold + ((inputdb - compThreshold) * compRatio) - inputdb; |
||||||
|
} |
||||||
|
} |
||||||
|
if (attdb <= compdb) compdb = (aCompAttack * compdb) + (aOneMinusCompAttack * attdb); |
||||||
|
else compdb = (aCompRelease * compdb) + (aOneMinusCompRelease * attdb); |
||||||
|
} |
||||||
|
else compdb = MAX_DB; |
||||||
|
|
||||||
|
//Brickwall Limiter
|
||||||
|
if (limiterEnabled) { |
||||||
|
float outdb = inputdb + compdb + makeupdb; |
||||||
|
if (outdb >= limitThreshold) limitdb = (aLimitAttack * limitdb) + (aOneMinusLimitAttack * (limitThreshold - outdb)); |
||||||
|
else limitdb *= aLimitRelease; |
||||||
|
} |
||||||
|
else limitdb = MAX_DB; |
||||||
|
|
||||||
|
//Compute linear gain
|
||||||
|
float totalGain = gatedb + compdb + makeupdb + limitdb; |
||||||
|
int32_t mult = dbToUnit(totalGain) * 65536.0f; |
||||||
|
|
||||||
|
//Apply gain to block
|
||||||
|
applyGain(block->data, last_mult, mult); |
||||||
|
last_mult = mult; |
||||||
|
|
||||||
|
//Transmit & release
|
||||||
|
transmit(block); |
||||||
|
release(block); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,196 @@ |
|||||||
|
/* Audio Library for Teensy 3.X
|
||||||
|
* Dynamics Processor (Gate, Compressor & Limiter) |
||||||
|
* Copyright (c) 2018, Marc Paquette (marc@dacsystemes.com) |
||||||
|
* Based on analyse_rms, effect_envelope & mixer objects by Paul Stoffregen |
||||||
|
* |
||||||
|
* Development of this audio library was funded by PJRC.COM, LLC by sales of |
||||||
|
* Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop |
||||||
|
* open source software by purchasing Teensy or other PJRC products. |
||||||
|
* |
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
* of this software and associated documentation files (the "Software"), to deal |
||||||
|
* in the Software without restriction, including without limitation the rights |
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
* copies of the Software, and to permit persons to whom the Software is |
||||||
|
* furnished to do so, subject to the following conditions: |
||||||
|
* |
||||||
|
* The above copyright notice, development funding notice, and this permission |
||||||
|
* notice shall be included in all copies or substantial portions of the Software. |
||||||
|
* |
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||||
|
* THE SOFTWARE. |
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef effect_dynamics_h_ |
||||||
|
#define effect_dynamics_h_ |
||||||
|
|
||||||
|
#include "Arduino.h" |
||||||
|
#include "AudioStream.h" |
||||||
|
|
||||||
|
#define MIN_DB -110.0f |
||||||
|
#define MAX_DB 0.0f |
||||||
|
|
||||||
|
#define MIN_T 0.03f //Roughly 1 block
|
||||||
|
#define MAX_T 4.00f |
||||||
|
|
||||||
|
#define RATIO_OFF 1.0f |
||||||
|
#define RATIO_INFINITY 60.0f |
||||||
|
|
||||||
|
class AudioEffectDynamics : public AudioStream |
||||||
|
{ |
||||||
|
public: |
||||||
|
AudioEffectDynamics(void) : AudioStream(1, inputQueueArray) { |
||||||
|
|
||||||
|
gate(); |
||||||
|
compression(); |
||||||
|
limit(); |
||||||
|
autoMakeupGain(); |
||||||
|
} |
||||||
|
|
||||||
|
//Sets the gate parameters.
|
||||||
|
//threshold is in dbFS
|
||||||
|
//attack & release are in seconds
|
||||||
|
void gate(float threshold = -50.0f, float attack = MIN_T, float release = 0.3f, float hysterisis = 6.0f) { |
||||||
|
|
||||||
|
gateEnabled = threshold > MIN_DB; |
||||||
|
|
||||||
|
gateThresholdOpen = constrain(threshold, MIN_DB, MAX_DB); |
||||||
|
gateThresholdClose = gateThresholdOpen - constrain(hysterisis, 0.0f, 6.0f); |
||||||
|
|
||||||
|
float gateAttackTime = constrain(attack, MIN_T, MAX_T); |
||||||
|
float gateReleaseTime = constrain(release, MIN_T, MAX_T); |
||||||
|
|
||||||
|
aGateAttack = timeToAlpha(gateAttackTime); |
||||||
|
aOneMinusGateAttack = 1.0f - aGateAttack; |
||||||
|
aGateRelease = timeToAlpha(gateReleaseTime); |
||||||
|
aOneMinusGateRelease = 1.0f - aGateRelease; |
||||||
|
|
||||||
|
gatedb = MIN_DB; |
||||||
|
} |
||||||
|
|
||||||
|
//Sets the compression parameters.
|
||||||
|
//threshold & kneeWidth are in db(FS)
|
||||||
|
//attack and release are in seconds
|
||||||
|
//ratio is expressed as x:1 i.e. 1 for no compression, 60 for brickwall limiting
|
||||||
|
//Set kneeWidth to 0 for hard knee
|
||||||
|
void compression(float threshold = -40.0f, float attack = MIN_T, float release = 0.5f, float ratio = 35.0f, float kneeWidth = 6.0f) { |
||||||
|
|
||||||
|
compEnabled = threshold < MAX_DB; |
||||||
|
|
||||||
|
compThreshold = constrain(threshold, MIN_DB, MAX_DB); |
||||||
|
float compAttackTime = constrain(attack, MIN_T, MAX_T); |
||||||
|
float compReleaseTime = constrain(release, MIN_T, MAX_T); |
||||||
|
compRatio = 1.0f / constrain(abs(ratio), RATIO_OFF, RATIO_INFINITY); |
||||||
|
float compKneeWidth = constrain(abs(kneeWidth), 0.0f, 32.0f); |
||||||
|
computeMakeupGain(); |
||||||
|
|
||||||
|
aCompAttack = timeToAlpha(compAttackTime); |
||||||
|
aOneMinusCompAttack = 1.0f - aCompAttack; |
||||||
|
aCompRelease = timeToAlpha(compReleaseTime); |
||||||
|
aOneMinusCompRelease = 1.0f - aCompRelease; |
||||||
|
aHalfKneeWidth = compKneeWidth / 2.0f; |
||||||
|
aTwoKneeWidth = 1.0f / (compKneeWidth * 2.0f); |
||||||
|
aKneeRatio = compRatio - 1.0f; |
||||||
|
aLowKnee = compThreshold - aHalfKneeWidth; |
||||||
|
aHighKnee = compThreshold + aHalfKneeWidth; |
||||||
|
|
||||||
|
compdb = MIN_DB; |
||||||
|
} |
||||||
|
|
||||||
|
//Sets the hard limiter parameters
|
||||||
|
//threshold is in dbFS
|
||||||
|
//attack & release are in seconds
|
||||||
|
void limit(float threshold = -3.0f, float attack = MIN_T, float release = MIN_T) { |
||||||
|
|
||||||
|
limiterEnabled = threshold < MAX_DB; |
||||||
|
|
||||||
|
limitThreshold = constrain(threshold, MIN_DB, MAX_DB); |
||||||
|
float limitAttackTime = constrain(attack, MIN_T, MAX_T); |
||||||
|
float limitReleaseTime = constrain(release, MIN_T, MAX_T);
|
||||||
|
|
||||||
|
computeMakeupGain(); |
||||||
|
|
||||||
|
aLimitAttack = timeToAlpha(limitAttackTime); |
||||||
|
aOneMinusLimitAttack = 1.0f - aLimitAttack; |
||||||
|
aLimitRelease = timeToAlpha(limitReleaseTime); |
||||||
|
|
||||||
|
limitdb = MIN_DB; |
||||||
|
} |
||||||
|
|
||||||
|
//Enables automatic makeup gain setting
|
||||||
|
//headroom is in dbFS
|
||||||
|
void autoMakeupGain(float headroom = 6.0f) { |
||||||
|
|
||||||
|
mgAutoEnabled = true; |
||||||
|
mgHeadroom = constrain(headroom, 0.0f, 60.0f); |
||||||
|
computeMakeupGain(); |
||||||
|
} |
||||||
|
|
||||||
|
//Sets a fixed makeup gain value.
|
||||||
|
//gain is in dbFS
|
||||||
|
void makeupGain(float gain = 0.0f) { |
||||||
|
|
||||||
|
mgAutoEnabled = false; |
||||||
|
makeupdb = constrain(gain, -12.0f, 24.0f); |
||||||
|
} |
||||||
|
|
||||||
|
private: |
||||||
|
audio_block_t *inputQueueArray[1]; |
||||||
|
|
||||||
|
bool gateEnabled = false; |
||||||
|
float gateThresholdOpen; |
||||||
|
float gateThresholdClose; |
||||||
|
float gatedb; |
||||||
|
|
||||||
|
bool compEnabled = false; |
||||||
|
float compThreshold; |
||||||
|
float compRatio; |
||||||
|
float compdb; |
||||||
|
|
||||||
|
bool limiterEnabled = false; |
||||||
|
float limitThreshold; |
||||||
|
float limitdb; |
||||||
|
|
||||||
|
bool mgAutoEnabled; |
||||||
|
float mgHeadroom; |
||||||
|
float makeupdb; |
||||||
|
|
||||||
|
float aGateAttack; |
||||||
|
float aOneMinusGateAttack; |
||||||
|
float aGateRelease; |
||||||
|
float aOneMinusGateRelease; |
||||||
|
float aHalfKneeWidth; |
||||||
|
float aTwoKneeWidth; |
||||||
|
float aKneeRatio; |
||||||
|
float aLowKnee; |
||||||
|
float aHighKnee; |
||||||
|
float aCompAttack; |
||||||
|
float aOneMinusCompAttack; |
||||||
|
float aCompRelease; |
||||||
|
float aOneMinusCompRelease; |
||||||
|
float aLimitAttack; |
||||||
|
float aOneMinusLimitAttack; |
||||||
|
float aLimitRelease; |
||||||
|
|
||||||
|
int32_t last_mult; |
||||||
|
|
||||||
|
void computeMakeupGain() { |
||||||
|
if (mgAutoEnabled) { |
||||||
|
makeupdb = -compThreshold + (compThreshold * compRatio) + limitThreshold - mgHeadroom; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
//Computes smoothing time constants for a 10% to 90% change
|
||||||
|
float timeToAlpha(float time) { |
||||||
|
return expf(-0.9542f / (((float)AUDIO_SAMPLE_RATE_EXACT / (float)AUDIO_BLOCK_SAMPLES) * time)); |
||||||
|
} |
||||||
|
|
||||||
|
virtual void update(void); |
||||||
|
}; |
||||||
|
|
||||||
|
#endif |
@ -0,0 +1,50 @@ |
|||||||
|
/* ----------------------------------------------------------------------
|
||||||
|
* 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. |
||||||
|
** ------------------------------------------------------------------- */ |
||||||
|
|
||||||
|
float log2f_approx_coeff[4] = {1.23149591368684f, -4.11852516267426f, 6.02197014179219f, -3.13396450166353f}; |
||||||
|
|
||||||
|
float log2f_approx(float X) |
||||||
|
{ |
||||||
|
float *C = &log2f_approx_coeff[0]; |
||||||
|
float Y; |
||||||
|
float F; |
||||||
|
int E; |
||||||
|
|
||||||
|
// This is the approximation to log2()
|
||||||
|
F = frexpf(fabsf(X), &E); |
||||||
|
|
||||||
|
// Y = C[0]*F*F*F + C[1]*F*F + C[2]*F + C[3] + E;
|
||||||
|
Y = *C++; |
||||||
|
Y *= F; |
||||||
|
Y += (*C++); |
||||||
|
Y *= F; |
||||||
|
Y += (*C++); |
||||||
|
Y *= F; |
||||||
|
Y += (*C++); |
||||||
|
Y += E; |
||||||
|
return(Y); |
||||||
|
} |
||||||
|
|
||||||
|
// https://codingforspeed.com/using-faster-exponential-approximation/
|
||||||
|
inline float expf_approx(float x) { |
||||||
|
x = 1.0f + x / 1024; |
||||||
|
x *= x; x *= x; x *= x; x *= x; |
||||||
|
x *= x; x *= x; x *= x; x *= x; |
||||||
|
x *= x; x *= x; |
||||||
|
return x; |
||||||
|
} |
||||||
|
|
||||||
|
inline float unitToDb(float unit) { |
||||||
|
return 6.02f * log2f_approx(unit); |
||||||
|
} |
||||||
|
|
||||||
|
inline float dbToUnit(float db) { |
||||||
|
return expf_approx(db * 2.302585092994046f * 0.05f); |
||||||
|
} |
Loading…
Reference in new issue