|
|
|
/* Stereo plate reverb for Teensy 4
|
|
|
|
*
|
|
|
|
* Author: Piotr Zapart
|
|
|
|
* www.hexefx.com
|
|
|
|
*
|
|
|
|
* Copyright (c) 2020 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_platereverb_F32.h"
|
|
|
|
|
|
|
|
#define INP_ALLP_COEFF (0.65f)
|
|
|
|
#define LOOP_ALLOP_COEFF (0.65f)
|
|
|
|
|
|
|
|
#define TREBLE_LOSS_FREQ (0.3f)
|
|
|
|
#define TREBLE_LOSS_FREQ_MAX (0.08f)
|
|
|
|
#define BASS_LOSS_FREQ (0.06f)
|
|
|
|
|
|
|
|
#define RV_MASTER_LOWPASS_F (0.6f) // master lowpass scaled frequency coeff.
|
|
|
|
|
|
|
|
|
|
|
|
AudioEffectPlateReverb_F32::AudioEffectPlateReverb_F32() : AudioStream_F32(2, inputQueueArray_f32) { begin();}
|
|
|
|
|
|
|
|
bool AudioEffectPlateReverb_F32::begin()
|
|
|
|
{
|
|
|
|
inputGainSet = 0.5f;
|
|
|
|
inputGain = 0.5f;
|
|
|
|
inputGain_tmp = 0.5f;
|
|
|
|
wet_gain = 1.0f; // default mode: wet signal only
|
|
|
|
dry_gain = 0.0f;
|
|
|
|
in_allp_k = INP_ALLP_COEFF;
|
|
|
|
loop_allp_k = LOOP_ALLOP_COEFF;
|
|
|
|
rv_time_scaler = 1.0f;
|
|
|
|
rv_time_k = 0.2f;
|
|
|
|
pitch_semit = 0;
|
|
|
|
pitchShim_semit = 0;
|
|
|
|
|
|
|
|
if(!in_allp_1L.init(&in_allp_k)) return false;
|
|
|
|
if(!in_allp_2L.init(&in_allp_k)) return false;
|
|
|
|
if(!in_allp_3L.init(&in_allp_k)) return false;
|
|
|
|
if(!in_allp_4L.init(&in_allp_k)) return false;
|
|
|
|
|
|
|
|
if(!in_allp_1R.init(&in_allp_k)) return false;
|
|
|
|
if(!in_allp_2R.init(&in_allp_k)) return false;
|
|
|
|
if(!in_allp_3R.init(&in_allp_k)) return false;
|
|
|
|
if(!in_allp_4R.init(&in_allp_k)) return false;
|
|
|
|
|
|
|
|
in_allp_out_L = 0.0f;
|
|
|
|
in_allp_out_R = 0.0f;
|
|
|
|
|
|
|
|
if(!lp_allp_1.init(&loop_allp_k)) return false;
|
|
|
|
if(!lp_allp_2.init(&loop_allp_k)) return false;
|
|
|
|
if(!lp_allp_3.init(&loop_allp_k)) return false;
|
|
|
|
if(!lp_allp_4.init(&loop_allp_k)) return false;
|
|
|
|
|
|
|
|
lp_allp_out = 0.0f;
|
|
|
|
|
|
|
|
if(!lp_dly1.init(LP_DLY1_BUF_LEN)) return false;
|
|
|
|
if(!lp_dly2.init(LP_DLY2_BUF_LEN)) return false;
|
|
|
|
if(!lp_dly3.init(LP_DLY3_BUF_LEN)) return false;
|
|
|
|
if(!lp_dly4.init(LP_DLY4_BUF_LEN)) return false;
|
|
|
|
|
|
|
|
lp_hidamp_k = 1.0f;
|
|
|
|
lp_lodamp_k = 0.0f;
|
|
|
|
flt1.init(BASS_LOSS_FREQ, &lp_lodamp_k, TREBLE_LOSS_FREQ, &lp_hidamp_k);
|
|
|
|
flt2.init(BASS_LOSS_FREQ, &lp_lodamp_k, TREBLE_LOSS_FREQ, &lp_hidamp_k);
|
|
|
|
flt3.init(BASS_LOSS_FREQ, &lp_lodamp_k, TREBLE_LOSS_FREQ, &lp_hidamp_k);
|
|
|
|
flt4.init(BASS_LOSS_FREQ, &lp_lodamp_k, TREBLE_LOSS_FREQ, &lp_hidamp_k);
|
|
|
|
|
|
|
|
master_lp_k = 1.0f;
|
|
|
|
master_hp_k = 0.0f;
|
|
|
|
flt_masterL.init(0.08f, &master_hp_k, 0.1f, &master_lp_k);
|
|
|
|
flt_masterR.init(0.08f, &master_hp_k, 0.1f, &master_lp_k);
|
|
|
|
|
|
|
|
if(!pitchL.init()) return false;
|
|
|
|
if(!pitchR.init()) return false;
|
|
|
|
pitchL.setPitch(1.0f); //natural pitch
|
|
|
|
pitchR.setPitch(1.0f); //natural pitch
|
|
|
|
pitchL.setTone(0.36f);
|
|
|
|
pitchR.setTone(0.36f);
|
|
|
|
pitchL.setMix(0.0f);
|
|
|
|
pitchR.setMix(0.0f);
|
|
|
|
|
|
|
|
shimmerRatio = 0.0f;
|
|
|
|
if(!pitchShimL.init()) return false;
|
|
|
|
if(!pitchShimR.init()) return false;
|
|
|
|
pitchShimL.setPitch(2.0f);
|
|
|
|
pitchShimR.setPitch(2.0f);
|
|
|
|
pitchShimL.setTone(0.26f);
|
|
|
|
pitchShimR.setTone(0.26f);
|
|
|
|
pitchShimL.setMix(0.0f);
|
|
|
|
pitchShimR.setMix(0.0f);
|
|
|
|
|
|
|
|
flags.bypass = 1;
|
|
|
|
flags.freeze = 0;
|
|
|
|
initialised = true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AudioEffectPlateReverb_F32::update()
|
|
|
|
{
|
|
|
|
#if defined(__IMXRT1062__)
|
|
|
|
if (!initialised) return;
|
|
|
|
audio_block_f32_t *blockL, *blockR;
|
|
|
|
int16_t i;
|
|
|
|
float acc;
|
|
|
|
float rv_time;
|
|
|
|
uint32_t offset;
|
|
|
|
float lfo_fr;
|
|
|
|
// handle bypass, 1st call will clean the buffers to avoid continuing the previous reverb tail
|
|
|
|
if (flags.bypass)
|
|
|
|
{
|
|
|
|
if (!flags.cleanup_done && bp_mode != BYPASS_MODE_TRAILS)
|
|
|
|
{
|
|
|
|
in_allp_1L.reset();
|
|
|
|
in_allp_2L.reset();
|
|
|
|
in_allp_3L.reset();
|
|
|
|
in_allp_4L.reset();
|
|
|
|
in_allp_1R.reset();
|
|
|
|
in_allp_2R.reset();
|
|
|
|
in_allp_3R.reset();
|
|
|
|
in_allp_4R.reset();
|
|
|
|
lp_allp_1.reset();
|
|
|
|
lp_allp_2.reset();
|
|
|
|
lp_allp_3.reset();
|
|
|
|
lp_allp_4.reset();
|
|
|
|
lp_dly1.reset();
|
|
|
|
lp_dly2.reset();
|
|
|
|
lp_dly3.reset();
|
|
|
|
lp_dly4.reset();
|
|
|
|
flags.cleanup_done = 1;
|
|
|
|
}
|
|
|
|
switch(bp_mode)
|
|
|
|
{
|
|
|
|
case BYPASS_MODE_PASS:
|
|
|
|
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);
|
|
|
|
return;
|
|
|
|
break;
|
|
|
|
case BYPASS_MODE_OFF:
|
|
|
|
blockL = AudioStream_F32::allocate_f32();
|
|
|
|
if (!blockL) return;
|
|
|
|
memset(&blockL->data[0], 0, blockL->length*sizeof(float32_t));
|
|
|
|
AudioStream_F32::transmit(blockL, 0);
|
|
|
|
AudioStream_F32::transmit(blockL, 1);
|
|
|
|
AudioStream_F32::release(blockL);
|
|
|
|
return;
|
|
|
|
break;
|
|
|
|
case BYPASS_MODE_TRAILS:
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
flags.cleanup_done = 0;
|
|
|
|
rv_time = rv_time_k;
|
|
|
|
|
|
|
|
for (i=0; i < blockL->length; i++)
|
|
|
|
{
|
|
|
|
// do the LFOs
|
|
|
|
lfo1.update();
|
|
|
|
lfo2.update();
|
|
|
|
|
|
|
|
inputGain += (inputGainSet - inputGain) * 0.25f;
|
|
|
|
|
|
|
|
acc = blockL->data[i] * inputGain;
|
|
|
|
|
|
|
|
// chained input allpasses, channel L
|
|
|
|
acc = in_allp_1L.process(acc);
|
|
|
|
acc = in_allp_2L.process(acc);
|
|
|
|
acc = in_allp_3L.process(acc);
|
|
|
|
in_allp_out_L = in_allp_4L.process(acc);
|
|
|
|
in_allp_out_L = pitchL.process(in_allp_out_L);
|
|
|
|
|
|
|
|
// chained input allpasses, channel R
|
|
|
|
acc = blockR->data[i] * inputGain;
|
|
|
|
|
|
|
|
acc = in_allp_1R.process(acc);
|
|
|
|
acc = in_allp_2R.process(acc);
|
|
|
|
acc = in_allp_3R.process(acc);
|
|
|
|
in_allp_out_R = in_allp_4R.process(acc);
|
|
|
|
|
|
|
|
acc = pitchShimR.process(lp_allp_out + in_allp_out_R); // shimmer
|
|
|
|
|
|
|
|
acc = lp_dly1.process(acc);
|
|
|
|
acc = flt1.process(acc) * rv_time * rv_time_scaler;
|
|
|
|
|
|
|
|
acc = lp_allp_2.process(acc + in_allp_out_L);
|
|
|
|
acc = lp_dly2.process(acc);
|
|
|
|
acc = flt2.process(acc) * rv_time * rv_time_scaler;
|
|
|
|
|
|
|
|
acc = pitchShimL.process(acc + in_allp_out_R); // shimmer
|
|
|
|
|
|
|
|
acc = lp_allp_3.process(acc);
|
|
|
|
acc = lp_dly3.process(acc);
|
|
|
|
acc = flt3.process(acc) * rv_time * rv_time_scaler;
|
|
|
|
|
|
|
|
acc = lp_allp_4.process(acc + in_allp_out_L);
|
|
|
|
acc = lp_dly4.process(acc);
|
|
|
|
|
|
|
|
lp_allp_out = flt4.process(acc) * rv_time * rv_time_scaler;
|
|
|
|
|
|
|
|
acc = lp_dly1.getTap(lp_dly1_offset_L) * 0.8f;
|
|
|
|
acc += lp_dly2.getTap(lp_dly2_offset_L) * 0.7f;
|
|
|
|
acc += lp_dly3.getTap(lp_dly3_offset_L) * 0.6f;
|
|
|
|
acc += lp_dly4.getTap(lp_dly4_offset_L) * 0.5f;
|
|
|
|
|
|
|
|
// Master lowpass filter
|
|
|
|
acc = flt_masterL.process(acc);
|
|
|
|
|
|
|
|
blockL->data[i] = acc * wet_gain + blockL->data[i] * dry_gain;
|
|
|
|
// ChannelR
|
|
|
|
acc = lp_dly1.getTap(lp_dly1_offset_R) * 0.8f;
|
|
|
|
acc += lp_dly2.getTap(lp_dly2_offset_R) * 0.7f;
|
|
|
|
acc += lp_dly3.getTap(lp_dly3_offset_R) * 0.6f;
|
|
|
|
acc += lp_dly4.getTap(lp_dly4_offset_R) * 0.5f;
|
|
|
|
// Master lowpass filter
|
|
|
|
acc = flt_masterR.process(acc);
|
|
|
|
blockR->data[i] = acc * wet_gain + blockR->data[i] * dry_gain;
|
|
|
|
|
|
|
|
// modulate the delay lines
|
|
|
|
// delay 1
|
|
|
|
lfo1.get(BASIC_LFO_PHASE_0, &offset, &lfo_fr); // lfo1 sin output
|
|
|
|
acc = lp_dly1.getTap(offset, lfo_fr);
|
|
|
|
lp_dly1.write_toOffset(acc, LFO_AMPL*2);
|
|
|
|
lp_dly1.updateIndex();
|
|
|
|
|
|
|
|
// delay 2
|
|
|
|
lfo1.get(BASIC_LFO_PHASE_90, &offset, &lfo_fr); // lfo1 cos output
|
|
|
|
acc = lp_dly2.getTap(offset, lfo_fr);
|
|
|
|
lp_dly2.write_toOffset(acc, LFO_AMPL*2);
|
|
|
|
lp_dly2.updateIndex();
|
|
|
|
|
|
|
|
// delay 3
|
|
|
|
lfo2.get(BASIC_LFO_PHASE_0, &offset, &lfo_fr); // lfo2 sin output
|
|
|
|
acc = lp_dly3.getTap(offset, lfo_fr);
|
|
|
|
lp_dly3.write_toOffset(acc, LFO_AMPL*2);
|
|
|
|
lp_dly3.updateIndex();
|
|
|
|
|
|
|
|
// delay 4
|
|
|
|
lfo2.get(BASIC_LFO_PHASE_90, &offset, &lfo_fr); // lfo2 cos output
|
|
|
|
acc = lp_dly4.getTap(offset, lfo_fr);
|
|
|
|
lp_dly4.write_toOffset(acc, LFO_AMPL*2);
|
|
|
|
lp_dly4.updateIndex();
|
|
|
|
}
|
|
|
|
if (LFO_AMPL != LFO_AMPLset)
|
|
|
|
{
|
|
|
|
lfo1.setDepth(LFO_AMPL);
|
|
|
|
lfo2.setDepth(LFO_AMPL);
|
|
|
|
LFO_AMPL = LFO_AMPLset;
|
|
|
|
}
|
|
|
|
AudioStream_F32::transmit(blockL, 0);
|
|
|
|
AudioStream_F32::transmit(blockR, 1);
|
|
|
|
AudioStream_F32::release(blockL);
|
|
|
|
AudioStream_F32::release(blockR);
|
|
|
|
#endif
|
|
|
|
}
|