Audio to MIDI on a Phone: What the Result Actually Looks Like

The preview is one fixed 900 by 150 bitmap that CSS stretches, so it does not reflow on a small screen - it squashes. I converted the same file in six viewports and measured what each one gets.

Short answer: the conversion is not smaller on a phone - the picture is. The piano roll is a fixed 900 by 150 pixel bitmap, and CSS only stretches it to fit. On screens 720 pixels wide and narrower the CSS height is also cut from 150 to 120, so the image is squeezed 0.8 vertically and about 0.29 to 0.39 horizontally - two different ratios, which visibly flattens the drawing. The note labels are drawn at 10 bitmap pixels, which lands at 2.91 CSS pixels on a 320-pixel viewport, 3.52 at 375, and 10.16 on a desktop. The timeline shrinks from 251 CSS px per second down to 72. And yet the downloaded .mid is 105 bytes with the same MD5 in all six viewports, and the copied note list is character-for-character identical. Convert on the phone if that is where the audio is; do not read the result off the phone.

Everything below is measured on the live page in a real browser, or read out of its own source. Where a number comes from a rendered pixel rather than from the source, I say which.

Why the preview does not reflow

The element is declared with a fixed pixel size:

<canvas id="roll" width="900" height="150"></canvas>

Those two numbers are the bitmap. They are what drawRoll() sees when it computes where to put things - it reads roll.width and roll.height, not the element's on-screen size:

function drawRoll(notes) {
  var ctx = roll.getContext('2d');
  var W = roll.width, H = roll.height;   // always 900 x 150
  ...
}

Then CSS separates those numbers from what the screen shows:

canvas {
  width: 100%; height: 150px; display: block;
}

@media (max-width: 720px) {
  canvas { height: 120px; }
}

So there are three different sizes at once, and only one of them was chosen by the drawing code:

SizeWho sets itOn a 320-pixel viewport
Bitmapwidth="900" height="150" in the markup900 x 150
Layout (CSS pixels)width:100%, height:120px262 x 120
Physical (device pixels)The screen's device pixel ratio524 x 240

The drawing is always done at 900 by 150 and then handed to the browser as a picture to be scaled. Nothing is recomputed for the smaller size - the bar positions, the label positions, the line positions are all fixed in bitmap coordinates. A narrower window rescales the whole picture, it does not re-lay it out.

That distinction is the whole article. If the canvas had been redrawn at the new size, a phone would get a narrower but still proportioned view of the same melody. What actually happens is that one drawing is squeezed into a smaller rectangle, and everything in it shrinks together.

The breakpoint compresses the two axes by different amounts

The @media rule changes the height to 120 but leaves the width at 100%. So at 720 pixels and below, the picture is squeezed vertically by 0.8 no matter how wide the screen is, while the horizontal squeeze depends entirely on the viewport. The two ratios stop matching, and the drawing is distorted rather than merely shrunk.

It happens on one specific pixel. I converted the same file at a series of viewport widths:

ViewportCanvas CSSVertical scale Horizontal scaleDistortion (x/y)
1440914 x 1501.0001.0161.016
981900 x 1501.0001.0001.000
760679 x 1501.0000.7540.754
721640 x 1501.0000.7110.711
720647 x 1200.8000.7190.899
717644 x 1200.8000.7160.895
500427 x 1200.8000.4740.593
375302 x 1200.8000.3360.419

At 721 pixels the canvas is 640 by 150 and the distortion is 0.71. One pixel narrower, at 720, it becomes 647 by 120 - wider and shorter at the same time - and the distortion drops to 0.90. Fourteen pixels of viewport change nothing about the audio and everything about the shape of the picture.

Down at the usual phone widths the distortion gets severe. At a 320-pixel viewport the horizontal squeeze is 0.291 and the vertical is 0.800 - a ratio of 0.364. Anything drawn as a square in bitmap coordinates is drawn on screen at roughly 36 percent of its proper width relative to its height. The bars themselves are rectangles, so they just look flatter; a round cap or a diagonal would lean. The picture is not a scaled-down copy of the desktop view. It is a different shape.

Practical consequence: do not compare a phone screenshot with a desktop screenshot and conclude anything about the music. They are two different distortions of the same drawing.

The note labels go under 4 CSS pixels

Labels are set with one line in the drawing code:

ctx.font = '10px sans-serif';
ctx.fillText('C' + (Math.floor(m / 12) - 1), x0 - 4, yy + bh / 2 + 3);

Ten pixels, in bitmap units. After the browser scales the bitmap, the on-screen size is 10 x (CSS width / 900). I measured the CSS width in six viewports and converted:

