Moved effect_delay_ext8 to third-party software.

Added effect_compressor (from OpenAudio-library) to third-party software.
dev
Holger Wirtz 2 years ago
parent 11c7ed8e1d
commit 3a97367755
  1. 2
      MicroDexed.ino
  2. 2
      UI.hpp
  3. 10
      third-party/effect_compressor/library.properties
  4. 155
      third-party/effect_compressor/src/effect_compressor.cpp
  5. 228
      third-party/effect_compressor/src/effect_compressor.h
  6. 10
      third-party/effect_delay_ext8/library.properties
  7. 0
      third-party/effect_delay_ext8/src/effect_delay_ext8.cpp
  8. 0
      third-party/effect_delay_ext8/src/effect_delay_ext8.h

@ -50,7 +50,7 @@
#include "synth_mda_epiano.h" #include "synth_mda_epiano.h"
#include <effect_stereo_panorama.h> #include <effect_stereo_panorama.h>
#if defined(USE_DELAY_8M) #if defined(USE_DELAY_8M)
#include "effect_delay_ext8.h" #include <effect_delay_ext8.h>
#endif #endif
// Audio engines // Audio engines

@ -40,7 +40,7 @@
#include "dexed_sd.h" #include "dexed_sd.h"
#include <effect_stereo_panorama.h> #include <effect_stereo_panorama.h>
#if defined(USE_DELAY_8M) #if defined(USE_DELAY_8M)
#include "effect_delay_ext8.h" #include <effect_delay_ext8.h>
#endif #endif
#define _LCDML_DISP_cols LCD_cols #define _LCDML_DISP_cols LCD_cols

@ -0,0 +1,10 @@
name=effect_compressor
version=1.0
author=
maintainer=
sentence=
paragraph=
category=Audio
url=
architectures=teensy,avr
license=

@ -0,0 +1,155 @@
/*
* Taken and adapted from: https://github.com/chipaudette/OpenAudio_ArduinoLibrary
*
* AudioEffectCompressor2_F32.cpp
*
* Bob Larkin W7PUA 22 January 2021
* See AudioEffectCompressor2_F32.h for details
*
* MIT License. Use at your own risk.
*/
#include "effect_compressor.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::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::update(void) {
float vAbs, vPeak;
float vInDB = 0.0f;
float vOutDB = 0.0f;
float targetGain;
// Receive the input audio data
audio_block_t *block = AudioStream::receiveWritable();
if (!block) return;
// Allocate memory for the output
audio_block_t *out_block = AudioStream::allocate();
if (!out_block) {
release(block);
return;
}
arm_q15_to_float(block->data, block_f32, AUDIO_BLOCK_SAMPLES);
// Find the smoothed envelope, target gain and compressed output
vPeak = vPeakSave;
for (int k=0; k<block->length; 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
arm_float_to_q15(block_f32, block->data, AUDIO_BLOCK_SAMPLES);
AudioStream::release(block);
AudioStream::transmit(out_block); // send the FIR output
AudioStream::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::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];
}
}

@ -0,0 +1,228 @@
/*
* Taken and adapted from: https://github.com/chipaudette/OpenAudio_ArduinoLibrary
*
* 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_COMPRESSOR_H
#define _AUDIO_EFFECT_COMPRESSOR_H
#include <Arduino.h>
#include <AudioStream.h>
// 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 : public AudioStream
{
//GUI: inputs:1, outputs:1 //this line used for automatic generation of GUI node
//GUI: shortName: Compressor2
public:
AudioEffectCompressor2(void): AudioStream(1, inputQueueArray) {
setAttackReleaseSec(0.005f, 0.100f);
}
AudioEffectCompressor2(const AudioSettings &settings): AudioStream(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_t *inputQueueArray[1];
float block_f32[AUDIO_BLOCK_SAMPLES];
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

@ -0,0 +1,10 @@
name=effect_delay_ext8
version=1.0
author=
maintainer=
sentence=
paragraph=
category=Audio
url=
architectures=teensy,avr
license=
Loading…
Cancel
Save