MP3 to MIDI: Which Files Work, and Why the Extension Does Not Matter

26 files pushed through the real converter: 17 containers, plus the same MP3 under nine different names. The name never changed anything. The browser did.

Short answer: this page never looks at your file's name or its type. Its only check is size - above 80 MiB it turns the file away, and that is the whole gate. Everything else is decided by your browser's own decoder. I gave one 65,244-byte MP3 nine different extensions - .txt, .pdf, .mid, .mp4, .wav, .MP3, and none at all - and all nine converted to the same 8 notes. I also fed it video: an MP4, a MOV, an MKV and a WebM all converted, because the browser pulls the audio track out of the container before the page ever sees the data. Of 17 containers, four were refused: AIFF, WMA, AMR, and a video file with no audio track.

Everything below was measured on this page's own converter running in a real browser at easyaudiotomidi.com. Real files were pushed into the file input, the real Convert button was clicked, and the page's own status line and readouts were read back. The one exception is the drop zone, where I dispatched a real drop event carrying a real File object - noted again where it matters.

The page has no type check at all

This is the root of everything else, so it is worth stating precisely. There is exactly one place in the page where a file type appears, and it is an attribute on the hidden file input:

<input type="file" id="file" accept="audio/*" hidden>

The function that receives the file does not read file.type, and neither does anything else. It checks one thing:

function acceptFile(file) {
  if (!file) { return; }
  if (file.size > MAX_BYTES) { ...turn it away... return; }
  currentName = file.name;
  ... fr.readAsArrayBuffer(file);
}

From there the raw bytes go straight into the browser's decoder, with no format check in between:

p = ctx.decodeAudioData(arrayBuffer, resolve, reject);

So the sequence is: size check, then hand the bytes to the browser and see what happens. The file information line under the drop zone reflects that - it prints the name and the size in KB and nothing else, so the page never shows you a type either.

Same bytes, nine names

To test this directly I took one MP3 file (65,244 bytes) and made byte-identical copies with different names, then ran each through the converter. The right-hand column is the browser's own guess at the type, which is derived from the name - and which the decoder then ignores.

Name given to the fileType the browser reportedResult
mp3_128.mp3audio/mpegConverted 8 note(s), Range C4 - C5
mp3_as.txttext/plainidentical
mp3_as.pdfapplication/pdfidentical
mp3_as.midaudio/mididentical
mp3_as.mp4video/mp4identical
mp3_as.wavaudio/wavidentical
mp3_as_upper.MP3audio/mpegidentical
mp3_noext(empty)identical
wav_as.mp3 (WAV bytes)audio/mpegidentical

"Identical" means all of it: the same Converted 8 note(s) from 4.0 s of audio message, the same Range C4 - C5 readout, and the same decoded buffer handed to the page - 1 channel, 44,100 Hz, 176,400 samples, in every one of the nine cases. The last row is the interesting one: those are WAV bytes wearing an .mp3 name, the browser called them audio/mpeg, and they still decoded as WAV. The name did not change what the file was, and it did not change what the page did with it.

What this does not prove. These files were set into the input programmatically, which bypasses the file dialog and therefore bypasses the accept filter. So this measures the decode path, not the picker. The picker is covered separately below, where the answer turns out to be different.

Which containers decoded

All of these were generated from the same 4-second monophonic melody, so the audio content is the same throughout and only the container or codec changes. Every row was pushed into the input and converted on the live page.

Container / codecResult on the page
WAV, 16-bit PCMConverted 8 note(s), C4 - C5
WAV, 32-bit floatConverted 8 note(s), C4 - C5
WAV, 8 kHzConverted 8 note(s), C4 - C5
WAV, 6 channels (5.1)Converted 8 note(s), C4 - C5
MP3, 128 kbpsConverted 8 note(s), C4 - C5
FLACConverted 8 note(s), C4 - C5
OGG Vorbis, q5Converted 8 note(s), C4 - C5
WebM / Opus, 96 kbpsConverted 8 note(s), C4 - C5
M4A (MP4 container) / AACConverted 8 note(s), C4 - C5
Raw AAC, ADTS framingConverted 8 note(s), C4 - C5
MP4 container, audio onlyConverted 8 note(s), C4 - C5
MP4 video, H.264 + AACConverted 8 note(s), C4 - C5
MOV video, H.264 + AACConverted 8 note(s), C4 - C5
MKV video, H.264 + AACConverted 8 note(s), C4 - C5
WebM video, VP9 + OpusConverted 8 note(s), C4 - C5
AIFF, 16-bit PCMCould not decode this file.
WMA v2 (ASF container)Could not decode this file.
AMR-NB, 8 kHzCould not decode this file.
MP4 video, no audio trackCould not decode this file.

