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 <wirtz@parasitstudio.de>
Co-authored-by: probonopd <probonopd@users.noreply.github.com>
pull/61/head
Holger 2 years ago committed by GitHub
parent 99cefaefd3
commit 3b51ed8477
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 25
      README.md
  2. 2
      Synth_Dexed
  3. 2
      build.sh
  4. 3
      src/Makefile
  5. 8
      src/Synth_Dexed.mk
  6. 5
      src/dexedadapter.h
  7. 467
      src/effect_platervbstereo.cpp
  8. 232
      src/effect_platervbstereo.h
  9. 18
      src/minidexed.cpp
  10. 3
      src/minidexed.h
  11. 1
      src/minidexed.ini

@ -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

@ -1 +1 @@
Subproject commit 5bceb75ce5b8a7620cdd469ca7bacb7d0afcd5e1
Subproject commit 70293ae5998643c706244b090504dde8b4097851

@ -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

@ -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

@ -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]

@ -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)

@ -0,0 +1,467 @@
/* Stereo plate reverb for Teensy 4
*
* Adapted for MiniDexed (Holger Wirtz <dcoredump@googlemail.com>)
*
* 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 <stdio.h>
#include <cstdlib>
#include <assert.h>
#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<<LFO_AMPL_BITS) + 1) // lfo amplitude
#define LFO_READ_OFFSET (LFO_AMPL>>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<<LFO_FRAC_BITS)-1) // mask for the above
#define LFO1_FREQ_HZ (1.37f) // LFO1 frequency in Hz
#define LFO2_FREQ_HZ (1.52f) // LFO2 frequency in Hz
#define RV_MASTER_LOWPASS_F (0.6f) // master lowpass scaled frequency coeff.
const int16_t AudioWaveformSine[257] = {
0, 804, 1608, 2410, 3212, 4011, 4808, 5602, 6393, 7179,
7962, 8739, 9512, 10278, 11039, 11793, 12539, 13279, 14010, 14732,
15446, 16151, 16846, 17530, 18204, 18868, 19519, 20159, 20787, 21403,
22005, 22594, 23170, 23731, 24279, 24811, 25329, 25832, 26319, 26790,
27245, 27683, 28105, 28510, 28898, 29268, 29621, 29956, 30273, 30571,
30852, 31113, 31356, 31580, 31785, 31971, 32137, 32285, 32412, 32521,
32609, 32678, 32728, 32757, 32767, 32757, 32728, 32678, 32609, 32521,
32412, 32285, 32137, 31971, 31785, 31580, 31356, 31113, 30852, 30571,
30273, 29956, 29621, 29268, 28898, 28510, 28105, 27683, 27245, 26790,
26319, 25832, 25329, 24811, 24279, 23731, 23170, 22594, 22005, 21403,
20787, 20159, 19519, 18868, 18204, 17530, 16846, 16151, 15446, 14732,
14010, 13279, 12539, 11793, 11039, 10278, 9512, 8739, 7962, 7179,
6393, 5602, 4808, 4011, 3212, 2410, 1608, 804, 0, -804,
-1608, -2410, -3212, -4011, -4808, -5602, -6393, -7179, -7962, -8739,
-9512,-10278,-11039,-11793,-12539,-13279,-14010,-14732,-15446,-16151,
-16846,-17530,-18204,-18868,-19519,-20159,-20787,-21403,-22005,-22594,
-23170,-23731,-24279,-24811,-25329,-25832,-26319,-26790,-27245,-27683,
-28105,-28510,-28898,-29268,-29621,-29956,-30273,-30571,-30852,-31113,
-31356,-31580,-31785,-31971,-32137,-32285,-32412,-32521,-32609,-32678,
-32728,-32757,-32767,-32757,-32728,-32678,-32609,-32521,-32412,-32285,
-32137,-31971,-31785,-31580,-31356,-31113,-30852,-30571,-30273,-29956,
-29621,-29268,-28898,-28510,-28105,-27683,-27245,-26790,-26319,-25832,
-25329,-24811,-24279,-23731,-23170,-22594,-22005,-21403,-20787,-20159,
-19519,-18868,-18204,-17530,-16846,-16151,-15446,-14732,-14010,-13279,
-12539,-11793,-11039,-10278, -9512, -8739, -7962, -7179, -6393, -5602,
-4808, -4011, -3212, -2410, -1608, -804, 0
};
AudioEffectPlateReverb::AudioEffectPlateReverb(float32_t samplerate)
{
input_attn = 0.5f;
in_allp_k = INP_ALLP_COEFF;
memset(in_allp1_bufL, 0, sizeof(in_allp1_bufL));
memset(in_allp2_bufL, 0, sizeof(in_allp2_bufL));
memset(in_allp3_bufL, 0, sizeof(in_allp3_bufL));
memset(in_allp4_bufL, 0, sizeof(in_allp4_bufL));
in_allp1_idxL = 0;
in_allp2_idxL = 0;
in_allp3_idxL = 0;
in_allp4_idxL = 0;
memset(in_allp1_bufR, 0, sizeof(in_allp1_bufR));
memset(in_allp2_bufR, 0, sizeof(in_allp2_bufR));
memset(in_allp3_bufR, 0, sizeof(in_allp3_bufR));
memset(in_allp4_bufR, 0, sizeof(in_allp4_bufR));
in_allp1_idxR = 0;
in_allp2_idxR = 0;
in_allp3_idxR = 0;
in_allp4_idxR = 0;
in_allp_out_R = 0.0f;
memset(lp_allp1_buf, 0, sizeof(lp_allp1_buf));
memset(lp_allp2_buf, 0, sizeof(lp_allp2_buf));
memset(lp_allp3_buf, 0, sizeof(lp_allp3_buf));
memset(lp_allp4_buf, 0, sizeof(lp_allp4_buf));
lp_allp1_idx = 0;
lp_allp2_idx = 0;
lp_allp3_idx = 0;
lp_allp4_idx = 0;
loop_allp_k = LOOP_ALLOP_COEFF;
lp_allp_out = 0.0f;
memset(lp_dly1_buf, 0, sizeof(lp_dly1_buf));
memset(lp_dly2_buf, 0, sizeof(lp_dly2_buf));
memset(lp_dly3_buf, 0, sizeof(lp_dly3_buf));
memset(lp_dly4_buf, 0, sizeof(lp_dly4_buf));
lp_dly1_idx = 0;
lp_dly2_idx = 0;
lp_dly3_idx = 0;
lp_dly4_idx = 0;
lp_hidamp_k = 1.0f;
lp_lodamp_k = 0.0f;
lp_lowpass_f = HI_LOSS_FREQ;
lp_hipass_f = LO_LOSS_FREQ;
lpf1 = 0.0f;
lpf2 = 0.0f;
lpf3 = 0.0f;
lpf4 = 0.0f;
hpf1 = 0.0f;
hpf2 = 0.0f;
hpf3 = 0.0f;
hpf4 = 0.0f;
master_lowpass_f = RV_MASTER_LOWPASS_F;
master_lowpass_l = 0.0f;
master_lowpass_r = 0.0f;
lfo1_phase_acc = 0;
lfo1_adder = (UINT32_MAX + 1)/(samplerate * LFO1_FREQ_HZ);
lfo2_phase_acc = 0;
lfo2_adder = (UINT32_MAX + 1)/(samplerate * LFO2_FREQ_HZ);
send_level = 0.0;
}
// #define sat16(n, rshift) signed_saturate_rshift((n), 16, (rshift))
void AudioEffectPlateReverb::doReverb(uint16_t len, int16_t audioblock[][2])
{
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;
static bool cleanup_done = false;
// handle bypass, 1st call will clean the buffers to avoid continuing the previous reverb tail
if (bypass)
{
if (!cleanup_done)
{
memset(in_allp1_bufL, 0, sizeof(in_allp1_bufL));
memset(in_allp2_bufL, 0, sizeof(in_allp2_bufL));
memset(in_allp3_bufL, 0, sizeof(in_allp3_bufL));
memset(in_allp4_bufL, 0, sizeof(in_allp4_bufL));
memset(in_allp1_bufR, 0, sizeof(in_allp1_bufR));
memset(in_allp2_bufR, 0, sizeof(in_allp2_bufR));
memset(in_allp3_bufR, 0, sizeof(in_allp3_bufR));
memset(in_allp4_bufR, 0, sizeof(in_allp4_bufR));
memset(lp_allp1_buf, 0, sizeof(lp_allp1_buf));
memset(lp_allp2_buf, 0, sizeof(lp_allp2_buf));
memset(lp_allp3_buf, 0, sizeof(lp_allp3_buf));
memset(lp_allp4_buf, 0, sizeof(lp_allp4_buf));
memset(lp_dly1_buf, 0, sizeof(lp_dly1_buf));
memset(lp_dly2_buf, 0, sizeof(lp_dly2_buf));
memset(lp_dly3_buf, 0, sizeof(lp_dly3_buf));
memset(lp_dly4_buf, 0, sizeof(lp_dly4_buf));
cleanup_done = true;
}
return;
}
cleanup_done = false;
rv_time = rv_time_k;
for (i=0; i < len; 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 = (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;
}
}

@ -0,0 +1,232 @@
/* Stereo plate reverb for Teensy 4
*
* Adapted for use in MiniDexed (Holger Wirtz <wirtz@parasitstudio.de>)
*
* 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 <stdint.h>
#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<typename T>
inline static T min(const T& a, const T& b) {
return a < b ? a : b;
}
template<typename T>
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

@ -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");
}

@ -38,6 +38,7 @@
#include <circle/i2cmaster.h>
#include <circle/multicore.h>
#include <circle/soundbasedevice.h>
#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

@ -4,6 +4,7 @@
# Sound device
SoundDevice=pwm
#SoundDevice=hdmi
SampleRate=48000
#ChunkSize=256
DACI2CAddress=0

Loading…
Cancel
Save