add files, readme update

pull/2/head
pio 10 months ago
parent 699abca893
commit 8801c8f122
  1. 13
      README.md
  2. 22
      src/basic_DSPutils.h
  3. 63
      src/effect_gainStereo_F32.h
  4. 174
      src/effect_noiseGateStereo_F32.h
  5. 311
      src/effect_reverbsc_F32.cpp
  6. 149
      src/effect_reverbsc_F32.h
  7. 408
      src/effect_springreverb_F32.cpp
  8. 181
      src/effect_springreverb_F32.h
  9. 153
      src/filter_3bandeq.h

@ -14,6 +14,16 @@ Mono to stereo converter.
**AudioEffectPlateReverb_F32**
Stereo plate reverb.
**AudioEffectSpringReverb_F32**
Stereo spring reverb emulation.
**AudioEffectReverbSc_F32**
8 delay line stereo FDN reverb, based on work by Sean Costello.
Optional PSRAM use for the delay buffers.
**AudioEffectNoiseGateStereo_F32**
Stereo noise gate with external SideChain input.
**AudioFilterToneStackStereo_F32**
Stereo guitar tone stack (EQ) emulator.
@ -24,6 +34,9 @@ Slightly modified original equalizer component, added bypass system.
Stereo guitar/bass cabinet emulator using low latency uniformly partitioned convolution.
10 cabinet impulse responses built in.
**AudioFilterEqualizer3band_F32**
Simple 3 band (Treble, Mid, Bass) equalizer.
## I/O
**AudioInputI2S2_F32**
**AudioOutputI2S2_F32**

@ -0,0 +1,22 @@
#ifndef _BASIC_DSPUTILS_H_
#define _BASIC_DSPUTILS_H_
#include <arm_math.h>
static inline void mix_pwr(float32_t mix, float32_t *wetMix, float32_t *dryMix);
static inline void mix_pwr(float32_t mix, float32_t *wetMix, float32_t *dryMix)
{
// Calculate mix parameters
// A cheap mostly energy constant crossfade from SignalSmith Blog
// https://signalsmith-audio.co.uk/writing/2021/cheap-energy-crossfade/
float32_t x2 = 1.0f - mix;
float32_t A = mix*x2;
float32_t B = A * (1.0f + 1.4186f * A);
float32_t C = B + mix;
float32_t D = B + x2;
*wetMix = C * C;
*dryMix = D * D;
}
#endif // _BASIC_DSPUTILS_H_

@ -0,0 +1,63 @@
/*
* AudioEffectGain_F32
*
* Created: Chip Audette, November 2016
* Purpose; Apply digital gain to the audio data. Assumes floating-point data.
*
* This processes a single stream fo audio data (ie, it is mono)
*
* MIT License. use at your own risk.
*/
#ifndef _AudioEffectGainStereo_F32_h
#define _AudioEffectGainStereo_F32_h
#include <arm_math.h> //ARM DSP extensions. for speed!
#include <AudioStream_F32.h>
class AudioEffectGainStereo_F32 : public AudioStream_F32
{
public:
// constructor
AudioEffectGainStereo_F32(void) : AudioStream_F32(2, inputQueueArray_f32){};
AudioEffectGainStereo_F32(const AudioSettings_F32 &settings) : AudioStream_F32(2, inputQueueArray_f32){};
void update(void)
{
audio_block_f32_t *blockL, *blockR;
blockL = AudioStream_F32::receiveWritable_f32(0);
blockR = AudioStream_F32::receiveWritable_f32(1);
if (!blockL || !blockR)
{
if (blockL)
AudioStream_F32::release(blockL);
if (blockR)
AudioStream_F32::release(blockR);
return;
}
arm_scale_f32(blockL->data, gain, blockL->data, blockL->length); // use ARM DSP for speed!
arm_scale_f32(blockR->data, gain, blockR->data, blockR->length);
AudioStream_F32::transmit(blockL, 0);
AudioStream_F32::transmit(blockR, 1);
AudioStream_F32::release(blockL);
AudioStream_F32::release(blockR);
}
// methods to set parameters of this module
void setGain(float g) { gain = g; }
void setGain_dB(float gain_dB)
{
float gain = pow(10.0, gain_dB / 20.0);
setGain(gain);
}
// methods to return information about this module
float getGain(void) { return gain; }
float getGain_dB(void) { return 20.0 * log10(gain); }
private:
audio_block_f32_t *inputQueueArray_f32[2]; // memory pointer for the input to this module
float gain = 1.0f; // default value
};
#endif

@ -0,0 +1,174 @@
/*
* AudioEffectNoiseGate_F32
*
* Created: Max Huster, Feb 2021
* Purpose: This module mutes the Audio completly, when it's below a given threshold.
*
* This processes a single stream fo audio data (ie, it is mono)
*
* MIT License. use at your own risk.
*/
#ifndef _AudioEffectNoiseGateStereo_F32_h
#define _AudioEffectNoiseGateStereo_F32_h
#include <arm_math.h> //ARM DSP extensions. for speed!
#include <AudioStream_F32.h>
class AudioEffectNoiseGateStereo_F32 : public AudioStream_F32
{
public:
// constructor
AudioEffectNoiseGateStereo_F32(void) : AudioStream_F32(4, inputQueueArray_f32){};
AudioEffectNoiseGateStereo_F32(const AudioSettings_F32 &settings) : AudioStream_F32(4, inputQueueArray_f32){};
void update(void)
{
audio_block_f32_t *blockL, *blockR, *blockSideChL, *blockSideChR, *blockSideCh, *blockGain;
blockL = AudioStream_F32::receiveWritable_f32(0);
blockR = AudioStream_F32::receiveWritable_f32(1);
blockSideChL = AudioStream_F32::receiveReadOnly_f32(2); // side chain inputL
blockSideChR = AudioStream_F32::receiveReadOnly_f32(3); // side chain inputR
blockSideCh = AudioStream_F32::allocate_f32(); // allocate new block for summed L+R
blockGain = AudioStream_F32::allocate_f32(); // create a new audio block for the gain
if (!blockL || !blockR || !blockSideChL || !blockSideChR || !blockSideCh || !blockGain)
{
if (blockSideChL) AudioStream_F32::release(blockSideChL);
if (blockSideChR) AudioStream_F32::release(blockSideChR);
if (blockSideCh) AudioStream_F32::release(blockSideCh);
if (blockGain) AudioStream_F32::release(blockGain);
if (blockL) AudioStream_F32::release(blockL);
if (blockR) AudioStream_F32::release(blockR);
return;
}
// sum L + R
arm_add_f32(blockSideChL->data, blockSideChR->data, blockSideCh->data, blockSideCh->length);
arm_scale_f32(blockSideCh->data, 0.5f, blockSideCh->data, blockSideCh->length); // divide by 2
// calculate the desired gain
calcGain(blockSideCh, blockGain);
// smooth the "blocky" gain block
calcSmoothedGain(blockGain);
// multiply it to the input singal
arm_mult_f32(blockGain->data, blockL->data, blockL->data, blockL->length);
arm_mult_f32(blockGain->data, blockR->data, blockR->data, blockR->length);
// release gainBlock
AudioStream_F32::release(blockGain);
AudioStream_F32::release(blockSideCh);
AudioStream_F32::release(blockSideChL);
AudioStream_F32::release(blockSideChR);
// transmit the block and be done
AudioStream_F32::transmit(blockL, 0);
AudioStream_F32::transmit(blockR, 1);
AudioStream_F32::release(blockL);
AudioStream_F32::release(blockR);
}
void setThreshold(float dbfs)
{
// convert dbFS to linear value to comapre against later
linearThreshold = pow10f(dbfs / 20.0f);
}
void setOpeningTime(float timeInSeconds)
{
openingTimeConst = expf(-1.0f / (timeInSeconds * AUDIO_SAMPLE_RATE));
}
void setClosingTime(float timeInSeconds)
{
closingTimeConst = expf(-1.0f / (timeInSeconds * AUDIO_SAMPLE_RATE));
}
void setHoldTime(float timeInSeconds)
{
holdTimeNumSamples = timeInSeconds * AUDIO_SAMPLE_RATE;
}
bool infoIsOpen()
{
return _isOpenDisplay;
}
private:
float32_t linearThreshold;
float32_t prev_gain_dB = 0;
float32_t openingTimeConst, closingTimeConst;
float lastGainBlockValue = 0;
int32_t counter, holdTimeNumSamples = 0;
audio_block_f32_t *inputQueueArray_f32[4];
bool falling = false;
bool _isOpen = false;
bool _isOpenDisplay = false;
bool _extSideChain = true;
void calcGain(audio_block_f32_t *input, audio_block_f32_t *gainBlock)
{
_isOpen = false;
for (int i = 0; i < input->length; i++)
{
// take absolute value and compare it to the set threshold
bool isAboveThres = abs(input->data[i]) > linearThreshold;
_isOpen |= isAboveThres;
// if above the threshold set volume to 1 otherwise to 0, we did not account for holdtime
gainBlock->data[i] = isAboveThres ? 1 : 0;
// if we are falling and are above the threshold, the level is not falling
if (falling & isAboveThres)
{
falling = false;
}
// if we have a falling signal
if (falling || lastGainBlockValue > gainBlock->data[i])
{
// check whether the hold time is not reached
if (counter < holdTimeNumSamples)
{
// signal is (still) falling
falling = true;
counter++;
gainBlock->data[i] = 1.0f;
}
// otherwise the signal is already muted due to the line: "gainBlock->data[i] = isAboveThres ? 1 : 0;"
}
// note the last gain value, so we can compare it if the signal is falling in the next sample
lastGainBlockValue = gainBlock->data[i];
}
// note the display value
_isOpenDisplay = _isOpen;
};
// this method applies the "opening" and "closing" constants to smooth the
// target gain level through time.
void calcSmoothedGain(audio_block_f32_t *gain_block)
{
float32_t gain;
float32_t one_minus_opening_const = 1.0f - openingTimeConst;
float32_t one_minus_closing_const = 1.0f - closingTimeConst;
for (int i = 0; i < gain_block->length; i++)
{
gain = gain_block->data[i];
// smooth the gain using the opening or closing constants
if (gain > prev_gain_dB)
{ // are we in the opening phase?
gain_block->data[i] = openingTimeConst * prev_gain_dB + one_minus_opening_const * gain;
}
else
{ // or, we're in the closing phase
gain_block->data[i] = closingTimeConst * prev_gain_dB + one_minus_closing_const * gain;
}
// save value for the next time through this loop
prev_gain_dB = gain_block->data[i];
}
return; // the output here is gain_block
}
};
#endif