ViewportCanvas CSS widthLabel, CSS px Label, device px
Desktop 144091410.1610.16
Tablet 7687027.8015.60
Phone 4123543.9310.33
Phone 3933353.7211.17
Phone 3753173.527.04
Phone 3202622.915.82

The body text on that same page is 14.5px at all six widths, and the readout numbers are 18px. So the labels inside the picture end up between a quarter and a fifth the size of the text around them. On the 320-pixel case they are 2.91 CSS pixels: the stem of a capital C, the two rounded ends, the small crossbar. At that size what reaches the screen is a grey smudge, not a note name.

The device pixel column is worth a second look, because it looks reassuring and is not. The 393-pixel phone reports 11.17 device pixels for a label, which is more device pixels than the desktop's 10.16 - because the screen is denser. But device pixels are not what you read with. Apparent size is what you read with: the label is 3.72 CSS pixels of visual angle, and packing eleven physical pixels into that does not make it legible, it only makes the smudge sharper.

The two labels that would anchor the picture are the ones at the top and bottom of the pitch range. In the 375-pixel conversion of my file the top label was a dark smudge rather than a readable C5, and the bottom label was clipped by the canvas edge anyway - that clipping is a separate quirk of the drawing code, and it happens at every screen size. Everything between them has no label at all, because the code only labels Cs. So on a phone the picture has no usable scale marker.

The timeline shrinks by a factor of three and a half

Because the horizontal scale of the drawing is also fixed in bitmap coordinates, every second of audio occupies the same fraction of the bitmap on every device - and therefore a wildly different number of CSS pixels once the bitmap is scaled.

The plot area inside the canvas runs from x = 34 to x = 896, 862 bitmap pixels. In the file I used, the last note ends at tMax = 3.488 s, so the scale is 862 / 3.488 bitmap pixels per second, scaled by the CSS ratio:

ViewportPlot width, CSS pxPer second, CSS px Gap between consecutive notes, CSS px
Desktop 1440875.4251.0120.4
Tablet 768672.4192.892.5
Phone 412339.197.246.7
Phone 393320.992.044.2
Phone 375303.687.041.8
Phone 320250.971.934.5

On the 320-pixel viewport a whole second of audio is drawn 71.9 CSS pixels wide instead of 251.0 - a factor of 3.49. The eight notes I converted are separated by 34.5 CSS pixels instead of 120.4. Both numbers are still visible; what is gone is the room to tell whether a specific note started a little early or a little late.

Note that CSS pixels here, not device pixels, are the right unit for judging this. A phone packs roughly two to three device pixels into each CSS pixel, so the line is physically sharp. It is small. Sharp and small.

Bar height loses a fifth, the gap between bars loses half a pixel

Bars are sized from the pitch range of the result. In my test the eight notes span C4 to C5, twelve semitones, and the code computes:

var bh = Math.max(3, (y1 - y0) / (hi - lo + 1));   // (146) / 13 = 10.92
ctx.fillRect(bx, by, bw, Math.max(2, bh - 1));     // 9.92 tall, filling 10.92 of pitch

So each bar is 9.92 bitmap pixels tall and each semitone gets 10.92, leaving exactly 1.000 bitmap pixel of white between bars that are one semitone apart in pitch. Scale that:

ViewportBar height, CSS pxSemitone step, CSS px Gap between neighbouring bars
Desktop 1440 / Tablet 7689.9210.921.00 CSS px
Phone 393 / 412 / 375 / 3207.948.740.80 CSS px

The vertical squeeze costs each bar a fifth of its height. The white line between two neighbouring semitones drops from 1.00 to 0.80 CSS pixels. The vertical scale does not divide evenly into device pixels either - at a device pixel ratio of 2 it lands at 1.6 device pixels, below what the sampling grid resolves cleanly - so the edge between two neighbouring blue bars comes out blended rather than crisp. On the rendered 375-pixel screenshot you can see the boundary between consecutive bars as a soft edge.

In the file I used, every consecutive pair of notes is also a semitone apart, so this is the worst case rather than the common one - a melody with a gap of several semitones between consecutive notes keeps clean space. But for a chromatic run, which is exactly where you would most want to see the notes separately, the separators are the first thing to go.

What stays exactly the same

This is the part that matters more than the appearance. I ran the same 4-second file through the same controls in all six viewports and captured what the page itself produced:

Desktop 1440Tablet 768Phone 320
Notes found888
Pitch range readoutC4 - C5C4 - C5C4 - C5
Copied note listidentical, first line 1 0.000 0.432 0.432 100 C4
Downloaded .mid105 bytes105 bytes105 bytes
.mid MD54c69bd4e64dc5eb28caaebec7641aa24
Non-white pixels in the bitmap10,192 of 135,000, hash 1932024484

