Show HN: Render audio waveforms to HTML canvas using WebGPU
Hey HN. I built this quick and dirty component to render audio waveforms using WebGPU. I just published it to NPM.
It's the first time I use WebGPU and it's been a while since I write shaders. Feedback is very welcome!
GitHub: https://github.com/mrkev/webgpu-waveform Examples: https://aykev.dev/webgpu-waveform
73 comments
[ 3.3 ms ] story [ 151 ms ] threadhttps://twitter.com/aykev/status/1780000204122460484
It's more code, so I said "later". I also wanted the first example to be easy to read so people wouldn't get lost in the source. But since it's such a common use-case I think I'll add it too. Thanks for the feedback!
I tried to make a web components here: https://lit.dev/playground/#gist=8cf935c3869bf4790653cd6fadf...
But the webgpu-waveform module isn't loading. Looks like you have a hard-coded import of "../../react@18.2.0/index.js", which isn't going to be a portable import even for the React users. For the non-React users, they shouldn't have to install React to get the module loading.
Could you make two entrypoints, one for React, the other for plain JS?
Not ideal, but works.
I'll find a way around this, or maybe split the package into a react version and a vanilla version. Thanks for the feedback! I think you're right, a custom element would be nice to have
I tried to build this using WebGL before WebGPU was publicly available, but WebGL is very tailored to certain workflows and I didn't make much progress. It really felt like I was using the wrong tool for the job. Maybe I'm just too much of a noob... though I did manage to build it with WebGPU after all haha.
Perhaps a Safari TP bug? I'd appreciate some browser version info so I can dig deeper.
Pretty current firefox on Linux. Oh well.
https://bigwav.app
Every vertical line is a dom element.
I was considering moving to GPU rendering as the dom approach can take a long time to re render on changes.
I've turned on in chrome://flags
>WebGPU Developer Features Enables web applications to access WebGPU features intended only for use during development. – Mac, Windows, Linux, ChromeOS, Android, Fuchsia, Lacros #enable-webgpu-developer-features
Chrome is up to date Version 123.0.6312.123 (Official Build) (64-bit)
RTX2060
https://caniuse.com/webgpu
If you're using Chrome and getting this error, do share more info.
As for justification, it was justified. Rendering waveforms is O(n) on pixels, and each can be rendered independently. I'm sure one can get creative with caching images at different resolutions to efficiently implement zooming, but otherwise it's noticeably slow.
https://www.youtube.com/watch?v=qeUAHHPt-LY
Its a waveform shaped like a cow that sounds like a cow haha
Happy to open/share the code, just haven't had time.
PS (yours didn't work for me in Safari)
Re Safari: Unfortunately WebGPU support is still WIP. I'll add a notice on the site haha. https://caniuse.com/webgpu
There are two parts: first the C++/wasm that analyses the audio and generates a bitmap that has width 30*duration and not very high. It effectively draws a line from each audio sample to the next, with alpha, but this can be optimised like crazy to a point where the time it takes is unnoticeable.
The second part is the WebGL that the bitmap (aka texture) and renders to the canvas. In principle this could be rotated, spun in 3d, mapped to a sphere or any other crazy GPU manipulations. WebGL isn't too painful and works everywhere I tried including the Android webview in my Bungee Timewarp app.
In the extreme we have the "Spotify" visualisations of vertical bars with gaps in between. I believe this is popular because it looks slightly better than a solid waveform lump with an aliased edge.
To avoid aliasing you need to use more than two pixel colours.
The aliasing in question is of the audio not the pixels, so no more colours does not help.
Consider: would you draw a line, or a circle using only a foreground and a background pixel colour?
That's 99% simple pixelation, not aliasing. And far less of a problem than the true aliasing in question.
Not by e.g. Wikipedia.
https://en.m.wikipedia.org/wiki/Aliasing
> This article is about aliasing in signal processing, including computer graphics.
In computer graphics, the relevant aliasing is spatial aliasing, in fact mentioned in the article: the signal is the fundamental shape (such as a font glyph or a triangle mesh or whatever), and the samples are the pixels.
In the specific application of a waveform, a typical "CD quality" audio file has 44.1 thousand samples per second, and say, 16 bits per sample. If we want to display the waveform of one second of audio horizontally on an entire standard low-density full HD computer screen, we have 1920 samples to fit our 1 second of audio data, and 1080 samples of amplitude with which to paint the amplitude.
Putting it into signal processing terms, The signal frequency here is 44.1Khz, and the sampling frequency is 1.92Khz. Do you see how aliasing applies now? We want to represent f_Signal / f_Sample = 22.96875 samples of audio with 1 sample.
In practice you get an even worse ratio, because we usually want more than 1 second of waveform to be visible on a region that isn't the entire screen.
No. The signal components being aliased are frequencies e.g. repeating patterns.
"aliasing is the overlapping of frequency components resulting from a sample rate below the Nyquist rate."
That is why the example is a brick wall and the result is moire banding. Nothing like your shapes and jaggies.
What you've mistaken for aliasing is simply pixellation.
In the case of Moiré patterns in pictures, we have lines in the real world that need to fit into pixels that fit a larger area than the Nyquist rate of those lines. The Moiré effect in pictures is just the interference pattern caused by this aliasing.
If you look at just a column of the image, and imagine the signal as being the brightness varying over the Y coordinates, you can imagine the mortar being an occasional regular pulse, and when your sampling rate (the pixel density) isn't enough, you get aliasing: you skip over, or overrepresent, the mortar to brick ratio, variably along the signal.
https://imgur.com/a/BiZcxG5
Now if you look at the graph in that picture, doesn't that look awfully similar to what happens if you try to sample an audio file at an inferior rate for display purposes?
In fact, try it right now, download Audacity, go to Generate>Tone, click OK with whatever settings it's fine, press Shift+Z to go down to sample level zoom, then start zooming out. Eventually, you'll see some interesting patterns, which are exactly the sort of aliasing caused by resampling I'm talking about:
https://i.imgur.com/bX2IFp8.png
I see this and agree. This is true aliasing. I believe this is the OP's "super aliased" look.
However I disagree that this is "the same thing" as the jaggies that can be avoided by more colours.
This cannot be avoided by more colours. It can be avoided only by increased resampling rate.
By sampling the signal more often ("multi-sample anti aliasing"), also known as increasing the resampling rate, then representing that with a wider bit depth (not just 1 bit "yes/no", but multiple bits forming a color/opacity), since we do have more than 1 bit per pixel that can be used already.
I'll give it to you that this is "anti aliasing", not "not having aliasing in the first place", but the Fourier argument above is the reason why in computer graphics we practically always have to "settle for" AA instead.
That's not the cause. The cause is simply that too few of the samples are plotted.
Your min/max solution succeeds by ensuring all significant samples are plotted.
I would disagree that the latter can result in the "super aliased" in question.
Drawing one line per sample leaves very little aliasing.
It's definitely gonna look better than just skipping samples, but you're also gonna draw a lot of unnecessary lines.
I had a WebGPU scope demo using vanila JS here, but it's not connected to AudioContext at all.
https://stackblitz.com/edit/vitejs-vite-cuc9vs
Still, I had to use the old WebGL solution for https://glicol.org since the WebGPU support seems to be pretty slow at the moment. Perhaps you can add some kind of detection and downgrade mechanism?
In Emurse, we render waveform, and also pitch plots of audio recordings using canvas. It's pretty fast, but ours are a bit lower-resolution. The most resource-intensive part of the operation is actually processing the processing the audio.