Granular experts needed :-) = de clicking of loops in realtime

jo geisler's icon

Dear all,

I would like to hear from our granular experts what technics they used to eliminate clicks due to differences in the amplitudes (beginning & end of a loop) of audio loop. As granular looper often have the possibility to browser through the loop in realtime there should be knowledge available that I would like to utilize for my own non granular audio looper project (100% in Gen Codebox Code):

I implemented declicking by a introduction of a amplitude ramp from 0 to 1 and from 1 to 0 at the beginning & end of the loop. I am aware of also utilizing zero crossing in combination with the above method.

I suspect there must be other methods as in granular looping the loops are so tiny that probably ramping the amplitude would take to long ( I suspect).

So could you tell me your box of tricks how you guys do that?

Thanks for your help.

Jo

Dario's icon

You have to perform some kind of windowing on the loops to remove the clicks. This thread may be of use to you:

https://cycling74.com/forums/windowing-for-simple-looper

jo geisler's icon

Hey Dario, read through your post.

I suspect this is the same method that I am using but with a different approach.

My understanding of the windowing is that you have a separate buffer where you store the ramp / s-curve / sin- curve or whatever curve you want the amplitude to be modified with. This ramp / curve will then be triggered at the beginning and the end of a loop with the same sample counter that also drives the looper.

So it is still the same thing that I am doing with an introduction of calculated amplitude ramp. So I am calculating and you are writing the data for the fade in and fade out in a buffer. Correct?

Unfortunately this is not getting me closer to my question: here an example:

If the loop gets really small into the granular world (< 100 samples) there need to be a adjustment of the window / ramp / curve duration. Otherwise the loop would gradually disappear...

Is there then any other method to be utilized other than fading in and out an amplitude?

The second question I am having is what is the most efficient window / ramp / curve to avoid clicks. I use a ramp up to now. Is an s-curve any better? or a tanning curve??

Best regards

Jo

florian1947's icon

Most likely, the breaks will not happen exactly at sample vector borders, resulting in clicks that you cannot fix in MSP. Gen is your friend.

jo geisler's icon

As stated in my initial post above I use 100% gen code so I don´t run into the issue you descibing.

jo geisler's icon

Any other granular experts on this topic?

MuShoo's icon

Using [wave] in gen~ is what you're looking for, far as I can tell. Wave will automatically scale/interpolate the "amplitude" buffer you reference to whatever speed you need it to be.

Basically, everything should be driven by phasor or your own 0->1 oscillator (also in Gen!). You drive your [play] object using that phasor, and the wave object using the same phasor. Your amplitude waveform and playback waveform are then completely phaselocked, and the speed you play them back at doesn't really matter - wave will interpolate for you. I've gone down to less than a millisecond grain size (48 samples, at 48khz) without any windowing-based clicks happening. The result still tends to be a bit noisy just because the grain size is so small.

Hanning curve is probably your best bet, or at small grain sizes just a straight up triangle or trapezoidal waveform.

Alternately if you're really against doing it the way just about every other granular engine I've ever seen works (amplitude windowing) you could try experimenting with [rampsmooth] on your output, but that's essentially just a lowpass filter. Other than that, only thing I could think of would involve having a good chunk of lookahead as to your next few grains, and trying to bend end of one grain towards the beginning of the next one.

jo geisler's icon

Hey Mushoo,

thats exactly the information I was looking for :-)

Fully understood what you wrote and using the wave object in gen this should be no problem implementing.

Nevertheless I have a questions concerning the "amplitude buffer" you implemented going down to 1ms grains:

Could you explain to me what the duration (size of the amplitude buffer) is and how the waveform looks like? I assume it is a hanning window that goes from 0 to 1 then stays at 1 for x samples and then goes down to 0 again.

Also I would be interested how you pre fill the buffer with the hanning window information.

In case you would have an example I would appreciate it :-)

Thanks for your support.

Jo

Evan's icon

Hey Jo,
I recently added some granular stuff to a Looper I've been working on, and tried a few different approaches for the windowing.

The technique I settled on was filling an external MSP buffer with a window (send the message (fill, hanning) to a buffer object, for instance), and then used the sample operator in gen to read through that buffer for each grain.

You could probably set something up in gen~ to write a curve/ramp/function into an internal buffer when the patch is loaded, if you want to stay completely in gen~. I think you get modest performance improvements when you aren't referencing external buffers, but I haven't found it necessary to do so.

The sample operator takes a 0-1 index, and interpolates the output. So simply massage the grain read index to a 0-1 range (a modulo, or wrap operation is helpful here) and you should be good to go. Any example is buried beneath a bunch of other layers of complexity, so I don't really have any code snippet to share, but I'm happy to help in any way I can.

jo geisler's icon

Hey Evan, thanks for you advice. I think I have no problem prefilling the buffer from the max side and accessing it from the gen side and then accessing it the way you described it :-)

Concerning the Hanning Window I have a question: prefilling it with a message "fill 1" & "apply hanning" will provide me with a curve that looks like a "small hill", gradually rising - staying a short time at the top and gradually falling down to 0.

I I would apply this to my loop it would fade in and out and would have a rhythmical, ducking effect which I don´t want.

So is there a way that I get a steep tanning window, where it goes very quickly to the top, stays there for most of the time (e.g. 99% of the time) and then goes quickly back down to 0? I would assume this is what I need to avoid the clicks at the beginning and the end of the loops.

In addition i would be interested in your experience how big the buffer (e.g 2000 samples ???) for the hanning window needs to be as I suspect it needs to have a least some resolutions that every step of the hanning window is represented and that there are no big jumps of data points.

