[sdiy] Tap tempo question

Brian Willoughby brianw at audiobanshee.com
Sat Feb 6 21:46:29 CET 2021

On Feb 3, 2021, at 13:20, Didier Leplae via Synth-diy wrote:
> My programming skills are rudimentary at best, but I’m very interested in the low pass filtering concept and will look into to it. Thanks!
> On Feb 2, 2021, at 8:03 PM, Brian Willoughby wrote:
>> On Feb 2, 2021, at 02:59, Tom Wiltshire wrote:
>>> Averaging makes some sense where you’ve got a human tapping, and Brian is right that weighted averages are a good idea to give more recent times more importance. Rather than doing hard sums, on a PIC it makes sense to do this weighting by using bit shifts (so choose from weights of 1, 0.5, 0.25, or 0.125!!).
>> A low-pass filter with a coefficient of 0.5 would automatically create that weighted series (1, 0.5, 0.25, 0.125). It wouldn't even be necessary to store the last four values because the filter state would already have them summed.
>> It would probably be helpful to compare the latest period against the slowest tempo, and if it's even longer than that then just zero-out the filter state to reset. Of course, don't use the filter output if it's zero. Instead, just leave the tempo at the latest setting.
>> Brian Willoughby


You may have already found everything you need, but I had a moment to write, so I'll try to give some quick pointers.

A single-pole lowpass filter can be implemented in code as follows:

typedef float sample_t;	// your setup may not use float, so adjust the following as needed
sample_t a = coefficient;	// coefficient is between 0 and 1
sample_t b = 1.0 - a;	// the key is that a+b produces the largest sample value, not more
static sample_t output = (input * b) + (output * a);

That coefficient ends up determining the corner frequency of the lowpass filter. Note that a + b = 1, so it's almost like a wet/dry mix of the old and new values. When a == 0.5, b == 0.5, you end up with the simple average. The average of two values would be (a + b) / 2, and if you multiply each input by 0.5 before adding them, then it's the same result, thanks to the associativity and commutativity of multiplication and addition. i.e. (x + y) / 2 == x/2 + y/2 == x * 0.5 + y * 0.5

This may look like it's only averaging two values, but over time the output contains traces of old values. At any given time, the output contains 0.5 of the input, 0.25 of the previous input, 0.125 of the input before that, and so on. Depending upon whether you're handling 8-bit, 10-bit, 12-bit, 16-bit, or greater values for the period, the length of time that the average spans is extended - which is to say that the impulse response is approximately infinite due to the feedback, apart from quantization noise.

Putting this into terms of Tom's suggestion, you can avoid multiplication ... input * b ... output * a ... by simply fixing the values of a and b at 0.5, and using the bit shift instruction to divide by two. This assumes your processor does not have a single-cycle multiply opcode (in which case shift and multiple might take the same time). Bit shifts are basically always a single cycle, and as fast as the fastest instructions. Most processors have a carry flag that can participate in the shift operation.

So, for example, if you have an 8-bit processor, you could add 8-bit input and 8-bit output, giving a 9-bit sum including the carry bit, then shift right to bring the carry bit into the result, truncating the least significant bit. This helps resolution because if you shifted each input separately, then you'd only be adding two 7-bit numbers due to truncation. It's also a little faster to do one add and one bit shift, rather than two bit shifts and an add.

The same concepts work for 16-bit and 32-bit processors, it's just that the registers are larger. I would build the math around the resolution of your timer peripheral, which might be 16-bit and might be 32-bit, depending upon your processor. I'm assuming that you have some sort of time reference, so that you can measure the time between taps of the tempo button. You'll want to consider the lowest tempo and the resolution of the timer to decide how large the values need to be.

I suppose that you could also use other power-of-two coefficient pairs, like 0.75 and 0.25, or 0.875 and 0.125, but those would require slightly more code.

I hope this helps. It's a lot longer than I expected it to be.

Brian Willoughby

More information about the Synth-diy mailing list