Stereo Plate Reverb for Teensy4.x

Pio

Well-known member
Here is something i have been working recently and already used it in a few projects:
a new implementation of a plate type reverb sound for Teensy4.X.
It's an allpass based reverb using two LFOs to modulate the delay lines inside the reverb chain. The result is more spread, less ringing reverb tail.
Internally all the calculations are done on float32_t, the i/o runs on int16, so it's compatible with a standard audio library.
You can hear a sound sample here:

https://soundcloud.com/hexeguitar/t40gfx-plate-reverb

And the code can be found here:

https://github.com/hexeguitar/t40fx

I will be putting more new audio components there in the future.

Reverb comes with a set of controls:
size(float n) - will set the reverb time
lowpass(float n) - controls the output lowpass filter. Often it is convenient to limit the high band in the reverb sound to make it sit better in the whole mix.
hidamp(float n) - treble dampening control
lodamp(float n) - bass dampening control

I have written a simple example project following a typical mixing console scheme with reverb placed in an AUX loop. Each sound source can go into "Reverb Send" mixers and then the output of the reverb is mixed with the dry signals using final mixer4 stages:

StereoPlateReverb.png

However, since i don't own the new audio adaptor rev D, but use my own audio board i wasn't able to test the example project on a real hardware. I'd appreciate letting me know if there are any problems.
Otherwise, you can use the component as any other, just include the header, use AudioConnection to route the input and output stereo signals.

One note about ram usage: as default (reverb is part of a larger project) i've put the buffers into DMAMEM to leave more DTCM ram for other things. In that case the buffers are declared outside the reverb class, so only one instance of reverb is allowed. Simply comment out the #REVERB_USE_DMAMEM inside the header file to move the variables back to DTCM.

Hope you like it!
Piotr
 
Thank you for posting this Piotr! I loaded it into my project and it sounds great.

I uploaded a sample vocal recording of it in action. To avoid copyright issues I found a vocal recording on archive.org.

 
Jay, that's a nice sample, thanks for posting.
I noticed i should maybe reverse the "lowpass" operation. Right now the parameter controls the cutoff frequency, so lower settings give more darker sound. Which is actually consistent with "Tone" knob found on many FX. However the name could be understood as "amount of lowpass" and increasing the control should result in darkening the sound. What do you think?

Currently i have the reverb working together with another new component, the IR convolution based guitar cabinet simulator:
(reverb part starts at 1:38 https://youtu.be/0fVbkKVKO9M?t=98

Looks pretty cool! I'll have to give this a try. What is the processor usage % on the T4?
The processorUsageMax function reports about 5% for the reverb object. The included example prints out the RAM usage and the CPU load in 1 second intervals.
 
I noticed i should maybe reverse the "lowpass" operation. Right now the parameter controls the cutoff frequency, so lower settings give more darker sound. Which is actually consistent with "Tone" knob found on many FX. However the name could be understood as "amount of lowpass" and increasing the control should result in darkening the sound. What do you think?

I'm not in tune with the standard here, but it felt right to me. Turning up the lowpass knob "did something". Turning it down seemed to "do less". lol.
 
The difference between the hidamp and lowpass is:
- hidamp is a lowpass filter placed inside the reverb tank, causing an increasing treble loss as the reverb tail fades out,
- lowpass is a static filter applied at the end of the algorithm, controls overall amount of treble in the reverb sound.

I have just pushed an update changing the taper of the lowpass control to semi-log (n^3 approximated). It improves the control response, linear taper is not the best one for controlling the filter cutoff frequency.
 
Hello Piotr,

Awesome work. Is it safe to say that the lodamp "minimizes" the effect of the room size and hidamp "minimizes the effect of the lowpass knob? In my video you see I turn up the room size and because it was getting loud I went over to the lodamp to turn that up (thus minimizing the "loudness"). Then the lopass and hidamp also work similarly - one is the antithesis of the other. Is that right?

I guess it makes sense? When you turn the lowpass knob it opens up the highs, and then you can dampen the highs with the hidamp... Just my 2 cents, no problems here. I just might swap the order of my encoders. Reverb, Room Size, lodamp, lowpass, hidamp.

Jay
 
Hi Pio, I'm trying to use this reverb on a project, but got an error, any idea about what is it? using vscode with platformio:
Code:
.pio/build/teensy41/src/Tukra_1.41.ino.cpp.o: In function `_GLOBAL__sub_I_myusb':
Tukra_1.41.ino.cpp:(.text.startup._GLOBAL__sub_I_myusb+0x2450): undefined reference to `AudioEffectPlateReverb::AudioEffectPlateReverb()'
collect2: error: ld returned 1 exit status
*** [.pio/build/teensy41/firmware.elf] Error 1
 
Sounds like you aren't linking the proper object files in your project and it can't find the constructor function. I would recommend you try to use the Arduino IDE first, and when you get that working, you can figure out how to get it going in another IDE. If it works in Arduino IDE and doesn't work in another IDE, it's unlikely the author will be able to help much.
 
M4ngu
make sure both of the files, effect_platervbstereo.h and effect_platervbstereo.cpp are in the same directory as your main file.
And of course, there header is included properly:
Code:
#include "Audio.h"
#include "effect_platervbstereo.h"
If you can post the code, or a part of it maybe we can help further.
 
M4ngu
make sure both of the files, effect_platervbstereo.h and effect_platervbstereo.cpp are in the same directory as your main file.
And of course, there header is included properly:
Code:
#include "Audio.h"
#include "effect_platervbstereo.h"
If you can post the code, or a part of it maybe we can help further.

thx dude, placed the .h and .cpp files in the 'src' folder (instead of the 'include' one) and compiled fine ;-)
 