@ -0,0 +1,311 @@
#include "effect_reverbsc_F32.h"
#define REVERBSC_DLYBUF_SIZE 98936
#define DELAYPOS_SHIFT 28
#define DELAYPOS_SCALE 0x10000000
#define DELAYPOS_MASK 0x0FFFFFFF
#ifndef M_PI
#define M_PI 3.14159265358979323846 /* pi */
#endif
/* kReverbParams[n][0] = delay time (in seconds) */
/* kReverbParams[n][1] = random variation in delay time (in seconds) */
/* kReverbParams[n][2] = random variation frequency (in 1/sec) */
/* kReverbParams[n][3] = random seed (0 - 32767) */
static const float32_t kReverbParams[8][4] =
{
{(2473.0f / AUDIO_SAMPLE_RATE_EXACT), 0.0010f, 3.100f, 1966.0f},
{(2767.0f / AUDIO_SAMPLE_RATE_EXACT), 0.0011f, 3.500f, 29491.0f},
{(3217.0f / AUDIO_SAMPLE_RATE_EXACT), 0.0017f, 1.110f, 22937.0f},
{(3557.0f / AUDIO_SAMPLE_RATE_EXACT), 0.0006f, 3.973f, 9830.0f},
{(3907.0f / AUDIO_SAMPLE_RATE_EXACT), 0.0010f, 2.341f, 20643.0f},
{(4127.0f / AUDIO_SAMPLE_RATE_EXACT), 0.0011f, 1.897f, 22937.0f},
{(2143.0f / AUDIO_SAMPLE_RATE_EXACT), 0.0017f, 0.891f, 29491.0f},
{(1933.0f / AUDIO_SAMPLE_RATE_EXACT), 0.0006f, 3.221f, 14417.0f}
};
static int DelayLineMaxSamples(float32_t sr, float32_t i_pitch_mod, int n);
static int DelayLineBytesAlloc(float32_t sr, float32_t i_pitch_mod, int n);
static const float32_t kOutputGain = 0.35f;
static const float32_t kJpScale = 0.25f;
AudioEffectReverbSc_F32::AudioEffectReverbSc_F32(bool use_psram) : AudioStream_F32(2, inputQueueArray_f32)
{
sample_rate_ = AUDIO_SAMPLE_RATE_EXACT;
feedback_ = 0.7f;
lpfreq_ = 10000;
i_pitch_mod_ = 1;
damp_fact_ = 0.195847f; // ~16kHz
int i, n_bytes = 0;
n_bytes = 0;
if (use_psram) aux_ = (float32_t *) extmem_malloc(REVERBSC_DLYBUF_SIZE*sizeof(float32_t));
else
{
aux_ = (float32_t *) malloc(REVERBSC_DLYBUF_SIZE*sizeof(float32_t));
flags.memsetup_done = 1;
}
if (!aux_) return;
for (i = 0; i < 8; i++)
{
if (n_bytes > REVERBSC_DLYBUF_SIZE)
return;
delay_lines_[i].buf = (aux_) + n_bytes;
InitDelayLine(&delay_lines_[i], i);
n_bytes += DelayLineBytesAlloc(AUDIO_SAMPLE_RATE_EXACT, 1, i);
}
mix(0.5f);
flags.bypass = 0;
flags.freeze = 0;
initialised = true;
}
static int DelayLineMaxSamples(float32_t sr, float32_t i_pitch_mod, int n)
{
float32_t max_del;
max_del = kReverbParams[n][0];
max_del += (kReverbParams[n][1] * (float32_t)i_pitch_mod * 1.125);
return (int)(max_del * sr + 16.5);
}
static int DelayLineBytesAlloc(float32_t sr, float32_t i_pitch_mod, int n)
{
int n_bytes = 0;
n_bytes += (DelayLineMaxSamples(sr, i_pitch_mod, n) * (int)sizeof(float32_t));
return n_bytes;
}
void AudioEffectReverbSc_F32::NextRandomLineseg(ReverbScDl_t *lp, int n)
{
float32_t prv_del, nxt_del, phs_inc_val;
/* update random seed */
if (lp->seed_val < 0)
lp->seed_val += 0x10000;
lp->seed_val = (lp->seed_val * 15625 + 1) & 0xFFFF;
if (lp->seed_val >= 0x8000)
lp->seed_val -= 0x10000;
/* length of next segment in samples */
lp->rand_line_cnt = (int)((sample_rate_ / kReverbParams[n][2]) + 0.5f);
prv_del = (float32_t)lp->write_pos;
prv_del -= ((float32_t)lp->read_pos + ((float32_t)lp->read_pos_frac / (float32_t)DELAYPOS_SCALE));
while (prv_del < 0.0)
prv_del += lp->buffer_size;
prv_del = prv_del / sample_rate_; /* previous delay time in seconds */
nxt_del = (float32_t)lp->seed_val * kReverbParams[n][1] / 32768.0f;
/* next delay time in seconds */
nxt_del = kReverbParams[n][0] + (nxt_del * (float32_t)i_pitch_mod_);
/* calculate phase increment per sample */
phs_inc_val = (prv_del - nxt_del) / (float32_t)lp->rand_line_cnt;
phs_inc_val = phs_inc_val * sample_rate_ + 1.0;
lp->read_pos_frac_inc = (int)(phs_inc_val * DELAYPOS_SCALE + 0.5f);
}
void AudioEffectReverbSc_F32::InitDelayLine(ReverbScDl_t *lp, int n)
{
float32_t read_pos;
/* calculate length of delay line */
lp->buffer_size = DelayLineMaxSamples(sample_rate_, 1, n);
lp->dummy = 0;
lp->write_pos = 0;
/* set random seed */
lp->seed_val = (int)(kReverbParams[n][3] + 0.5f);
/* set initial delay time */
read_pos = (float32_t)lp->seed_val * kReverbParams[n][1] / 32768.0f;
read_pos = kReverbParams[n][0] + (read_pos * (float32_t)i_pitch_mod_);
read_pos = (float32_t)lp->buffer_size - (read_pos * sample_rate_);
lp->read_pos = (int)read_pos;
read_pos = (read_pos - (float32_t)lp->read_pos) * (float32_t)DELAYPOS_SCALE;
lp->read_pos_frac = (int)(read_pos + 0.5);
/* initialise first random line segment */
NextRandomLineseg(lp, n);
/* clear delay line to zero */
lp->filter_state = 0.0f;
for (int i = 0; i < lp->buffer_size; i++)
{
lp->buf[i] = 0;
}
}
void AudioEffectReverbSc_F32::update()
{
#if defined(__IMXRT1062__)
if (!initialised) return;
if ( !flags.memsetup_done)
{
memset(aux_, 0, REVERBSC_DLYBUF_SIZE*sizeof(float32_t));
arm_dcache_flush_delete(aux_, REVERBSC_DLYBUF_SIZE*sizeof(float32_t));
flags.memsetup_done = 1;
return;
}
audio_block_f32_t *blockL, *blockR;
int16_t i;
float32_t a_in_l, a_in_r, a_out_l, a_out_r, dryL, dryR;
float32_t vm1, v0, v1, v2, am1, a0, a1, a2, frac;
ReverbScDl_t *lp;
int read_pos;
uint32_t n;
int buffer_size; /* Local copy */
float32_t damp_fact = damp_fact_;
if (flags.bypass)
{
if (dry_gain > 0.0f) // if dry/wet mixer is used
{
blockL = AudioStream_F32::receiveReadOnly_f32(0);
blockR = AudioStream_F32::receiveReadOnly_f32(1);
if (!blockL || !blockR)
{
if (blockL) AudioStream_F32::release(blockL);
if (blockR) AudioStream_F32::release(blockR);
return;
}
AudioStream_F32::transmit(blockL, 0);
AudioStream_F32::transmit(blockR, 1);
AudioStream_F32::release(blockL);
AudioStream_F32::release(blockR);
}
blockL = AudioStream_F32::allocate_f32();
if (!blockL) return;
arm_fill_f32(0.0f, blockL->data, blockL->length);
AudioStream_F32::transmit(blockL, 0);
AudioStream_F32::transmit(blockL, 1);
AudioStream_F32::release(blockL);
return;
}
blockL = AudioStream_F32::receiveWritable_f32(0);
blockR = AudioStream_F32::receiveWritable_f32(1);
if (!blockL || !blockR)
{
if (blockL)
AudioStream_F32::release(blockL);
if (blockR)
AudioStream_F32::release(blockR);
return;
}
for (i = 0; i < blockL->length; i++)
{
/* calculate "resultant junction pressure" and mix to input signals */
a_in_l = a_out_l = a_out_r = 0.0f;
dryL = blockL->data[i] * input_gain;
dryR = blockR->data[i] * input_gain;
for (n = 0; n < 8; n++)
{
a_in_l += delay_lines_[n].filter_state;
}
a_in_l *= kJpScale;
a_in_r = a_in_l + dryR;
a_in_l = a_in_l + dryL;
/* loop through all delay lines */
for (n = 0; n < 8; n++)
{
lp = &delay_lines_[n];
buffer_size = lp->buffer_size;
/* send input signal and feedback to delay line */
lp->buf[lp->write_pos] = (float32_t)((n & 1 ? a_in_r : a_in_l) - lp->filter_state);
if (++lp->write_pos >= buffer_size) lp->write_pos -= buffer_size;
/* read from delay line with cubic interpolation */
if (lp->read_pos_frac >= DELAYPOS_SCALE)
{
lp->read_pos += (lp->read_pos_frac >> DELAYPOS_SHIFT);
lp->read_pos_frac &= DELAYPOS_MASK;
}
if (lp->read_pos >= buffer_size)
lp->read_pos -= buffer_size;
read_pos = lp->read_pos;
frac = (float32_t)lp->read_pos_frac * (1.0f / (float32_t)DELAYPOS_SCALE);
/* calculate interpolation coefficients */
a2 = frac * frac;
a2 *= (1.0f / 6.0f);
a1 = frac;
a1 += 1.0f;
a1 *= 0.5f;
am1 = a1 - 1.0f;
a0 = 3.0f * a2;
a1 -= a0;
am1 -= a2;
a0 -= frac;
/* read four samples for interpolation */
if (read_pos > 0 && read_pos < (buffer_size - 2))
{
vm1 = (float32_t)(lp->buf[read_pos - 1]);
v0 = (float32_t)(lp->buf[read_pos]);
v1 = (float32_t)(lp->buf[read_pos + 1]);
v2 = (float32_t)(lp->buf[read_pos + 2]);
}
else
{
/* at buffer wrap-around, need to check index */
if (--read_pos < 0) read_pos += buffer_size;
vm1 = (float32_t)lp->buf[read_pos];
if (++read_pos >= buffer_size) read_pos -= buffer_size;
v0 = (float32_t)lp->buf[read_pos];
if (++read_pos >= buffer_size) read_pos -= buffer_size;
v1 = (float32_t)lp->buf[read_pos];
if (++read_pos >= buffer_size) read_pos -= buffer_size;
v2 = (float32_t)lp->buf[read_pos];
}
v0 = (am1 * vm1 + a0 * v0 + a1 * v1 + a2 * v2) * frac + v0;
/* update buffer read position */
lp->read_pos_frac += lp->read_pos_frac_inc;
// apply filter
v0 = (lp->filter_state - v0) * damp_fact + v0;
/* mix to output */
if (n & 1) a_out_r += v0;
else a_out_l += v0;
v0 *= (float32_t)feedback_; // apply feedback
lp->filter_state = v0; // save filter - this will make the reverb volume constant
/* start next random line segment if current one has reached endpoint */
if (--(lp->rand_line_cnt) <= 0)
{
NextRandomLineseg(lp, n);
}
}
blockL->data[i] = a_out_l * wet_gain + blockL->data[i] * dry_gain;
blockR->data[i] = a_out_r * wet_gain + blockR->data[i] * dry_gain;
} // end block processing
AudioStream_F32::transmit(blockL, 0);
AudioStream_F32::transmit(blockR, 1);
AudioStream_F32::release(blockL);
AudioStream_F32::release(blockR);
#endif
}
void AudioEffectReverbSc_F32::freeze(bool state)
{
flags.freeze = state;
if (state)
{
feedback_tmp = feedback_; // store the settings
damp_fact_tmp = damp_fact_;
input_gain_tmp = input_gain;
__disable_irq();
feedback_ = 1.0f; // infinite reverb
input_gain = freeze_ingain;
__enable_irq();
}
else
{
__disable_irq();
feedback_ = feedback_tmp;
damp_fact_ = damp_fact_tmp;
input_gain = input_gain_tmp;
__enable_irq();
}
}

