i started today with a positive outlook. it's ship day for a new product (or an update of an existing one) and i had a few small bugs to fix, plus a showstopper that i already had a direct solution for in mind.
things went pretty well according to plan - i pushed out the showstopper fix in only 15 minutes, and had received confirmation reports within the hour. next i slowly worked through the last of the bug-fixes which took the better part of the day, but i got there, somehow. had a little bit of a celebratory wander around the studio, talking to my workmates and co-habitants about how good it feels to have this pretty much wrapped up (it's dominated the last 2 months of my life, and on-and-off before that for 18 months)!
then something occured to me. a little niggle i'd been keeping in the back of my head. i was finally listening to the sounds of the little project, and while the new functionality was sounding fantastic, there was a kind of warbling to the standard tones. often times these things are purely in one's mind, but i sat there and listened, comparing between old and new hardware. it was totally there. the pitch of a thing that is designed to produce a steady tone, was gently squirreling around.
shock horror! a sense of dread. maybe the last $20k i just sank into this production might be going straight in the trash. what was wrong with the new version of the hardware?! the only thing i changed was the digital detection of a switch - that couldn't possibly be affecting the functionality - it's only a GPIO pin. what else changed? the power supply..
long story short, we shifted to using a switch-mode power supply internally on this updated project. this allows the power conversion (from 12v down to 3v3 for the microcontroller) to be over 80% efficient, whereas prior it was less than 60% efficient and passed through 2 different regulators (switching and linear). added (perhaps blinding) benefit, is it decreased the unit cost by some $2.50. i checked there was sufficient power supply filtering and it was fine on the schematic.
next i started poking around with the scope. thanks to my genius repair-focussed employee natalie, this revision had test points for all the important voltages on the back. they all tested out as solid rails. no ripple was visible, and the peak-to-peak accuracy seemed really good at around 35mV, heavily focussed in the center.
i scoped the ADC traces where the pitch input signals hit the uC, and they were a little fuzzy, but no big jittery wandering as i was expecting. it had to be something else. it had to be the power supply! what else could it be. i scoped the 3v3 analog rail again. it looked fine to my eyes. i zoomed right in and all looked well .. except .. little dots were appearing in the bottom of the screen. single dots on my old 90's scope. the kind of dots you generally ignore, but they were happening repeatedly. i turned on peak-to-peak voltage detection and there it was "100mV". these dots weren't random dots, they were part of the voltage readout!
static. it must be static. but what is static anyway?! i don't know to be honest. i didn't want to spend all day staring at the datasheet for the power-supply i'd put in there as the only thing that would lead to was changing the hardware itself. we'd already spent wayyy too much time fixing hardware for this product in the past -- that was the whole reason for the re-design. i had to turn to the code. i had to make it work. we'd come too far. the firmware was monumentally better than the original version. we had to ship!
this is how i came to an early evening exploration into signal smoothing & noise rejection.
this is great for smoothing out random noise at a relatively low level. you just finetune the ratio of old-vs-new to balance between better random-noise rejection and the settling time to reach a new value. if you only move gradually toward new inputs, you reject the randomness very well, but the signal will only 'glide' toward the new destination. this is particularly problematic for pitch, as there is a long tail to the glide (which theoretically never converges). i turned this up all the way to where the glides took 30 seconds (they need to be less than 30ms) and was surprised to find..
the signal still wobbled around! how could this be?! after some ranting, raving, and rubber-ducking with my other workmate, i arrived at a different conclusion - the problem wasn't random-noise, but rather random little spikes in the signal! i did some measurements to print out the stream of values that were being detected by the ADC and they weren't hugely spikey, but there was definite moments where it seemed to jump a little too far out of range. this is only a 12bit ADC so even a change of 4 or 5 LSBs is significant when amplified out to the range of pitch it is controlling.
alternatively it's possible to just take the average of the last n samples and thus reduce the impact of the spurious samples impact to no-more than the surrounding 'good' samples. i implemented it as a simple queue, storing the last set of values. an added benefit here is that the 'glide' behaviour of the RC-filter is replaced with a 'linear' characteristic. once a value has changed to a new state, it will go there in a fixed amount of time, directly related to length of the averaging queue.
i implemented this and tried sizes of 5 then 35, and after still seeing no change, 505. the signal still moved around, albeit in a much slower kind of way! this couldn't be the solution either.
the main problem i was having is that static knob positions were resulting in jittery tones, thus if i could just know when the knob was stationary, i could lock the pitch to the current state and then pick up again when the knob moved.
this was a fun one to implement and of course i got it wrong at first (it would only move if i turned the knob fast enough). eventually though i dialed it in. choosing the hysteresis value was interesting to see the kinds of effects, but eventually i found something i was happy with. once a change was detected, i did a simple 50% mix of the current output and new destination, just to help smooth any quick bumps.
this was a solid solution after some refining of the hysteresis values. it tracked the static pitch quite well, and yet the step size was small enough that it didn't sound obviously discreet in it's movement.. until one turned the knob very slowly with a tone of rich harmonics - all those lovely aliased artifacts were jumping around. it was close, but i hadn't quite nailed it yet.
i packed up and was about to get on my bike to ride home, when it occured to me. this whole hysteresis thing is just a binary form of the more general idea: for small changes, the output should move slowly (or stop) for large changes, the output should move quickly this is the idea that i'm still running with right now.
rather if we break down the problem into the two abovementioned parts, what we need is a filter that follows this small-slow vs large-quick approach. we can implement this by simply using the rate-of-change of the input to control the coefficient of the filter! rate-of-change is easy as we just implemented it for the hysteresis approach as the absolute-difference of output to input.
there's 2 different areas where previous ideas should be integrated:
the first is this idea of exponential vs linear glide time. when moving small distances, the linear time is better for smoothing extraneous signals. while moving large distances requires a more rapidly moving slope to avoid obvious artifacts.
the second idea is about integrating smoothing into the rate-of-change detector, vs. the input into the slope-sensitive-smoother. the linear filter introduces a fixed delay equal to it's length, which is not ideal for the signal itself (latency always feels bad). on the other hand, it shouldn't be a huge problem for control the rate-of-change this way, as large changes will start moving (albeit slowly) before the filter picks up, while a little overshoot (in terms of smoothing time) isn't a problem, as the ear is unlikely to notice the pitch warble if it's only at the onset of a new note. the filter is good at rejecting spurious noise, so it seems appropriate for ignoring spikes by keeping the r-o-c relatively static
conversely, the exponential smoother acts with a single stage of delay, pushing it toward the real-time use of smoothing the signal itself (controlled by the r-o-c detector). the filter is quick to respond to big changes, so it will deal well with quick note changes (often these occur as large steps).
by combining the benefits of the different averaging techniques, and abstracting the idea of hysteresis to a continuous function, we've arrived at an efficient, responsive, and resilient smoothing technique. it is suited to deal with both white noise, as well as spurious noisy elements in a stream. the coefficients of both averagers, plus the scale of modulation by rate-of-change, can be customized to the use-case.
it was only by thinking about the problem with a multi-faceted approach that this conclusion was able to be reached. the answer didn't come about through textbook knowledge, but through practical & lateral thinking about the problem in its surrounding context.
furthermore, it's obvious that more work needs to be done here, beyond just this alogrithm itself. the power supply is still the main cause for concern, particularly considering it features in new designs forthcoming. investigations into different forms of hardware filtering are also a necessary next step, though not applicable in this context.