TypeScript modules With Emscripten and CMake, part 1
When I set out to create an NPM package for SoundSwallower, I was unable to find much relevant information in the Emscripten documentation or elsewhere on the Web, so I have written this guide, which will hopefully be useful to anyone doing similar things.
This is the first of a series of posts, which will be collected in a single table of contents when complete.
Should you read this guide?
If you have, or wish to write, a library written in C, or which exposes a C API, and which uses CMake as its build system, and you wish to package it:
- as a CommonJS or ES6 module
- for the Web and for Node.js
- with type definitions for TypeScript
- on npmjs.com
Then yes, you should read this guide!
If you have a library written in Rust, then you don’t need this guide, since you have much better documentation already.
If you have a library written in C++ which exposes a C++ API, you should really reconsider your life choices.
Prerequisites
I presume, here, that you know what WebAssembly is and roughly how it works, and that you have already installed:
- Some recent version of CMake (not the one that ships with CentOS 7)
- Emscripten and
emsdk
- Git
I have only tested this stuff on Ubuntu 22.04 with Chrome and Firefox, if there is anything missing to make it work on reasonable development environments elsewhere, let me know. It ought to work with Windows Subsystem for Linux or MSYS2, and probably can work on MacOS as well.
Overview
As I mentioned, this guide comes from my experience with SoundSwallower, and if you want, you can just go and see how I did things there. Because it’s a fairly complicated library and API, I won’t use it for this guide, but rather something simpler, which even has “simple” in its name, specifically Kiss FFT, a library which implements the Fast Fourier Transform (FFT from here on).
Although, predictably, a complete WebAssembly package for Kiss FFT is already available on npm, the goal here is not to compete with that package, but to demonstrate the workings of Emscripten and the process of integrating a WebAssembly build into a CMake build system. If you just want to do an FFT in your application, I strongly recommend using that existing package!
We will go through the process of building a library, then we will test this library in an excessively simple web application, which records from your microphone and displays a spectrogram.
Why would you ever want to do this? Well, the Web Audio API, in its infinite wisdom, provides an FFT implementation that is useless for anything other than making an animated VU meter, and though there is a good and fast FFT implementation inside WebRTC, there is no way to access it, or any of the other useful WebRTC functionality like voice activity detection from JavaScript. So, if we want to do something actually useful, we have no choice but to reimplement an FFT, either in JavaScript (this works just fine, and is what wavesurfer.js does) or in some other language, which we will compile to WebAssembly (or JavaScript). As you can guess, we will do the latter, since that’s the subject of this guide!
Since we are keeping this simple, we will create a module which wraps
exactly one API, namely the real-valued FFT in
kiss_fftr.h
.
Nonetheless, this is a very typical C API, so it should be applicable
to various other libraries.
Initial build
First, let’s make sure that we can actually build Kiss FFT with
Emscripten. To check out the source code and configure it to build in
a subdirectory called jsbuild
:
git clone https://github.com/mborgerding/kissfft.git
cd kissfft
# This is expected to fail
emcmake cmake -S . -B jsbuild -DCMAKE_BUILD_TYPE=Debug
Oops! That didn’t work at all, because it depends on some other libraries that Emscripten doesn’t know about. We are not going to port these libraries here - instead, we’ll look in the README, which helpfully tells us how to disable those parts of the build, and also to build it as a static library only:
emcmake cmake -S . -B jsbuild -DCMAKE_BUILD_TYPE=Debug \
-DKISSFFT_TOOLS=OFF -DKISSFFT_STATIC=ON -DKISSFFT_TEST=OFF
Much better! We can build the library, giving us a file called
libkissfft-float.a
which we don’t exactly know what to do with:
cmake --build jsbuild
This is not a WebAssembly file, nor is it a JavaScript file, but some kind of intermediate representation that Emscripten will turn into one or both of them. To produce a JavaScript/WebAssembly library, we have to “link” it into a JavaScript module. Ultimately we’ll set up CMake to do this for us, but just to try it out, you can run:
emcc -o kissfft.js jsbuild/libkissfft-float.a -sMODULARIZE=1
This will produce the files kissfft.js
and kissfft.wasm
in
jsbuild
. You should even be able to import this module in the Node
REPL:
> require("./kissfft.js");
Hooray! You have built a module … or have you?
On further inspection you’ll notice that kissfft.wasm
is
suspiciously small. Indeed, if you “disassemble” it (actually just
convert it to text format) using this handy online
tool, you will see
some Emscripten runtime functions… and definitely nothing Kiss FFT
related. If you peruse the copious boilerplate in kisfft.js
, same
thing. What the h*ck?
What’s next
In the next post, we will
learn how to export functions from C code so they are accessible to
JavaScript code, how to pass blocks of data from JavaScript
TypedArray
objects to C functions, and how to handle returned blocks of data.