@ -0,0 +1,149 @@
/*
* ReverbSC
* 8 delay line stereo FDN reverb, with feedback matrix based upon physical modeling
* scattering junction of 8 lossless waveguides of equal characteristic impedance.
* Based on Csound orchestra version by Sean Costello.
*
* Original Author(s): Sean Costello, Istvan Varga
* Year: 1999, 2005
* Ported to soundpipe by: Paul Batchelor
*
* Ported to Teensy4 and OpenAudio_ArduinoLibrary:
* 01.2024 Piotr Zapart www.hexefx.com
*
* Fixes, changes:
* - In the original code the reverb level is affected by the feedback control, fixed
* - Optional
*
*/
#ifndef _EFFECT_REVERBSC_F32_H_
#define _EFFECT_REVERBSC_F32_H_
#include <Arduino.h>
#include "Audio.h"
#include "AudioStream.h"
#include "AudioStream_F32.h"
#include "arm_math.h"
#include "basic_DSPutils.h"
class AudioEffectReverbSc_F32 : public AudioStream_F32
{
public:
AudioEffectReverbSc_F32(bool use_psram = false);
~AudioEffectReverbSc_F32(){};
virtual void update();
typedef struct
{
int write_pos; /**< write position */
int buffer_size; /**< buffer size */
int read_pos; /**< read position */
int read_pos_frac; /**< fractional component of read pos */
int read_pos_frac_inc; /**< increment for fractional */
int dummy; /**< dummy var */
int seed_val; /**< randseed */
int rand_line_cnt; /**< number of random lines */
float32_t filter_state; /**< state of filter */
float32_t *buf; /**< buffer ptr */
} ReverbScDl_t;
inline void feedback(const float32_t &fb)
{
if (flags.freeze) return;
float32_t inGain;
float32_t feedb = 2.0f * fb - fb*fb;
feedb = map(feedb, 0.0f, 1.0f, 0.1f, feedb_max);
feedback_tmp = feedb;
inGain = map(feedb, 0.1f, feedb_max, 0.5f, 0.2f);
__disable_irq();
input_gain = inGain;
feedback_ = feedb;
__enable_irq();
}
inline void lowpass(float32_t val)
{
if (flags.freeze) return;
val = constrain(val, 0.0f, 0.96f);
val = val*val*val;
if (damp_fact_ != val)
{
damp_fact_tmp = val;
__disable_irq();
damp_fact_ = val;
__enable_irq();
}
}
void mix(float32_t mix)
{
mix = constrain(mix, 0.0f, 1.0f);
float dry, wet;
mix_pwr(mix, &wet, &dry);
__disable_irq();
wet_gain = wet;
dry_gain = dry;
__enable_irq();
}
void wet_level(float32_t wet)
{
wet_gain = constrain(wet, 0.0f, 1.0f);
}
void dry_level(float32_t dry)
{
dry_gain = constrain(dry, 0.0f, 1.0f);
}
void freeze(bool state);
bool freeze_tgl() {flags.freeze ^= 1; freeze(flags.freeze); return flags.freeze;}
bool freeze_get() {return flags.freeze;}
bool bypass_get(void) {return flags.bypass;}
void bypass_set(bool state)
{
flags.bypass = state;
if (state) freeze(false); // disable freeze in bypass mode
}
bool bypass_tgl(void)
{
flags.bypass ^= 1;
if (flags.bypass) freeze(false); // disable freeze in bypass mode
return flags.bypass;
}
uint32_t getBfAddr()
{
float32_t *addr = aux_;
return (uint32_t)addr;
}
private:
struct flags_t
{
unsigned bypass: 1;
unsigned freeze: 1;
unsigned memsetup_done: 1;
}flags = {0, 0, 0};
audio_block_f32_t *inputQueueArray_f32[2];
void NextRandomLineseg(ReverbScDl_t *lp, int n);
void InitDelayLine(ReverbScDl_t *lp, int n);
float32_t feedback_, feedback_tmp;
float32_t lpfreq_;
float32_t i_pitch_mod_;
float32_t sample_rate_;
float32_t damp_fact_, damp_fact_tmp;
bool initialised = false;
ReverbScDl_t delay_lines_[8];
float32_t *aux_; // main delay line storage buffer, placed either in RAM2 or PSRAM
float32_t dry_gain = 0.5f;
float32_t wet_gain = 0.5f;
float32_t input_gain = 0.5f;
float32_t input_gain_tmp = 0.5f;
float32_t freeze_ingain = 0.05f;
static constexpr float32_t feedb_max = 0.99f;
};
#endif // _EFFECT_REVERBSC_H_

