biggy facd5c5b5e chore: move build output under build/ and scripts into scripts/
Layout:
- scripts/ holds the four build scripts plus the new clean.sh
- cmake/ holds mingw64-toolchain.cmake
- the scripts default to build/linux, build/macos, build/windows, build/harness
- docs/ holds the audit documents, with the round 1 task list archived

Cleanup:
- scripts/clean.sh removes generated output, with --dry-run, --staging,
  --legacy and --all, and refuses tracked, protected or outside paths
- the Windows VST3 manifest now comes from cmake/moduleinfo.json.in, so its
  version, vendor and class IDs follow cmake/Plugin.cmake instead of a
  hand-written heredoc that could drift
- deleted the unused Source/GUI/Slider, which no code constructed
- fixed the hidden double processBlock overload: three warning lines to zero
- unslopped the build scripts, the two .cjs helpers and the CMake comments
- .gitignore covers the upstream JUCE Projucer and DemoRunner binaries

Verified: five scripts pass bash -n; the harness passes CTest with zero
warning lines; the Linux VST3 and Standalone link; the Windows cross-build
passes its x86-64, runtime-import and entry-point checks; each configure
exposes only its own targets; and a full build leaves Source/ and
third_party/JUCE unchanged. The generated manifest is identical to the
previous hand-written one.
2026-10-06 09:06:53 +02:00

SerumAlt

A wavetable synthesiser experiment built with JUCE 7, CMake and C++17. It targets VST3, AU and Standalone formats, with a workflow inspired by Serum and Vital. This is a fun proof of concept, not a commercial release. Development favors working builds and hands-on listening over elaborate test infrastructure.

SerumAlt

Features

  • Two wavetable oscillators, each with 256 frames of 2048 samples, bilinear morphing, seven warp effects plus bypass, and up to 16 unison voices.
  • Ten factory wavetables, built before audio rendering and kept in memory.
  • Sine/triangle sub oscillator and white/pink noise.
  • Two filter slots with seven models, drive, key tracking and serial, parallel or split routing.
  • Four ADSR envelopes and four free-running or tempo-synced LFOs, including editable step and freehand shapes.
  • A modulation matrix and four assignable macros.
  • Nine effect types in eight reorderable slots, with independent state per slot.
  • A non-destructive RAVE boost for unison, spread, drive, Hyper and reverb.
  • Sixteen factory presets and a scalable vector GUI with waveform, envelope, LFO and schematic filter previews.

Architecture

Source/
  PluginProcessor.{h,cpp}     APVTS, presets, state, RAVE/UI-scale glue
  PluginEditor.{h,cpp}        Tabbed GUI, owned controls, scaling, previews
  Params.{h,cpp}              Parameter IDs, indexed groups, enums, mappings
  Wavetable.{h,cpp}           256×2048 tables, prebuilt by the engine
  Oscillator.{h,cpp}          Morph/warp/unison oscillator
  SubOscillator.{h,cpp}       Sine/triangle sub
  NoiseOscillator.{h,cpp}     White/pink noise
  Filter.{h,cpp}              Seven filter models and cached coefficients
  FilterBank.{h,cpp}          Two slots and routing
  Envelope.{h,cpp}            ADSR with curve
  LFO.{h,cpp}                 Continuous phase, sync and shape editing
  ModulationMatrix.{h,cpp}    Connections
  FXProcessor.{h,cpp}         Independent effect state per rack slot
  EffectUnits/                Hyper, Chorus, Flanger, Phaser, Distortion, EQ,
                              Compressor, Delay, Reverb
  MacroControls.{h,cpp}       Four assignable macros
  RAVEButton.{h,cpp}          Render-time boost, without rewriting parameters
  SynthVoice.{h,cpp}          Per-voice oscillators, filters and envelopes
  Engine.{h,cpp}              32 voices, MIDI timing, control snapshots, FX
  GUI/                        Controls and previews
  Presets/FactoryPresets.cpp  Factory preset definitions
  Resources/                  SVG assets

The engine splits rendering at MIDI event positions and into segments of at most 64 samples. Buffers and effect instances are prepared ahead of time. GUI edits use a control-side copy; audio tries to capture that copy once per host block and keeps its previous settings if an edit is still in progress.

