Overview

This guide walks you through real-time pitch detection from two common sources:

  1. πŸ–Š Microphone / Line-in audio via the Web Audio API (JavaScript), PyAudio + aubio (Python), and PortAudio + aubio (C / C++)
  2. 🎹 MIDI note messages – receiving events and translating MIDI note numbers to frequencies

For each stack you’ll find:

1 Β· Detecting Pitch from a Microphone (JavaScript / Web Audio)

1.1 Signal Chain

  1. getUserMedia({audio:true}) ➝ MediaStream
  2. new AudioContext()
  3. createMediaStreamSource(stream)
  4. ScriptProcessorNode (broadest support) or AudioWorklet
  5. Frame β†’ Float32Array ➝ Pitch algorithm ➝ frequency (Hz)

1.2 Core Code (ES5-friendly)

// 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);}

1.3 Important Parameters

Parameter / MethodDescriptionTypical Range
BUFSamples per analysis frame (hop-size)512 – 4096
AMP_GATERMS gate; ignore very quiet audio0.003 (β‰ˆ-50 dB) – 0.03 (β‰ˆ-30 dB)
Pitchfinder.YIN(opts)opts.threshold (dip), opts.probabilityThreshold (quality)0.1 – 0.2 / 0.8 – 0.95

1.4 Alternative Algorithms

1.5 Python 3 Example (PyAudio + aubio)

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()

1.6 C / C++ Skeleton (PortAudio + aubio-C)

// 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 */

2 Β· Reading Pitch from MIDI Data

(fast & exact)

2.1 MIDI Basics

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)

2.2 Web MIDI API (ES5)

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');
        }
      };
  });
}

2.3 Node / Electron ( 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`);
});

2.4 Python 3 (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')

2.5 Parameter Reference

FieldDescriptionValue range
0x9nStatus byte (note-on, channel n)0x90 – 0x9F
keyMIDI note number0 – 127
velocityLoudness; 0 = note-off shortcut0 – 127

3 Β· Latency & Accuracy Tips

4 Β· FAQ

Q 1 Β· Why does YIN sometimes return 0 or NaN?

The frame failed its periodicity probability test. Lower probabilityThreshold or pad with zeros for FFT methods.

Q 2 Β· How can I detect chords?

Use Constant-Q Transform or Pitch Class Profile (lib: meyda JS, librosa Python) then pattern-match triads.

Q 3 Β· Why is Web MIDI silent in Safari?

Safari (2025-05) still flags Web MIDI behind Develop β†’ Experimental Features β†’ MIDI – System Exclusive. Enable and reload.