Trying to reduce the minimum reverb size (because it'll be almost for percussion) I've modified those lines in the effect_platervbstereo.h, but maybe is not the right way to proceed, any advice would be appreciated
Code:
    void size(float n)
    {
        n = constrain(n, 0.0, 1.0);

        //n = map (n, 0.0, 1.0, [COLOR="#FF0000"]0.2[/COLOR], rv_time_k_max);
        n = map (n, 0.0, 1.0, [COLOR="#FF0000"]0.02[/COLOR], rv_time_k_max);

        //float32_t attn = 0.5 * map(n, 0.0, rv_time_k_max, [COLOR="#FF0000"]0.5[/COLOR], 1.0);
        float32_t attn = 0.5 * map(n, 0.0, rv_time_k_max, [COLOR="#FF0000"]0.05[/COLOR], 1.0);

        AudioNoInterrupts();
        rv_time_k = n;
        input_attn = attn;
        AudioInterrupts();
    }
 
I'd leave the attn as it is. It is used to attenuate the input signal depending on the reverb time, creates more attenuation for long reverb tail to avoid clipping.
The n calculation looks fine.
However, for shorter "room" type reverbs, more often used for drums a different reverb algorithm would be better. I'd like to code a few more different reverbs (room, spring reverb emulation), just not sure when i'll have time for this.
 
thanks PIO, yes I know plate is not be the most appropriate type of reverb for drum sounds, but I've testing yours and it really sounds much better than the Teensy reverb object that was using before, nice stereo image and better 'integration' in the mix.
 
Hi again PIO, would like to know if is there a reason to use AudioNoInterrupts(); in the header file instead of __disable_irq();
I though was not recommended to use it because of this:
"The special AudioNoInterrupts() and AudioInterrupts() functions should NOT within your object's functions. These are meant to allow the Arduino sketch to disable audio library updates across multiple function calls, perhaps to several different objects. They work separate from __disable_irq()."
https://www.pjrc.com/teensy/td_libs_AudioNewObjects.html
 
M4ngu
Hi again PIO, would like to know if is there a reason to use AudioNoInterrupts(); in the header file instead of __disable_irq();
I might be wrong with my assumptions, but the thinking behind this was:
__disable_irq() disables all interrupts, everything including USB, timers etc.
AudioNoInterrupts(); disables the software interrupt only, where all the audio processing happens. The variables i'm updating within the disable/enable block are accessed in the AudioInterrupt only. So, that option seemed to be more correct. But i might be wrong here. It happens only in functions updating the parameters which are called from the main loop, never inside or called from the update function.

danixdj
can it work for T 3.6 also?
I can't test it with T3.6 on hardware, but i was able to compile a basic sketch using the reverb object. Reverb requires about 125KB of ram, T3.6 has enough. Not sure about the load.
To make it compile for T3.6 make sure the
Code:
//#define REVERB_USE_DMAMEM
in the effect_platervbstereo.h is commented out.
 
Should I merge this into the audio library? I see it already has the MIT license header. :)

Would be nice to get it into 1.54-beta7 so more people can get access and test it... before a 1.54 release.


