Can i modulate the delay time?

Hi @M4ngu - GitHub was set to private for some reason, should be ok now.

Cheers Paul

for some reason (that I don't fully understand) I can't use it :-(
it's probably because I'm using the alternative dynamic audio library, just in case you're curious about it this is what the compiler says:
Code:
.pio/build/teensy40/src/main.cpp.o: In function `EffectDelay2::setParam(int, float)':
main.cpp:(.text._ZN12EffectDelay28setParamEif[_ZN12EffectDelay28setParamEif]+0x40): undefined reference to `AudioEffectDelay10tap::delaysmooth(unsigned char, float, short)'
.pio/build/teensy40/src/main.cpp.o: In function `EffectDelay2::~EffectDelay2()':
main.cpp:(.text._ZN12EffectDelay2D0Ev[_ZN12EffectDelay2D5Ev]+0x8c): undefined reference to `vtable for AudioEffectDelay10tap'
.pio/build/teensy40/src/main.cpp.o: In function `EffectDelay2::~EffectDelay2()':
main.cpp:(.text._ZN12EffectDelay2D2Ev[_ZN12EffectDelay2D5Ev]+0x80): undefined reference to `vtable for AudioEffectDelay10tap'
.pio/build/teensy40/src/main.cpp.o: In function `constructor()':
main.cpp:(.text._Z11constructorv+0xf5c): undefined reference to `AudioEffectDelay10tap::begin(short*, unsigned long)'
main.cpp:(.text._Z11constructorv+0xfc8): undefined reference to `vtable for AudioEffectDelay10tap'
 
@M4ngu sorry I'm not familiar with the alternative dynamic lib.

Just checking a couple of basics have you copied both the .h and .cpp files into the same directory as the sketch and have you included the next object in your sketch with #include "effect_delay10tap.h"? Can you include your sketch?

Cheers, Paul
 
@houtson yes, the files are placed in the right place and the #include it's correct. the code it's very very long, will give a try in a fresh new project, this one has grown too much and I'm getting this sort of errors in the last step of compilation not only with your library. Thanks you!
 
I think for use with the Dynamic Audio Objects library you need a destructor for the AudioEffectDelay10tap object. It can be very simple because it doesn't manage its own delay memory, so a definition like
Code:
~AudioEffectDelay10tap() {SAFE_RELEASE_INPUTS();}
in the .h file should do the job. This is needed to ensure that any audio block that's been sent to a delay object but not yet released in an update() does in fact get released, and isn't left marked as "in use", if the object is deleted.

I did some work on a true modulated delay object, see this thread. I don't think I re-invented the wheel! With mine, modulating a 15ms delay by ±5ms gets you samples delayed by between 10ms and 20ms. As I understand it (I could be wrong), AudioEffectDelay10tap will give you 100% of the 10ms delay, 100% of the 20ms delay, or n% of one and (100-n)% of the other, added together. Is that right?
 
@h4yn0nnym0u5e - to avoid any confusion, the AudioEffectDelay10tap isn't intended as a modulated delay, it was written just to be able to change the delay taps in real time (like with a pot.) without any pops or clicks. it either cross fades between the current and desired time (delayfade()) or smoothly scrubs between current and desired (delaysmooth()). You can do that across simultaneously across all the taps which can lead to some weird effects.
 
I'm still slightly confused ... but I think less so ... it's not that hard to do! So if I'm understanding it, delayFade() is (internally) like having two taps set to different delay times, with a 2-channel mixer that fades from one to the other when you call delayFade(). And delaySmooth() is a single tap, which you can smoothly change the delay time on.

Assuming that's right, my modulated delay can't natively do either of those: you could emulate delayFade() with two AudioEffectFade objects and a mixer, but it'd use 2 taps; and emulate delaySmooth() with an AudioSythWaveformDc connected via a low-pass filter to a modulation input. Both are more complex than having it built in to the object, but of course the modulation means you can just wire LFOs (or whatever) to the modulation inputs and let the audio updates get on with it.
 
I'm still slightly confused ... but I think less so ... it's not that hard to do! So if I'm understanding it, delayFade() is (internally) like having two taps set to different delay times, with a 2-channel mixer that fades from one to the other when you call delayFade(). And delaySmooth() is a single tap, which you can smoothly change the delay time on.

yes that's it - fade over specified number of milliseconds or smooth scrubbing through the buffer at a set rate. both of them really just for making delay time changes musical without pops or clicks but both methods sound very different. I use a version of these on a re-make of a vintage musical effect ( the SpaceStation)


Assuming that's right, my modulated delay can't natively do either of those: you could emulate delayFade() with two AudioEffectFade objects and a mixer, but it'd use 2 taps; and emulate delaySmooth() with an AudioSythWaveformDc connected via a low-pass filter to a modulation input. Both are more complex than having it built in to the object, but of course the modulation means you can just wire LFOs (or whatever) to the modulation inputs and let the audio updates get on with it.

Yes that's right and agree a modulation input on these would be useful (having a play about with that at the moment)

Cheers Paul
 
Mmmm ... 11-bit 16kHz vintage-ness ... tasty! And a use for AudioEffectBitcrusher. I downloaded the service manual and can't quite figure out why they need 24 taps, guess it must be to do with the PROM magic using 13 of them for the preset configurations. Still, that's only a bit over 64k of the Teensy RAM needed, so plenty of grunt left to do other stuff if you want.
 
why they need 24 taps.
It's an unusual architecture there are 8 main taps and then for the reverb there are 16 modulated taps (modulated in anti-phase pairs to try and cancel out pitch change) - it doesn't sound like a modern realistic reverb, its got it own character! Nice write up here if interested
 
Not bad, and I found the patent, too (slightly less dodgy link than in the write-up...). Not quite sure how to interpret the table stating in section 6, particularly the "delay increment". Anyway, it motivated me to make a minor change to my modulated delay, so it can now accept a negative modulation depth. This means you can connect your 8 LFOs to two modulation inputs, set to depths of ±8ms, and get the delay system done with 8 LFOs and two delay blocks. Add one more delay block for the fixed taps, a few filters and mixers, job done! Oh, and the bitcrusher. I guess for full fidelity you'd set the sample rate to 48kHz and then bitcrush to exactly 16kHz / 11 bit.

Suggested topology:
Code:
#include <Audio.h>

// GUItool: begin automatically generated code
AudioInputI2S            i2s1;           //xy=208,72
AudioSynthWaveform       LFO1;      //xy=290,361
AudioSynthWaveform       LFO2; //xy=292,394
AudioSynthWaveform       LFO3; //xy=296,427
AudioSynthWaveform       LFO4; //xy=298,460
AudioSynthWaveform       LFO5; //xy=300,518
AudioSynthWaveform       LFO6; //xy=302,551
AudioSynthWaveform       LFO7; //xy=306,584
AudioSynthWaveform       LFO8; //xy=308,617
AudioEffectBitcrusher    bitcrusherL;    //xy=346,57
AudioEffectBitcrusher    bitcrusherR; //xy=349,98
AudioEffectDelayExternal delayExt1;      //xy=490,403
AudioMixer4              mixerIn;         //xy=495,183
AudioEffectDelayExternal delayExt2; //xy=500,560
AudioFilterBiquad        biquad1;        //xy=632,183
AudioMixer4              mixer1; //xy=671,371
AudioMixer4              mixer2; //xy=680,437
AudioMixer4              mixer3; //xy=682,522
AudioMixer4              mixer4; //xy=691,591
AudioEffectDelayExternal delayExt3; //xy=849,250
AudioMixer4              mixer5; //xy=868,415
AudioMixer4              mixer6; //xy=1007,212
AudioMixer4              mixer7; //xy=1016,281
AudioFilterBiquad        biquadL;        //xy=1148,212
AudioFilterBiquad        biquadR; //xy=1149,281
AudioMixer4              mixerOutL; //xy=1317,129
AudioMixer4              mixerOutR; //xy=1325,208
AudioOutputI2S           i2sOut;         //xy=1474,177

AudioConnection          patchCord1(i2s1, 0, bitcrusherL, 0);
AudioConnection          patchCord2(i2s1, 1, bitcrusherR, 0);
AudioConnection          patchCord3(LFO1, 0, delayExt1, 1);
AudioConnection          patchCord4(LFO1, 0, delayExt1, 2);
AudioConnection          patchCord5(LFO2, 0, delayExt1, 3);
AudioConnection          patchCord6(LFO2, 0, delayExt1, 4);
AudioConnection          patchCord7(LFO3, 0, delayExt1, 5);
AudioConnection          patchCord8(LFO3, 0, delayExt1, 6);
AudioConnection          patchCord9(LFO4, 0, delayExt1, 7);
AudioConnection          patchCord10(LFO4, 0, delayExt1, 8);
AudioConnection          patchCord11(LFO5, 0, delayExt2, 1);
AudioConnection          patchCord12(LFO5, 0, delayExt2, 2);
AudioConnection          patchCord13(LFO6, 0, delayExt2, 3);
AudioConnection          patchCord14(LFO6, 0, delayExt2, 4);
AudioConnection          patchCord15(LFO7, 0, delayExt2, 5);
AudioConnection          patchCord16(LFO7, 0, delayExt2, 6);
AudioConnection          patchCord17(LFO8, 0, delayExt2, 7);
AudioConnection          patchCord18(LFO8, 0, delayExt2, 8);
AudioConnection          patchCord19(bitcrusherL, 0, mixerIn, 0);
AudioConnection          patchCord20(bitcrusherL, 0, mixerOutL, 0);
AudioConnection          patchCord21(bitcrusherR, 0, mixerIn, 1);
AudioConnection          patchCord22(bitcrusherR, 0, mixerOutR, 0);
AudioConnection          patchCord23(delayExt1, 0, mixer1, 0);
AudioConnection          patchCord24(delayExt1, 1, mixer1, 1);
AudioConnection          patchCord25(delayExt1, 2, mixer1, 2);
AudioConnection          patchCord26(delayExt1, 3, mixer1, 3);
AudioConnection          patchCord27(delayExt1, 4, mixer2, 0);
AudioConnection          patchCord28(delayExt1, 5, mixer2, 1);
AudioConnection          patchCord29(delayExt1, 6, mixer2, 2);
AudioConnection          patchCord30(delayExt1, 7, mixer2, 3);
AudioConnection          patchCord31(mixerIn, biquad1);
AudioConnection          patchCord32(delayExt2, 0, mixer3, 0);
AudioConnection          patchCord33(delayExt2, 1, mixer3, 1);
AudioConnection          patchCord34(delayExt2, 2, mixer3, 2);
AudioConnection          patchCord35(delayExt2, 3, mixer3, 3);
AudioConnection          patchCord36(delayExt2, 4, mixer4, 0);
AudioConnection          patchCord37(delayExt2, 5, mixer4, 1);
AudioConnection          patchCord38(delayExt2, 6, mixer4, 2);
AudioConnection          patchCord39(delayExt2, 7, mixer4, 3);
AudioConnection          patchCord40(biquad1, 0, delayExt1, 0);
AudioConnection          patchCord41(biquad1, 0, delayExt2, 0);
AudioConnection          patchCord42(biquad1, 0, delayExt3, 0);
AudioConnection          patchCord43(mixer1, 0, mixer5, 0);
AudioConnection          patchCord44(mixer2, 0, mixer5, 1);
AudioConnection          patchCord45(mixer3, 0, mixer5, 2);
AudioConnection          patchCord46(mixer4, 0, mixer5, 3);
AudioConnection          patchCord47(delayExt3, 0, mixer6, 0);
AudioConnection          patchCord48(delayExt3, 1, mixer6, 1);
AudioConnection          patchCord49(delayExt3, 2, mixer6, 2);
AudioConnection          patchCord50(delayExt3, 3, mixer6, 3);
AudioConnection          patchCord51(delayExt3, 4, mixer7, 0);
AudioConnection          patchCord52(delayExt3, 5, mixer7, 1);
AudioConnection          patchCord53(delayExt3, 6, mixer7, 2);
AudioConnection          patchCord54(delayExt3, 7, mixer7, 3);
AudioConnection          patchCord55(mixer5, 0, mixerIn, 2);
AudioConnection          patchCord56(mixer6, biquadL);
AudioConnection          patchCord57(mixer7, biquadR);
AudioConnection          patchCord58(biquadL, 0, mixerOutL, 1);
AudioConnection          patchCord59(biquadR, 0, mixerOutR, 1);
AudioConnection          patchCord60(mixerOutL, 0, i2sOut, 0);
AudioConnection          patchCord61(mixerOutR, 0, i2sOut, 1);

AudioControlSGTL5000     audioShield;    //xy=1483,227
// GUItool: end automatically generated code

2023-09-02 15_06_17-Audio System Design Tool for Teensy Audio Library.jpg
 
Last edited:
I guess for full fidelity you'd set the sample rate to 48kHz and then bitcrush to exactly 16kHz / 11 bit.

I sample at 44.1 and use this - a port of a part of the Mutable Instrument Plait euro rack module to simulate the sample rate + some extra bit crushing.

On the topology you need another tap on the main delay for feedback (you'll see why I made it 10 taps!) and the LFO pairs run in anti-phase (tap1+ve tap2-ve etc.). The patent is a good read as is the service manual.

Cheers Paul
 
Isn't the feedback just the mix of the 16-tap delay? I know the hardware has the Reverb / Echo selector, but that'd just be a parameter change for the Teensy. Looking at scope photo #5 in the service manual, you can see RAS/CAS pulses for the ADC write, followed by 24 more for the tap reads:
2023-09-02 15_46_29-ursa_major_sst-282_space_station.pdf - Adobe Acrobat Reader (64-bit).jpg
The service manual copy I found online has almost unreadable schematics, so it's a bit hard to follow...

The anti-phase LFOs are (as of this morning!) catered for within the modulated delay object - just set modulation input 2N to +4ms depth, and 2N+1 to -4ms depth, and use full-scale triangle LFOs so they go from -1.0 to +1.0. The calculation is done per-tap anyway within the object, and it saves having to add 8 amplifiers with -1.0 gain.
 
I'm sure the original has a dedicated echo feedback tap but you might be right that you could double duty one of the reverb taps.

That's nice on the negative modulation, a couple of questions on your modulated delay object: would it work on a teensy without external memory? what sort of frequency do you think you could modulate up to, is there a practical limit (I'm looking at another vintage digital effect - a Lexicon SPT97 - and looking to modulate from 0.5x delay time to 1.5x at a rate from 0.05Hz to 500Hz)?

Cheers, Paul
 
Just took a look at the spec for the Lexicon - default 480ms delay, upgradeable to 960ms or a whopping 1920ms ... at 20kHz ... assuming I found the right beast. That's 84,672 samples, taking 173k @ 44.1kHz by my calculation, so you'd fit it into heap on an unmodified Teensy 4.x. Your maximum delay for 0.5 - 1.5x modulation is 1920/1.5 = 1280ms, with a modulation depth of 640ms. Assuming a triangle wave modulation, at 500Hz you sweep the entire delay range (56448 samples) in 1ms, or nearly 3 times in one audio update. I think (complex calculation omitted here :)) that's an interval of 640 samples between "used" ones in the delay buffer. With the existing algorithm, that's going to work really badly, as it's working on an assumption of a reasonable sample interval, and also that memory loads might be expensive because they could be coming from SPI memory.

Having said that, it'd be possible to change the approach a bit, since at worst we only ever need to load 256 samples from delay memory (assuming the existing linear interpolation for "in between" samples; a better interpolation might still only need 512 samples). From heap or EXTMEM that's trivial, and from SPI it's probably still doable, even if woefully inefficient.

I could certainly take a look at tweaking the modulated delay object, if you don't fancy doing it yourself. I put it out there but so far haven't had any feedback, so either it doesn't work, or doesn't fulfil a pressing need as it is!
 
I think for use with the Dynamic Audio Objects library you need a destructor for the AudioEffectDelay10tap object. It can be very simple because it doesn't manage its own delay memory, so a definition like
Code:
~AudioEffectDelay10tap() {SAFE_RELEASE_INPUTS();}
in the .h file should do the job. This is needed to ensure that any audio block that's been sent to a delay object but not yet released in an update() does in fact get released, and isn't left marked as "in use", if the object is deleted.

I did some work on a true modulated delay object, see this thread. I don't think I re-invented the wheel! With mine, modulating a 15ms delay by ±5ms gets you samples delayed by between 10ms and 20ms. As I understand it (I could be wrong), AudioEffectDelay10tap will give you 100% of the 10ms delay, 100% of the 20ms delay, or n% of one and (100-n)% of the other, added together. Is that right?

yes, that is exactly the first think I tried but looks like there's a bug ....I copied the rectifier effect to a new one (just changing the name of the file and object) and I get similar errors, probably because I'm not using the last Arduino version and not the last dynamic audio library version.

BTW what I need is a delay that does not click when the tempo is changed,
 
You should be able to use Teensyduino 1.59 beta 3 with this version of cores and the latest dynamic audio library. I'm not planning to support old versions, there are simply too many combinations to deal with! I do make use of a couple of "portable" installs of Arduino, which is really handy for checking to see if stuff works without wrecking your normal install.

The bad news is that the modulated delay isn't ported into the dynamic library at the moment, pending getting a bit of feedback as to whether it fulfils a real need. You could try it in a non-dynamic install to see if it might work for you, which would encourage me to port it in sooner rather than later.

I assume when you say "tempo" is changed, you mean the tap delay is changed using delay(tap#, milliseconds)? To do a non-clicky version using the modulated delay, you'd wire an AudioSynthWaveformDc object to the modulation input. Set the tap to 2N milliseconds delay, and the modulation depth to N milliseconds. Then DCobject.amplitude(M) gives a delay of N*(1+M) milliseconds, where M is in the allowed range of -1.0 to 1.0. That will still click, but DCobject.amplitude(M,K) will do a smooth change over K milliseconds.

If you want support on "looks like there's a bug" type issues, please follow the forum rule, and post code and full information. Maybe not on this thread, unless it's relevant to modulating the delay time.
 
Back
Top