Thanks for your support.

Jo

Evan's icon

I I would apply this to my loop it would fade in and out and would have a rhythmical, ducking effect which I don´t want.

That will happen if you use any type of window really, although it will be more pronounced or have different characteristics depending on the window. I've found that if you add a bit of randomness to the start point and length of the grains you can avoid some of the pitch/rhythmic artifacts you get from quickly repeating windows of identical length.

So is there a way that I get a steep tanning window, where it goes very quickly to the top, stays there for most of the time (e.g. 99% of the time)\

I am sure there is, but there's no prebuilt solution as far as I know. Just quickly thinking about it, using a buffer of 4096 samples - you could take the equation for the hanning window, and fill the first 64 samples with the first half of a 128 sample hanning, fill the next 3968 with 1, and fill the remaining 64 samples with the later half of the 128 sample hanning. You could do this in gen~, or you could do it in javascript. You could easily do a trapezoidal thing as well.

ramp = 64;
for (i = 0; i < 4096; i ++){
            if(i < ramp){
                 window.poke(1, i, i/ramp);
            }else if(i >= ramp && i < (length - ramp)){
                window.poke(1,i,1);
            }else{
                window.poke(1,i,(length - 1 - i)/ramp);
            }
        }
    }
}

I don't know how much the size of the window matters, but I generally work in powers of 2. The sample operator does linear interpolation, so you could probably work in smaller values, but I'm not really sure what the advantages/disadvantages are of using a smaller window compared to a larger one.

jo geisler's icon

Evan, exzellent information. I will try out to get the buffer filled with a custom steep hanning window. So i will try with your suggestions and report back if it worked :-)

I assume there is a need to have a certain size of the amplitude buffer as I would suspect that if the window is to short there are jumps in the data points that could lead to clicks. But I am not sure.

I will try out and find out :-)

Jo

jo geisler's icon

If I do it in Gen I will provide the code for the steep hamming buffer window :-)

Evan's icon

The code will probably look pretty similar to the js, as far as structure goes. Make sure you create and set a flag that will make the calculation only happen on the first sample of the DSP routine. You may already know this, but is something i needed to learn when working with for loops in gen. The bit in the first and last conditional will have to calculate the hanning, rather then filling it linearly.

jo geisler's icon

Yep. I will try :-)

Cptnfantasy's icon

Have you checked out using a Tukey window? the attack/release slopes are variable and you have alot of options in terms implementation as you can modify the 'ratio' value to suite you grainsize.

Calculating many Tukey windows on-the-fly can get cpu intensive in my experience, so if you are dealing with polyphony you can pre-calculate several several windows and decide which to use based on your grain size, or you can apply the window once at the start of each grain and store the windowed grain in a buffer for playback.

Max Patch
Copy patch and select New From Clipboard in Max.

jo geisler's icon

Hey Captn :-),

what a coincidence! I was just reading about the Turkey Window as it has parameter that moves the window between a Hanning and a pure rectangle (there is a parameter that drives the steepness of the curve). So I could use this to fill my buffer directly and could also recalculate it on the fly to see what the minimal time is to get all the clicks that are caused by amplitude differences.

Many thanks as I spend already 3 hours to code the Turnkey Equation without success :-)

Jo

jo geisler's icon

... just found another great thing out: there is no need for me to write the stuff into a buffer. The function that you provided can be used in realtime within gen. Using your gen function:

win = tukeyWin0(phase, r);

Parameter "phase": I would only need to ensure that I have a ramp signal from 0 to 1 which would reflect my loop length (0 = beginning of the loop; 1= end of the loop)

Parameter "r": And the steepness of the window will be managed by the "r" parameter.

So I can loop and adjust the parameter to see what the best value is. So I think I will have a click free audio looper that is also able to go down into the granular looping dimensions without clicking :-)

I will need to implement this in the coming days :-)

Thanks :)

Jo

jo geisler's icon

Hi all,

thanks everybody sharing the knowledge.

Here is the patcher for all of you who need to put a Turkey Window into a buffer. Due to the nature of the Turkey function the steepness of the curve is variable so this is perfect for declicking audio or granular loops.

Open the patcher, select your buffer size (duration in samples that you need) and the coefficient that will change the steepness of the curve (1 very gentle ramping > 10 very quick ramping). Then push the button and the window is written to the buffer.

I implemented it fully documented in my gen code box.

Thanks to Evan, Mushoo and Cptnfantasy for the initial thoughts and the original patch that I modified.

Jo

Tukey_Window.maxpat
Max Patch

jo geisler's icon

Misspelled it is the Tukey window not Turkey :-)

Evan's icon

For real though, if you ever figure out the turkey window, do let us know ;)

bertrandfraysse's icon

here is an optimized turkey function with conditionality

tukeyWin0(x, r)
{
    output = 1;
    a = .5 / r;
    if (!((1 - x) > a && x > a))
    {
        if (x < a) {output = (1 - cos (twopi * r * x)) * .5;}
        else {output = (1 - cos (twopi * r * (1 - x))) * .5;}
    }
return output;
}

an other with cycle in place of cos

tukeyWin0(x, r)
{
    output = 1;
    a = .5 / r;
    if (!((1 - x) > a && x > a))
    {
        if (x < a) {output = (1 - cycle (r * x, index = "phase")) * .5;}
        else {output = (1 - cycle (r * (1 - x), index = "phase")) * .5;}
    }
return output;
}

jo geisler's icon

Thanks :-)

Roman Thilenius's icon

i am not a gen user, but whenever i had clicking in granular stuff, i did something wrong with windowing.