@ -0,0 +1,408 @@
/* Stereo spring reverb for Teensy 4
*
* Author: Piotr Zapart
* www.hexefx.com
*
* Copyright (c) 2024 by Piotr Zapart
*
* 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 <Arduino.h>
#include "effect_springreverb_F32.h"
#define INP_ALLP_COEFF (0.6f)
#define CHIRP_ALLP_COEFF (-0.6f)
#define TREBLE_LOSS_FREQ (0.55f)
#define BASS_LOSS_FREQ (0.36f)
AudioEffectSpringReverb_F32::AudioEffectSpringReverb_F32() : AudioStream_F32(2, inputQueueArray)
{
input_attn = 0.5f;
rv_time_k = 0.8f;
in_allp_k = INP_ALLP_COEFF;
bool memOK = true;
if(!sp_lp_allp1a.init(&in_allp_k)) memOK = false;
if(!sp_lp_allp1b.init(&in_allp_k)) memOK = false;
if(!sp_lp_allp1c.init(&in_allp_k)) memOK = false;
if(!sp_lp_allp1d.init(&in_allp_k)) memOK = false;
if(!sp_lp_allp2a.init(&in_allp_k)) memOK = false;
if(!sp_lp_allp2b.init(&in_allp_k)) memOK = false;
if(!sp_lp_allp2c.init(&in_allp_k)) memOK = false;
if(!sp_lp_allp2d.init(&in_allp_k)) memOK = false;
if(!lp_dly1.init()) memOK = false;
if(!lp_dly2.init()) memOK = false;
// chirp allpass chain
sp_chrp_alp1_buf = (float *)malloc(SPRVB_CHIRP_AMNT*SPRVB_CHIRP1_LEN*sizeof(float));
sp_chrp_alp2_buf = (float *)malloc(SPRVB_CHIRP_AMNT*SPRVB_CHIRP2_LEN*sizeof(float));
sp_chrp_alp3_buf = (float *)malloc(SPRVB_CHIRP_AMNT*SPRVB_CHIRP3_LEN*sizeof(float));
sp_chrp_alp4_buf = (float *)malloc(SPRVB_CHIRP_AMNT*SPRVB_CHIRP4_LEN*sizeof(float));
if (!sp_chrp_alp1_buf) memOK = false;
if (!sp_chrp_alp2_buf) memOK = false;
if (!sp_chrp_alp3_buf) memOK = false;
if (!sp_chrp_alp4_buf) memOK = false;
memset(sp_chrp_alp1_buf, 0, SPRVB_CHIRP_AMNT*SPRVB_CHIRP1_LEN*sizeof(float));
memset(sp_chrp_alp2_buf, 0, SPRVB_CHIRP_AMNT*SPRVB_CHIRP2_LEN*sizeof(float));
memset(sp_chrp_alp3_buf, 0, SPRVB_CHIRP_AMNT*SPRVB_CHIRP3_LEN*sizeof(float));
memset(sp_chrp_alp4_buf, 0, SPRVB_CHIRP_AMNT*SPRVB_CHIRP4_LEN*sizeof(float));
in_BassCut_k = 0.0f;
in_TrebleCut_k = 0.95f;
lp_BassCut_k = 0.0f;
lp_TrebleCut_k = 1.0f;
flt_in.init(BASS_LOSS_FREQ, &in_BassCut_k, TREBLE_LOSS_FREQ, &in_TrebleCut_k);
flt_lp1.init(BASS_LOSS_FREQ, &lp_BassCut_k, TREBLE_LOSS_FREQ, &lp_TrebleCut_k);
flt_lp2.init(BASS_LOSS_FREQ, &lp_BassCut_k, TREBLE_LOSS_FREQ, &lp_TrebleCut_k);
mix(0.5f);
cleanup_done = true;
if (memOK) initialized = true;
}
void AudioEffectSpringReverb_F32::update()
{
#if defined(__IMXRT1062__)
audio_block_f32_t *blockL, *blockR;
int i, j;
float32_t inL, inR, dryL, dryR;
float32_t acc;
float32_t lp_out1, lp_out2, mono_in, dry_in;
float32_t rv_time;
uint32_t allp_idx;
uint32_t offset;
float lfo_fr;
if (!initialized) return;
if (bp)
{
if (!cleanup_done)
{
sp_lp_allp1a.reset();
sp_lp_allp1b.reset();
sp_lp_allp1c.reset();
sp_lp_allp1d.reset();
sp_lp_allp2a.reset();
sp_lp_allp2b.reset();
sp_lp_allp2c.reset();
sp_lp_allp2d.reset();
lp_dly1.reset();
lp_dly2.reset();
memset(sp_chrp_alp1_buf, 0, SPRVB_CHIRP_AMNT*SPRVB_CHIRP1_LEN*sizeof(float));
memset(sp_chrp_alp2_buf, 0, SPRVB_CHIRP_AMNT*SPRVB_CHIRP2_LEN*sizeof(float));
memset(sp_chrp_alp3_buf, 0, SPRVB_CHIRP_AMNT*SPRVB_CHIRP3_LEN*sizeof(float));
memset(sp_chrp_alp4_buf, 0, SPRVB_CHIRP_AMNT*SPRVB_CHIRP4_LEN*sizeof(float));
cleanup_done = true;
}
if (dry_gain > 0.0f) // if dry/wet mixer is used
{
blockL = AudioStream_F32::receiveReadOnly_f32(0);
blockR = AudioStream_F32::receiveReadOnly_f32(1);
if (!blockL || !blockR)
{
if (blockL) AudioStream_F32::release(blockL);
if (blockR) AudioStream_F32::release(blockR);
return;
}
AudioStream_F32::transmit(blockL, 0);
AudioStream_F32::transmit(blockR, 1);
AudioStream_F32::release(blockL);
AudioStream_F32::release(blockR);
}
blockL = AudioStream_F32::allocate_f32();
if (!blockL) return;
arm_fill_f32(0.0f, blockL->data, blockL->length);
AudioStream_F32::transmit(blockL, 0);
AudioStream_F32::transmit(blockL, 1);
AudioStream_F32::release(blockL);
return;
}
cleanup_done = false;
blockL = AudioStream_F32::receiveWritable_f32(0);
blockR = AudioStream_F32::receiveWritable_f32(1);
if (!blockL || !blockR)
{
if (blockL)
AudioStream_F32::release(blockL);
if (blockR)
AudioStream_F32::release(blockR);
return;
}
rv_time = rv_time_k;
for (i=0; i < blockL->length; i++)
{
lfo.update();
dryL = blockL->data[i];
dryR = blockR->data[i];
dry_in = (dryL + dryR) * input_attn;
mono_in = flt_in.process(dry_in)* (1.0f + in_BassCut_k*-1.5f); // add highpass gain compaensation?
acc = lp_dly1.getTap(0) * rv_time; // get DLY1 output
lp_out1 = flt_lp1.process(acc); // filter it
acc = sp_lp_allp1a.process(lp_out1);
acc = sp_lp_allp1b.process(acc);
acc = sp_lp_allp1c.process(acc);
acc = sp_lp_allp1d.process(acc);
acc = lp_dly2.process(acc + mono_in) * rv_time;
lp_out2 = flt_lp2.process(acc);
acc = sp_lp_allp2a.process(lp_out2);
acc = sp_lp_allp2b.process(acc);
acc = sp_lp_allp2c.process(acc);
acc = sp_lp_allp2d.process(acc);
lp_dly1.write_toOffset(acc + mono_in, 0);
lp_dly1.updateIndex();
inL = inR = (lp_out1 + lp_out2);
j = 0;
while(j < SPRVB_CHIRP_AMNT)
{
// 1st 4 are left channel
allp_idx = j*SPRVB_CHIRP1_LEN + chrp_alp1_idx[j];
acc = sp_chrp_alp1_buf[allp_idx] + inL * chrp_allp_k[0];
sp_chrp_alp1_buf[allp_idx] = inL - chrp_allp_k[0] * acc;
inL = acc;
if (++chrp_alp1_idx[j] >= SPRVB_CHIRP1_LEN) chrp_alp1_idx[j] = 0;
allp_idx = (j+1)*SPRVB_CHIRP1_LEN + chrp_alp1_idx[j+1];
acc = sp_chrp_alp1_buf[allp_idx] + inL * chrp_allp_k[1];
sp_chrp_alp1_buf[allp_idx] = inL - chrp_allp_k[1] * acc;
inL = acc;
if (++chrp_alp1_idx[j+1] >= SPRVB_CHIRP1_LEN) chrp_alp1_idx[j+1] = 0;
allp_idx = (j+2)*SPRVB_CHIRP1_LEN + chrp_alp1_idx[j+2];
acc = sp_chrp_alp1_buf[allp_idx] + inL * chrp_allp_k[2];
sp_chrp_alp1_buf[allp_idx] = inL - chrp_allp_k[2] * acc;
inL = acc;
if (++chrp_alp1_idx[j+2] >= SPRVB_CHIRP1_LEN) chrp_alp1_idx[j+2] = 0;
allp_idx = (j+3)*SPRVB_CHIRP1_LEN + chrp_alp1_idx[j+3];
acc = sp_chrp_alp1_buf[allp_idx] + inL * chrp_allp_k[3];
sp_chrp_alp1_buf[allp_idx] = inL - chrp_allp_k[3] * acc;
inL = acc;
if (++chrp_alp1_idx[j+3] >= SPRVB_CHIRP1_LEN) chrp_alp1_idx[j+3] = 0;
// channel R
allp_idx = (j+4)*SPRVB_CHIRP1_LEN + chrp_alp1_idx[j+4];
acc = sp_chrp_alp1_buf[allp_idx] + inR * chrp_allp_k[3];
sp_chrp_alp1_buf[allp_idx] = inR - chrp_allp_k[3] * acc;
inR = acc;
if (++chrp_alp1_idx[j+4] >= SPRVB_CHIRP1_LEN) chrp_alp1_idx[j+4] = 0;
allp_idx = (j+5)*SPRVB_CHIRP1_LEN + chrp_alp1_idx[j+5];
acc = sp_chrp_alp1_buf[allp_idx] + inR * chrp_allp_k[2];
sp_chrp_alp1_buf[allp_idx] = inR - chrp_allp_k[2] * acc;
inR = acc;
if (++chrp_alp1_idx[j+5] >= SPRVB_CHIRP1_LEN) chrp_alp1_idx[j+5] = 0;
allp_idx = (j+6)*SPRVB_CHIRP1_LEN + chrp_alp1_idx[j+6];
acc = sp_chrp_alp1_buf[allp_idx] + inR * chrp_allp_k[1];
sp_chrp_alp1_buf[allp_idx] = inR - chrp_allp_k[1] * acc;
inR = acc;
if (++chrp_alp1_idx[j+6] >= SPRVB_CHIRP1_LEN) chrp_alp1_idx[j+6] = 0;
allp_idx = (j+7)*SPRVB_CHIRP1_LEN + chrp_alp1_idx[j+7];
acc = sp_chrp_alp1_buf[allp_idx] + inR * chrp_allp_k[0];
sp_chrp_alp1_buf[allp_idx] = inR - chrp_allp_k[0] * acc;
inR = acc;
if (++chrp_alp1_idx[j+7] >= SPRVB_CHIRP1_LEN) chrp_alp1_idx[j+7] = 0;
j = j + 8;
}
j = 0;
while(j < SPRVB_CHIRP_AMNT)
{ // channel L
allp_idx = j*SPRVB_CHIRP2_LEN + chrp_alp2_idx[j];
acc = sp_chrp_alp2_buf[allp_idx] + inL * chrp_allp_k[0];
sp_chrp_alp2_buf[allp_idx] = inL - chrp_allp_k[0] * acc;
inL = acc;
if (++chrp_alp2_idx[j] >= SPRVB_CHIRP2_LEN) chrp_alp2_idx[j] = 0;
allp_idx = (j+1)*SPRVB_CHIRP2_LEN + chrp_alp2_idx[j+1];
acc = sp_chrp_alp2_buf[allp_idx] + inL * chrp_allp_k[1];
sp_chrp_alp2_buf[allp_idx] = inL - chrp_allp_k[1] * acc;
inL = acc;
if (++chrp_alp2_idx[j+1] >= SPRVB_CHIRP2_LEN) chrp_alp2_idx[j+1] = 0;
allp_idx = (j+2)*SPRVB_CHIRP2_LEN + chrp_alp2_idx[j+2];
acc = sp_chrp_alp2_buf[allp_idx] + inL * chrp_allp_k[2];
sp_chrp_alp2_buf[allp_idx] = inL - chrp_allp_k[2] * acc;
inL = acc;
if (++chrp_alp2_idx[j+2] >= SPRVB_CHIRP2_LEN) chrp_alp2_idx[j+2] = 0;
allp_idx = (j+3)*SPRVB_CHIRP2_LEN + chrp_alp2_idx[j+3];
acc = sp_chrp_alp2_buf[allp_idx] + inL * chrp_allp_k[3];
sp_chrp_alp2_buf[allp_idx] = inL - chrp_allp_k[3] * acc;
inL = acc;
if (++chrp_alp2_idx[j+3] >= SPRVB_CHIRP2_LEN) chrp_alp2_idx[j+3] = 0;
// channel R
allp_idx = (j+4)*SPRVB_CHIRP2_LEN + chrp_alp2_idx[j+4];
acc = sp_chrp_alp2_buf[allp_idx] + inR * chrp_allp_k[3];
sp_chrp_alp2_buf[allp_idx] = inR - chrp_allp_k[3] * acc;
inR = acc;
if (++chrp_alp2_idx[j+4] >= SPRVB_CHIRP2_LEN) chrp_alp2_idx[j+4] = 0;
allp_idx = (j+5)*SPRVB_CHIRP2_LEN + chrp_alp2_idx[j+5];
acc = sp_chrp_alp2_buf[allp_idx] + inR * chrp_allp_k[2];
sp_chrp_alp2_buf[allp_idx] = inR - chrp_allp_k[2] * acc;
inR = acc;
if (++chrp_alp2_idx[j+5] >= SPRVB_CHIRP2_LEN) chrp_alp2_idx[j+5] = 0;
allp_idx = (j+6)*SPRVB_CHIRP2_LEN + chrp_alp2_idx[j+6];
acc = sp_chrp_alp2_buf[allp_idx] + inR * chrp_allp_k[1];
sp_chrp_alp2_buf[allp_idx] = inR - chrp_allp_k[1] * acc;
inR = acc;
if (++chrp_alp2_idx[j+6] >= SPRVB_CHIRP2_LEN) chrp_alp2_idx[j+6] = 0;
allp_idx = (j+7)*SPRVB_CHIRP2_LEN + chrp_alp2_idx[j+7];
acc = sp_chrp_alp2_buf[allp_idx] + inR * chrp_allp_k[0];
sp_chrp_alp2_buf[allp_idx] = inR - chrp_allp_k[0] * acc;
inR = acc;
if (++chrp_alp2_idx[j+7] >= SPRVB_CHIRP2_LEN) chrp_alp2_idx[j+7] = 0;
j = j + 8;
}
j = 0;
while(j < SPRVB_CHIRP_AMNT)
{ // channel L
allp_idx = j*SPRVB_CHIRP3_LEN + chrp_alp3_idx[j];
acc = sp_chrp_alp3_buf[allp_idx] + inL * chrp_allp_k[0];
sp_chrp_alp3_buf[allp_idx] = inL - chrp_allp_k[0] * acc;
inL = acc;
if (++chrp_alp3_idx[j] >= SPRVB_CHIRP3_LEN) chrp_alp3_idx[j] = 0;
allp_idx = (j+1)*SPRVB_CHIRP3_LEN + chrp_alp3_idx[j+1];
acc = sp_chrp_alp3_buf[allp_idx] + inL * chrp_allp_k[1];
sp_chrp_alp3_buf[allp_idx] = inL - chrp_allp_k[1] * acc;
inL = acc;
if (++chrp_alp3_idx[j+1] >= SPRVB_CHIRP3_LEN) chrp_alp3_idx[j+1] = 0;
allp_idx = (j+2)*SPRVB_CHIRP3_LEN + chrp_alp3_idx[j+2];
acc = sp_chrp_alp3_buf[allp_idx] + inL * chrp_allp_k[2];
sp_chrp_alp3_buf[allp_idx] = inL - chrp_allp_k[2] * acc;
inL = acc;
if (++chrp_alp3_idx[j+2] >= SPRVB_CHIRP3_LEN) chrp_alp3_idx[j+2] = 0;
allp_idx = (j+3)*SPRVB_CHIRP3_LEN + chrp_alp3_idx[j+3];
acc = sp_chrp_alp3_buf[allp_idx] + inL * chrp_allp_k[3];
sp_chrp_alp3_buf[allp_idx] = inL - chrp_allp_k[3] * acc;
inL = acc;
if (++chrp_alp3_idx[j+3] >= SPRVB_CHIRP3_LEN) chrp_alp3_idx[j+3] = 0;
// channel R
allp_idx = (j+4)*SPRVB_CHIRP3_LEN + chrp_alp3_idx[j+4];
acc = sp_chrp_alp3_buf[allp_idx] + inR * chrp_allp_k[3];
sp_chrp_alp3_buf[allp_idx] = inR - chrp_allp_k[3] * acc;
inR = acc;
if (++chrp_alp3_idx[j+4] >= SPRVB_CHIRP3_LEN) chrp_alp3_idx[j+4] = 0;
allp_idx = (j+5)*SPRVB_CHIRP3_LEN + chrp_alp3_idx[j+5];
acc = sp_chrp_alp3_buf[allp_idx] + inR * chrp_allp_k[2];
sp_chrp_alp3_buf[allp_idx] = inR - chrp_allp_k[2] * acc;
inR = acc;
if (++chrp_alp3_idx[j+5] >= SPRVB_CHIRP3_LEN) chrp_alp3_idx[j+5] = 0;
allp_idx = (j+6)*SPRVB_CHIRP3_LEN + chrp_alp3_idx[j+6];
acc = sp_chrp_alp3_buf[allp_idx] + inR * chrp_allp_k[1];
sp_chrp_alp3_buf[allp_idx] = inR - chrp_allp_k[1] * acc;
inR = acc;
if (++chrp_alp3_idx[j+6] >= SPRVB_CHIRP3_LEN) chrp_alp3_idx[j+6] = 0;
allp_idx = (j+7)*SPRVB_CHIRP3_LEN + chrp_alp3_idx[j+7];
acc = sp_chrp_alp3_buf[allp_idx] + inR * chrp_allp_k[0];
sp_chrp_alp3_buf[allp_idx] = inR - chrp_allp_k[0] * acc;
inR = acc;
if (++chrp_alp3_idx[j+7] >= SPRVB_CHIRP3_LEN) chrp_alp3_idx[j+7] = 0;
j = j + 8;
}
j = 0;
while(j < SPRVB_CHIRP_AMNT)
{ // channel L
allp_idx = j*SPRVB_CHIRP4_LEN + chrp_alp4_idx[j];
acc = sp_chrp_alp4_buf[allp_idx] + inL * chrp_allp_k[0];
sp_chrp_alp4_buf[allp_idx] = inL - chrp_allp_k[0] * acc;
inL = acc;
if (++chrp_alp4_idx[j] >= SPRVB_CHIRP4_LEN) chrp_alp4_idx[j] = 0;
allp_idx = (j+1)*SPRVB_CHIRP4_LEN + chrp_alp4_idx[j+1];
acc = sp_chrp_alp4_buf[allp_idx] + inL * chrp_allp_k[1];
sp_chrp_alp4_buf[allp_idx] = inL - chrp_allp_k[1] * acc;
inL = acc;
if (++chrp_alp4_idx[j+1] >= SPRVB_CHIRP4_LEN) chrp_alp4_idx[j+1] = 0;
allp_idx = (j+2)*SPRVB_CHIRP4_LEN + chrp_alp4_idx[j+2];
acc = sp_chrp_alp4_buf[allp_idx] + inL * chrp_allp_k[1];
sp_chrp_alp4_buf[allp_idx] = inL - chrp_allp_k[1] * acc;
inL = acc;
if (++chrp_alp4_idx[j+2] >= SPRVB_CHIRP4_LEN) chrp_alp4_idx[j+2] = 0;
allp_idx = (j+3)*SPRVB_CHIRP4_LEN + chrp_alp4_idx[j+3];
acc = sp_chrp_alp4_buf[allp_idx] + inL * chrp_allp_k[3];
sp_chrp_alp4_buf[allp_idx] = inL - chrp_allp_k[3] * acc;
inL = acc;
if (++chrp_alp4_idx[j+3] >= SPRVB_CHIRP4_LEN) chrp_alp4_idx[j+3] = 0;
// channel R
allp_idx = (j+4)*SPRVB_CHIRP4_LEN + chrp_alp4_idx[j+4];
acc = sp_chrp_alp4_buf[allp_idx] + inR * chrp_allp_k[3];
sp_chrp_alp4_buf[allp_idx] = inR - chrp_allp_k[3] * acc;
inR = acc;
if (++chrp_alp4_idx[j+4] >= SPRVB_CHIRP4_LEN) chrp_alp4_idx[j+4] = 0;
allp_idx = (j+5)*SPRVB_CHIRP4_LEN + chrp_alp4_idx[j+5];
acc = sp_chrp_alp4_buf[allp_idx] + inR * chrp_allp_k[2];
sp_chrp_alp4_buf[allp_idx] = inR - chrp_allp_k[2] * acc;
inR = acc;
if (++chrp_alp4_idx[j+5] >= SPRVB_CHIRP4_LEN) chrp_alp4_idx[j+5] = 0;
allp_idx = (j+6)*SPRVB_CHIRP4_LEN + chrp_alp4_idx[j+6];
acc = sp_chrp_alp4_buf[allp_idx] + inR * chrp_allp_k[1];
sp_chrp_alp4_buf[allp_idx] = inR - chrp_allp_k[1] * acc;
inR = acc;
if (++chrp_alp4_idx[j+6] >= SPRVB_CHIRP4_LEN) chrp_alp4_idx[j+6] = 0;
allp_idx = (j+7)*SPRVB_CHIRP4_LEN + chrp_alp4_idx[j+7];
acc = sp_chrp_alp4_buf[allp_idx] + inR * chrp_allp_k[0];
sp_chrp_alp4_buf[allp_idx] = inR - chrp_allp_k[0] * acc;
inR = acc;
if (++chrp_alp4_idx[j+7] >= SPRVB_CHIRP4_LEN) chrp_alp4_idx[j+7] = 0;
j = j + 8;
}
// modulate the allpass filters
lfo.get(BASIC_LFO_PHASE_0, &offset, &lfo_fr);
acc = sp_lp_allp1d.getTap(offset+1, lfo_fr);
sp_lp_allp1d.write_toOffset(acc, (lfo_ampl<<1)+1);
lfo.get(BASIC_LFO_PHASE_90, &offset, &lfo_fr);
acc = sp_lp_allp2d.getTap(offset+1, lfo_fr);
sp_lp_allp2d.write_toOffset(acc, (lfo_ampl<<1)+1);
blockL->data[i] = inL * wet_gain + dryL * dry_gain;
blockR->data[i] = inR * wet_gain + dryR * dry_gain;
}
AudioStream_F32::transmit(blockL, 0);
AudioStream_F32::transmit(blockR, 1);
AudioStream_F32::release(blockL);
AudioStream_F32::release(blockR);
#endif
}

@ -0,0 +1,181 @@
/* Stereo spring reverb for Teensy 4
*
* Author: Piotr Zapart
* www.hexefx.com
*
* Copyright (c) 2024 by Piotr Zapart
*
* 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_SPRINGREVERB_F32_H
#define _EFFECT_SPRINGREVERB_F32_H
#include <Arduino.h>
#include "Audio.h"
#include "AudioStream.h"
#include "AudioStream_F32.h"
#include "arm_math.h"
#include "basic_components.h"
// Chirp allpass params
#define SPRVB_CHIRP_AMNT 16 //must be mult of 8
#define SPRVB_CHIRP1_LEN 3
#define SPRVB_CHIRP2_LEN 5
#define SPRVB_CHIRP3_LEN 6
#define SPRVB_CHIRP4_LEN 7
#define SPRVB_ALLP1A_LEN (224)
#define SPRVB_ALLP1B_LEN (420)
#define SPRVB_ALLP1C_LEN (856)
#define SPRVB_ALLP1D_LEN (1089)
#define SPRVB_ALLP2A_LEN (156)
#define SPRVB_ALLP2B_LEN (478)
#define SPRVB_ALLP2C_LEN (956)
#define SPRVB_ALLP2D_LEN (1289)
#define SPRVB_DLY1_LEN (1945)
#define SPRVB_DLY2_LEN (1363)
class AudioEffectSpringReverb_F32 : public AudioStream_F32
{
public:
AudioEffectSpringReverb_F32();
~AudioEffectSpringReverb_F32(){};
virtual void update();
void time(float n)
{
n = constrain(n, 0.0f, 1.0f);
n = map (n, 0.0f, 1.0f, 0.7f, rv_time_k_max);
float32_t attn = map(n, 0.0f, rv_time_k_max, 0.5f, 0.2f);
__disable_irq();
rv_time_k = n;
input_attn = attn;
__enable_irq();
}
void treble_cut(float n)
{
n = 1.0f - constrain(n, 0.0f, 1.0f);
__disable_irq();
lp_TrebleCut_k = n;
__enable_irq();
}
void bass_cut(float n)
{
n = constrain(n, 0.0f, 1.0f);
n = 2.0f * n - (n*n);
__disable_irq();
in_BassCut_k = -n;
__enable_irq();
}
void mix(float m)
{
float32_t dry, wet;
m = constrain(m, 0.0f, 1.0f);
mix_pwr(m, &wet, &dry);
__disable_irq();
wet_gain = wet;
dry_gain = dry;
__enable_irq();
}
void wet_level(float wet)
{
wet = constrain(wet, 0.0f, 6.0f);
__disable_irq();
wet_gain = wet;
__enable_irq();
}
void dry_level(float dry)
{
dry = constrain(dry, 0.0f, 1.0f);
__disable_irq();
dry_gain = dry;
__enable_irq();
}
float32_t get_size(void) {return rv_time_k;}
bool bypass_get(void) {return bp;}
void bypass_set(bool state) {bp = state;}
bool bypass_tgl(void)
{
bp ^= 1;
return bp;
}
private:
audio_block_f32_t *inputQueueArray[2];
float32_t input_attn;
float32_t wet_gain;
float32_t dry_gain;
float32_t in_allp_k; // input allpass coeff (default 0.6)
float32_t chrp_allp_k[4] = {-0.7f, -0.65f, -0.6f, -0.5f};
bool bp = false;
bool cleanup_done = false;
uint16_t chrp_alp1_idx[SPRVB_CHIRP_AMNT] = {0};
uint16_t chrp_alp2_idx[SPRVB_CHIRP_AMNT] = {0};
uint16_t chrp_alp3_idx[SPRVB_CHIRP_AMNT] = {0};
uint16_t chrp_alp4_idx[SPRVB_CHIRP_AMNT] = {0};
static constexpr float32_t rv_time_k_max = 0.97f;
float32_t rv_time_k;
AudioFilterAllpass<SPRVB_ALLP1A_LEN> sp_lp_allp1a;
AudioFilterAllpass<SPRVB_ALLP1B_LEN> sp_lp_allp1b;
AudioFilterAllpass<SPRVB_ALLP1C_LEN> sp_lp_allp1c;
AudioFilterAllpass<SPRVB_ALLP1D_LEN> sp_lp_allp1d;
AudioFilterAllpass<SPRVB_ALLP2A_LEN> sp_lp_allp2a;
AudioFilterAllpass<SPRVB_ALLP2B_LEN> sp_lp_allp2b;
AudioFilterAllpass<SPRVB_ALLP2D_LEN> sp_lp_allp2c;
AudioFilterAllpass<SPRVB_ALLP2D_LEN> sp_lp_allp2d;
float32_t *sp_chrp_alp1_buf;
float32_t *sp_chrp_alp2_buf;
float32_t *sp_chrp_alp3_buf;
float32_t *sp_chrp_alp4_buf;
AudioBasicDelay<SPRVB_DLY1_LEN> lp_dly1;
AudioBasicDelay<SPRVB_DLY2_LEN> lp_dly2;
float32_t in_TrebleCut_k;
float32_t in_BassCut_k;
float32_t lp_TrebleCut_k;
float32_t lp_BassCut_k;
AudioFilterShelvingLPHP flt_in;
AudioFilterShelvingLPHP flt_lp1;
AudioFilterShelvingLPHP flt_lp2;
static const uint8_t lfo_ampl = 10;
AudioBasicLfo lfo = AudioBasicLfo(1.35f, lfo_ampl);
bool initialized = false;
};
#endif

@ -0,0 +1,153 @@
//----------------------------------------------------------------------------
// 3 Band EQ :)
//
// EQ.C - Main Source file for 3 band EQ
// (c) Neil C / Etanza Systems / 2K6
// Shouts / Loves / Moans = etanza at lycos dot co dot uk
//
// This work is hereby placed in the public domain for all purposes, including
// use in commercial applications.
// The author assumes NO RESPONSIBILITY for any problems caused by the use of
// this software.
//----------------------------------------------------------------------------
// NOTES :
// - Original filter code by Paul Kellet (musicdsp.pdf)
// - Uses 4 first order filters in series, should give 24dB per octave
// - Now with P4 Denormal fix :)
//----------------------------------------------------------------------------
#ifndef _FILTER_3BANDEQ_H_
#define _FILTER_3BANDEQ_H_
#include "Arduino.h"
#include "AudioStream_F32.h"
#include "arm_math.h"
#include "mathDSP_F32.h"
#include "basic_components.h"
class AudioFilterEqualizer3band_F32 : public AudioStream_F32
{
public:
AudioFilterEqualizer3band_F32(void) : AudioStream_F32(1, inputQueueArray)
{
setBands(500.0f, 3000.0f);
}
void setBands(float32_t bassF, float32_t trebleF)
{
trebleF = 2.0f * sinf(M_PI * (trebleF / AUDIO_SAMPLE_RATE_EXACT));
bassF = 2.0f * sinf(M_PI * (bassF / AUDIO_SAMPLE_RATE_EXACT));
__disable_irq();
lowpass_f = bassF;
hipass_f = trebleF;
__enable_irq();
}
void treble(float32_t t)
{
__disable_irq();
treble_g = t;
__enable_irq();
}
void mid(float32_t m)
{
__disable_irq();
mid_g = m;
__enable_irq();
}
void bass(float32_t b)
{
__disable_irq();
bass_g = b;
__enable_irq();
}
void set(float32_t t, float32_t m, float32_t b)
{
__disable_irq();
treble_g = t;
mid_g = m;
bass_g = b;
__enable_irq();
}
void update()
{
audio_block_f32_t *block;
int i;
block = AudioStream_F32::receiveWritable_f32();
if (!block)
return;
if (bp) // bypass mode
{
AudioStream_F32::transmit(block);
AudioStream_F32::release(block);
return;
}
for (i = 0; i < block->length; i++)
{
float32_t lpOut, midOut, hpOut; // Low / Mid / High - Sample Values
float32_t sample = block->data[i]; // give some headroom
// Filter #1 (lowpass)
f1p0 += (lowpass_f * (sample - f1p0)) + vsa;
f1p1 += (lowpass_f * (f1p0 - f1p1));
f1p2 += (lowpass_f * (f1p1 - f1p2));
f1p3 += (lowpass_f * (f1p2 - f1p3));
lpOut = f1p3;
// // Filter #2 (highpass)
f2p0 += (hipass_f * (sample - f2p0)) + vsa;
f2p1 += (hipass_f * (f2p0 - f2p1));
f2p2 += (hipass_f * (f2p1 - f2p2));
f2p3 += (hipass_f * (f2p2 - f2p3));
hpOut = sdm3 - f2p3;
midOut = sdm3 - (lpOut + hpOut);
// // Scale, Combine and store
lpOut *= bass_g;
midOut *= mid_g;
hpOut *= treble_g;
// // Shuffle history buffer
sdm3 = sdm2;
sdm2 = sdm1;
sdm1 = sample;
block->data[i] = (lpOut + midOut + hpOut);
}
AudioStream_F32::transmit(block);
AudioStream_F32::release(block);
}
void bypass_set(bool s) { bp = s; }
bool bypass_tgl()
{
bp ^= 1;
return bp;
}
private:
audio_block_f32_t *inputQueueArray[1];
bool bp = false;
float32_t f1p0 = 0.0f;
float32_t f1p1 = 0.0f;
float32_t f1p2 = 0.0f;
float32_t f1p3 = 0.0f;
float32_t f2p0 = 0.0f;
float32_t f2p1 = 0.0f;
float32_t f2p2 = 0.0f;
float32_t f2p3 = 0.0f;
float32_t sdm1 = 0.0f;
float32_t sdm2 = 0.0f; // 2
float32_t sdm3 = 0.0f; // 3
static constexpr float32_t vsa = (1.0 / 4294967295.0); // Very small amount (Denormal Fix)
float32_t lpreg;
float32_t hpreg;
float32_t lowpass_f;
float32_t bass_g = 1.0f;
float32_t hipass_f;
float32_t treble_g = 1.0f;
float32_t mid_g = 1.0f;
};
#endif
Loading…
Cancel
Save