The AIFF refusal is worth pausing on, because AIFF is an uncompressed format and people reasonably expect it to work. It is the browser that will not open it, not this page - the decoder raised EncodingError: Unable to decode audio data before any pitch detection ran.

Every file arrives at 44,100 Hz

One detail fell out of the measurement that explains more than it looks like. The page resamples whatever it gets down to 16 kHz, and its own code compares the source rate against that target. But the buffer the browser hands over was reported as 44,100 Hz in every single case - including the 8 kHz WAV and the 48 kHz Opus. That is the audio context's rate on this machine, not your file's. So by the time the page's own resampler runs, the rate is already normalised. What the mixdown and resample do after that point is measured in what sample rate and channels do before detection.

The 6-channel file

The browser did not downmix the 5.1 WAV - it handed the page all six channels. The page then averaged them, and because all of the signal sat in one channel, the average came out at one sixth of the original level: 0.3322 RMS per channel became 0.0554 after averaging. The notes survived it, but the velocities did not. The same melody as a mono WAV came back with every note at velocity 100; the 6-channel version came back with the same eight notes at the same times and velocities from 68 to 93.

Video files: the browser does the demuxing

The FAQ on the converter says there is no video button and that the page does not demux video, and that is accurate - there is no video button and the page has no demuxer. But "the page does not demux" is not the same as "video will not work", because the browser's decoder does the demuxing for it.

An MP4 with H.264 video and an AAC audio track converted to 8 notes, and I ran it twice to confirm it was not a fluke. A MOV, an MKV and a WebM with VP9 video and Opus audio converted too. In each case the page received a clean 1-channel, 44,100 Hz, 4.0-second buffer - the video track was simply discarded before the page ever saw the data.

The boundary is the audio track, not the extension. An MP4 holding only video, with no audio stream at all, failed with the same cannot-decode message as the AIFF. That is the honest shape of it: if the container holds audio your browser understands, you get notes; if it holds no audio, you get an error.

Where the type does matter: the file dialog

The accept="audio/*" attribute on the input is not decoration. It is what your operating system's file dialog uses to decide which files to show you when you click the drop zone to browse. That is the only place a type check exists in the whole flow, and it is enforced by the browser and the OS, not by the page.

The drop zone itself has no filter. Dragging a file onto it goes through a different handler that receives the file directly and passes it to the same acceptFile function, with nothing in between. I tested that by dispatching a real drop event carrying a real File object:

Dropped onto the zoneResult
MP3 renamed melody.txt (declared text/plain)Converted 8 note(s), C4 - C5
MP3 renamed melody.pdf (declared application/pdf)Converted 8 note(s), C4 - C5
MP3 named melody.mp3 (control)Converted 8 note(s), C4 - C5
Real MP4 video, holiday.mp4Converted 8 note(s), C4 - C5
Real MOV video, clip.movConverted 8 note(s), C4 - C5
WMA (control for failure)Could not decode this file.

One detail from that run is the cleanest proof of the whole mechanism. While those files were being dropped, the hidden file input still held the previous file - webm_video.webm - and the information line showed the dropped name instead. The drop path never touches the input element, so the accept attribute cannot constrain it. A drop ends at the decoder, exactly like a browsed file does, which is why the WMA drop failed in the same way the WMA upload did.

The failure message will not tell you what it refused

All four refusals - AIFF, WMA, AMR and the audio-less MP4 - produced the identical text, and that text names WMA specifically:

Could not decode this file.
Your browser refused it, which usually means the format is not one it can
decode (WMA is a common case).
Detail: Unable to decode audio data

So an AIFF file is told that WMA is a common case. The message is a fixed string with the browser's own error text appended, not a diagnosis of the file you actually handed over. What the page leaves behind after that message - the previous note list, the previous .mid, and the file input still holding the refused file - is measured message by message in every failure message and what it leaves behind.

What this means in practice

Honest boundaries