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.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.