Say “up” and this page scrolls up. Say “down” and it
goes down. It keeps going until you say “stop” — because speech
arrives with a delay of several hundred milliseconds, a word is a far better fit for setting a
direction than for nudging a distance. “Faster” and
“slower” change pace, “top” and
“bottom” jump to the ends. Every stage is on the right, from microphone
level to the pixels per second going into scrollTop.
01
A word is not a button
Speech recognition returns its first guess a few hundred milliseconds after you start
speaking, and revises it afterwards. Anything mapped to a word inherits that delay, so
commands here set a state that persists — a direction to keep going in
— rather than a one-shot nudge you would have to repeat.
02
Interim results are the whole game
Waiting for a final transcript costs roughly a second, most of it spent waiting for
you to stop talking. Acting on interim results cuts that to a few hundred milliseconds
— at the price of guesses that get revised under you, which is why every word is
consumed exactly once.
03
Continuous recognition is not continuous
Chrome ends the session on its own after a stretch of silence, quietly, with no error.
A voice interface that does not restart itself appears to work perfectly and then stops
responding a minute later, which is a far worse failure than never starting.
04
The meter is local, the words are not
The level bars come from an AnalyserNode running on your machine, which
is why they respond instantly. The transcript comes back from a server. Seeing the bars
move while no words appear tells you which half of the chain is struggling.
05
Every word is consumed once
Interim results arrive as a growing string — "up", then
"up down" — re-delivering earlier words each time. Counting how many
tokens of each result have already been acted on is what stops a single
“up” from firing five times as the guess is refined.
06
Stop has to be the easy word
It is the one command you may need in a hurry, so it is matched from the widest set
of synonyms, and Esc does the same thing without speaking. A control you cannot cancel
quickly is not a control.
End — say “top” to go back
MicrophoneOff
Floor —
StateIdle
Last word—
Confidence—
Latency—
Direction—
Velocity—
Progress0%
Signal / noise—
Gate—
Noise rejects0
Alternatives—
Restarts0
Heard—
Latency
Matching
Waits for the final transcript and accepts near misses
Noise gate
Suppression
Commands need real speech above the room's noise floor
How it works — the signal chain
Six stages between a word and a scrollbar
Two paths leave the microphone. One stays on this machine and drives the level meter
instantly; the other goes to a recognition service and comes back as text, several hundred
milliseconds later. Only the second one can tell “up” from “down”.
Stage 01
Capture
One getUserMedia stream, shared. The recogniser would open its own, but
then the meter could not show you whether the microphone was actually picking anything up.
out → MediaStream
Stage 02
Analyse · local
An AnalyserNode gives a running RMS of the waveform, drawn as bars. This
path never leaves the machine and never waits for anything.
out → level · 0–1
Stage 03
Recognise · remote
SpeechRecognition in continuous mode with interim results on. This is the
stage that needs a network and the stage that sets the latency floor.
out → transcript
Stage 04
Tokenise
The transcript is split into words and matched against a synonym table. Matching whole
tokens rather than searching the string keeps “up” from firing inside
“upset” or “supper”.
out → tokens[]
Stage 05
Consume once
Each result index remembers how many of its tokens have already been acted on, so
words re-delivered by later interim updates are skipped rather than re-fired.
out → command
Stage 06
Integrate
The command sets a direction; velocity eases toward direction × speed and
accumulates into scrollTop, stopping itself at either end.
out → scrollTop · px
Vocabulary
What it listens for
Each command accepts several words, because recognisers mishear short ones constantly and a
single accepted spelling makes the interface feel broken rather than approximate.
Say
Effect
up · upward, upwards, rise, higher
Scroll up until told otherwise
down · downward, downwards, lower
Scroll down until told otherwise
stop · halt, freeze, pause, wait, enough
Stop — also bound to Esc
faster · quicker, quickly, speed
Increase speed by one step
slower · slow, slowly, gentle
Decrease speed by one step
top · beginning, start, home
Glide to the very top
bottom · end, finish
Glide to the very bottom
Accuracy
The words this needs are the words recognisers are worst at
“Up” and “down” are single syllables carrying almost no acoustic
information, and the recogniser is running a language model built for dictation — one
that has never seen anyone say “up” on its own and would much rather hear
“app”, “cup” or “hop”, all of which are more probable in
ordinary English. Four things push back against that, and they stack.
Fix 01
Read every alternative
The recogniser is asked for six guesses instead of one, and all six are scanned for a
command. For short words the right answer is very often the second or third — it
simply scored lower than a longer real word. The Alternatives readout shows which
one won: a pick consistently above #1 means the top guess cannot be trusted.
was → 1 guess · now → 6
Fix 02
Wait for the final transcript
Interim results are the first thing the recogniser thinks of, and they get revised
constantly. Acting only on finals costs roughly half a second and removes most misfires.
That is the Latency switch, and it now defaults to Accurate — switch it to
Fast if you would rather have the speed back.
cost → ~500ms
Fix 03
Accept near misses
A table of substitutions actually observed in testing — app,
cup, hop for up; dawn, town,
done for down — plus edit-distance matching for longer words. Only
words of four letters or more get fuzzy treatment: at three letters a single edit reaches
half the vocabulary.
switch → Forgiving / Exact
Fix 04
Protect “stop”
“Stop” and “top” differ by one dropped consonant, and confusing
them is uniquely bad: you ask it to halt and it flings you to the top of the document. So
top is only honoured when no alternative heard stop.
The dangerous direction of the ambiguity is closed off.
tie → stop wins
There is also a plain setting worth checking before blaming any of the above: the
language. Matching an Indian or British speaker against en-US loses
accuracy on vowels for no reason at all, and “down” is mostly vowel. The page
now starts on your browser's own English variant when it has one, and the selector sits
next to the microphone button.
Noise
You cannot clean the audio the recogniser hears
This is the constraint everything here is built around. SpeechRecognition opens
its own microphone capture inside the browser and offers no way to hand it a processed
stream — there is no input to attach a filter graph to. So the usual approach, denoise
the audio and pass it along, is simply not available. What is available is three things that
work around it.
Layer 01
Ask the browser to do it
The capture is opened with noiseSuppression,
echoCancellation and autoGainControl named explicitly rather
than left to defaults. These run inside WebRTC's own processing chain, upstream of
everything, and are the only denoising that reaches the recogniser at all.
reaches → recogniser
Layer 02
Band-limit before measuring
A highpass at 85 Hz and a lowpass at 4 kHz bracket the speech band. Fan hum,
traffic and desk thumps live below that and would otherwise sit in the measured noise
floor as though they were voice — pushing the gate threshold up until quiet words
stopped registering.
reaches → meter, gate
Layer 03
Gate the commands, not the audio
The room is sampled for 1.3 s at startup and its 60th percentile becomes the noise
floor. A command is only accepted if speech rose a set margin above that floor recently
enough to have produced the transcript. Noise the recogniser hallucinated words from is
counted and dropped.
reaches → decisions
The gate consults when speech last happened rather than the level at this instant,
because a final transcript lands several hundred milliseconds after the speaker stopped
— testing the microphone at the moment a command arrives would reject every real word.
The floor also tracks slowly downward whenever the input sits below it, so a room that goes
quiet is followed automatically, while speech can never drag it up.
Reading the numbers.Signal / noise
shows how far above the floor you currently are — if speaking normally does not push it
past the gate threshold, move closer or recalibrate. Noise rejects counts commands
dropped for having no speech behind them; a number that climbs while the room is empty means
the floor was calibrated during noise, and Recalibrate room fixes it.
The failure everyone ships
Continuous recognition stops on its own
Setting continuous = true does not mean the session runs forever. Chrome ends
it after a stretch of silence and fires onend with no error at all — so a
voice interface that does not restart itself works beautifully for about a minute and then
goes deaf, with nothing on screen to say why.
Restarting naively creates the opposite failure. If the microphone is blocked or the network
is down, onend fires immediately every time, and an unconditional restart
becomes a tight loop hammering the service. So restarts here are counted and spaced: a
normal silence timeout restarts almost at once, while repeated ends inside a second back off
progressively. The Restarts readout exposes the count, because a number that climbs
steadily while you are talking is the signature of a recogniser that cannot reach its
service.
This one is not private, and the difference matters. The hand sensor ran its model
entirely on your machine — no video ever left the page. Speech recognition in Chrome
does not work that way: audio is streamed to Google's servers for transcription, which is why
it needs a network connection and why it can stop working without warning. The level meter is
local, the words are not. Nothing is recorded or stored by this page, but the audio does leave
your machine while the microphone is on.
The microphone never starts on its own. Nothing is requested until you press the
button, Esc or the corner Stop ends both the stream and the recogniser, and the
wheel, trackpad and arrow keys keep working throughout. Recognition needs Chrome or Edge
— Firefox does not implement the API, and the page says so rather than appearing broken.