開發歷程:把麥克風變成四種工具——調音器、分貝計、節拍器、變聲器

這是 LocalPapa Notes 開發紀錄系列的第十篇。前面寫過手機的「動作感測器」;這一篇換一個感測器——麥克風。同一條 Web Audio 管線,我們做出了四個工具:調音器分貝計節拍器變聲器

它們共用同一個前提:麥克風也是一種感測器,而瀏覽器能直接讀它——全程本地,不錄音、不上傳

一條共用的音訊管線

四個工具的起點幾乎一樣:getUserMedia({audio}) 拿到麥克風,接上 AudioContext

有個反直覺的共通細節:我們刻意關掉 echoCancellation、noiseSuppression 與 AGC(自動增益)。這些功能對通話很好,但對量測是災難——它們會「美化」訊號,破壞真實的音高與能量。要做調音器和分貝計,你要的是未經修飾的原始波形

調音器:用「自相關」聽出音高

調音器要回答的問題是:「你現在這個音,是哪個音、準不準?」

它用 ACF2+ 自相關(autocorrelation) 演算法偵測基頻:拿時域波形,先用 RMS 設靜音門檻(太小聲就不判),裁去首尾雜訊穩定訊號,算出自相關函數後找出週期峰值 T0,再用拋物線插值細化,頻率 = 取樣率 / T0。這是業界對單音偵測穩定常用的做法。

有了頻率,換算成音名與音分就是數學:midi = round(12·log2(freq/A4)+69),偏差 cents = 1200·log2(freq/最近音頻率)。指針再用加權平滑(smooth·0.7 + cents·0.3)避免亂跳,誤差 <5 音分就轉綠判定為準。A4 基準還能調(415–466Hz),給古樂與不同樂團用。

分貝計:誠實面對「測不準」

分貝計沿用同一條麥克風管線,用 RMS 求能量、dB = 20·log10(rms) + 校正值,畫出 min/avg/max 與滾動歷史曲線。

但這裡有個誠實問題:瀏覽器拿不到手機麥克風的硬體校正參數。也就是說,我們無法給出真正準確的絕對分貝

很多 App 會假裝自己很準。我們選擇相反:在 UI 和 FAQ 明確標示這是「未校正的相對估計」,並提供一個校正滑桿讓你對齊已知參考值。能做的就做好,做不到的就老實說——這比給一個看似專業、實則誤導的數字更負責任。

節拍器:為什麼不能用 setInterval

節拍器看起來最簡單,卻藏著最深的一課。

直覺會用 setInterval 每隔一段時間「叮」一聲。但這會飄拍——JavaScript 主執行緒一卡頓、分頁一被節流,計時就跑掉了。對節拍器來說,這是硬傷。

正解是 Chris Wilson 的「A Tale of Two Clocks」做法:用 setTimeout 每 25ms 低頻喚醒一個排程器,把所有落在「現在 + 前瞻 0.1 秒」窗口內的拍子,用 osc.start(精確時間) 預先排程在音訊硬體時鐘上。聲音何時響由音訊硬體決定,完全不受 JS 卡頓影響。視覺節拍燈也對齊同一個音訊時鐘(而非各自計時),所以燈和聲音永遠同步。

這是整個系列我最喜歡的一課:把計時交給對的時鐘。 該由音訊硬體負責的事,別交給 JavaScript。

變聲器:瀏覽器沒有的「即時變調」

變聲器最有趣,因為瀏覽器沒有原生的即時 pitch shift。

我們用了另一個 Chris Wilson 的演算法 Jungle:用兩條 DelayNode,各自以「鋸齒狀變化的延遲時間」調變,再用「等功率交叉淡化」在兩線之間平滑切換——就能在沒有音檔、低延遲的前提下即時升降調。

更好玩的是把效果串成一條可組合的鏈:變調 → 濾波器 → 環形調變(做機器人聲)→ 回音 → 監聽+錄音。環形調變這段用了 AudioParam 可累加的特性:正常時直通,機器人模式時才真正乘上載波。預設音效一鍵套用所有參數,並用 setTargetAtTime 平滑切換避免爆音。錄音則用 MediaRecorder 接一條獨立的輸出,錄完可試聽下載。

一個容易被忽略的共通點:把麥克風還回去

四個工具都會用到麥克風,也都做了同一件容易被忽略的事——收尾。停止時、以及頁面 pagehide 時,一律停掉 MediaStream 的音軌、close() 掉 AudioContext。

不然麥克風會一直被佔用,那顆「錄音中」的小紅點不會消失,使用者會嚇到。借了硬體,就要還乾淨。

學到的事

  • 麥克風是一個被低估的感測器。 跟動作感測器一樣,它是瀏覽器能直接觸及、AI 對話卻碰不到的硬體入口。
  • 量測類工具要關掉「美化」。 echoCancellation/AGC 對通話好,對量測是雜訊。
  • 把計時交給對的時鐘。 節拍器的穩定,靠的是音訊硬體時鐘,不是 setInterval
  • 誠實標示精度,比假裝準確更專業。 分貝計的「未校正相對估計」就是這個原則。
  • 借了硬體要還乾淨。 釋放音軌、關閉 context,是音訊工具的基本禮貌。

一以貫之的問題

從動作感測器到麥克風,這條線把系列的問題推得更廣:瀏覽器能直接讀的硬體訊號,能不能就地變成有用、好玩、又誠實的工具?

調音、測噪、打拍、變聲——一個麥克風,四個答案,全是:可以。

下一篇開發紀錄,我們會走進那條醞釀已久的長線——把整個網站變成 AI agent 讀得懂的知識包(OKF)。想追蹤系列?把 LocalPapa Notes 加入書籤吧。

Dev Log: Turning the Microphone into Four Tools — Tuner, Decibel Meter, Metronome, Voice Changer

This is the tenth post in the LocalPapa Notes dev-log series. Earlier we covered the phone's "motion sensors"; this time we switch sensors — to the microphone. From one Web Audio pipeline we built four tools: a tuner, a decibel meter, a metronome and a voice changer.

They share one premise: the microphone is a sensor too, and the browser can read it directly — entirely locally, no recording, no upload.

One shared audio pipeline

All four tools start almost identically: getUserMedia({audio}) for the mic, connected to an AudioContext.

There's a counter-intuitive shared detail: we deliberately turn off echoCancellation, noiseSuppression and AGC (automatic gain control). Those are great for calls but disastrous for measurement — they "beautify" the signal, destroying the true pitch and energy. For a tuner and a decibel meter, you want the unprocessed raw waveform.

Tuner: hearing pitch via autocorrelation

The tuner answers: "the note you're playing — which note is it, and is it in tune?"

It detects the fundamental frequency with the ACF2+ autocorrelation algorithm: take the time-domain waveform, set a silence gate via RMS (too quiet → don't judge), trim leading/trailing noise to stabilise the signal, compute the autocorrelation function, find the periodic peak T0, then refine with parabolic interpolation: frequency = sample rate / T0. It's a standard, stable approach for monophonic detection.

With the frequency, note name and cents are just maths: midi = round(12·log2(freq/A4)+69), deviation cents = 1200·log2(freq/nearest-note freq). The needle is weighted-smoothed (smooth·0.7 + cents·0.3) to stop it jittering, and turns green within 5 cents. The A4 reference is adjustable (415–466Hz) for early music and different ensembles.

Decibel meter: honest about "you can't measure it precisely"

The decibel meter reuses the same mic pipeline, computes energy via RMS, dB = 20·log10(rms) + calibration, and draws min/avg/max plus a rolling history curve.

But there's an honesty problem: the browser can't get the phone microphone's hardware calibration. Which means we can't give a truly accurate absolute decibel reading.

Many apps pretend they're precise. We chose the opposite: the UI and FAQ clearly label this as an "uncalibrated relative estimate," with a calibration slider to align against a known reference. Do well what you can; be honest about what you can't — that's more responsible than a number that looks professional but misleads.

Metronome: why you can't use setInterval

The metronome looks simplest but hides the deepest lesson.

The instinct is setInterval to go "tick" at intervals. But that drifts — the moment the JavaScript main thread stutters or the tab gets throttled, timing slips. For a metronome, that's fatal.

The correct approach is Chris Wilson's "A Tale of Two Clocks": use setTimeout to wake a scheduler at a low frequency (every 25ms), and schedule every beat falling within a "now + 0.1s lookahead" window via osc.start(precise time) on the audio hardware clock. When a sound fires is decided by the audio hardware, completely immune to JS stutter. The visual beat lights align to that same audio clock (rather than timing themselves), so lights and sound stay forever in sync.

This is my favourite lesson in the whole series: hand timing to the right clock. What the audio hardware should own, don't give to JavaScript.

Voice changer: the "live pitch shift" the browser lacks

The voice changer is the most fun, because the browser has no native real-time pitch shift.

We used another Chris Wilson algorithm, Jungle: two DelayNodes, each modulated with a "sawtooth-varying delay time," cross-faded with "equal-power crossfade" to switch smoothly between the two — achieving live pitch up/down with no audio file and low latency.

The fun part is chaining effects into a composable graph: pitch shift → filter → ring modulation (robot voice) → echo → monitor + recording. The ring-modulation stage uses AudioParam's additive nature: pass-through normally, only truly multiplying by a carrier in robot mode. Preset effects apply all parameters in one click, switched smoothly via setTargetAtTime to avoid pops. Recording taps a separate output through MediaRecorder, playable and downloadable when done.

An easily-missed shared point: give the microphone back

All four tools use the mic, and all do the same easily-missed thing — teardown. On stop, and on page pagehide, they stop the MediaStream's audio tracks and close() the AudioContext.

Otherwise the mic stays occupied, that little red "recording" dot won't disappear, and users get spooked. Borrow the hardware, return it clean.

What we learned

  • The microphone is an underrated sensor. Like motion sensors, it's a hardware entry point the browser can reach directly but an AI chat can't.
  • Measurement tools must turn off "beautification." echoCancellation/AGC are good for calls, noise for measurement.
  • Hand timing to the right clock. The metronome's stability comes from the audio hardware clock, not setInterval.
  • Honestly labelling precision is more professional than faking accuracy. The decibel meter's "uncalibrated relative estimate" is that principle.
  • Return borrowed hardware clean. Releasing tracks and closing the context is basic courtesy for audio tools.

The same question, broadened

From motion sensors to the microphone, this line broadens the series' question: the hardware signals a browser can read directly — can they become useful, fun and honest tools, right there?

Tuning, noise metering, click tracks, voice changing — one microphone, four answers, all: yes.

In the next dev log, we step into a long-brewing thread — turning the whole site into a knowledge pack AI agents can read (OKF). Want to follow the series? Bookmark LocalPapa Notes.

探索 39+ 個隱私優先的瀏覽器工具
全程本地運算、檔案不上傳。
前往 LocalPapa →
Explore 39+ privacy-first browser tools
Everything runs locally — your files never leave your device.
Visit LocalPapa →

想看英文版?點右上角 EN 切換語言。

Prefer Chinese? Tap at the top-right to switch.