From 11ce9224099ca70e43798be577cb79d555ca0592 Mon Sep 17 00:00:00 2001 From: pio Date: Thu, 31 Dec 2020 15:29:09 +0100 Subject: [PATCH] Plate reverb added --- Hx_PlateReverb/Hx_PlateReverb.ino | 221 +++++++++++ Hx_PlateReverb/StereoPlateReverb.png | Bin 0 -> 27176 bytes Hx_PlateReverb/effect_platervbstereo.cpp | 484 +++++++++++++++++++++++ Hx_PlateReverb/effect_platervbstereo.h | 211 ++++++++++ README.md | 50 ++- 5 files changed, 965 insertions(+), 1 deletion(-) create mode 100644 Hx_PlateReverb/Hx_PlateReverb.ino create mode 100644 Hx_PlateReverb/StereoPlateReverb.png create mode 100644 Hx_PlateReverb/effect_platervbstereo.cpp create mode 100644 Hx_PlateReverb/effect_platervbstereo.h diff --git a/Hx_PlateReverb/Hx_PlateReverb.ino b/Hx_PlateReverb/Hx_PlateReverb.ino new file mode 100644 index 0000000..b6582dc --- /dev/null +++ b/Hx_PlateReverb/Hx_PlateReverb.ino @@ -0,0 +1,221 @@ +/** + Example project for the Stereo Plate reverb audio component + (c) 31.12.2020 by Piotr Zapart www-hexefx.com + + Attention!!! Works with Teensy 4.x only! + + The audio path follows a typical scheme used in mixing consoles + where the reverb is put into aux path. + Each source (like i2s or PlaySDWav etc) has a Reverb Send Level control + The Stereo reverb output is mixed then with the dry signals using the output mixers. + + +*/ + +#include +#include "Audio.h" +#include "effect_platervbstereo.h" + +#define I2S_REVERB_SEND_CH 0 +#define SDWAV_REVERB_SEND_CH 1 +#define REVERB_MIX_CH 1 +#define I2S_MIX_CH 0 + +AudioPlaySdWav playSdWav; +AudioInputI2S i2s_in; +AudioMixer4 reverb_send_L; +AudioMixer4 reverb_send_R; +AudioEffectPlateReverb reverb; +AudioMixer4 mixer_out_L; +AudioMixer4 mixer_out_R; +AudioOutputI2S i2s_out; + +AudioConnection patchCord1(playSdWav, 0, reverb_send_L, 1); // wav player L reverb send +AudioConnection patchCord2(playSdWav, 0, mixer_out_L, 2); // wav player L into output mixer +AudioConnection patchCord3(playSdWav, 1, reverb_send_R, 1); // wav player R reverb send +AudioConnection patchCord4(playSdWav, 1, mixer_out_R, 2); // wav player R into output mixer + +AudioConnection patchCord5(i2s_in, 0, mixer_out_L, 0); // i2s out L into output mixer +AudioConnection patchCord6(i2s_in, 1, mixer_out_R, 0); // i2s out R into output mixer + +AudioConnection patchCord7(i2s_in, 0, reverb_send_L, 0); // i2s out reverb send L +AudioConnection patchCord8(i2s_in, 1, reverb_send_R, 0); // i2s out reverb send R + +AudioConnection patchCord9(reverb_send_L, 0, reverb, 0); // reverb inputs +AudioConnection patchCord10(reverb_send_R, 0, reverb, 1); + +AudioConnection patchCord11(reverb, 0, mixer_out_L, 1); // reverb out into output mixer +AudioConnection patchCord12(reverb, 1, mixer_out_R, 1); + +AudioConnection patchCord13(mixer_out_L, 0, i2s_out, 0); // output mixers -> codec DAC +AudioConnection patchCord14(mixer_out_R, 0, i2s_out, 1); + +AudioControlSGTL5000 codec; + + +uint32_t timeLast = 0, timeNow = 0; + +void flexRamInfo(void); +void i2s_set_rev_send(float32_t lvl); +void reverb_set_volume(float32_t lvl); +void wav_set_rev_send(float32_t lvl); + +void setup() +{ + Serial.begin(115200); + //while(!Serial); + delay(1000); + Serial.println("--------------------------"); + Serial.println("T40_GFX - stereo plate reverb"); +#ifdef REVERB_USE_DMAMEM + Serial.println("DMAMEM is used for reverb buffers"); +#endif + AudioMemory(12); + codec.enable(); + codec.volume(0.0); // headphones not used + codec.inputSelect(AUDIO_INPUT_LINEIN); + codec.lineInLevel(2); + codec.lineOutLevel(31); + flexRamInfo(); + + i2s_set_rev_send(0.7); + reverb_set_volume(0.6); + + reverb.size(1.0); // max reverb length + reverb.lowpass(0.3); // sets the reverb master lowpass filter + reverb.lodamp(0.1); // amount of low end loss in the reverb tail + reverb.hidamp(0.2); // amount of treble loss in the reverb tail + reverb.diffusion(1.0); // 1.0 is the detault setting, lower it to create more "echoey" reverb + +} + +void loop() +{ + timeNow = millis(); + if (timeNow - timeLast > 1000) + { + Serial.print("Reverb CPU load = "); + Serial.println(reverb.processorUsageMax()); + timeLast = timeNow; + } + +} + + +void flexRamInfo(void) +{ // credit to FrankB, KurtE and defragster ! +#if defined(__IMXRT1052__) || defined(__IMXRT1062__) + int itcm = 0; + int dtcm = 0; + int ocram = 0; + Serial.print("FlexRAM-Banks: ["); + for (int i = 15; i >= 0; i--) + { + switch ((IOMUXC_GPR_GPR17 >> (i * 2)) & 0b11) + { + case 0b00: + Serial.print("."); + break; + case 0b01: + Serial.print("O"); + ocram++; + break; + case 0b10: + Serial.print("D"); + dtcm++; + break; + case 0b11: + Serial.print("I"); + itcm++; + break; + } + } + Serial.print("] ITCM: "); + Serial.print(itcm * 32); + Serial.print(" KB, DTCM: "); + Serial.print(dtcm * 32); + Serial.print(" KB, OCRAM: "); + Serial.print(ocram * 32); +#if defined(__IMXRT1062__) + Serial.print("(+512)"); +#endif + Serial.println(" KB"); + extern unsigned long _stext; + extern unsigned long _etext; + extern unsigned long _sdata; + extern unsigned long _ebss; + extern unsigned long _flashimagelen; + extern unsigned long _heap_start; + + Serial.println("MEM (static usage):"); + Serial.println("RAM1:"); + + Serial.print("ITCM = FASTRUN: "); + Serial.print((unsigned)&_etext - (unsigned)&_stext); + Serial.print(" "); + Serial.print((float)((unsigned)&_etext - (unsigned)&_stext) / ((float)itcm * 32768.0) * 100.0); + Serial.print("% of "); + Serial.print(itcm * 32); + Serial.print("kb "); + Serial.print(" ("); + Serial.print(itcm * 32768 - ((unsigned)&_etext - (unsigned)&_stext)); + Serial.println(" Bytes free)"); + + Serial.print("DTCM = Variables: "); + Serial.print((unsigned)&_ebss - (unsigned)&_sdata); + Serial.print(" "); + Serial.print((float)((unsigned)&_ebss - (unsigned)&_sdata) / ((float)dtcm * 32768.0) * 100.0); + Serial.print("% of "); + Serial.print(dtcm * 32); + Serial.print("kb "); + Serial.print(" ("); + Serial.print(dtcm * 32768 - ((unsigned)&_ebss - (unsigned)&_sdata)); + Serial.println(" Bytes free)"); + + Serial.println("RAM2:"); + Serial.print("OCRAM = DMAMEM: "); + Serial.print((unsigned)&_heap_start - 0x20200000); + Serial.print(" "); + Serial.print((float)((unsigned)&_heap_start - 0x20200000) / ((float)512 * 1024.0) * 100.0); + Serial.print("% of "); + Serial.print(512); + Serial.print("kb"); + Serial.print(" ("); + Serial.print(512 * 1024 - ((unsigned)&_heap_start - 0x20200000)); + Serial.println(" Bytes free)"); + + Serial.print("FLASH: "); + Serial.print((unsigned)&_flashimagelen); + Serial.print(" "); + Serial.print(((unsigned)&_flashimagelen) / (2048.0 * 1024.0) * 100.0); + Serial.print("% of "); + Serial.print(2048); + Serial.print("kb"); + Serial.print(" ("); + Serial.print(2048 * 1024 - ((unsigned)&_flashimagelen)); + Serial.println(" Bytes free)"); + +#endif +} + +void i2s_set_rev_send(float32_t lvl) +{ + lvl = constrain(lvl, 0.0, 1.0); + reverb_send_L.gain(I2S_REVERB_SEND_CH, lvl); + reverb_send_R.gain(I2S_REVERB_SEND_CH, lvl); +} + + +void reverb_set_volume(float32_t lvl) +{ + lvl = constrain(lvl, 0.0, 1.0); + mixer_out_L.gain(REVERB_MIX_CH, lvl); + mixer_out_R.gain(REVERB_MIX_CH, lvl); +} + +void wav_set_rev_send(float32_t lvl) +{ + lvl = constrain(lvl, 0.0, 1.0); + reverb_send_L.gain(SDWAV_REVERB_SEND_CH, lvl); + reverb_send_R.gain(SDWAV_REVERB_SEND_CH, lvl); +} diff --git a/Hx_PlateReverb/StereoPlateReverb.png b/Hx_PlateReverb/StereoPlateReverb.png new file mode 100644 index 0000000000000000000000000000000000000000..26390faa6608f2f9900ec5e7da850bd89f0f6148 GIT binary patch literal 27176 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 >kmk%V)QzM~Up_nMrAT9ci8ZbOw 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 literal 0 HcmV?d00001 diff --git a/Hx_PlateReverb/effect_platervbstereo.cpp b/Hx_PlateReverb/effect_platervbstereo.cpp new file mode 100644 index 0000000..e389e85 --- /dev/null +++ b/Hx_PlateReverb/effect_platervbstereo.cpp @@ -0,0 +1,484 @@ +/* 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 +#include "effect_platervbstereo.h" +#include "utility/dspinst.h" +#include "synth_waveform.h" + +#define INP_ALLP_COEFF (0.65) +#define LOOP_ALLOP_COEFF (0.65) + +#define HI_LOSS_FREQ (0.3) +#define HI_LOSS_FREQ_MAX (0.08) +#define LO_LOSS_FREQ (0.06) + +#define LFO_AMPL_BITS (5) // 2^LFO_AMPL_BITS will be the LFO amplitude +#define LFO_AMPL ((1<>1) // read offset = half the amplitude +#define LFO_FRAC_BITS (16 - LFO_AMPL_BITS) // fractional part used for linear interpolation +#define LFO_FRAC_MASK ((1< 16 +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +#endif +#if AUDIO_BLOCK_SAMPLES > 32 +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +#endif +#if AUDIO_BLOCK_SAMPLES > 48 +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +#endif +#if AUDIO_BLOCK_SAMPLES > 64 +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +#endif +#if AUDIO_BLOCK_SAMPLES > 80 +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +#endif +#if AUDIO_BLOCK_SAMPLES > 96 +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +#endif +#if AUDIO_BLOCK_SAMPLES > 112 +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +#endif +} }; + +void AudioEffectPlateReverb::update() +{ + const audio_block_t *blockL, *blockR; + +#if defined(__ARM_ARCH_7EM__) + audio_block_t *outblockL; + audio_block_t *outblockR; + int i; + float32_t input, acc, temp1, temp2; + uint16_t temp16; + float32_t rv_time; + + // for LFOs: + int16_t lfo1_out_sin, lfo1_out_cos, lfo2_out_sin, lfo2_out_cos; + int32_t y0, y1; + int64_t y; + uint32_t idx; + + blockL = receiveReadOnly(0); + blockR = receiveReadOnly(1); + outblockL = allocate(); + outblockR = allocate(); + if (!outblockL || !outblockR) { + if (outblockL) release(outblockL); + if (outblockR) release(outblockR); + if (blockL) release((audio_block_t *)blockL); + if (blockR) release((audio_block_t *)blockR); + return; + } + + if (!blockL) blockL = &zeroblock; + if (!blockR) blockR = &zeroblock; + // convert data to float32 + arm_q15_to_float((q15_t *)blockL->data, input_blockL, AUDIO_BLOCK_SAMPLES); + arm_q15_to_float((q15_t *)blockR->data, input_blockR, AUDIO_BLOCK_SAMPLES); + + rv_time = rv_time_k; + + for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) + { + // do the LFOs + lfo1_phase_acc += lfo1_adder; + idx = lfo1_phase_acc >> 24; // 8bit lookup table address + y0 = AudioWaveformSine[idx]; + y1 = AudioWaveformSine[idx+1]; + idx = lfo1_phase_acc & 0x00FFFFFF; // lower 24 bit = fractional part + y = (int64_t)y0 * (0x00FFFFFF - idx); + y += (int64_t)y1 * idx; + lfo1_out_sin = (int32_t) (y >> (32-8)); // 16bit output + idx = ((lfo1_phase_acc >> 24)+64) & 0xFF; + y0 = AudioWaveformSine[idx]; + y1 = AudioWaveformSine[idx + 1]; + y = (int64_t)y0 * (0x00FFFFFF - idx); + y += (int64_t)y1 * idx; + lfo1_out_cos = (int32_t) (y >> (32-8)); // 16bit output + + lfo2_phase_acc += lfo2_adder; + idx = lfo2_phase_acc >> 24; // 8bit lookup table address + y0 = AudioWaveformSine[idx]; + y1 = AudioWaveformSine[idx+1]; + idx = lfo2_phase_acc & 0x00FFFFFF; // lower 24 bit = fractional part + y = (int64_t)y0 * (0x00FFFFFF - idx); + y += (int64_t)y1 * idx; + lfo2_out_sin = (int32_t) (y >> (32-8)); //32-8->output 16bit, + idx = ((lfo2_phase_acc >> 24)+64) & 0xFF; + y0 = AudioWaveformSine[idx]; + y1 = AudioWaveformSine[idx + 1]; + y = (int64_t)y0 * (0x00FFFFFF - idx); + y += (int64_t)y1 * idx; + lfo2_out_cos = (int32_t) (y >> (32-8)); // 16bit output + + input = input_blockL[i] * input_attn; + // chained input allpasses, channel L + acc = in_allp1_bufL[in_allp1_idxL] + input * in_allp_k; + in_allp1_bufL[in_allp1_idxL] = input - in_allp_k * acc; + input = acc; + if (++in_allp1_idxL >= sizeof(in_allp1_bufL)/sizeof(float32_t)) in_allp1_idxL = 0; + + acc = in_allp2_bufL[in_allp2_idxL] + input * in_allp_k; + in_allp2_bufL[in_allp2_idxL] = input - in_allp_k * acc; + input = acc; + if (++in_allp2_idxL >= sizeof(in_allp2_bufL)/sizeof(float32_t)) in_allp2_idxL = 0; + + acc = in_allp3_bufL[in_allp3_idxL] + input * in_allp_k; + in_allp3_bufL[in_allp3_idxL] = input - in_allp_k * acc; + input = acc; + if (++in_allp3_idxL >= sizeof(in_allp3_bufL)/sizeof(float32_t)) in_allp3_idxL = 0; + + acc = in_allp4_bufL[in_allp4_idxL] + input * in_allp_k; + in_allp4_bufL[in_allp4_idxL] = input - in_allp_k * acc; + in_allp_out_L = acc; + if (++in_allp4_idxL >= sizeof(in_allp4_bufL)/sizeof(float32_t)) in_allp4_idxL = 0; + + input = input_blockR[i] * input_attn; + + // chained input allpasses, channel R + acc = in_allp1_bufR[in_allp1_idxR] + input * in_allp_k; + in_allp1_bufR[in_allp1_idxR] = input - in_allp_k * acc; + input = acc; + if (++in_allp1_idxR >= sizeof(in_allp1_bufR)/sizeof(float32_t)) in_allp1_idxR = 0; + + acc = in_allp2_bufR[in_allp2_idxR] + input * in_allp_k; + in_allp2_bufR[in_allp2_idxR] = input - in_allp_k * acc; + input = acc; + if (++in_allp2_idxR >= sizeof(in_allp2_bufR)/sizeof(float32_t)) in_allp2_idxR = 0; + + acc = in_allp3_bufR[in_allp3_idxR] + input * in_allp_k; + in_allp3_bufR[in_allp3_idxR] = input - in_allp_k * acc; + input = acc; + if (++in_allp3_idxR >= sizeof(in_allp3_bufR)/sizeof(float32_t)) in_allp3_idxR = 0; + + acc = in_allp4_bufR[in_allp4_idxR] + input * in_allp_k; + in_allp4_bufR[in_allp4_idxR] = input - in_allp_k * acc; + in_allp_out_R = acc; + if (++in_allp4_idxR >= sizeof(in_allp4_bufR)/sizeof(float32_t)) in_allp4_idxR = 0; + + // input allpases done, start loop allpases + input = lp_allp_out + in_allp_out_R; + acc = lp_allp1_buf[lp_allp1_idx] + input * loop_allp_k; // input is the lp allpass chain output + lp_allp1_buf[lp_allp1_idx] = input - loop_allp_k * acc; + input = acc; + if (++lp_allp1_idx >= sizeof(lp_allp1_buf)/sizeof(float32_t)) lp_allp1_idx = 0; + + acc = lp_dly1_buf[lp_dly1_idx]; // read the end of the delay + lp_dly1_buf[lp_dly1_idx] = input; // write new sample + input = acc; + if (++lp_dly1_idx >= sizeof(lp_dly1_buf)/sizeof(float32_t)) lp_dly1_idx = 0; // update index + + // hi/lo shelving filter + temp1 = input - lpf1; + lpf1 += temp1 * lp_lowpass_f; + temp2 = input - lpf1; + temp1 = lpf1 - hpf1; + hpf1 += temp1 * lp_hipass_f; + acc = lpf1 + temp2*lp_hidamp_k + hpf1*lp_lodamp_k; + acc = acc * rv_time * rv_time_scaler; // scale by the reveb time + + input = acc + in_allp_out_L; + + acc = lp_allp2_buf[lp_allp2_idx] + input * loop_allp_k; + lp_allp2_buf[lp_allp2_idx] = input - loop_allp_k * acc; + input = acc; + if (++lp_allp2_idx >= sizeof(lp_allp2_buf)/sizeof(float32_t)) lp_allp2_idx = 0; + acc = lp_dly2_buf[lp_dly2_idx]; // read the end of the delay + lp_dly2_buf[lp_dly2_idx] = input; // write new sample + input = acc; + if (++lp_dly2_idx >= sizeof(lp_dly2_buf)/sizeof(float32_t)) lp_dly2_idx = 0; // update index + // hi/lo shelving filter + temp1 = input - lpf2; + lpf2 += temp1 * lp_lowpass_f; + temp2 = input - lpf2; + temp1 = lpf2 - hpf2; + hpf2 += temp1 * lp_hipass_f; + acc = lpf2 + temp2*lp_hidamp_k + hpf2*lp_lodamp_k; + acc = acc * rv_time * rv_time_scaler; + + input = acc + in_allp_out_R; + + acc = lp_allp3_buf[lp_allp3_idx] + input * loop_allp_k; + lp_allp3_buf[lp_allp3_idx] = input - loop_allp_k * acc; + input = acc; + if (++lp_allp3_idx >= sizeof(lp_allp3_buf)/sizeof(float32_t)) lp_allp3_idx = 0; + acc = lp_dly3_buf[lp_dly3_idx]; // read the end of the delay + lp_dly3_buf[lp_dly3_idx] = input; // write new sample + input = acc; + if (++lp_dly3_idx >= sizeof(lp_dly3_buf)/sizeof(float32_t)) lp_dly3_idx = 0; // update index + // hi/lo shelving filter + temp1 = input - lpf3; + lpf3 += temp1 * lp_lowpass_f; + temp2 = input - lpf3; + temp1 = lpf3 - hpf3; + hpf3 += temp1 * lp_hipass_f; + acc = lpf3 + temp2*lp_hidamp_k + hpf3*lp_lodamp_k; + acc = acc * rv_time * rv_time_scaler; + + input = acc + in_allp_out_L; + + acc = lp_allp4_buf[lp_allp4_idx] + input * loop_allp_k; + lp_allp4_buf[lp_allp4_idx] = input - loop_allp_k * acc; + input = acc; + if (++lp_allp4_idx >= sizeof(lp_allp4_buf)/sizeof(float32_t)) lp_allp4_idx = 0; + acc = lp_dly4_buf[lp_dly4_idx]; // read the end of the delay + lp_dly4_buf[lp_dly4_idx] = input; // write new sample + input = acc; + if (++lp_dly4_idx >= sizeof(lp_dly4_buf)/sizeof(float32_t)) lp_dly4_idx= 0; // update index + // hi/lo shelving filter + temp1 = input - lpf4; + lpf4 += temp1 * lp_lowpass_f; + temp2 = input - lpf4; + temp1 = lpf4 - hpf4; + hpf4 += temp1 * lp_hipass_f; + acc = lpf4 + temp2*lp_hidamp_k + hpf4*lp_lodamp_k; + acc = acc * rv_time * rv_time_scaler; + + lp_allp_out = acc; + + // channel L: +#ifdef TAP1_MODULATED + temp16 = (lp_dly1_idx + lp_dly1_offset_L + (lfo1_out_cos>>LFO_FRAC_BITS)) % (sizeof(lp_dly1_buf)/sizeof(float32_t)); + temp1 = lp_dly1_buf[temp16++]; // sample now + if (temp16 >= sizeof(lp_dly1_buf)/sizeof(float32_t)) temp16 = 0; + temp2 = lp_dly1_buf[temp16]; // sample next + input = (float32_t)(lfo1_out_cos & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k + acc = (temp1*(1.0-input) + temp2*input)* 0.8; +#else + temp16 = (lp_dly1_idx + lp_dly1_offset_L) % (sizeof(lp_dly1_buf)/sizeof(float32_t)); + acc = lp_dly1_buf[temp16]* 0.8; +#endif + + +#ifdef TAP2_MODULATED + temp16 = (lp_dly2_idx + lp_dly2_offset_L + (lfo1_out_sin>>LFO_FRAC_BITS)) % (sizeof(lp_dly2_buf)/sizeof(float32_t)); + temp1 = lp_dly2_buf[temp16++]; + if (temp16 >= sizeof(lp_dly2_buf)/sizeof(float32_t)) temp16 = 0; + temp2 = lp_dly2_buf[temp16]; + input = (float32_t)(lfo1_out_sin & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k + acc += (temp1*(1.0-input) + temp2*input)* 0.7; +#else + temp16 = (lp_dly2_idx + lp_dly2_offset_L) % (sizeof(lp_dly2_buf)/sizeof(float32_t)); + acc += (temp1*(1.0-input) + temp2*input)* 0.6; +#endif + + temp16 = (lp_dly3_idx + lp_dly3_offset_L + (lfo2_out_cos>>LFO_FRAC_BITS)) % (sizeof(lp_dly3_buf)/sizeof(float32_t)); + temp1 = lp_dly3_buf[temp16++]; + if (temp16 >= sizeof(lp_dly3_buf)/sizeof(float32_t)) temp16 = 0; + temp2 = lp_dly3_buf[temp16]; + input = (float32_t)(lfo2_out_cos & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k + acc += (temp1*(1.0-input) + temp2*input)* 0.6; + + temp16 = (lp_dly4_idx + lp_dly4_offset_L + (lfo2_out_sin>>LFO_FRAC_BITS)) % (sizeof(lp_dly4_buf)/sizeof(float32_t)); + temp1 = lp_dly4_buf[temp16++]; + if (temp16 >= sizeof(lp_dly4_buf)/sizeof(float32_t)) temp16 = 0; + temp2 = lp_dly4_buf[temp16]; + input = (float32_t)(lfo2_out_sin & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k + acc += (temp1*(1.0-input) + temp2*input)* 0.5; + + // Master lowpass filter + temp1 = acc - master_lowpass_l; + master_lowpass_l += temp1 * master_lowpass_f; + + outblockL->data[i] =(int16_t)(master_lowpass_l * 32767.0); //sat16(output * 30, 0); + + // Channel R + #ifdef TAP1_MODULATED + temp16 = (lp_dly1_idx + lp_dly1_offset_R + (lfo2_out_cos>>LFO_FRAC_BITS)) % (sizeof(lp_dly1_buf)/sizeof(float32_t)); + temp1 = lp_dly1_buf[temp16++]; // sample now + if (temp16 >= sizeof(lp_dly1_buf)/sizeof(float32_t)) temp16 = 0; + temp2 = lp_dly1_buf[temp16]; // sample next + input = (float32_t)(lfo2_out_cos & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k + + acc = (temp1*(1.0-input) + temp2*input)* 0.8; + #else + temp16 = (lp_dly1_idx + lp_dly1_offset_R) % (sizeof(lp_dly1_buf)/sizeof(float32_t)); + acc = lp_dly1_buf[temp16] * 0.8; + #endif +#ifdef TAP2_MODULATED + temp16 = (lp_dly2_idx + lp_dly2_offset_R + (lfo1_out_cos>>LFO_FRAC_BITS)) % (sizeof(lp_dly2_buf)/sizeof(float32_t)); + temp1 = lp_dly2_buf[temp16++]; + if (temp16 >= sizeof(lp_dly2_buf)/sizeof(float32_t)) temp16 = 0; + temp2 = lp_dly2_buf[temp16]; + input = (float32_t)(lfo1_out_cos & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k + acc += (temp1*(1.0-input) + temp2*input)* 0.7; +#else + temp16 = (lp_dly2_idx + lp_dly2_offset_R) % (sizeof(lp_dly2_buf)/sizeof(float32_t)); + acc += (temp1*(1.0-input) + temp2*input)* 0.7; +#endif + temp16 = (lp_dly3_idx + lp_dly3_offset_R + (lfo2_out_sin>>LFO_FRAC_BITS)) % (sizeof(lp_dly3_buf)/sizeof(float32_t)); + temp1 = lp_dly3_buf[temp16++]; + if (temp16 >= sizeof(lp_dly3_buf)/sizeof(float32_t)) temp16 = 0; + temp2 = lp_dly3_buf[temp16]; + input = (float32_t)(lfo2_out_sin & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k + acc += (temp1*(1.0-input) + temp2*input)* 0.6; + + temp16 = (lp_dly4_idx + lp_dly4_offset_R + (lfo1_out_sin>>LFO_FRAC_BITS)) % (sizeof(lp_dly4_buf)/sizeof(float32_t)); + temp1 = lp_dly4_buf[temp16++]; + if (temp16 >= sizeof(lp_dly4_buf)/sizeof(float32_t)) temp16 = 0; + temp2 = lp_dly4_buf[temp16]; + input = (float32_t)(lfo2_out_cos & LFO_FRAC_MASK) / ((float32_t)LFO_FRAC_MASK); // interp. k + acc += (temp1*(1.0-input) + temp2*input)* 0.5; + + // Master lowpass filter + temp1 = acc - master_lowpass_r; + master_lowpass_r += temp1 * master_lowpass_f; + outblockR->data[i] =(int16_t)(master_lowpass_r * 32767.0); + + } + transmit(outblockL, 0); + transmit(outblockR, 1); + release(outblockL); + release(outblockR); + if (blockL != &zeroblock) release((audio_block_t *)blockL); + if (blockR != &zeroblock) release((audio_block_t *)blockR); + +#elif defined(KINETISL) + blockL = receiveReadOnly(0); + if (blockL) release(blockL); + blockR = receiveReadOnly(1); + if (blockR) release(blockR); +#endif +} diff --git a/Hx_PlateReverb/effect_platervbstereo.h b/Hx_PlateReverb/effect_platervbstereo.h new file mode 100644 index 0000000..650d5d2 --- /dev/null +++ b/Hx_PlateReverb/effect_platervbstereo.h @@ -0,0 +1,211 @@ +/* Stereo plate reverb for Teensy 4 + * + * Author: Piotr Zapart + * www.hexefx.com + * + * Copyright (c) 2020 by Piotr Zapart + * + * 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 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. + */ + +/*** + * Algorithm based on plate reverbs developed for SpinSemi FV-1 DSP chip + * + * Allpass + modulated delay line based lush plate reverb + * + * Input parameters are float in range 0.0 to 1.0: + * + * size - reverb time + * hidamp - hi frequency loss in the reverb tail + * lodamp - low frequency loss in the reverb tail + * lowpass - output/master lowpass filter, useful for darkening the reverb sound + * diffusion - lower settings will make the reverb tail more "echoey", optimal value 0.65 + * + */ + + +#ifndef _EFFECT_PLATERVBSTEREO_H +#define _EFFECT_PLATERVBSTEREO_H + +#include +#include "Audio.h" +#include "AudioStream.h" +#include "arm_math.h" + + +// if uncommented will place all the buffers in the DMAMEM section ofd the memory +// works with single instance of the reverb only +#define REVERB_USE_DMAMEM + +/*** + * Loop delay modulation: comment/uncomment to switch sin/cos + * modulation for the 1st or 2nd tap, 3rd tap is always modulated + * more modulation means more chorus type sounding reverb tail + */ +//#define TAP1_MODULATED +#define TAP2_MODULATED + +class AudioEffectPlateReverb : public AudioStream +{ +public: + AudioEffectPlateReverb(); + virtual void update(); + + void size(float n) + { + n = constrain(n, 0.0, 1.0); + n = map (n, 0.0, 1.0, 0.2, rv_time_k_max); + float32_t attn = 0.5 * map(n, 0.0, rv_time_k_max, 0.5, 1.0); + AudioNoInterrupts(); + rv_time_k = n; + input_attn = attn; + AudioInterrupts(); + } + + void hidamp(float n) + { + n = constrain(n, 0.0, 1.0); + AudioNoInterrupts(); + lp_hidamp_k = 1.0 - n; + AudioInterrupts(); + } + + void lodamp(float n) + { + n = constrain(n, 0.0, 1.0); + AudioNoInterrupts(); + lp_lodamp_k = -n; + rv_time_scaler = 1.0 - n * 0.12; // limit the max reverb time, otherwise it will clip + AudioInterrupts(); + } + + void lowpass(float n) + { + n = constrain(n, 0.0, 1.0); + n = map(n, 0.0, 1.0, 0.05, 1.0); + master_lowpass_f = n; + } + + void diffusion(float n) + { + n = constrain(n, 0.0, 1.0); + n = map(n, 0.0, 1.0, 0.005, 0.65); + AudioNoInterrupts(); + in_allp_k = n; + loop_allp_k = n; + AudioInterrupts(); + } + + float32_t get_size(void) {return rv_time_k;} +private: + audio_block_t *inputQueueArray[2]; +#ifndef REVERB_USE_DMAMEM + float32_t input_blockL[AUDIO_BLOCK_SAMPLES]; + float32_t input_blockR[AUDIO_BLOCK_SAMPLES]; +#endif + float32_t input_attn; + + float32_t in_allp_k; // input allpass coeff (default 0.6) +#ifndef REVERB_USE_DMAMEM + float32_t in_allp1_bufL[224]; // input allpass buffers + float32_t in_allp2_bufL[420]; + float32_t in_allp3_bufL[856]; + float32_t in_allp4_bufL[1089]; +#endif + uint16_t in_allp1_idxL; + uint16_t in_allp2_idxL; + uint16_t in_allp3_idxL; + uint16_t in_allp4_idxL; + float32_t in_allp_out_L; // L allpass chain output +#ifndef REVERB_USE_DMAMEM + float32_t in_allp1_bufR[156]; // input allpass buffers + float32_t in_allp2_bufR[520]; + float32_t in_allp3_bufR[956]; + float32_t in_allp4_bufR[1289]; +#endif + uint16_t in_allp1_idxR; + uint16_t in_allp2_idxR; + uint16_t in_allp3_idxR; + uint16_t in_allp4_idxR; + float32_t in_allp_out_R; // R allpass chain output +#ifndef REVERB_USE_DMAMEM + float32_t lp_allp1_buf[2303]; // loop allpass buffers + float32_t lp_allp2_buf[2905]; + float32_t lp_allp3_buf[3175]; + float32_t lp_allp4_buf[2398]; +#endif + uint16_t lp_allp1_idx; + uint16_t lp_allp2_idx; + uint16_t lp_allp3_idx; + uint16_t lp_allp4_idx; + float32_t loop_allp_k; // loop allpass coeff (default 0.6) + float32_t lp_allp_out; +#ifndef REVERB_USE_DMAMEM + float32_t lp_dly1_buf[3423]; + float32_t lp_dly2_buf[4589]; + float32_t lp_dly3_buf[4365]; + float32_t lp_dly4_buf[3698]; +#endif + uint16_t lp_dly1_idx; + uint16_t lp_dly2_idx; + uint16_t lp_dly3_idx; + uint16_t lp_dly4_idx; + + const uint16_t lp_dly1_offset_L = 201; + const uint16_t lp_dly2_offset_L = 145; + const uint16_t lp_dly3_offset_L = 1897; + const uint16_t lp_dly4_offset_L = 280; + + const uint16_t lp_dly1_offset_R = 1897; + const uint16_t lp_dly2_offset_R = 1245; + const uint16_t lp_dly3_offset_R = 487; + const uint16_t lp_dly4_offset_R = 780; + + float32_t lp_hidamp_k; // loop high band damping coeff + float32_t lp_lodamp_k; // loop low baand damping coeff + + float32_t lpf1; // lowpass filters + float32_t lpf2; + float32_t lpf3; + float32_t lpf4; + + float32_t hpf1; // highpass filters + float32_t hpf2; + float32_t hpf3; + float32_t hpf4; + + float32_t lp_lowpass_f; // loop lowpass scaled frequency + float32_t lp_hipass_f; // loop highpass scaled frequency + + float32_t master_lowpass_f; + float32_t master_lowpass_l; + float32_t master_lowpass_r; + + const float32_t rv_time_k_max = 0.95; + float32_t rv_time_k; // reverb time coeff + float32_t rv_time_scaler; // with high lodamp settings lower the max reverb time to avoid clipping + + uint32_t lfo1_phase_acc; // LFO 1 + uint32_t lfo1_adder; + + uint32_t lfo2_phase_acc; // LFO 2 + uint32_t lfo2_adder; +}; + +#endif // _EFFECT_PLATEREV_H diff --git a/README.md b/README.md index da470cc..bd4ef9d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,50 @@ # t40fx -Teensy4.0 Audio Lib Components +Teensy4.0 Audio Lib Components: + +## Stereo Plate Reverb +--- +Fully stereo in/out reverb component for the standard 16bit Audio library. + +### Connections: +Reverb requires stereo in and out connenctions. +### API: + +```void size(float32_t n);``` +sets the reverb time. Parameter range: 0.0 to 1.0. +Example: +```reverb.size(1.0); // set the reverb time to maximum``` + + +```void lowpass(float32_t n);``` +sets the reverb master lowpass filter. Parameter range: 0.0 to 1.0. +Example: +```reverb.lowpass(0.7); // darken the reverb sound``` + + +```void hidamp(float32_t n);``` +sets the treble loss. Parameter range: 0.0 to 1.0. +Example: +```reverb.hidamp(1.0); // max hi band dampening results in darker sound ``` + + +```void lodamp(float32_t n);``` +sets the bass cut. Parameter range: 0.0 to 1.0. +Example: +```reverb.lodamp(0.5); // cut more bass in the reverb tail to make the sound brighter ``` + +Audio connections used in the exmaple project: +![alt text][pic1] + +### Additional config: + +by default the reverb places it's buffers into OCRAM/DMAMEM region. +Comment out the +```#define REVERB_USE_DMAMEM``` +line in the ```effect_platervbstereo.h``` file to place the variables into the DCTM ram region. +___ + +Copyright 12.2020 by Piotr Zapart +www.hexefx.com + + +[pic1]: effect_platervbstereo/StereoPlateReverb.png "Stereo plate reverb connections" \ No newline at end of file