This guide walks you through real-time pitch detection from two common sources:
For each stack youβll find:
MediaStream
new AudioContext()
createMediaStreamSource(stream)
ScriptProcessorNode
(broadest support) or AudioWorklet
// ES5 β 100 % legacy-safe var BUF = 2048; // analysis hop-size var AMP_GATE = 0.006; // β -45 dBFS var audioCtx, procNode, yin; (function init(){ // β import YIN polyfill var s = document.createElement('script'); s.src = 'https://cdn.jsdelivr.net/npm/pitchfinder@2.3.0/dist/pitchfinder.iife.js'; s.onload = askMic; document.head.appendChild(s); })(); function askMic(){ navigator.mediaDevices.getUserMedia({audio:true}).then(function(str){ audioCtx = new (window.AudioContext||webkitAudioContext)(); var src = audioCtx.createMediaStreamSource(str); procNode = audioCtx.createScriptProcessor(BUF,1,1); yin = Pitchfinder.YIN(); // defaults: 44100 Hz src.connect(procNode); procNode.connect(audioCtx.destination); // gain-0 hack optional procNode.onaudioprocess = onAudio; }).catch(function(e){ alert(e); }); } var buf = new Float32Array(BUF); function onAudio(ev){ var data = ev.inputBuffer.getChannelData(0); buf.set(data); if(rms(buf) < AMP_GATE) return; var hz = yin(buf); if(hz) console.log('π΅',hz.toFixed(2),'Hz'); } function rms(a){var s=0;for(var i=0;i<a.length;i++)s+=a[i]*a[i];return Math.sqrt(s/a.length);}
Parameter / Method | Description | Typical Range |
---|---|---|
BUF | Samples per analysis frame (hop-size) | 512 β 4096 |
AMP_GATE | RMS gate; ignore very quiet audio | 0.003 (β-50 dB) β 0.03 (β-30 dB) |
Pitchfinder.YIN(opts) | opts.threshold (dip), opts.probabilityThreshold (quality) | 0.1 β 0.2 / 0.8 β 0.95 |
import pyaudio, aubio, numpy as np, time BUFFER = 2048 p = pyaudio.PyAudio() stream = p.open(format=pyaudio.paFloat32, channels=1, rate=44100, input=True, frames_per_buffer=BUFFER) pitch_o = aubio.pitch("yin", BUFFER*2, BUFFER, 44100) pitch_o.set_unit("Hz"); pitch_o.set_silence(-40) print("Listening⦠Ctrl-C to stop"); time.sleep(.3) try: while True: data = np.frombuffer(stream.read(BUFFER,exception_on_overflow=False), dtype=np.float32) hz = pitch_o(data)[0] if hz > 0: print(" %.2f Hz" % hz) except KeyboardInterrupt: stream.stop_stream(); stream.close(); p.terminate()
// compile with: gcc mic_pitch.c -laubio -lportaudio -lm #include <aubio/aubio.h> #include <portaudio.h> /* init PortAudio, open stream, call aubio_pitch_do in callback */
(fast & exact)
A note-on message [0x9n, key, vel]
carries a MIDI note number (key 0 β 127
).
Convert to frequency via:
f (Hz) = 440 * 2^((key - 69) / 12)
if(navigator.requestMIDIAccess){ navigator.requestMIDIAccess().then(function(acc){ var inputs = acc.inputs.values(); for(var inp of inputs) inp.onmidimessage = function(e){ var d = e.data; // Uint8Array(3) if((d[0] & 0xf0) === 0x90 && d[2]!==0){ // note-on var note = d[1]; var hz = 440 * Math.pow(2,(note-69)/12); console.log('πΉ', note, '->', hz.toFixed(2), 'Hz'); } }; }); }
midi
or easymidi
)const midi = require('easymidi'); const input = new midi.Input('IAC Driver Bus 1'); input.on('noteon', msg => { const hz = 440 * Math.pow(2,(msg.note-69)/12); console.log(`Note ${msg.note} β ${hz.toFixed(2)} Hz`); });
mido
+ python-rtmidi
)import mido, math with mido.open_input() as port: for msg in port: if msg.type == 'note_on' and msg.velocity: hz = 440 * 2 ** ((msg.note-69)/12) print(msg.note, '->', f'{hz:.2f} Hz')
Field | Description | Value range |
---|---|---|
0x9n | Status byte (note-on, channel n) | 0x90 β 0x9F |
key | MIDI note number | 0 β 127 |
velocity | Loudness; 0 = note-off shortcut | 0 β 127 |
AMP_THRESHOLD
to 4 Γ ambient RMS measured while idle.SMOOTH_ALPHA β 0.25
) to tame jitter.localhost
in Chromium; Web MIDI requires a user gesture in Chrome β₯ 94.GetSpectrumData
, or embed aubio C library for < 10 ms latency.The frame failed its periodicity probability test. Lower probabilityThreshold
or pad with zeros for FFT methods.
Use Constant-Q Transform or Pitch Class Profile (lib: meyda
JS, librosa
Python) then pattern-match triads.
Safari (2025-05) still flags Web MIDI behind Develop β Experimental Features β MIDI β System Exclusive. Enable and reload.