Every one of those is the same at 320 pixels as at 1440. The bitmap itself - the 900 by 150 thing the code drew - is pixel-identical in all six runs: same count of non-white pixels, same hash. The screen size does not enter the conversion at any point. It only changes the glass you look at the result through.

I did not take that on trust either. The page reports its own numbers and it is the same page in every run, so a shared bug would produce the same wrong answer everywhere and look like consistency. What settles it is the .mid: 105 bytes, and one MD5 across all six viewports, read off the file the page handed to the browser for download.

That is worth stating plainly because it removes the question most people actually have. There is no mobile version of the conversion. Same detector, same resampling to 16000 Hz, same minMs filter, same output. The phone is not a degraded mode.

What the four readouts do on a phone

Above the canvas there are four numbers - notes found, duration, pitch range, analysis time - and they are the part of the result card that adapts properly. At and above 768 pixels they sit on one row. At 393 pixels and below they wrap onto two rows.

ViewportReadouts onResult card height Canvas top, from page top
14401 row296 px595 px
7681 row296 px595 px
4122 rows380 px807 px
3932 rows380 px807 px
3752 rows380 px854 px
3202 rows380 px945 px

The text stays 14.5px and the readout numbers stay 18px at every width - I read the computed styles. So on a phone the numbers are as legible as anywhere else on the page, and they wrap instead of shrinking. The roll is the only thing that shrinks.

The last column is the part that decides whether you ever see the result. At 375 and 320 pixels the canvas sits below the first screenful - 854 and 945 pixels down a 667- and 640-pixel viewport. Converting does not scroll the page, so on a small phone you press Convert, the status line updates, and the picture is somewhere below the fold. You have to scroll to it. On a 393- or 412-pixel phone it just fits inside the first screen. That is a one-line difference in layout behaviour that changes the experience more than any of the scaling, and it comes from the drop area growing taller as the buttons wrap.

Nothing overflows, and the buttons grow rather than shrink

The good news, and it is worth checking rather than assuming: no viewport in my set scrolls sideways. Document scroll width equalled client width in all six. The layout does not break; the picture inside it loses detail.

The controls are the opposite of the canvas. Under 720 pixels the buttons take the full card width:

ControlDesktop 1440Phone 320
Convert134 x 42262 x 42
Download .mid128 x 42262 x 42
Copy note list117 x 42262 x 42
Drop area914 x 105262 x 149

Height is a constant 42 pixels everywhere. That is slightly under the 44-pixel minimum that mobile accessibility guidance commonly cites - it is 42 as measured, and it does not change with viewport width. I am reporting the number, not calling it a problem I hit while using the page. The drop area gets taller on phones (105 to 149) because the buttons inside it stack.

So the interactive parts are fine on a phone. The picture is the one thing that does not adapt.

What to actually do on a phone

If the audio is on your phone - a voice memo, a recording from a browser tab - convert there. The result is the same file you would get on a desktop; the page does no server round trip, so nothing is uploaded and there is no smaller mobile budget being spent. What you should not do is look at the roll and decide from it.

If you are deciding whether a conversion is worth keeping, the picture is not the right instrument at any size - the checks that actually catch mistakes are all numeric. And if you want to know why the horizontal axis behaves the way it does regardless of screen, what the piano roll actually shows covers the scaling the preview applies internally.

How I measured this

  1. Read the source. The canvas attributes, the canvas rule, the @media (max-width: 720px) block, and drawRoll() were read directly out of the live page's HTML. Every formula quoted above is copied from there, not paraphrased.
  2. Ran the real page in six viewports. A real browser was driven over its debugging protocol, the viewport and device pixel ratio were set with Emulation.setDeviceMetricsOverride, a real 4-second WAV was pushed into the file input, and Convert was clicked with real mouse events - press and release, at the button's own coordinates. The six viewports were 1440 x 900 at 1x, 768 x 1024 at 2x, 412 x 915 at 2.625x, 393 x 852 at 3x, 375 x 667 at 2x and 320 x 640 at 2x.
  3. Measured three things per run. The canvas attributes (the bitmap), its getBoundingClientRect() (CSS pixels), and getBoundingClientRect() x devicePixelRatio (device pixels). The label sizes in the table above are computed as 10 x CSS width / 900, which is what the source implies; the drawing code's own 10px is a bitmap-pixel value.
  4. Captured the page's own output. The .mid was taken by intercepting URL.createObjectURL so that the exact bytes handed to the download were read, not a re-generated file. The note list was taken by intercepting navigator.clipboard.writeText and reading the string the page tried to write. Neither is reconstructed.
  5. Swept the breakpoint separately. Eleven viewport widths from 1440 down to 375, one conversion each, recording the canvas CSS size and the computed height. That produced the 721 / 720 pair - the step is measured, not inferred from the stylesheet.

Honest boundaries