Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion 7.x/cmake/WasmPlugins.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,13 @@ function(configure_wasm_plugin target)
target_compile_options(${target} PRIVATE
-fPIC "SHELL:-mllvm -wasm-enable-sjlj"
"$<$<COMPILE_LANGUAGE:C>:-std=gnu11>"
"$<$<COMPILE_LANGUAGE:CXX>:-fno-exceptions;-nostdinc++>"
"$<$<COMPILE_LANGUAGE:CXX>:-nostdinc++>"
"$<$<COMPILE_LANGUAGE:CXX>:-isystem${WASM_CXX_INCLUDE_DIR}>")
if(WASM_CXX_EXCEPTIONS)
target_compile_options(${target} PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:-fwasm-exceptions>")
else()
target_compile_options(${target} PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:-fno-exceptions>")
endif()
target_link_options(${target} PRIVATE
-shared -nostdlib -nostartfiles
"LINKER:-Bsymbolic"
Expand Down
39 changes: 34 additions & 5 deletions 7.x/src/stk/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
option(BUILD_STK_OPCODES "Build the stk opcodes" ON)
option(STK_EMBED_RAWWAVES "Embed STK's raw-wave files in the plugin" ${WASM})

# The STK sources are bundled under this directory (include/, src/ and
# rawwaves/) so that a patched copy is always built, independent of any system
Expand All @@ -8,20 +9,48 @@ if(BUILD_STK_OPCODES)
file(GLOB stk_srcs "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
list(APPEND stk_srcs "${CMAKE_CURRENT_SOURCE_DIR}/stkOpcodes.cpp")

if(WASM)
if(NOT WASM_LIBCXX_EXCEPTIONS OR NOT WASM_CXX_EXCEPTIONS_INCLUDE_DIR OR NOT WASM_LIBUNWIND)
message(FATAL_ERROR "STK needs the WASM C++ exception runtime and unwinder")
endif()
set(WASM_CXX_EXCEPTIONS ON)
set(WASM_LIBCXX "${WASM_LIBCXX_EXCEPTIONS}")
set(WASM_CXX_INCLUDE_DIR "${WASM_CXX_EXCEPTIONS_INCLUDE_DIR}")
endif()
make_plugin(stkops "${stk_srcs}")
if(WASM)
target_link_libraries(stkops PRIVATE "${WASM_LIBUNWIND}")
endif()
target_include_directories(stkops PRIVATE
${CSOUND_INCLUDE_DIRS}
"${CMAKE_CURRENT_SOURCE_DIR}/include"
"${CMAKE_CURRENT_SOURCE_DIR}/src")
target_compile_definitions(stkops PRIVATE BUILD_PLUGINS)

if(STK_EMBED_RAWWAVES)
file(GLOB rawwaves CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/rawwaves/*.raw")
set(embedded_waves "${CMAKE_CURRENT_BINARY_DIR}/stk_rawwaves.inc")
add_custom_command(OUTPUT "${embedded_waves}"
COMMAND ${CMAKE_COMMAND}
"-DRAWWAVE_DIR=${CMAKE_CURRENT_SOURCE_DIR}/rawwaves"
"-DOUTPUT_FILE=${embedded_waves}"
-P "${CMAKE_CURRENT_SOURCE_DIR}/EmbedRawwaves.cmake"
DEPENDS ${rawwaves} "${CMAKE_CURRENT_SOURCE_DIR}/EmbedRawwaves.cmake"
VERBATIM)
target_sources(stkops PRIVATE "${embedded_waves}")
target_include_directories(stkops PRIVATE "${CMAKE_CURRENT_BINARY_DIR}")
target_compile_definitions(stkops PRIVATE STK_EMBED_RAWWAVES)
endif()

# Install the rawwaves used by the STK instruments and make them the
# default raw wave path; the RAWWAVE_PATH environment variable overrides it.
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/rawwaves"
DESTINATION "${PLUGIN_INSTALL_DIR}/stk")
if(PLUGIN_INSTALL_DIR)
target_compile_definitions(stkops PRIVATE
DEFAULT_RAWWAVE_PATH="${PLUGIN_INSTALL_DIR}/stk/rawwaves")
if(NOT STK_EMBED_RAWWAVES)
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/rawwaves"
DESTINATION "${PLUGIN_INSTALL_DIR}/stk")
if(PLUGIN_INSTALL_DIR)
target_compile_definitions(stkops PRIVATE
DEFAULT_RAWWAVE_PATH="${PLUGIN_INSTALL_DIR}/stk/rawwaves")
endif()
endif()

message(STATUS "Building STK opcodes")
Expand Down
21 changes: 21 additions & 0 deletions 7.x/src/stk/EmbedRawwaves.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generate byte arrays from the bundled files; no decoder is needed at runtime.
file(GLOB waves "${RAWWAVE_DIR}/*.raw")
list(SORT waves)
file(WRITE "${OUTPUT_FILE}" "// Generated by EmbedRawwaves.cmake.\n")
set(entries "")
foreach(wave IN LISTS waves)
get_filename_component(name "${wave}" NAME)
string(MAKE_C_IDENTIFIER "${name}" symbol)
file(READ "${wave}" bytes HEX)
string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," bytes "${bytes}")
file(APPEND "${OUTPUT_FILE}" "static const unsigned char ${symbol}[] = {${bytes}};\n")
string(APPEND entries " {\"${name}\", ${symbol}, sizeof(${symbol})},\n")
endforeach()
file(APPEND "${OUTPUT_FILE}" "
static const struct {
const char *name;
const unsigned char *bytes;
size_t size;
} stkRawwaves[] = {
${entries}};
")
54 changes: 51 additions & 3 deletions 7.x/src/stk/stkOpcodes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@
*
* The STK sources are bundled in this directory (include/, src/ and
* rawwaves/) so that a patched copy is always used, independent of any system
* or package-manager STK installation. The rawwaves path defaults to the
* installed location (DEFAULT_RAWWAVE_PATH) and can be overridden with the
* RAWWAVE_PATH environment variable.
* or package-manager STK installation. STK_EMBED_RAWWAVES stages the bundled
* samples at module initialization; otherwise the rawwaves path defaults to
* the installed location (DEFAULT_RAWWAVE_PATH). The RAWWAVE_PATH environment
* variable overrides either default.
*
* All these opcodes are named "STK" + the STK classname,
* e.g. "STKBowed" for the Bowed instrument.
Expand All @@ -53,6 +54,16 @@
#include <string>
#include <vector>

#ifdef STK_EMBED_RAWWAVES
#include <cerrno>
#ifdef _WIN32
#include <direct.h>
#else
#include <sys/stat.h>
#endif
#include "stk_rawwaves.inc"
#endif

#include "BandedWG.h"
#include "BeeThree.h"
#include "BlowBotl.h"
Expand Down Expand Up @@ -584,8 +595,45 @@ OENTRY oentries[] = {

PUBLIC int32_t csoundModuleCreate(CSOUND *csound) { return 0; }

#ifdef STK_EMBED_RAWWAVES
// Keep STK's file reader unchanged by staging the embedded bytes at module init.
static bool writeEmbeddedRawwaves(CSOUND *csound, const char *directory) {
#ifdef _WIN32
const int result = _mkdir(directory);
#else
const int result = mkdir(directory, 0700);
#endif
if (result != 0 && errno != EEXIST) {
csound->Message(csound, "STK: cannot create raw-wave directory %s\n", directory);
return false;
}
for (const auto &wave : stkRawwaves) {
const std::string filename = std::string(directory) + "/" + wave.name;
FILE *file = fopen(filename.c_str(), "wb");
if (!file) {
csound->Message(csound, "STK: cannot open embedded raw wave %s\n", filename.c_str());
return false;
}
const bool written = fwrite(wave.bytes, 1, wave.size, file) == wave.size;
const int closed = fclose(file);
if (!written || closed != 0) {
csound->Message(csound, "STK: cannot write embedded raw wave %s\n", filename.c_str());
return false;
}
}
return true;
}
#endif

PUBLIC int32_t csoundModuleInit(CSOUND *csound) {
const char *path = csound->GetEnv(csound, "RAWWAVE_PATH");
#ifdef STK_EMBED_RAWWAVES
if (path == NULL || strlen(path) == 0) {
path = ".csound-stk-rawwaves";
if (!writeEmbeddedRawwaves(csound, path))
return NOTOK;
}
#endif
#ifdef DEFAULT_RAWWAVE_PATH
if (path == NULL || strlen(path) == 0)
path = DEFAULT_RAWWAVE_PATH;
Expand Down
10 changes: 6 additions & 4 deletions THIRD_PARTY.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ The package also contains code from these libraries:
| GMM++ (GetFEM) | Linear algebra | `dist/licenses/gmm.txt`; LGPL-3.0-or-later with the exception in the headers |
| libpng | Image | `dist/licenses/libpng.txt` |
| zlib | Image | `dist/licenses/zlib.txt` |
| STK and its raw-wave samples | STK instruments | `dist/licenses/stk.txt` |
| wasi-libc, musl, cloudlibc, musl-fts | C runtime | `dist/licenses/wasi-libc/` |
| LLVM libc++, libc++abi, compiler-rt | C++ runtime and compiler support | `dist/licenses/libcxx.txt`, `libcxxabi.txt`, `compiler-rt.txt` |
| LLVM libc++, libc++abi, libunwind, compiler-rt | C++ runtime and compiler support | `dist/licenses/libcxx.txt`, `libcxxabi.txt`, `libunwind.txt`, `compiler-rt.txt` |

The Nix build copies these notices from the source used to build each library.
[`wasm/nixpkgs.nix`](https://github.com/csound/plugins/blob/develop/wasm/nixpkgs.nix)
pins their source versions. The GMM++ headers live in this repository. The build
uses upstream CMake and make options for static PIC libraries; it adds no source
patches. `wasm/default.nix` records those options.
pins their source versions. The GMM++ headers and STK sources and samples live
in this repository. The build uses upstream CMake and make options for static
PIC libraries, plus a small CMake target for LLVM's WASM unwinder; it adds no
source patches. `wasm/default.nix` records those options.
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export declare const plugins: Readonly<{
chua: string;
image: string;
linearAlgebra: string;
stk: string;
}>;
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export const plugins = Object.freeze({
chua: new URL("./dist/chua.wasm", import.meta.url).href,
image: new URL("./dist/image.wasm", import.meta.url).href,
linearAlgebra: new URL("./dist/linear_algebra.wasm", import.meta.url).href,
stk: new URL("./dist/stkops.wasm", import.meta.url).href,
});
23 changes: 17 additions & 6 deletions wasm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

WASM opcode plugins for the double-precision Csound 7 browser engine.

This first package includes Chua (`chuap`), image, and linear algebra. This build
disables STK, FluidSynth, HDF5, Python, WebSockets, device, and GPU plugins.
This package includes Chua (`chuap`), image, linear algebra, and STK. This build
disables FluidSynth, HDF5, Python, WebSockets, device, and GPU plugins.
Csound 6 is not supported.

## Runtime dependency
Expand All @@ -24,6 +24,12 @@ import { plugins } from "@csound/plugins";
const csound = await Csound({ withPlugins: [plugins.chua, plugins.image] });
```

Use `plugins.stk` to load the STK instruments. Its single `stkops.wasm` file
contains all bundled raw-wave samples. At initialization, the plugin writes
them to Csound's virtual filesystem and sets its wave path. No extra downloads
or `RAWWAVE_PATH` setup are needed. An explicit `RAWWAVE_PATH` overrides the
embedded samples; that directory must already contain the files STK needs.

The exports are asset URL strings. Bundlers must copy the WASM files referenced
by `new URL(..., import.meta.url)`. You can also serve files from
`@csound/plugins/dist/` yourself and pass their URLs to `withPlugins`.
Expand Down Expand Up @@ -57,13 +63,18 @@ npm pack

Nix pins the toolchain and builds the existing `7.x` CMake targets with Ninja.
Dependencies use static PIC builds. The C runtime imports the engine's allocator;
it does not link its own heap. C++ plugins use libc++ without C++ exceptions.
it does not link its own heap. STK uses a separate libc++ build with C++ exceptions
and a static WASM unwinder. Other C++ plugins keep exceptions disabled.
CMake's `STK_EMBED_RAWWAVES` option embeds the samples and defaults to on for WASM.
Native builds keep using the installed raw-wave directory by default.
To use another Csound 7 SDK, set `CSOUND_PLUGIN_SDK` to an absolute archive path.
Set `NIX_SYSTEM=x86_64-linux` when using a Linux remote builder from macOS.

Tests load all plugins together, render Chua, compute a vector norm, and save and
reload a PNG through the WASI filesystem. Packing runs the tests and includes
the WASM files and their license notices. For work on the browser loader,
Tests load all plugins together, render Chua and STK, compute a vector norm, and
save and reload a PNG through the WASI filesystem. STK tests also cover loading
it alone, overriding its wave path, and catching a missing-sample error.
Packing runs the tests and includes the WASM files and their license notices.
For work on the browser loader,
`CSOUND_BROWSER_URL` can point to an absolute file URL of a local browser bundle;
leave it unset to test the published package as CI does.

Expand Down
38 changes: 35 additions & 3 deletions wasm/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
}:
let
wasi = pkgs.pkgsCross.wasi32.clangStdenv;
# Nix's WASI wrapper otherwise forces -fno-exceptions after CMake's flags.
# Let each target select its exception mode instead.
wasiCxx = pkgs.overrideCC wasi (wasi.cc.override (old: {
nixSupport = old.nixSupport // {
cc-cflags = builtins.filter (flag: flag != "-fno-exceptions") old.nixSupport.cc-cflags;
};
}));
libc = pkgs.pkgsCross.wasi32.wasilibc.overrideAttrs (old: {
NIX_CFLAGS_COMPILE = (old.NIX_CFLAGS_COMPILE or []) ++ [ "-fPIC" ];
# Use the engine's allocator. wasi-libc also skips its fixed symbol list
Expand All @@ -19,6 +26,26 @@ let
];
dontStrip = true;
});
libunwindSource = pkgs.pkgsCross.wasi32.llvmPackages.libunwind.src;
libunwind = wasi.mkDerivation {
pname = "libunwind-wasm-pic";
inherit (libcxx) version;
src = ./libunwind;
nativeBuildInputs = [ pkgs.cmake pkgs.ninja ];
cmakeFlags = [ "-DLIBUNWIND_SOURCE_DIR=${libunwindSource}/libunwind" ];
dontStrip = true;
};
libcxxExceptions = (libcxx.override { stdenv = wasiCxx; }).overrideAttrs (old: {
pname = "libcxx-wasm-exceptions";
cmakeFlags = old.cmakeFlags ++ [
"-DLIBCXX_ENABLE_EXCEPTIONS=ON"
"-DLIBCXX_ADDITIONAL_LIBRARIES="
"-DLIBCXXABI_ENABLE_EXCEPTIONS=ON"
];
preConfigure = (old.preConfigure or "") + ''
cmakeFlagsArray+=("-DCMAKE_CXX_FLAGS=-fexceptions -fwasm-exceptions -I${libunwindSource}/libunwind/include")
'';
});
lock = builtins.fromJSON (builtins.readFile ../package-lock.json);
wasmBin = lock.packages."node_modules/@csound/wasm-bin";
sdkArchive = if csoundSdkArchive == null then null else builtins.path {
Expand Down Expand Up @@ -56,7 +83,7 @@ let
NIX_CFLAGS_COMPILE = [ "-mllvm" "-wasm-enable-sjlj" ];
dontStrip = true;
};
in wasi.mkDerivation {
in wasiCxx.mkDerivation {
pname = "csound-plugins-wasm";
version = (builtins.fromJSON (builtins.readFile ../package.json)).version;
src = pkgs.lib.cleanSource ../7.x;
Expand All @@ -71,13 +98,16 @@ in wasi.mkDerivation {
"-DWASM_SETJMP=${libc}/lib/libsetjmp.a"
"-DWASM_LIBCXX=${libcxx}/lib/libc++.a"
"-DWASM_CXX_INCLUDE_DIR=${libcxx.dev}/include/c++/v1"
"-DWASM_LIBCXX_EXCEPTIONS=${libcxxExceptions}/lib/libc++.a"
"-DWASM_CXX_EXCEPTIONS_INCLUDE_DIR=${libcxxExceptions.dev}/include/c++/v1"
"-DWASM_LIBUNWIND=${libunwind}/lib/libunwind.a"
"-DEIGEN3_INCLUDE_DIR=${pkgs.eigen}/include/eigen3"
"-DZLIB_LIBRARY=${zlib}/lib/libzlibstatic.a"
"-DPNG_LIBRARY=${libpng}/lib/libpng.a"
"-DBUILD_CHUA_OPCODES=ON"
"-DBUILD_IMAGE_OPCODES=ON"
"-DBUILD_LINEAR_ALGEBRA_OPCODES=ON"
"-DBUILD_STK_OPCODES=OFF"
"-DBUILD_STK_OPCODES=ON"
"-DBUILD_FLUID_OPCODES=OFF"
"-DBUILD_HDF5_OPCODES=OFF"
"-DBUILD_WEBSOCKET_OPCODE=OFF"
Expand All @@ -95,12 +125,14 @@ in wasi.mkDerivation {
cp ${libc.src}/fts/musl-fts/COPYING $out/share/licenses/wasi-libc/musl-fts.txt
cp ${libcxx.src}/libcxx/LICENSE.TXT $out/share/licenses/libcxx.txt
cp ${libcxx.src}/libcxxabi/LICENSE.TXT $out/share/licenses/libcxxabi.txt
cp ${libunwindSource}/libunwind/LICENSE.TXT $out/share/licenses/libunwind.txt
cp ${../7.x/src/stk/LICENSE} $out/share/licenses/stk.txt
cp ${pkgs.pkgsCross.wasi32.llvmPackages.compiler-rt.src}/compiler-rt/LICENSE.TXT $out/share/licenses/compiler-rt.txt
cp ${pkgs.eigen.src}/COPYING* $out/share/licenses/eigen/
sed -n '1,/^=*[*][/]$/p' ${../7.x/src/linear_algebra/gmm/gmm.h} > $out/share/licenses/gmm.txt
tar -xOf ${pkgs.libpng.src} libpng-${pkgs.libpng.version}/LICENSE > $out/share/licenses/libpng.txt
tar -xOf ${pkgs.zlib.src} zlib-${pkgs.zlib.version}/LICENSE > $out/share/licenses/zlib.txt
for plugin in chua image linear_algebra; do
for plugin in chua image linear_algebra stkops; do
test -s "$out/lib/$plugin.wasm"
done
'';
Expand Down
12 changes: 12 additions & 0 deletions wasm/libunwind/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
cmake_minimum_required(VERSION 3.16)
project(CsoundWasmUnwind C)

# LLVM 21's main build also selects assembly files that do not support WASM.
# The VM unwinds the stack; this source supplies the C++ exception interface.
add_library(unwind STATIC "${LIBUNWIND_SOURCE_DIR}/src/Unwind-wasm.c")
set_target_properties(unwind PROPERTIES POSITION_INDEPENDENT_CODE ON)
target_compile_features(unwind PRIVATE c_std_11)
target_compile_options(unwind PRIVATE -fwasm-exceptions)
target_compile_definitions(unwind PRIVATE _LIBUNWIND_HIDE_SYMBOLS)
target_include_directories(unwind PRIVATE "${LIBUNWIND_SOURCE_DIR}/include")
install(TARGETS unwind ARCHIVE DESTINATION lib)
2 changes: 1 addition & 1 deletion wasm/scripts/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ fi
nix-build wasm/default.nix "${args[@]}"
staging=$(mktemp -d .wasm-dist.XXXXXX)
trap 'rm -rf "$staging"' EXIT
for plugin in chua image linear_algebra; do
for plugin in chua image linear_algebra stkops; do
install -m 644 "result-wasm/lib/$plugin.wasm" "$staging/$plugin.wasm"
done
cp -R result-wasm/share/licenses "$staging/licenses"
Expand Down
Loading
Loading