On the matter of interrupts, within audio objects generally you should use __disable_irq() and of course disable for as short a time as possible. Even if you're only disabling the audio software interrupt, generally you should save AudioNoInterrupts() for users to employ within their Arduino sketch code rather than use it inside your audio object.

The intended model is Arduino sketch code might wish to make changes to multiple audio objects and have all take effect on the next 128 sample update. For example, maybe the Arduino code turns on a pink noise source, configures an envelope to shape it into a sharp percussive sound, and configures your reverb to make it more "full" sounding, and sends it to a mixer which needs its gain setting updated so the sound is heard at the intended level (maybe based on a recent analogRead indicating how hard a piezo disc or other sensor was struck by a drum stick). That code would call AudioNoInterrupts() before making any of these changes. As it calls functions from the noise synth, envelope effect, your reverb, and the mixer, those function calls might use __disable_irq(), but the intended model is they do not call AudioNoInterrupts() and AudioInterrupts(). Only the user's Arduino sketch code is intended to call AudioInterrupts(), after it has finished configuring a group of audio objects. If your reverb calls AudioInterrupts() after it's done updating the setting, you might mistakenly cause the audio library to begin a 128 sample update before the user's intended mixer gain change is made. Then the user's scuplted-noise percussive sound could be perceived very differently if the first 2.9 ms are at the wrong level. This usage model is only guaranteed to let Arduino sketch code update multiple objects in unison if the objects use __disable_irq() and save AudioNoInterrupts() for the higher level Arduino sketch code.

There are some exceptions to this general guideline, mostly revolving around objects which need to use other non-audio libraries to access storage media like the SD card. But for a "pure math" type object, definitely use __disable_irq() and save AudioNoInterrupts() for your user's discretion.
 
Thanks for the explanation Paul.
I have fixed the code using the __disable/__enable macros. Also changed all the float constants to use "f" postfix to ensure they are single precision floats.

And there is a new function, which imho might come handy in more complex projects: reverb bypass.
When the bypass is enabled, the reverb will clear its buffers at the 1st iteration to avoid continuing the previous reverb tail when the engine is turned on again.
When done it just returns from the update function saving the cycles for something else. This way there might be more CPU heavy processes used in alternate fashion, if RAM/FLASH space allows it, of course.
Using a mixer to bypass the reverb when not in use still makes it crunch all the zeros. It is also a way to immediately reset the sound without waiting for the tail to fade away.

Feel free to merge the code.

I have been working on a spring reverb simulation lately. Still requires some work and for now it's mono only. Here is a sample:
https://soundcloud.com/hexeguitar/teensy40-spring-reverb
 
Has this reverb code been merged into the Audio Library yet?

Also where can I find the source code for audio library 1.54-beta7?

Thanks
 
Hi,

I have tested this reverb. It is just great. It sounds really better than freeverb. It would be nice indeed if it was merged !
Emmanuel
 
This is indeed a really cool reverb! Should be added to the audio library. ::cool:

I was wondering what kind of tweaks would be necessary to make the roomsize even bigger for such a reverb. I don't mind using more of my teensy memory for this since it will only be used for reverb. I've looked into the code but it wasn't obvious. Can anyone shed some light on this perhaps?
 
To change the reverb time you could alter the rv_time_k_max parameter (max reverb time coeff)m which is used in the size function:

Code:
    void size(float n)
    {
        n = constrain(n, 0.0f, 1.0f);
        n = map (n, 0.0f, 1.0f, 0.2f, [COLOR="#FF0000"]rv_time_k_max[/COLOR]);
        float32_t attn = map(n, 0.0f, [COLOR="#FF0000"]rv_time_k_max[/COLOR], 0.5f, [COLOR="#008000"]0.25f[/COLOR]);
        __disable_irq();
        rv_time_k = n;
        input_attn = attn;
         __enable_irq();
    }
rv_time_k_max is set to 0.95 in the class declaration, you could increase it to 0.99. It will create a clipping, though, so the input signal gain has to be lowered. Marked in green. Experiment with values that will result in no clipping at the max reverb time settings.
One interesting effect using a very long reverb tail would be a kind of sound freeze:
Code:
    void freeze(void)
    {
        __disable_irq();
        rv_time_k = 0.999f;
        input_attn = 0.0f;
         __enable_irq();
    }
This will cut off the input signal, but set the time coeff to almost unity (actually try 1.0f) letting the signal already being in the reverb circulate indefinitely. Updating the size will turn it off.
 
Back
Top