Bipolar Average~ in Gen?

J F's icon

The title says it - I've found RMS averaging through the forum, but I need bipolar averaging for a specific function. Is this possible in gen?

Thanks!

Max Gardener's icon

The vanilla average~ object does bipolar averaging, doesn't it?

J F's icon

It does, but I'm trying to keep it in the Gen environment.

Graham Wakefield's icon

If this means simply averaging over the last N samples by adding them up and dividing by N, then one way is to have a [data] of size N, a write index that loops round the data writing in new values as they are received, and a routine to sum all the values in the data and divide by N (e.g. via a for loop). But there's a cheaper way that avoids the expensive for loop. On each passing sample frame, your sum should add the new sample, and subtract the value N samples ago; you can achieve this simply with a [delay] and a [+=] integrator.

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

J F's icon

This makes so much sense! Thank you so much Graham, this is perfect.

Graham Wakefield's icon

Two follow up notes about that patcher:

  • There is an issue that if you change the value of N dynamically, the sum may drift away from what it should be, because you might get the input value subtracted twice or not at all; sending 'reset' to the gen~ will fix it by clearing out the delay line and setting the sum to 0. Right now I can't think of a way to fix this.

  • It is a kind of windowed average but the window is a square shape, which means there's going to be some window noise if you are using this to track e.g. an oscillator. To make a more softly edged windowed average, you could either add some kind of non-leaky prefiltering of the input to spread values over time, or add some post-filtering of the average itself. Either way you will have a trade-off of the aliasing of the square window for the filtering effects, just like any windowed process.

Graham Wakefield's icon

Solved for dynamic window size by using a tape-delay analogy. The trick is to move the write head, not the read head -- and put the erase head at the reader, not the writer. That way, even if the period is shortened or lengthened, still each value written into the tape will be read out eactly once. Also realized that post-filtering makes more sense than pre-filtering, as it may help reduce zipper when changing the period.

Updated averager here:

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

J F's icon

Graham, this is excellent! This is going to be extremely useful.

Graham Wakefield's icon

A few years later, I found another solution to this problem that I like even better. The constraint is that the period N is only allowed to grow or shrink by 1 sample per passing sample frame. In many use cases this is perfectly reasonable.

Also a fun observation: a sliding window average is also known as a box filter, and the response looks a bit like linear interpolation (actually it is equal to a line~ kind of lag generator, so long as the input does not change too quickly). If you cascade more stages, the response smoothens into spline interpolation. A very handy tool to have!

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