Dev.to WebDev 🛠 Dev 👁 0 📖 3 min read

Piano key number vs. MIDI note number vs. frequency: a JavaScript reference

When I built a piano reference for the browser, I found it easy to put three different numbers into one vaguely named "number" field. They describe different things: A piano key number is a position counted from the le

When I built a piano reference for the browser, I found it easy to put three different numbers into one vaguely named "number" field. They describe different things:

  • A piano key number is a position counted from the left of a particular keyboard. Here, key 1 is A0 on a standard 88-key A0–C8 piano, counting black keys too.
  • A MIDI note number identifies a pitch position in MIDI's note-number space. Middle C is 60 and A4 is 69.
  • A frequency is a value in hertz. The calculated values below assume twelve-tone equal temperament with A4 = 440 Hz; MIDI note numbers alone do not measure an instrument's output.

With the convention that middle C is labeled C4, the distinction looks like this:

Note MIDI note 88-key piano position Calculated frequency
A0 21 1 27.50 Hz
C4 (middle C) 60 40 261.63 Hz
A4 69 49 440.00 Hz
C8 108 88 4186.01 Hz

The 4 in C4 is an octave label. It is neither key 4 nor finger 4. Some software uses another octave label for the same MIDI note, so compare the numeric MIDI value and the program's stated naming convention before assuming a transposition error. The MIDI Association's discussion of octave numbering explains that note 60 is middle C while octave names vary.

Derive the mapping for this piano

For an A0–C8 88-key piano, A0 has MIDI number 21 and is physical key 1. Moving one semitone to the right increments both numbers by one. Therefore:

pianoKey = midiNote - 20   // only for this A0–C8, key-1-at-A0 convention

For an arbitrary MIDI controller, a 61-key instrument, or a keyboard whose leftmost key is not A0, use that instrument's actual starting note instead. A key count by itself does not determine the pitch range.

Under the stated A440 equal-temperament assumption, twelve equal semitone steps double frequency:

frequencyHz = a4Hz * 2 ** ((midiNote - 69) / 12)

Here is a standalone JavaScript function. It rejects values outside this piano's range and keeps the unrounded frequency for later calculations:

function pianoReference(midiNote, a4Hz = 440) {
  if (!Number.isInteger(midiNote) || midiNote < 21 || midiNote > 108) {
    throw new RangeError("Expected an integer MIDI note from 21 to 108.");
  }
  if (!Number.isFinite(a4Hz) || a4Hz <= 0) {
    throw new RangeError("A4 reference must be a positive finite number.");
  }
  return {
    midiNote,
    pianoKey: midiNote - 20,
    frequencyHz: a4Hz * 2 ** ((midiNote - 69) / 12),
  };
}

console.log(pianoReference(60));
// { midiNote: 60, pianoKey: 40, frequencyHz: 261.6255653005986 }

The function intentionally rejects "60". A form can parse text at the UI boundary; the calculation function receives a validated number. Round to two decimals only when displaying a frequency.

Check the range, not just A4

A4 checks the frequency anchor but does not check the piano-key offset. A few assertions cover both ends and middle C:

const assert = require("node:assert/strict");

assert.equal(pianoReference(21).pianoKey, 1);
assert.equal(pianoReference(60).pianoKey, 40);
assert.equal(pianoReference(69).pianoKey, 49);
assert.equal(pianoReference(108).pianoKey, 88);
assert.equal(pianoReference(60).frequencyHz.toFixed(2), "261.63");
assert.throws(() => pianoReference("60"), RangeError);

for (let midi = 21; midi <= 96; midi++) {
  const lower = pianoReference(midi).frequencyHz;
  const upper = pianoReference(midi + 12).frequencyHz;
  assert.ok(Math.abs(upper / lower - 2) < 1e-12);
}

The calculation is a reference, not a tuner. A real piano may be tuned differently, and a synthesizer's current tuning may not match the assumption. Changing a4Hz changes the calculated table; it does not retune a connected instrument.

I used these distinctions in PianoGrid's frequency reference, which shows note name, physical key number, MIDI number, and calculated frequency side by side. I created PianoGrid; the code above is a separate teaching example rather than a copy of its production implementation.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.