The Copy button hands you six columns of plain text. I copied it out of the live converter eight times and took the text apart byte by byte.
Short answer: Copy note list produces plain text - a header line and
then one line per note, six columns separated by exactly two spaces. The times are the
detector's own seconds to three decimals, not the ticks stored in the
.mid file; to get a tick you need round(seconds x BPM / 60 x 480). Note names use
sharps only, and MIDI 60 is written C4, so a black key is
C#4 and never Db4. There is no trailing newline, and nothing is padded into
columns, so the text is not aligned when you look at it.
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 and Copy
buttons were clicked, and the text the page itself built was captured and saved to disk for byte-level
analysis. Where a number comes from somewhere else, it says so.
This is the complete output for an 8-note scale, with every control left at its default. Nothing is trimmed or reordered:
# Start(s) End(s) Dur(s) Vel Note
1 0.000 0.432 0.432 100 C4
2 0.480 0.944 0.464 100 D4
3 0.976 1.440 0.464 100 E4
4 1.488 1.936 0.448 100 F4
5 1.984 2.448 0.464 100 G4
6 2.480 2.944 0.464 100 A4
7 2.976 3.440 0.464 100 B4
8 3.488 3.936 0.448 100 C5
That is 294 bytes across 9 lines, and it is the whole clipboard payload. The page's own
status line confirms the size of what it copied: Copied 8 note(s) as text.
The whole function is short enough to read in full. This is it, unchanged:
function noteListText(notes, transpose) {
var lines = ['# Start(s) End(s) Dur(s) Vel Note'];
for (var i = 0; i < notes.length; i++) {
var n = notes[i];
lines.push(
(i + 1) + ' ' +
n.start.toFixed(3) + ' ' +
n.end.toFixed(3) + ' ' +
(n.end - n.start).toFixed(3) + ' ' +
n.vel + ' ' +
noteName(n.note + transpose)
);
}
return lines.join('\n');
}
| Column | What it is | Source |
|---|---|---|
# | Row number, starting at 1 | the loop counter |
Start(s) | Note start, in seconds, 3 decimals | the detector's own measurement |
End(s) | Note end, in seconds, 3 decimals | the detector's own measurement |
Dur(s) | End minus Start, computed in the page, 3 decimals | arithmetic on the two columns to its left |
Vel | The velocity the page decided, a whole number | derived from the note's RMS |
Note | The note name after Transpose | a name table plus the Transpose box |
Three consequences follow straight from that code, and all three are visible in the output above:
End - Start rounded to three decimals, so it can never disagree with its neighbours.Because the text is meant to be pasted into something else, the details matter more than they look. I saved the captured text to disk as raw bytes and measured it, and every one of the eight runs agreed:
| Property | Measured |
|---|---|
| Column separator | Two spaces, everywhere. No tabs, no commas, no semicolons. Scanning the whole text for whitespace runs found exactly one kind: ' '. |
| Line ending | A single LF. Zero carriage returns in any file. |
| Trailing newline | None. The byte count equals the sum of the line lengths plus one LF per gap - nothing is left over at the end. |
| Column padding | None. Values are not right-aligned or padded, so the columns do not line up. |
| Non-ASCII characters | Zero. The text is pure ASCII, including the # in sharp note names. |
The padding point is easy to see if you look at where each column starts. In the same file, the header
puts its second column at character 11, a row with a one-digit number puts it at 8, and a row with a
two-digit number puts it at 9. Nothing is being aligned - the separator is just two spaces and the field
widths float. The same is true of the Vel column: rows with a two-digit velocity and rows
with a three-digit velocity shift the Note column by one character.
Measured on four files, from a 3-note phrase to a 100-note passage:
| Notes detected | Bytes | Lines | Bytes per note line |
|---|---|---|---|
| 3 | 134 | 4 | 31.0 |
| 8 | 294 | 9 | 31.0 |
| 12 | 429 | 13 | 31.6 |
| 100 | 3,480 | 101 | 33.4 |
The header line is 38 bytes. Each note line then costs about 31 bytes, which holds while the row number is one or two digits; at 100 rows the row number itself takes three characters and the average rises to 33.4. So a few hundred notes is a few tens of kilobytes - small enough to paste anywhere.
The naming is done by one small function, and it makes two decisions worth knowing about:
var NOTE_NAMES = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
function noteName(m) {
if (m < 0 || m > 127) { return '?'; }
return NOTE_NAMES[m % 12] + (Math.floor(m / 12) - 1);
}
First, only sharps exist in this table. There is no flat name anywhere in it, so a black
key above C is always C# and never Db. If your DAW shows you flats, the note is
the same note - it is only spelled differently.
Second, the octave numbering puts MIDI 60 at C4. That is the convention where middle C is C4. Plenty of software calls the same note C3, and some call it C5, so a note name copied out of this page can look like it is an octave off when you compare it with a DAW. The number is unambiguous even when the name is not. Here is the mapping at the boundaries, generated by running this page's own function over the whole MIDI range:
| MIDI number | Name this page writes |
|---|---|
| 0 | C-1 |
| 12 | C0 |
| 24 | C1 |
| 36 | C2 |
| 48 | C3 |
| 60 | C4 |
| 69 | A4 |
| 72 | C5 |
| 127 | G9 |
| -1 or 128 | ? |
The last row is the interesting one. If a number falls outside 0 to 127 the function returns a single question mark instead of a name, and that is what you see in the list when Transpose pushes a note off the end of the range. In the same situation the .mid file still gets a number - it clamps to 0 or 127 - so the list and the file describe that note differently on purpose. That behaviour, and how Transpose interacts with the rest of the controls, is measured in what each control actually changes.
To check that the names really are just a rendering of the same numbers, I compared the
Note column against the note numbers inside the .mid the page wrote in the same run, using
this page's own naming function as the translator. Every row matched - 35 rows across four files, from
C4 at 60 up to C#5 at 73.
This is the part that trips people up when they try to reconcile the text with the file. The list shows seconds. The .mid file stores ticks. The conversion the page uses when it writes the file is:
ticksPerSec = (bpm / 60) * 480
tick = round(seconds * ticksPerSec)
So the same seconds map to different ticks depending on the Tempo box. I verified it against the actual files rather than trusting the formula: for every note start in four runs I read the tick out of the .mid with two independent parsers and compared it with the arithmetic. All 47 starts matched.
| Start (s) from the list | BPM 60, tick in file | BPM 120, tick in file | BPM 300, tick in file |
|---|---|---|---|
| 0.000 | 0 | 0 | 0 |
| 0.992 | 476 | 952 | 2,381 |
| 7.984 | 3,832 | 7,665 | 19,162 |
| 10.992 | 5,276 | 10,552 | 26,381 |
One clean consequence: the Tempo box never changes the copied text. I converted the same 12-note file at BPM 60, 120 and 300 and the three captured note lists were not merely equal in content - they had the same MD5 hash and the same 429 bytes. Tempo only ever reached the .mid file. What the file itself contains, byte by byte, is broken down in what MIDI file you actually get.
There is one case where the text and the file genuinely describe different music, and it is worth knowing before you rely on the list as a record of what you downloaded.
The list is rebuilt from scratch every time you click Copy, and it reads the Transpose box at that moment. The .mid file is only built during conversion. So if you convert, then change Transpose, then copy and download without converting again, you get two different answers:
| Step | Copied note list | Downloaded .mid |
|---|---|---|
| Converted with Transpose 0 | C4 E4 G4 | 60 bytes, notes 60 / 64 / 67 |
| Transpose changed to +12, not converted again | C5 E5 G5 | 60 bytes, byte-identical, still notes 60 / 64 / 67 |
The two files had the same MD5 hash. The copied text named notes that the file does not contain. So if the list is your record of a conversion, copy it and download the file at the same moment, and do not touch a control in between - or press Convert again first. The same "nothing re-runs until you press Convert" behaviour is behind several other surprises, and the file-versus-text split is the same reason the piano roll does not move when you change Transpose either.
That split runs through the whole results panel, not just the list. The readouts and the piano roll are frozen at the conversion too, and Download .mid never reads a control at all, while the status line gets rewritten by all three buttons - so the page can end on a green "saved" message for settings it is no longer using. Measured control by control in which parts of the page read the settings, and when.
The two-space separator is friendly to your eyes and slightly awkward for a parser. If you split each line on a single space character, every row breaks into 11 fields, five of them empty:
'1 0.000 0.976 0.976 49 C#4'.split(' ')
-> ['1', '', '0.000', '', '0.976', '', '0.976', '', '49', '', 'C#4']
Split on runs of whitespace instead - a regular expression like \s+, or the
"treat consecutive delimiters as one" option in a text-to-columns dialog - and you get exactly
6 fields, which is what the columns are:
'1 0.000 0.976 0.976 49 C#4'.split(/\s+/)
-> ['1', '0.000', '0.976', '0.976', '49', 'C#4']
Two more things to watch for. The first column of the header is a literal #, so a tool that
treats # as a comment marker will drop the header - or worse, a sharp note name. And the
time columns carry a unit in their header (Start(s)) but the values themselves are plain
numbers, so a spreadsheet will read them as numbers without any cleanup.
There is one case where the button is completely silent: if the conversion found no notes at all, the handler returns before it builds any text, so nothing is written and no message appears. An empty result is not the same as a failure - the .mid download still writes a 33-byte file with no notes in it. The full set of failure states is measured message by message in what the page leaves behind when a conversion does not work.
The other case is the clipboard itself. If the browser refuses the write, the page says so in plain words:
Copy failed - your browser blocked clipboard access.
I saw both outcomes while measuring. Clicking the button with a real mouse event resolved normally and
the status line read Copied 12 note(s) as text. Triggering the same button from a script
instead - which is not a user gesture - was refused by the browser and produced the blocked message
above. So the failure is about how the click reached the page, not about your audio.
Notes found readout says - they always agreed in every run.navigator.clipboard.writeText. I could not read the
operating system clipboard back afterwards - reading it is refused in the automation context I was
running in - so I verified the copy from the page's side, not by pasting somewhere else.