← All writing

Teaching the EleksWFD clock to ignore a quiet room

Using a recorded microphone trace to tune an adaptive noise floor, a loudness gate, and smooth LED decay on an ESP32 desk clock.

A sound-reactive clock needs to look quiet when the room is quiet. Music and claps should move the display, and the lights should settle after the sound stops.

For the EleksWFD firmware, I worked on that behavior using measurements recorded from the clock's own microphone. The saved trace contains 1,139 measurements over about 40 seconds: quiet room noise, activity, music and claps, then quiet again. It became an input that the microphone processing could be tested against repeatedly.

The resulting meter uses a slowly changing estimate of background noise, a threshold above that estimate, and a gradual fall in LED height. Frequency analysis then gives bass and treble different shapes on the display.

Reading the microphone

The clock's microphone sits behind an amplifier connected to the ESP32's analog input. On this board, that amplifier's output rests near zero volts and swings upward with sound. The ADC's attenuation setting affects both clipping on loud sounds and how much low-level signal falls into its dead zone.

The firmware uses 11 dB ADC attenuation and samples at 8 kHz. It processes blocks of 256 samples, representing 32 milliseconds of input. For each block, it subtracts the average sample value and calculates the root mean square, or RMS, of the remaining variation. That gives a measure of signal strength.

The recording contains RMS values near the bottom of the ADC's range during quiet portions and peaks above 1,000 during louder activity. Scaling those values directly to a short column of LEDs makes the choice of range difficult: quiet detail occupies little space beside the peaks.

Three plots of the recorded microphone trace: input RMS, relative input level with an adaptive floor and gate, and the replayed overall LED envelope. Quiet test windows stay at zero while louder activity produces repeated peaks.

The top plot shows recorded RMS values. The other plots replay those values through the current firmware's loudness and smoothing code. The decibel scale is relative to ADC readings, not calibrated sound-pressure level. The bottom plot shows an overall envelope before the bass/treble split.

Estimating the quiet level

The loudness calculation converts RMS to a logarithmic scale, then compares it with an adaptive noise floor. That floor estimates the room's quieter level and changes a little on each processed frame.

It rises slowly when the input is above it and falls faster when the input drops below it. A sudden clap therefore raises the input sharply while moving the background estimate only a small amount. In the plot, the input has narrow peaks and the floor changes gradually underneath them.

The gate sits 12 dB above that floor. Below the gate, the target display level is zero. Above it, another 28 dB maps the target from zero to full height. The gate and display range move with the estimated background level.

The implementation also clamps very small RMS values before taking the logarithm. The ADC can return blocks with no variation; those need a defined value in the calculation.

This is a decision about signal level. The clock does not need to identify a clap or recognize a piece of music to make the meter respond.

Letting the lights settle

A gate determines when the input deserves a response. The display also needs a rule for what happens after a peak.

The firmware lets a rising target raise the LEDs immediately. When the target falls, the displayed height decreases by up to 0.8 LED levels per processed frame before rounding to a whole number of LEDs. A full-height column takes roughly half a second to reach zero at the nominal frame interval, provided another sound does not raise it again.

A short excerpt from the recording showing an immediate target dropping to zero while the smoothed overall LED height falls in steps, then rises again at the next sound.

This excerpt uses the same recorded RMS values and C++ replay as the full trace. The dashed line is the immediate target; the solid line is the smoothed meter height. Around 30.25 seconds, the target reaches zero while the display continues its gradual fall. The next sound raises it again.

The calculation keeps the meter responsive to a new sound while giving each peak a visible tail.

Giving bass and treble different shapes

Loudness controls how much activity appears. The frequency analysis determines how that activity is distributed.

The firmware applies a Hann window and a fast Fourier transform to each block, then groups the energy into seven bands between 60 Hz and 4 kHz. The first three bands cover the bass side below 600 Hz; the remaining bands feed the treble side. Each column combines its relative frequency energy with the overall loudness level, then applies the display smoothing.

The same seven bands drive the grid's audio and mouth animations. Those animations can use the clock's microphone when the PC app is off.

The saved trace contains per-frame diagnostics rather than raw audio samples. Its RMS values are enough to replay the loudness gate and decay shown here. Frequency behavior is tested separately with generated signals: a low tone should favor the bass column, while a high tone should favor treble.

Turning the recording into a repeatable check

The microphone DSP is plain C++ that can be compiled on a PC. The test suite feeds it both synthetic sample buffers and the saved room trace.

The trace replay passes each recorded RMS value through the firmware's current loudness calculation and smoothing, in order. For these illustrations, that output was also checked against the repository's existing replay driver. The charts use the recorded timestamps, with the tests' checked intervals shaded.

In this replay, the overall meter stays at zero throughout both checked quiet windows. In the music-and-claps window, it is active for about 72% of frames and reaches the full 12-LED height. Those results describe this recording and these settings; another room provides a different input for tuning.

The synthetic tests cover silence, background hum and hiss, low and high tones, and the fall after a tone ends. The recorded trace adds the uneven timing and changing levels of a real room.

Keeping that trace means a change to the gate or decay can be checked against the same input before another hardware test. The DSP source, recorded trace, and microphone tests are in the repository. The EleksWFD project page covers the firmware and companion app.