From 3b51ed8477f5381632c6afe973b6b21c1190d519 Mon Sep 17 00:00:00 2001 From: Holger Date: Sun, 3 Apr 2022 15:31:21 +0200 Subject: [PATCH] Added code for enabling compressor (compressor code is in Synth_Dexed) / Fixes for README.md / Added stereo plate reverb (#59) * Additions for compressor. * Added code for enabling compressor of Synth_Dexed (with default values). * First try for adding a reverb effect. * Fixes for reverb. * Added full implementation of plate reverb. * -o KERNEL_MAX_SIZE=0x400000 https://github.com/probonopd/MiniDexed/pull/59#issuecomment-1086713102 Co-authored-by: Holger Wirtz Co-authored-by: probonopd --- README.md | 25 +- Synth_Dexed | 2 +- build.sh | 2 +- src/Makefile | 3 +- src/Synth_Dexed.mk | 8 +- src/dexedadapter.h | 5 + src/effect_platervbstereo.cpp | 467 ++++++++++++++++++++++++++++++++++ src/effect_platervbstereo.h | 232 +++++++++++++++++ src/minidexed.cpp | 18 +- src/minidexed.h | 3 + src/minidexed.ini | 1 + 11 files changed, 749 insertions(+), 17 deletions(-) create mode 100644 src/effect_platervbstereo.cpp create mode 100644 src/effect_platervbstereo.h diff --git a/README.md b/README.md index 51cba86..23f1818 100644 --- a/README.md +++ b/README.md @@ -88,27 +88,29 @@ __CAUTION:__ All GPIO numbers are [chip numbers](https://pinout.xyz/), not heade E.g., to build for Raspberry Pi 4 on a Ubuntu 20.04 build system, you can use the following example. See [`build.yml`](../../tree/main/.github/workflows/build.yml) for complete build steps that create versions for Raspberry Pi 1, 2, 3,and 4 in 32-bit and 64-bit as required. ``` +# Choose your RPi +export RPI=4 + git clone https://github.com/probonopd/MiniDexed cd MiniDexed +mkdir -p kernels sdcard # Recursively pull git submodules git submodule update --init --recursive -# Choose your RPi -export RPI=4 - # Install toolchain if [ "${RPI}" -gt 2 ] then - wget -q https://developer.arm.com/-/media/Files/downloads/gnu-a/10.3-2021.07/binrel/gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf.tar.xz + wget https://developer.arm.com/-/media/Files/downloads/gnu-a/10.3-2021.07/binrel/gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf.tar.xz else - wget -q https://developer.arm.com/-/media/Files/downloads/gnu-a/10.3-2021.07/binrel/gcc-arm-10.3-2021.07-x86_64-arm-none-eabi.tar.xz + wget https://developer.arm.com/-/media/Files/downloads/gnu-a/10.3-2021.07/binrel/gcc-arm-10.3-2021.07-x86_64-arm-none-eabi.tar.xz fi -tar xf gcc-arm-*-*.tar.xz +tar xvf gcc-arm-*-*.tar.xz export PATH=$(readlink -f ./gcc-*/bin/):$PATH # Build dependencies and MiniDexed ./build.sh +cp ./src/kernel*.img ./kernels/ # Get Raspberry Pi boot files cd ./circle-stdlib/libs/circle/boot @@ -120,14 +122,17 @@ fi cd - # Make zip that contains Raspberry Pi 4 boot files. The contents can be copied to a FAT32 formatted partition on a microSD card -mkdir -p sdcard cd sdcard ../getsysex.sh cd .. cp -r ./circle-stdlib/libs/circle/boot/* sdcard rm -rf sdcard/config*.txt sdcard/README sdcard/Makefile sdcard/armstub sdcard/COPYING.linux -cp ./src/config.txt ./src/minidexed.ini ./src/*img sdcard/ -zip -r MiniDexed_Raspberry_Pi_${RPI}.zip sdcard/* +cp ./src/config.txt ./src/minidexed.ini ./src/*img ./src/performance.ini sdcard/ +echo "usbspeed=full" > sdcard/cmdline.txt +cd sdcard +cp ../kernels/* . || true +zip -r ../MiniDexed_$GITHUB_RUN_NUMBER_$(date +%Y-%m-%d).zip * +cd - # Optionally, create a RPi image. This can be written to a microSD card using tools like Etcher or dd sudo apt install --yes mount parted @@ -145,7 +150,7 @@ sudo losetup -d "${DEV}" rm -r boot # Write to SD card -sudo dd if="${IMG}" of=/dev/mmcblk0 bs=128k status=progress +sudo dd if="${IMG}" of=/dev/mmcblk0 bs=512k status=progress ``` ## Acknowledgements diff --git a/Synth_Dexed b/Synth_Dexed index 5bceb75..70293ae 160000 --- a/Synth_Dexed +++ b/Synth_Dexed @@ -1 +1 @@ -Subproject commit 5bceb75ce5b8a7620cdd469ca7bacb7d0afcd5e1 +Subproject commit 70293ae5998643c706244b090504dde8b4097851 diff --git a/build.sh b/build.sh index 30772b1..271501a 100755 --- a/build.sh +++ b/build.sh @@ -23,7 +23,7 @@ fi # Build circle-stdlib library cd circle-stdlib/ make mrproper || true -./configure -r ${RPI} --prefix "${TOOLCHAIN_PREFIX}" ${OPTIONS} +./configure -r ${RPI} --prefix "${TOOLCHAIN_PREFIX}" ${OPTIONS} -o KERNEL_MAX_SIZE=0x400000 make -j # Build additional libraries diff --git a/src/Makefile b/src/Makefile index b9f2195..352dfca 100644 --- a/src/Makefile +++ b/src/Makefile @@ -8,7 +8,8 @@ CMSIS_DIR = ../CMSIS_5/CMSIS OBJS = main.o kernel.o minidexed.o config.o userinterface.o \ mididevice.o midikeyboard.o serialmididevice.o pckeyboard.o \ - sysexfileloader.o performanceconfig.o perftimer.o + sysexfileloader.o performanceconfig.o perftimer.o \ + effect_platervbstereo.o include ./Synth_Dexed.mk include ./Rules.mk diff --git a/src/Synth_Dexed.mk b/src/Synth_Dexed.mk index 7953394..b411b0b 100644 --- a/src/Synth_Dexed.mk +++ b/src/Synth_Dexed.mk @@ -20,7 +20,11 @@ OBJS += \ $(SYNTH_DEXED_DIR)/pitchenv.o \ $(SYNTH_DEXED_DIR)/porta.o \ $(SYNTH_DEXED_DIR)/sin.o \ - $(CMSIS_DSP_SOURCE_DIR)/SupportFunctions/SupportFunctions.o + $(CMSIS_DSP_SOURCE_DIR)/SupportFunctions/SupportFunctions.o \ + $(CMSIS_DSP_SOURCE_DIR)/BasicMathFunctions/BasicMathFunctions.o \ + $(CMSIS_DSP_SOURCE_DIR)/FastMathFunctions/FastMathFunctions.o \ + $(CMSIS_DSP_SOURCE_DIR)/FilteringFunctions/FilteringFunctions.o \ + $(CMSIS_DSP_SOURCE_DIR)/CommonTables/CommonTables.o INCLUDE += -I $(SYNTH_DEXED_DIR) INCLUDE += -I $(CMSIS_CORE_INCLUDE_DIR) @@ -28,4 +32,4 @@ INCLUDE += -I $(CMSIS_DSP_INCLUDE_DIR) INCLUDE += -I $(CMSIS_DSP_PRIVATE_INCLUDE_DIR) CXXFLAGS += -DARM_MATH_NEON -EXTRACLEAN = $(SYNTH_DEXED_DIR)/*.[od] $(CMSIS_DSP_SOURCE_DIR)/SupportFunctions/*.[od] +EXTRACLEAN = $(SYNTH_DEXED_DIR)/*.[od] $(CMSIS_DSP_SOURCE_DIR)/SupportFunctions/*.[od] $(CMSIS_DSP_SOURCE_DIR)/SupportFunctions/*.[od] $(CMSIS_DSP_SOURCE_DIR)/BasicMathFunctions/*.[od] $(CMSIS_DSP_SOURCE_DIR)/FastMathFunctions/*.[od] $(CMSIS_DSP_SOURCE_DIR)/FilteringFunctions/*.[od] $(CMSIS_DSP_SOURCE_DIR)/CommonTables/*.[od] diff --git a/src/dexedadapter.h b/src/dexedadapter.h index 7f2badc..a7c77cc 100644 --- a/src/dexedadapter.h +++ b/src/dexedadapter.h @@ -33,6 +33,11 @@ public: CDexedAdapter (uint8_t maxnotes, int rate) : Dexed (maxnotes, rate) { + Dexed::setCompressor(true); + if(Dexed::getCompressor()==true) + printf("Dexed-Compressor: enabled\n"); + else + printf("Dexed-Compressor: disabled\n"); } void loadVoiceParameters (uint8_t* data) diff --git a/src/effect_platervbstereo.cpp b/src/effect_platervbstereo.cpp new file mode 100644 index 0000000..4d987e9 --- /dev/null +++ b/src/effect_platervbstereo.cpp @@ -0,0 +1,467 @@ +/* Stereo plate reverb for Teensy 4 + * + * Adapted for MiniDexed (Holger Wirtz ) + * + * 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 +#include +#include "effect_platervbstereo.h" + +#define INP_ALLP_COEFF (0.65f) // default input allpass coeff +#define LOOP_ALLOP_COEFF (0.65f) // default loop allpass coeff + +#define HI_LOSS_FREQ (0.3f) // scaled center freq for the treble loss filter +// #define HI_LOSS_FREQ_MAX (0.08f) +#define LO_LOSS_FREQ (0.06f) // scaled center freq for the bass loss filter + +#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<> 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 = (float32_t(audioblock[i][0])/32767.0f) * 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 = (float32_t(audioblock[i][1])/32767.0f) * 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.0f-input) + temp2*input)* 0.8f; +#else + temp16 = (lp_dly1_idx + lp_dly1_offset_L) % (sizeof(lp_dly1_buf)/sizeof(float32_t)); + acc = lp_dly1_buf[temp16]* 0.8f; +#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.0f-input) + temp2*input)* 0.7f; +#else + temp16 = (lp_dly2_idx + lp_dly2_offset_L) % (sizeof(lp_dly2_buf)/sizeof(float32_t)); + acc += (temp1*(1.0f-input) + temp2*input)* 0.6f; +#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.0f-input) + temp2*input)* 0.6f; + + 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.0f-input) + temp2*input)* 0.5f; + + // Master lowpass filter + temp1 = acc - master_lowpass_l; + master_lowpass_l += temp1 * master_lowpass_f; + + int32_t out = audioblock[i][0] + int16_t(master_lowpass_l * 32767.0f * send_level); + if(out > INT16_MAX) + audioblock[i][0] = INT16_MAX; + else if(out < INT16_MIN) + audioblock[i][0] = INT16_MIN; + else + audioblock[i][0] = out; + + // 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.0f-input) + temp2*input)* 0.8f; + #else + temp16 = (lp_dly1_idx + lp_dly1_offset_R) % (sizeof(lp_dly1_buf)/sizeof(float32_t)); + acc = lp_dly1_buf[temp16] * 0.8f; + #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.0f-input) + temp2*input)* 0.7f; +#else + temp16 = (lp_dly2_idx + lp_dly2_offset_R) % (sizeof(lp_dly2_buf)/sizeof(float32_t)); + acc += (temp1*(1.0f-input) + temp2*input)* 0.7f; +#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.0f-input) + temp2*input)* 0.6f; + + 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.0f-input) + temp2*input)* 0.5f; + + // Master lowpass filter + temp1 = acc - master_lowpass_r; + master_lowpass_r += temp1 * master_lowpass_f; + + out = audioblock[i][1] + int16_t(master_lowpass_l * 32767.0f * send_level); + if(out > INT16_MAX) + audioblock[i][1] = INT16_MAX; + else if(out < INT16_MIN) + audioblock[i][1] = INT16_MIN; + else + audioblock[i][1] = out; + } +} diff --git a/src/effect_platervbstereo.h b/src/effect_platervbstereo.h new file mode 100644 index 0000000..3abc62d --- /dev/null +++ b/src/effect_platervbstereo.h @@ -0,0 +1,232 @@ +/* Stereo plate reverb for Teensy 4 + * + * Adapted for use in MiniDexed (Holger Wirtz ) + * + * 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 + * + */ + +#pragma once +#ifndef _EFFECT_PLATERVBSTEREO_H +#define _EFFECT_PLATERVBSTEREO_H + +#include "arm_math.h" +#include + +#define constrain(amt, low, high) ({ \ + __typeof__(amt) _amt = (amt); \ + __typeof__(low) _low = (low); \ + __typeof__(high) _high = (high); \ + (_amt < _low) ? _low : ((_amt > _high) ? _high : _amt); \ +}) + +/* +template +inline static T min(const T& a, const T& b) { + return a < b ? a : b; +} + +template +inline static T max(const T& a, const T& b) { + return a > b ? a : b; +} + +inline long maplong(long x, long in_min, long in_max, long out_min, long out_max) { + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; +} +*/ + +inline float32_t mapfloat(float32_t val, float32_t in_min, float32_t in_max, float32_t out_min, float32_t out_max) +{ + return (val - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; +} + + +/*** + * 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: + AudioEffectPlateReverb(float32_t samplerate); + void doReverb(uint16_t len, int16_t audioblock[][2]); + + void size(float n) + { + n = constrain(n, 0.0f, 1.0f); + n = mapfloat(n, 0.0f, 1.0f, 0.2f, rv_time_k_max); + float32_t attn = mapfloat(n, 0.0f, rv_time_k_max, 0.5f, 0.25f); + //__disable_irq(); + rv_time_k = n; + input_attn = attn; + //__enable_irq(); + } + + void hidamp(float n) + { + n = constrain(n, 0.0f, 1.0f); + //__disable_irq(); + lp_hidamp_k = 1.0f - n; + //__enable_irq(); + } + + void lodamp(float n) + { + n = constrain(n, 0.0f, 1.0f); + //__disable_irq(); + lp_lodamp_k = -n; + rv_time_scaler = 1.0f - n * 0.12f; // limit the max reverb time, otherwise it will clip + //__enable_irq(); + } + + void lowpass(float n) + { + n = constrain(n, 0.0f, 1.0f); + n = mapfloat(n*n*n, 0.0f, 1.0f, 0.05f, 1.0f); + master_lowpass_f = n; + } + + void diffusion(float n) + { + n = constrain(n, 0.0f, 1.0f); + n = mapfloat(n, 0.0f, 1.0f, 0.005f, 0.65f); + //__disable_irq(); + in_allp_k = n; + loop_allp_k = n; + //__enable_irq(); + } + + void send(float n) + { + send_level = constrain(n, 0.0f, 1.0f); + } + + float32_t get_size(void) {return rv_time_k;} + bool get_bypass(void) {return bypass;} + void set_bypass(bool state) {bypass = state;}; + void tgl_bypass(void) {bypass ^=1;} +private: + bool bypass = false; + float32_t send_level; + float32_t input_attn; + + float32_t in_allp_k; // input allpass coeff + 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]; + 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 + 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]; + 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 + 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]; + 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 + float32_t lp_allp_out; + float32_t lp_dly1_buf[3423]; + float32_t lp_dly2_buf[4589]; + float32_t lp_dly3_buf[4365]; + float32_t lp_dly4_buf[3698]; + 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; // delay line tap offets + 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.95f; + 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/src/minidexed.cpp b/src/minidexed.cpp index 4279444..d3f49c9 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -108,6 +108,16 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, m_CoreStatus[nCore] = CoreStatusInit; } #endif + + // BEGIN setup reverb + reverb = new AudioEffectPlateReverb(pConfig->GetSampleRate()); + reverb->size(0.7); + reverb->hidamp(0.5); + reverb->lodamp(0.5); + reverb->lowpass(0.3); + reverb->diffusion(0.2); + reverb->send(0.8); + // END setup reverb }; bool CMiniDexed::Initialize (void) @@ -549,6 +559,7 @@ void CMiniDexed::ProcessSound (void) // now mix the output of all TGs int16_t SampleBuffer[nFrames][2]; + assert (CConfig::ToneGenerators == 8); for (unsigned i = 0; i < nFrames; i++) { @@ -584,8 +595,11 @@ void CMiniDexed::ProcessSound (void) } } - if ( m_pSoundDevice->Write (SampleBuffer, sizeof SampleBuffer) - != (int) sizeof SampleBuffer) + // BEGIN adding reverb + reverb->doReverb(nFrames,SampleBuffer); + // END adding reverb + + if (m_pSoundDevice->Write (SampleBuffer, sizeof SampleBuffer) != (int) sizeof SampleBuffer) { LOGERR ("Sound data dropped"); } diff --git a/src/minidexed.h b/src/minidexed.h index 7632544..2e9efc8 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -38,6 +38,7 @@ #include #include #include +#include "effect_platervbstereo.h" class CMiniDexed #ifdef ARM_ALLOW_MULTI_CORE @@ -124,6 +125,8 @@ private: CPerformanceTimer m_GetChunkTimer; bool m_bProfileEnabled; + + AudioEffectPlateReverb* reverb; }; #endif diff --git a/src/minidexed.ini b/src/minidexed.ini index cd2123a..b9f33ce 100644 --- a/src/minidexed.ini +++ b/src/minidexed.ini @@ -4,6 +4,7 @@ # Sound device SoundDevice=pwm +#SoundDevice=hdmi SampleRate=48000 #ChunkSize=256 DACI2CAddress=0