Behavior notes

  • Loading a factory preset resets sound parameters before applying its settings, while preserving UI scale.
  • RAVE is automatable and saved with the project. It changes the rendered sound without moving the underlying knobs; turning it off removes the boost.
  • Modulation of Master and FX mixes uses global sources directly. Envelope, velocity, note and random sources for these destinations use the newest active voice.
  • Effect types can repeat across slots. Reordering swaps settings, not running delay or reverb tails. Preconstructing effects uses more memory but avoids allocating effect instances while audio is running.
  • Filter graphs are schematic previews, not measurements of the nonlinear DSP.
  • LFO shape data is stored as JSON text inside the project's XML state. Older saves containing only the literal [Array] did not preserve the shape data and fall back to the default pattern.

Building

The scripts live in scripts/. Each configures and builds its own directory under build/. Production builds disable the QA harness explicitly; the harness script creates no plugin or standalone targets. JUCE 7.0.12 is vendored under third_party/JUCE and is not downloaded by the scripts.

Prerequisites

The build scripts require Bash, CMake 3.22+, Ninja, and C/C++17 compilers. The harness script also uses CTest, included with CMake, when invoked with --run.

On Linux, install GCC or Clang, pkg-config, and the ALSA, FreeType, X11 and OpenGL development libraries. For Debian/Ubuntu:

sudo apt update
sudo apt install build-essential cmake ninja-build pkg-config \
    libasound2-dev libfreetype6-dev libx11-dev libxcomposite-dev \
    libxcursor-dev libxext-dev libxinerama-dev libxrandr-dev libxrender-dev \
    libgl1-mesa-dev

For Arch/CachyOS:

sudo pacman -S --needed base-devel cmake ninja pkgconf alsa-lib freetype2 \
    libx11 libxcomposite libxcursor libxext libxinerama libxrandr libxrender libglvnd

JACK is disabled by JUCE's default JUCE_JACK=0. Curl and the web browser are explicitly disabled in both project targets. Their development packages are not required for these builds, even though JUCE probes for them during configuration. See third_party/JUCE/docs/Linux Dependencies.md if enabling additional JUCE modules.

On macOS, install Apple's Command Line Tools with xcode-select --install, or select an installed Xcode toolchain. Install CMake and Ninja, for example with brew install cmake ninja if Homebrew is already installed. The macOS script checks that xcrun can locate the macOS SDK. It must run on a Mac; JUCE does not provide an Apple cross-compilation toolchain for Linux.

For Windows cross-compilation, also install MinGW-w64 on the Linux host:

sudo apt install gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64 binutils-mingw-w64-x86-64

On Arch/CachyOS, use sudo pacman -S --needed mingw-w64-gcc instead. Keep the native Linux tools and libraries installed: JUCE builds juceaide for the host before compiling the Windows plugin. The checked-in cmake/mingw64-toolchain.cmake selects the x86_64-w64-mingw32-* tools and isolates target library searches.

Production builds

Host Command Build directory Formats
Linux ./scripts/build_linux.sh build/linux/ VST3, Standalone
macOS ./scripts/build_macos.sh build/macos/ VST3, AU, Standalone
Linux targeting Windows x64 ./scripts/build_windows.sh build/windows/ VST3 only

Default Release artifacts:

  • Linux: build/linux/SerumAlt_artefacts/Release/VST3/SerumAlt.vst3 and build/linux/SerumAlt_artefacts/Release/Standalone/SerumAlt.
  • macOS: build/macos/SerumAlt_artefacts/Release/VST3/SerumAlt.vst3, build/macos/SerumAlt_artefacts/Release/AU/SerumAlt.component, and build/macos/SerumAlt_artefacts/Release/Standalone/SerumAlt.app.
  • Windows: build/windows/SerumAlt_artefacts/Release/VST3/SerumAlt.vst3, also copied to SerumAlt_Windows/SerumAlt.vst3.

The Windows script reuses the toolchain file without rewriting it. A cross-build cannot run JUCE's manifest helper, because that helper is a host tool, so the script copies the moduleinfo.json that CMake writes from cmake/moduleinfo.json.in. The plugin name, version, vendor, class IDs and VST3 SDK version come from variables in cmake/Plugin.cmake, so a version bump reaches the manifest on its own. MinGW links its C++ and threading runtimes statically via -static-libgcc -static-libstdc++ -static. Before staging, the script verifies the linked module is x86-64, imports none of those MinGW runtimes, and exports the VST3 entry points, so a module that a stock Windows host could not load fails the build instead of being staged silently.

