Reverse Delay Refining... Weird clicking
Hi! I'm trying to implement reverse delay on a different project that I have. Last week I have implemented different algorithms, but in all of them I have been dealing with weird clicks.To help demonstrate the problem I'm facing, I have build a very basic reverse delay here.... with a fixed buffer of sample rate size (so, 1 second), with variable time (difference between read and write starting position). Feedback control and mix between wet & dry. I've been using a single data object buffer, with poke and peek. I've tried different interpolations on peek, and have stick with linear or cubic. I also have a boundmode wrap at peek.
I'm attaching my code here, so you guys can check. The click is not SO pronounced... but I would like to remove it if possible.
This is the basic reverse code: (the mix is outside)

you have to use hann windowing function or something like that to avoid clicks
check this guy youtube
Wow, there have been 3 different posts about delay-based clicks just today!
The macro view: Think of the delay memory as being like a buffer~, but wrapped around end-to-end like a loop. There's a write position (the value going into poke) and a read position (the value going into peek). The write position is always moving along the buffer one sample at a time. The read position's distance from the write position sets the delay time.
There's at least two different possible causes for clicks.
1) Sudden changes to the read position (the value going into peek). That's like jumping around in the buffer~, and the sample value at one point is probably quite different to the value at another point, so it is no surprise that it can cause a click. ("Stutter" type effects are like this.)
2) The read position and the write position cross each other. Again, this will probably cause a click because in one sample you are reading the most recent input, in the next sample you are reading the input from a second ago (or however long the delay memory is).
The most obvious solution for (1) is to change the delay time gradually, using a lowpass filter or a slew limiter, so that it never makes any sudden jumps. This will have pitch artefacts. A filter will typically have an exponential response behaviour, which has a gloopy effect on pitch. A slew limiter on the other hand can have a linear behaviour, which sounds like a constant pitch transposition. E.g. moving the reader at 2x the speed of the writer will pitch up one octave. If the reader moves at exactly -2x the speed of the writer, you get a reverse delay.
But any change of read speed leads to problem (2), which has a different typical solution. (And incidentally, the same solution works to do delay time changes without filtering/slewing pitch artefacts.) The most common solution is to use two readers (two peeks) and crossfade between them, so that neither of these discontinuities ever get heard. E.g. for (1), update the reader position for whichever delay is currently not being heard, then crossfade over to it. For (2) you need to predict ahead of time when the reader & writer may cross, and perform the crossfade accordingly. E.g. for a reverse delay, if the current reader delay is N samples, then you make the new delay N+L samples, where L is your reverse loop period.
You may want to use an equal-power crossfader, or a smart crossfader that looks for opportune points in the waveform, etc as refinements.
3 times in a row... noticed that, too... and had a similar idea... if you consider that playing in reverse from a tapping buffer means that you have to jump in position, and that jumping to another position is a form of reading with different speed, you could search the forums for "vdb" and find out how it is done there since ever.
"use windowing!" - "eh, what?"
Thanks for tips. The most noise I was getting was from: 1 - too short buffer size... It was the same as max delay time, I just upgraded it to 3 times max delay time and the click reduces a lot... But in response .. the time of recording becomes too long some times.
2- I was using the same gen for Left and Right channels. And the same gen was reading the same data. I didn't realize the reference name inside data is shared between files. Now I have 2 data (tapeL and tapeR).
I get it that hann window should help, but I don't know how to implement it inside gen. I just see examples of using it with a buffer in Max. But I need to use data object inside gen.
I have already improved a lot my design... It has almost no clicks... But still getting some (with lower volume now). Maybe windowing would solve it.
Maybe windowing would solve it.
windowing is applied when you have something that predictably loops, or will fall within the envelope of a predictably 'windowed' period of time.
Graham's first guess is probably what's causing clicks(but i haven't ran into any clicks so i'm not entirely sure):
1) Sudden changes to the read position (the value going into peek). That's like jumping around in the buffer~, and the sample value at one point is probably quite different to the value at another point, so it is no surprise that it can cause a click.
One way to take care of this, that is more brute-force than the low-pass idea that Graham suggested is to duck signals out, let the parameter change happen which causes disruption of signal just as the signal is at silence, then duck the signal back in to full volume. You can also apply sine-based curvature to your ducking so that it will sound similar in its fade-out/fade-in to hann-windowing('windowing' is like the inverse, or upside-down version of 'ducking', one fades in then out, the other fades out, then back in... (to be semantically clear: the opposite of 'ducking' is more often referred to as 'gating', whereas 'windowing' is dependent on a specific amount of time, 'ducking'/'gating' is more in response to a trigger/threshold of some kind)).
I suggest ducking in your case because you're not sure where clicks are coming from yet, and here too, this particular example of ducking will always duck out then back in within the timeframe of a vectorsize(outputting the 'param' change using a 'latch' op only after fadeout within half a vectorsize), which makes it very useful for taking care of most clicks that come from gen~-based 'param' changes in case you run into that type of thing in the ongoing future:
i'm hoping i did it correctly, maybe others can verify, tweak, or add/improve to it.
[Edit: for posterity in case anyone needs a non-gen~ version of ducking with sine-based curvature using curve~ object, see here:
hope it can help 🍻
Hi, I cobbled together a windowed reverse delay a while
ago, based off one of the gen~ example pitchshifters and a forum post about synced muting a delay. Should be no clicks, and there's a little feedback for fun. You can also easily add the pitchshift back as well.