BACK TO BLOG
2026-07-19Meghmalhar Bhowmick

Why Linear Frequencies SUCK for Audio

ESP32C++Embedded SystemsAccessibilityPsychoacousticsHardware

At IIIT Delhi's Electronics Design Carnival in July 2025, I had two weeks to design and build a proximity navigation assistant for visually impaired users. The concept was simple: an ESP32 paired with an HC-SR04 ultrasonic sensor, a buzzer, and a 16×2 LCD. Get close to an obstacle, the buzz gets more urgent. Walk away, it calms down. Your ears do the spatial processing instead of your eyes.

Simple concept. My first implementation had the right hardware, the right wiring, and completely wrong feedback.

The math that felt wrong

The HC-SR04 fires a 40 kHz ultrasonic burst and times the echo:

distance (cm) = (echo_time_us x 0.034) / 2

Then I mapped that distance to a PWM buzzer frequency linearly:

int freq = map(distance, 200, 2, 200, 2500);
ledcWriteTone(BUZZER_CHANNEL, freq);

When I tested it, something felt fundamentally off. The sound changed way too slowly when I was far from an obstacle, then jumped erratically as I got close. Colleagues at the carnival waved their hands in front of the sensor and couldn't tell what was happening until they were practically touching it.

The code was correct. The math was correct. The problem was biology.

How human ears actually hear pitch

Human auditory perception doesn't work linearly. We perceive pitch in terms of musical intervals — ratios, not differences. The gap between 200 Hz and 400 Hz sounds like an octave. So does the gap between 1000 Hz and 2000 Hz. A 200 Hz difference at the low end equals an octave; the same 200 Hz difference at the high end is barely a noticeable step.

This is the Weber-Fechner law applied to acoustics: our sensory systems respond to the logarithm of stimulus intensity, not its raw magnitude. Linear mapping between distance and frequency means that a 10 cm change at 180 cm produces the same delta-Hz shift as a 10 cm change at 10 cm. But at 180 cm that shift is barely perceptible, and at 10 cm it should be screaming at you. Linear frequency makes the alarm feel flat and unresponsive right until it suddenly gets overwhelming.

The fix: exponential transfer function

I replaced the linear map with a logarithmic frequency curve. The formula:

f(d) = f_min * (f_max / f_min)^(1 - (d - d_min) / (d_max - d_min))

This means every equal-ratio decrease in distance produces an equal perceived pitch increase. Moving from 200 cm to 100 cm sounds like the same urgency jump as moving from 100 cm to 50 cm, or 50 cm to 25 cm. Equal perceptual intervals for equal fractional distance changes.

The moment I reflashed the firmware with this transfer function, the buzzer made sense. People picked it up without being told how to interpret it.

The HC-SR04 noise problem

There was a second issue: raw ultrasonic readings are noisy. Ultrasound bounces off angled furniture, carpet, and doorframes. Readings would spike from 45 cm to 180 cm and back in consecutive frames, making the buzzer stutter like a broken Geiger counter.

The fix was a sliding-window rolling average filter in C++:

distanceBuffer[bufIndex] = rawDistance;
bufIndex = (bufIndex + 1) % WINDOW_SIZE;
float smoothDistance = 0;
for (int i = 0; i < WINDOW_SIZE; i++) {
    smoothDistance += distanceBuffer[i];
}
smoothDistance /= WINDOW_SIZE;

Averaging the last N readings before feeding distance into the frequency function dilutes transient spikes into the window and keeps the acoustic output smooth. I landed on WINDOW_SIZE = 5 for a decent balance between noise rejection and response lag.

The physical wiring was actually harder than the code

The ESP32 has dozens of multiplexed GPIO pins with overlapping ADC, DAC, and I2C constraints. The HC-SR04 operates at 5V logic; the ESP32 runs at 3.3V. I wired the circuit wrong twice, misread the TRIG and ECHO pin assignments, and fried one ultrasonic transducer before finally getting a stable breadboard prototype. None of that showed up in code review. The datasheets tell you the pin voltages right there in plain text — I just didn't respect them enough on the first two attempts.

Takeaway

Actuators talk to people, not to equations. If you're building any kind of sensory feedback — acoustic, haptic, visual — your signal transfer function needs to match human perceptual biology, not raw physical measurement. Linear math feels broken to human senses. Logarithmic and gamma curves feel natural. Build for the ear, not the number line.

[ GALLERY ]