Shannon's Hatchet

something, but almost nothing

Friday, February 24, 2023

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:

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.