macOS builds target the host architecture by default. To request a universal binary:

CMAKE_OSX_ARCHITECTURES='arm64;x86_64' ./scripts/build_macos.sh

CMAKE_OSX_DEPLOYMENT_TARGET can also be passed to that script. Architecture and SDK settings persist in CMake's cache. Use a separate build directory when changing toolchains. These scripts build local artifacts, not signed or notarized releases, and do not install plugins into a DAW's plugin directories.

Independent QA harness

On Linux or macOS, build the console harness without production targets:

./scripts/build_harness.sh

Build and run the checks:

./scripts/build_harness.sh --run

Or rerun an already-built Release harness:

ctest --test-dir build/harness --build-config Release --output-on-failure --no-tests=error

The executable is build/harness/SerumAltTest_artefacts/Release/SerumAltTest. It renders the initial state and every factory preset, checks for non-finite samples and aggregate non-silence, and exercises the RAVE toggle. A failed check returns a nonzero exit status. This is a processor integration smoke test, not a complete DSP, GUI, or real-time safety test suite.

The harness does not open an editor or audio device, but it still compiles the real processor, editor and JUCE GUI dependencies. Its separate build directory and CMake options isolate its outputs from production bundles; it is not a DSP-only library. It is built and run natively, not as a Windows cross-compiled executable.

Build options and layout

  • BUILD_TYPE defaults to Release; set it to Debug for a debug build.
  • BUILD_DIR overrides a script's build directory. Relative paths are resolved from the repository root, even when the script is invoked elsewhere.
  • CMAKE_BUILD_PARALLEL_LEVEL defaults to 2 to limit memory use, including JUCE's configure-time helper build. Increase it if memory permits.
  • Windows-only OUTPUT_DIR overrides the staging directory.

For example:

BUILD_TYPE=Debug BUILD_DIR=build/harness_debug ./scripts/build_harness.sh --run
CMAKE_BUILD_PARALLEL_LEVEL=4 ./scripts/build_linux.sh

CMakeLists.txt owns the shared source list and target configuration. cmake/Plugin.cmake owns production formats and platform-specific linking. cmake/Harness.cmake owns the test executable and its CTest registration.

For direct CMake use, SERUMALT_BUILD_PLUGIN defaults to ON and SERUMALT_BUILD_TESTS defaults to OFF. A harness-only configure is:

cmake -S . -B build/harness -G Ninja -DCMAKE_BUILD_TYPE=Release \
    -DSERUMALT_BUILD_PLUGIN=OFF -DSERUMALT_BUILD_TESTS=ON
cmake --build build/harness --target SerumAltTest

An existing CMake cache retains its old option values; changing the defaults does not turn tests off in an old build/ directory. Use the scripts to set the flags explicitly. An alternate JUCE checkout can be selected with -DJUCE_ROOT=/path/to/JUCE when configuring directly with CMake.

Fresh builds

The scripts reuse their build directories without deleting files, resetting Git, or installing packages. build/ and SerumAlt_Windows/ are git-ignored. Leave existing builds intact and select an unused build directory for a fresh configure:

BUILD_DIR=build/linux_fresh ./scripts/build_linux.sh

Windows staging updates generated files without deleting extra files already in the destination. For a clean distribution bundle, choose unused build and output paths together:

BUILD_DIR=build/windows_fresh OUTPUT_DIR=build/windows_fresh/staged ./scripts/build_windows.sh

Cleaning

scripts/clean.sh removes generated output. A plain run removes the build trees under build/; --staging adds SerumAlt_Windows/, --legacy adds the build_* and cmake-build-* trees that earlier versions of the scripts left in the repository root, and --all is both. --dry-run prints every path with its size and removes nothing.

./scripts/clean.sh --dry-run --all
./scripts/clean.sh --all

The script never removes a tracked file, anything under Source/, third_party/ or .git, or a path outside the repository. It refuses to run when BUILD_DIR or OUTPUT_DIR names a directory outside the repository, because such a directory belongs to another checkout.

License

JUCE is licensed separately (see third_party/JUCE/LICENSE.md). This project is provided for evaluation/development purposes.

S
Description
Vibe-coded Serum alternative
Readme
15 MiB
0 Stars 1 Watchers 0 Forks
Languages
C++ 92.7%
Shell 3.6%
CMake 2%
JavaScript 1.7%