Skip to content

Latest commit

 

History

6 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Jukebox SDK — Linux Build Environment

Build Reason Rack Extensions on Linux.

Reason Studios only ships the Jukebox SDK build toolchain for Windows and macOS: the compiler (clang.exe), the LLVM tools (llc.exe, opt.exe, llvm-link.exe) and the linker (link.exe) are all Windows binaries. This project wraps them in a Docker image that runs them under Wine, and drives the whole thing from a small set of Python scripts.

The MSVC linker and the handful of Windows SDK libraries it needs are vendored directly in .docker/msvc-files/ — trimmed to the ~50 files this build actually touches, with a small link shell wrapper that translates Unix paths into Wine paths. The approach is inspired by msvc-wine, which solves the general problem of running MSVC under Wine; this repo doesn't use it or require it.

The result: ./build.sh local45 Debugging on your Linux box produces a signed-for-local-use Rack Extension you can drop into Reason.


Table of Contents


How it works

The build is a five-stage pipeline. Everything except build.sh runs inside the container, and every Windows .exe runs through Wine.

  vst/*.cpp + ShimABI/JukeboxABI.cpp
            │
            │  1. clang.exe  (-ccdsp, target phdsp64)
            ▼
       *.o.bc  (LLVM bitcode, one per source file)
            │
            │  2. opt.exe -ph-disable-globals      → rejects global state
            │     llvm-link.exe + libcpp.bc/libc.bc → one bitcode blob
            ▼
   MyRackExtension_static_library.bc
            │
            │  3. opt.exe  → Propellerhead instrumentation
            │               (timeout checks for Testing/Deployment, -strip for Deployment)
            ▼
       ...Inst.bc
            │
            │  4. llc.exe  -mtriple=x86_64-pc-win32-jbcrt -filetype=obj
            ▼
       ...Inst.obj
            │
            │  5. link.exe (MSVC, via Wine) /DLL /ENTRY:DSPMain
            │     + RackExtWrapperLib.lib + clang_rt.builtins + picolibc shim
            ▼
   MyRackExtension64.dll  ──┐
                            ├──► output/  (+ lua files, GUI images, version.txt)
   vst/*.lua, GUI/Output/ ──┘

Two things are worth knowing about stage 5. The linker runs twice: first against a dummy libc (dependencytest.dll) purely to prove your code pulls in no unexpected C runtime symbols, then for real against a wrapper libc. And /ENTRY:DSPMain is what makes the DLL a Rack Extension rather than an ordinary Windows library.

Stage 2's -ph-disable-globals pass is the one that most often surprises newcomers — the Jukebox DSP environment forbids mutable global state, and this pass is where that rule is enforced.


Prerequisites

Requirement Notes
Docker + Docker Compose The build never runs on the host directly
~5 GB disk Ubuntu base + Wine (64 and 32 bit)
The Jukebox SDK Already vendored under src/JukeboxSDK/
MSVC linker + Windows SDK libs Already vendored under .docker/msvc-files/ (~60 MB)

You do not need Wine, Python, Lua or clang on the host, and there is no toolchain to download — everything lives in the image or in the repo. Clone, docker compose up, build.


First-time setup

1. Build and start the container

cd .docker
docker compose up -d --build

The first build takes a while — it installs Wine (64 and 32 bit), runs wineboot under a virtual X server, and initialises the Wine prefix.

Verify it is up; the container name must be exactly jukebox-sdk-linux, because that is what build.sh looks for:

docker ps --format '{{.Names}}'   # expect: jukebox-sdk-linux

2. Build

cd ..
./build.sh local45 Debugging

Building

./build.sh <target> [platform] <configuration>
# Everyday development build
./build.sh local45 Debugging

# With timeout instrumentation, still unoptimised
./build.sh local45 Testing

# Optimised, stripped, NDEBUG
./build.sh local45 Deployment

# Platform is optional and currently only ever 64
./build.sh local45 64 Debugging

build.sh is a thin wrapper: it checks the container is running, then docker execs python3 /src/build.py inside it with the right environment variables. It passes your arguments straight through, so any usage error is reported by src/build.py.

VS Code users get the same three builds as launch configurations in .vscode/launch.json (Run and DebugBuild with bash script (…)).

Where the output lands

build.sh sets OUTPUT_DIR=/output/MyRackExtension, so the finished extension appears on the host in output/MyRackExtension/:

output/MyRackExtension/
├── MyRackExtension64.dll   # the Rack Extension itself
├── MyRackExtension64.pdb   # debug symbols
├── info.lua                # copied from vst/
├── motherboard_def.lua
├── realtime_controller.lua
├── display.lua
├── gui.lua                 # from vst/GUI/Output/
├── *.png                   # device panels, icons, filmstrips
├── English/texts.lua       # from vst/Resources/
└── version.txt             # SDK version + llc/clang --version output

The output directory is wiped at the start of each install step, so don't keep anything of your own in there.


Build configurations

The three configurations are genuinely different — this is where the real behaviour lives.

Debugging Testing Deployment
Optimisation -O0 -O0 -O2 (clang + llc)
Preprocessor DEBUG=1 DEBUG=1 NDEBUG=1
Debug info CodeView, standalone, unwind tables
Timeout checks no yes (1000 / 100) yes (1000 / 100)
Bitcode stripped no no yes (-strip)
Linker folding no no /OPT:REF /OPT:ICF

Use Debugging while you develop: timeout checks abort your DSP code when a callback runs long, which makes stepping through a debugger impossible. Switch to Testing to find out whether your DSP actually fits its time budget, and Deployment for anything you intend to ship.

Static analysis (clang --analyze) runs before every compile when STATIC_ANALYSIS=True, which is the default. It roughly doubles compile time; set it to False in build.sh for faster iteration.


Build targets

Target Status Description
local45 Working Local development build, installed straight into output/
universal45 Not implemented Platform-independent build for uploading to Reason Studios
optimized45 Not implemented Per-platform optimised build produced from a universal45

universal45 and optimized45 are accepted by the argument parser in src/build.py and then silently do nothing — only local45 reaches src/local.py. Distribution builds still need the official Windows or macOS SDK.


Project structure

.
├── build.sh                     # Host entry point — docker exec wrapper
├── .docker/
│   ├── Dockerfile               # Ubuntu 24.04 + wine64/wine32 + python3 + lua5.3 + xvfb
│   ├── docker-compose.yml       # Volume mounts and container name
│   └── msvc-files/              # Vendored MSVC linker + Windows SDK libs (~60 MB)
│       ├── bin/x64/link         # Wrapper: Unix→Wine path translation, noise filtering
│       ├── vc/tools/msvc/…      # link.exe + its DLLs, libcmt/libvcruntime/oldnames
│       └── kits/10/lib/…        # kernel32, Uuid, ucrt
│
├── src/                         # Build system — mounted at /src
│   ├── build.py                 # Entry point: argument parsing, target dispatch
│   ├── local.py                 # local45 target implementation
│   ├── common.py                # The pipeline: bitcode, instrumentation, linking, install
│   ├── clang.py                 # clang.exe flags for the phdsp64 target
│   ├── llc.py                   # llc.exe — bitcode → Windows .obj
│   ├── llvm.py                  # llvm-link.exe — bitcode merging, output naming
│   ├── lua.py                   # Reads product_id / development_version from info.lua
│   ├── command_runner.py        # subprocess + wine wrappers
│   ├── constants.py             # All paths and env-var handling
│   ├── utils.py                 # File helpers (copy, chmod, cleanup)
│   ├── debug.py                 # verbose_print
│   ├── .env                     # Defaults for docker compose
│   └── JukeboxSDK/              # Vendored SDK
│       ├── API/                 # Jukebox.h, JukeboxTypes.h
│       ├── version.txt
│       ├── getproductid.lua     # Lua helpers run against your info.lua
│       ├── getdevelopmentversion.lua
│       └── Tools/
│           ├── LLVM/
│           │   ├── Win/bin      # clang.exe, llc.exe, opt.exe, llvm-link.exe
│           │   └── Jukebox/     # libc / libcxx bitcode + headers for phdsp64
│           └── Libs/
│               ├── Jukebox/ShimABI/          # JukeboxABI.cpp — always compiled in
│               ├── RackExtensionWrapper/     # RackExtWrapperLib.lib + WinDLLExports.txt
│               └── VisualStudio/             # picolibc.c and per-config libs
│
├── vst/                         # YOUR Rack Extension — mounted at /vst
│   ├── *.cpp, *.h               # DSP and GUI sources (all *.cpp are compiled)
│   ├── info.lua                 # Device metadata: product_id, names, version
│   ├── motherboard_def.lua      # Properties, audio/CV sockets
│   ├── realtime_controller.lua  # RT bindings and diff handling
│   ├── display.lua              # Display/remote configuration
│   ├── GUI/Output/              # Pre-rendered panels, gui.lua, filmstrips
│   └── Resources/English/       # texts.lua — UI strings
│
├── output/                      # Build output — mounted at /output
└── tmp/                         # Mounted at /tmp/vst (see Known rough edges)

The sample device currently in vst/ is Daimyo, a polyphonic synth (com.soundhubb.Daimyo) — voice pool, envelopes, LFO, waveform tables and an operator graph. It is a useful reference for how the pieces fit together.


Configuration

Every path is a container path, because the build only ever runs inside Docker.

Variable Value set by build.sh Meaning
JUKEBOX_SDK_DIR /src/JukeboxSDK Vendored SDK root
SYNTH_DIR /vst Your source — scanned for *.cpp and *.lua
OUTPUT_DIR /output/MyRackExtension Install destination (wiped each build)
INTERMEDIATE_DIR /tmp/IntermediateLLVM Bitcode and objects; deleted before and after
RACK_EXTENSION_NAME MyRackExtension Drives the DLL filename
PLATFORM_NAME x64 Only x64 is supported
STATIC_ANALYSIS True Run clang --analyze before each compile
VERBOSE_BUILD False Print every command line as it runs

Two files define these, and it matters which one wins:

  • build.sh passes them explicitly with docker exec -e. These are the ones that apply to a build, and they override everything else.
  • src/.env is loaded by docker compose into the container environment. It applies if you run python3 /src/build.py by hand inside the container. Note that it sets OUTPUT_DIR=/output (not /output/MyRackExtension), so a manual build installs one directory higher.

To rename your extension, change RACK_EXTENSION_NAME in both places.


Bringing your own Rack Extension

  1. Drop your *.cpp / *.h into vst/. Every *.cpp at the top level of vst/ is compiled — there is no file list to maintain, but there are also no subdirectories scanned.
  2. Edit vst/info.lua: product_id (reverse-DNS, must match your Reason Studios registration), long_name, medium_name, short_name, version_number, device_type.
  3. Describe your device in vst/motherboard_def.lua — properties, audio and CV sockets.
  4. Wire up vst/realtime_controller.lua for RT-side bindings.
  5. Put your rendered GUI into vst/GUI/Output/ (panels, gui.lua, filmstrips, device icons). This build system does not render 2D GUI assetsvst/GUI2D/ is referenced in constants.py but nothing processes it. Render with the official RE2DRender tooling and commit the results.
  6. Translate UI strings in vst/Resources/English/texts.lua.
  7. ./build.sh local45 Debugging, then copy output/MyRackExtension/ into your Reason local Rack Extension folder.

A few constraints the toolchain will enforce whether you like it or not:

  • No mutable globals in DSP code — the -ph-disable-globals pass rejects them.
  • No C/C++ standard library beyond what Jukebox ships. You link against Jukebox's libc.bc / libcpp.bc, and the dependency-test link will fail loudly if you reach for anything else.
  • C++17, -ffreestanding, -fno-builtin, no aligned allocation.
  • -Werror=uninitialized — uninitialised variables are a build failure, not a warning.

Troubleshooting

Error: Docker container 'jukebox-sdk-linux' is not running

build.sh matches the container name exactly. Start it with cd .docker && docker compose up -d and confirm with docker ps --format '{{.Names}}'. If you run the image under a different name, point the wrapper at it: DOCKER_IMAGE=my-container ./build.sh local45 Debugging.

Wrong number of arguments / Unknown configuration

The configuration is case-sensitive and must be exactly Debugging, Testing or Deployment. Valid forms: local45 <Config> or local45 64 <Config>.

Link fails on kernel32.lib or a /LIBPATH: directory

The vendored toolchain is deliberately minimal — only the libraries this build links against are present. If you add code that needs another Windows library, you have to add the .lib to .docker/msvc-files/kits/10/lib/10.0.19041.0/um/x64/ yourself.

If instead you replaced or upgraded the vendored toolchain, its version must be updated in two places or the link will fail: the three *_lib_path variables in src/common.py, and MSVCVER / SDKVER in .docker/msvc-files/bin/x64/link. Both currently pin Windows SDK 10.0.19041.0 and MSVC 14.29.30133.

Wine errors, or clang.exe won't start

Rebuild the image so wineboot re-initialises the prefix: cd .docker && docker compose up -d --build --force-recreate. The Wine prefix lives in the container, not in a volume, so recreating is the fix.

The dependency-test link fails but your code compiles

You've pulled in a C runtime symbol Jukebox doesn't provide. The failing symbol name in the dependencytest.dll link output tells you which one — usually a <cmath>, <cstdio> or allocation call that isn't available in the DSP sandbox.

WARNING: No HD GUI folder / HD GUI folder is empty

vst/GUI/Output/ is missing or empty. The build continues and produces a working DLL, but the device will have no panel graphics.

Seeing what actually ran

Set VERBOSE_BUILD=True in build.sh to echo every clang/llc/opt/link command line. You can also work interactively:

docker exec -it jukebox-sdk-linux bash
python3 /src/build.py local45 Debugging

Known rough edges

Worth knowing before they cost you an afternoon:

  • tmp/ is mounted at /tmp/vst, but intermediates go to /tmp/IntermediateLLVM. The mount is effectively unused, and bitcode/object files are not visible on the host. They are also deleted before and after every build, so there is nothing to inspect after a successful run. To keep them, comment out the remove_temporary_folders() calls in src/local.py.
  • .gitignore excludes vst/* and output/* (keeping only the .gitkeep files). Your Rack Extension source is deliberately not tracked by this repository — keep it in its own repo, or unignore it if you want it versioned here.
  • build.sh uses docker exec -it, which needs a TTY. In CI, drop the -t.
  • The MSVC version is pinned in two unrelated filessrc/common.py and the .docker/msvc-files/bin/x64/link wrapper. They have to be changed together.
  • Only x64 is supported. platform is still threaded through local.build and clang.get_clang_libs (it selects the phdsp64 lib folder), but phdsp64 and x86_64 are hardcoded throughout clang.py, llc.py and common.py.
  • Static analysis writes .plist files into the source tree. clang --analyze in clang.py is invoked without -o, so one analyzer report per source file lands next to your code on every build. They are safe to delete; set STATIC_ANALYSIS=False to stop producing them.

License

The Jukebox SDK and the vendored MSVC/Windows SDK components are covered by their respective vendors' licenses — check that redistributing them suits your situation before publishing a fork. This repository provides only the Linux build harness around them.

About

The Reason Studios (ex PropellerHead) Jukebox SDK on linux

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages