YIN, a 266-sample lag window, and what happens at both edges of the range.
Short answer: this converter looks at 64 ms of audio at a time and asks one question - where does this signal repeat? The answer is found with YIN, searching lags from 8 to 266 samples at 16 kHz, which is 2000 Hz down to 60.15 Hz. I tested both edges with pure tones at the default sensitivity: 55-2170 Hz came back at the right pitch, 54 Hz and below produced no notes at all, and 2180 Hz and above came back one octave low instead of being thrown away.
Everything below is either read out of the page's own source or measured by me on a replica of that source. Nothing here is a description of how pitch detection "generally" works - it is how this one works, with the numbers to check.
The converter is a single HTML file with the analysis in plain JavaScript, so every parameter is readable. These six values decide what the tool can and cannot hear:
| Constant | Value | What it does |
|---|---|---|
| Analysis rate | 16,000 Hz | Everything is resampled down to this first. Nyquist limit 8 kHz. |
| Frame length | 1024 samples | 64 ms of audio per decision. One pitch per frame. |
| Frame hop | 256 samples | 16 ms. Frames overlap by 75%, so note boundaries snap to 16 ms. |
| Lowest pitch | 60 Hz | Becomes a lag cap of 266 samples. |
| Highest pitch | 2000 Hz | Becomes a lag floor of 8 samples. |
| YIN threshold | 0.15 | The default sensitivity setting (0.85 on the slider). |
Two more values matter at the edges of the analysis: a frame is discarded as silence when its RMS is below
0.006, and a candidate note is dropped when it is shorter than 60 ms - under four
frames.
YIN is not a neural network and it is not a lookup table. For each frame it runs four steps, and the order is the whole trick:
Step 3 is the part that was arrived at the hard way. The comment above the function in the page source records that the first version used plain autocorrelation, which picks the strongest peak - and for a periodic signal the peak two periods out is often slightly stronger than the one-period peak. The result was a C major scale coming back several octaves low. Taking the first qualifying dip instead of the strongest one is what fixed it.
This is also why the tool is monophonic. There is one period per frame, so there is one note per frame. A chord does not produce three notes; it produces a period that matches none of the three.
The two limits are not musical choices. They fall out of one line of arithmetic each:
minLag = floor(16000 / 2000) = 8 samples -> 16000 / 8 = 2000.00 Hz
maxLag = floor(16000 / 60) = 266 samples -> 16000 / 266 = 60.15 Hz
Note what the ceiling is not. It is not the sample rate: at 16 kHz the audio could represent anything up to 8 kHz. It is not the frame length either, which allows lags up to 512 samples. The ceiling is simply the shortest lag the search is allowed to try. Raise the top of the pitch range and the search starts at a smaller lag; the code does not, because below about eight samples the difference function starts lining up with the waveform's own shape rather than its period.
The bottom is the mirror image. The lag cap of 266 samples is what "60 Hz" means in practice, and 16000/266 is 60.15 Hz, not 60.00. There is a second, subtler cost down there: the difference function compares only 512 samples, and at 60 Hz one period is 266 samples - so the comparison window holds under two periods. At 2000 Hz the same window holds 64 periods. The low end is working with roughly a thirty-second of the evidence the high end gets.
The resample down to 16 kHz that happens before any of this is not a neutral step, though. It is a plain linear interpolation with no anti-alias filter, so content above 8 kHz folds back down rather than being removed - and some of it lands inside the very window described above. I measured that separately in what happens to the audio before the detector runs.
I generated pure sine tones at 2 seconds each, 44.1 kHz mono, and ran them through an exact replica of the page's detector at the default settings. Each 2-second tone produces 122 frames. Here is the bottom of the range first:
| Tone in | Frames accepted | Reported as | Error |
|---|---|---|---|
| 45 Hz | 0 / 122 | nothing | - |
| 50 Hz | 0 / 122 | nothing | - |
| 54 Hz | 0 / 122 | nothing | - |
| 55 Hz | 122 / 122 | 60.0 Hz | +9.2% |
| 57 Hz | 122 / 122 | 60.0 Hz | +5.3% |
| 59 Hz | 122 / 122 | 60.0 Hz | +1.8% |
| 60 Hz | 122 / 122 | 60.0 Hz | +0.06% |
| 62 Hz | 122 / 122 | 62.0 Hz | 0.00% |
| 65 Hz | 122 / 122 | 65.0 Hz | 0.00% |
Two different failures at the bottom, not one. Everything from 54 Hz down returned nothing at all - the detector looked, found no lag below the threshold, and refused to guess. From 55 to 59 Hz it did the opposite: it answered, and the answer was pinned to the lag cap, so all five tones came back as the same note, 60.0 Hz. A 55 Hz tone reported as 60 Hz is a 9% error, which is most of a semitone and enough to land on the wrong note name.
Now the top:
| Tone in | Frames accepted | Reported as | Error | Note out |
|---|---|---|---|---|
| 1000 Hz | 122 / 122 | 1001.9 Hz | +0.19% | B5 |
| 1500 Hz | 122 / 122 | 1502.8 Hz | +0.19% | F#6 |
| 1900 Hz | 122 / 122 | 1908.7 Hz | +0.46% | A#6 |
| 2000 Hz | 122 / 122 | 2013.5 Hz | +0.67% | B6 |
| 2100 Hz | 122 / 122 | 2104.8 Hz | +0.23% | C7 |
| 2170 Hz | 122 / 122 | 2179.5 Hz | +0.44% | C#7 |
| 2180 Hz | 122 / 122 | 1090.4 Hz | -50.0% | C#6 |
| 2200 Hz | 122 / 122 | 1100.4 Hz | -50.0% | C#6 |
| 2500 Hz | 122 / 122 | 1251.0 Hz | -50.0% | D#6 |
| 3000 Hz | 122 / 122 | 1499.2 Hz | -50.0% | F#6 |
| 4000 Hz | 122 / 122 | 2008.4 Hz | -49.8% | B6 |
The headline number is the 2000 Hz ceiling, but the measurements show the interesting part is what happens past it. The tool does not go quiet. It reports half the frequency - the detector finds the second period of the wave instead of the first, and the note comes out exactly one octave low. That is the same octave failure the autocorrelation version had, arriving from the other direction.
Two details worth pulling out of that table. First, the ceiling is soft at the top: 2100 and 2170 Hz were still tracked correctly, because the parabolic interpolation can place the period slightly below the 8-sample floor. Second, the octave error does not stay an error - a 4000 Hz tone was reported as 2008 Hz, which is an octave low but lands back inside the range and gets written out as a plausible-looking note. A wrong answer that looks right is worse than no answer, and this is where it comes from.
Equal-temperament frequencies, A4 = 440 Hz, against the measured working range of 55-2170 Hz:
| Note | Hz | Example | Inside the range? |
|---|---|---|---|
| B0 | 30.87 | 5-string bass, lowest string | No - returns nothing |
| E1 | 41.20 | 4-string bass, lowest string | No - returns nothing |
| A1 | 55.00 | bass A string | Marginal - reported as ~60 Hz |
| C2 | 65.41 | cello, lowest string | Yes |
| E2 | 82.41 | guitar, lowest string | Yes |
| C4 | 261.63 | middle C | Yes |
| A4 | 440.00 | reference pitch | Yes |
| C6 | 1046.50 | soprano high C | Yes |
| C7 | 2093.00 | top octave of an 88-key piano begins here | Yes, barely |
| E7 | 2637.02 | violin, upper positions on the E string | No - comes back an octave low |
| C8 | 4186.01 | highest note on an 88-key piano | No - comes back an octave low |
The practical consequence is that the bottom of the range bites far more often than the top. A bass line played on the low E of a four-string bass sits at 41.2 Hz, below the floor, and in testing that region returned no notes whatsoever. The top of the range is a narrow band - the last octave of a piano, or a violin in high positions - and it fails in a way you can see, because the notes come out an octave lower than what you played.
What happens to those notes once they are written out - the file format, the tick grid, and what the file leaves out - is picked apart in what is inside the downloaded .mid file.
Where the analysis runs - on your device rather than a server - is covered in MP3 to MIDI without uploading your file. How much the input bitrate moves the result was measured in 128 kbps vs 320 kbps MP3 to MIDI.