From 8801c8f122127e9ef863de001bc4bf092aa1fcf8 Mon Sep 17 00:00:00 2001 From: pio Date: Wed, 31 Jan 2024 19:45:54 +0100 Subject: [PATCH] add files, readme update --- README.md | 13 + src/basic_DSPutils.h | 22 ++ src/effect_gainStereo_F32.h | 63 +++++ src/effect_noiseGateStereo_F32.h | 174 +++++++++++++ src/effect_reverbsc_F32.cpp | 311 +++++++++++++++++++++++ src/effect_reverbsc_F32.h | 149 +++++++++++ src/effect_springreverb_F32.cpp | 408 +++++++++++++++++++++++++++++++ src/effect_springreverb_F32.h | 181 ++++++++++++++ src/filter_3bandeq.h | 153 ++++++++++++ 9 files changed, 1474 insertions(+) create mode 100644 src/basic_DSPutils.h create mode 100644 src/effect_gainStereo_F32.h create mode 100644 src/effect_noiseGateStereo_F32.h create mode 100644 src/effect_reverbsc_F32.cpp create mode 100644 src/effect_reverbsc_F32.h create mode 100644 src/effect_springreverb_F32.cpp create mode 100644 src/effect_springreverb_F32.h create mode 100644 src/filter_3bandeq.h diff --git a/README.md b/README.md index 4113574..eb4215f 100644 --- a/README.md +++ b/README.md @@ -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** diff --git a/src/basic_DSPutils.h b/src/basic_DSPutils.h new file mode 100644 index 0000000..06b6553 --- /dev/null +++ b/src/basic_DSPutils.h @@ -0,0 +1,22 @@ +#ifndef _BASIC_DSPUTILS_H_ +#define _BASIC_DSPUTILS_H_ + +#include + +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_ diff --git a/src/effect_gainStereo_F32.h b/src/effect_gainStereo_F32.h new file mode 100644 index 0000000..7f8afa2 --- /dev/null +++ b/src/effect_gainStereo_F32.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 DSP extensions. for speed! +#include + +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 \ No newline at end of file diff --git a/src/effect_noiseGateStereo_F32.h b/src/effect_noiseGateStereo_F32.h new file mode 100644 index 0000000..60769cf --- /dev/null +++ b/src/effect_noiseGateStereo_F32.h @@ -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 DSP extensions. for speed! +#include + +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 diff --git a/src/effect_reverbsc_F32.cpp b/src/effect_reverbsc_F32.cpp new file mode 100644 index 0000000..943f509 --- /dev/null +++ b/src/effect_reverbsc_F32.cpp @@ -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(); + } +} diff --git a/src/effect_reverbsc_F32.h b/src/effect_reverbsc_F32.h new file mode 100644 index 0000000..4935832 --- /dev/null +++ b/src/effect_reverbsc_F32.h @@ -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 +#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_ diff --git a/src/effect_springreverb_F32.cpp b/src/effect_springreverb_F32.cpp new file mode 100644 index 0000000..a76db57 --- /dev/null +++ b/src/effect_springreverb_F32.cpp @@ -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 +#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 +} \ No newline at end of file diff --git a/src/effect_springreverb_F32.h b/src/effect_springreverb_F32.h new file mode 100644 index 0000000..6c81366 --- /dev/null +++ b/src/effect_springreverb_F32.h @@ -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 +#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 sp_lp_allp1a; + AudioFilterAllpass sp_lp_allp1b; + AudioFilterAllpass sp_lp_allp1c; + AudioFilterAllpass sp_lp_allp1d; + + AudioFilterAllpass sp_lp_allp2a; + AudioFilterAllpass sp_lp_allp2b; + AudioFilterAllpass sp_lp_allp2c; + AudioFilterAllpass 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 lp_dly1; + AudioBasicDelay 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 \ No newline at end of file diff --git a/src/filter_3bandeq.h b/src/filter_3bandeq.h new file mode 100644 index 0000000..b5de3d2 --- /dev/null +++ b/src/filter_3bandeq.h @@ -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 \ No newline at end of file