I imagine a good many of us are working on light-sound visualisation projects. In common with these projects is the need to somehow scale input values appropriately. When I took the funky plank to a jam I found that my attempts at scaling for dynamic volume inputs wasn’t up to scratch.
So let’s recap the problem: First of all, we need to set the gain of whatever circuit is feeding the analog input pins on MCUs where presumably we will be trying to avoid clipping at maximum value. However this means that very low volume levels will barely result in a signal for us to act on. By extension a loud signal or noise (at the lower end) presents some input we want to ignore. In fact in this application here the signal comes out of an MSGEQ7, but it’s potentially applicable to direct audio input, but you’d need to somehow capture amplitude.
My best attempt at solving this is an approach I’d describe as follows:
Set up “lowest input value” moving average array (Arduino Running Average library)
Set up corresponding “highest input value” moving average array
When it comes to deciding what a level really means, work out the actual bandwidth (difference between lowest and highest). Then take the input signal, minus the lowest (average), then times by full_scale_value*actual_bandwidth, constrain to full scale and map to desired value.
In software, your approach is pretty much how you do it: keep a running historical “min” and “max” value pair and scale your output based on them. The problem is that audio range is logarithmic and the bitdepth of the analog inputs gives you pretty crappy signal-to-noise .
You could do it entirely in the analog domain with an auto-gain circuit like: http://electronicdesign.com/analog/effective-agc-amplifier-can-be-built-nominal-cost
or one of those digitally-controlled programmable gain op-amps. Search around for “AGC opamp single supply” or “programmable gain opamp single supply” if you want to go one of those routes.
I’ve also found this wikipedia page to be enlightening:
since it shows how low-quality the nominal 10-bit (usable 8-bit) analog inputs of the Arduino are for audio use.
Ah interesting thanks. I did briefly look at doing compression on the analog side but since I have 12 bits of resolution on a Teensy I thought it would be better to do it there. Also it took many days to debug my electret microphone amp as it was… (turned out to be a buggered electret, hah).
Oh well if you’re on Teensy, you should get much better analog SNR and so your min/max windowing could work. You might want to extend the size of your min & max running average arrays to something pretty large. (e.g. instead of 10 values, maybe 200)