Files
JaySynth/CLAUDE.md
T

6.2 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

What this is

JaySynth is a subtractive/wavetable software synthesizer built as a VST2 audio plugin using JUCE (bundled at sdk/juce/JUCE-3.1.1) and the Steinberg VST2.4 SDK (sdk/vst/vstsdk2.4). It targets Linux (.so) and Windows (.dll) hosts. Development/testing happens against the Carla plugin host (~/jaysynth+tal.carxp) and Reaper.

Build

Building is plain GNU Make, driven by two submodule-provided include files (submodule/make/defaults.mk, compile.mk, link.mk, pulled in via MAKE_HOME). There is no CMake/autotools.

make                # build release .so into build/linux/release/
make CONFIG=debug    # debug build
make install         # copies waves.bin + JaySynth.so into ~/.vst/jaysynth/
make run             # install, then launch Carla with the test project (~/jaysynth+tal.carxp)
make bear            # distclean + rebuild via `bear` to regenerate compile_commands.json (used by clangd)

Windows build uses Makefile.win with the same PACKAGES structure but different tool names (Source, Synth, Fir targets instead of lowercase).

Submodules must be checked out (git submodule update --init) before building:

  • submodule/make — shared Makefile fragments (compiler flags, link rules)
  • submodule/fir — FIR/resampling C library used by the synth core

sdk/juce and sdk/vst are vendored SDKs, not submodules — they're expected to already exist at those paths.

There are no automated tests in this repo; verification is manual, by building, installing, and playing the plugin in a host (Carla/Reaper — see .vscode/launch.json for debug configs).

Architecture

The codebase has a hard split between a portable C DSP core and a C++/JUCE plugin shell, built as separate sub-Makefiles and linked together:

  • src/synth/ — pure C DSP engine (no JUCE, no C++). Oscillators (vco.c, BLIT-based band-limited synthesis in blit.c), wavetables (wavetable.c, mw_wave_data.c — Waldorf Microwave/PPG-style wavetables), filter (vcf.c), envelopes (env.c), LFOs (lfo.c), noise (noise.c), and parameter scaling (param_scale.c). synth_float_t is a build-time typedef (double on Linux, float on Windows — set via -Dsynth_float_t=... in the Makefiles).
    • voice.h/voice.c is the center of this layer: a voice_t (per-voice state: 2 VCOs, 4 LFOs, 4 envelopes, 1 VCF) driven by a shared voice_common_t (global LFO/env/VCF/noise tables and shared per-block buffers, to avoid recomputing identical modulation sources per voice). All per-voice parameters are addressed by the flat VOICE_PARAM_* enum in voice.h; modulation routing (FM/AM/PWM sources for oscillators, filter cutoff/Q sources) is expressed as source/amount/op triples pointing into VOICE_MOD_SRC_*.
    • Voice audio rendering can run multi-threaded: JaySynthThread (in src/plug/JaySynth.h) splits the voice array across CPU cores, each thread calling VoiceProcessDataV.
  • src/plug/ — the JUCE AudioProcessor/Synthesiser plugin implementation, C++11.
    • PluginProcessor.{h,cpp} (JaySynthAudioProcessor) is the VST entry point: parameter get/set, program (patch) management, and all patch/bank XML and VST .fxp/.fxb import/export.
    • JaySynth.{h,cpp} (class JaySynth : public Synthesiser) owns the array of voice_t (via JaySynthVoice), MIDI note/CC/NRPN handling, humanize/unison/monophonic modes, and MIDI clock sync (JK_MidiClock).
    • JaySynthSound is a patch: a flat parameter[SYNTH_NUM_PARAMS] array plus a JaySynthMidiCC (per-parameter MIDI CC/NRPN assignment table). JaySynthAudioProcessor holds NUM_PROGRAMS (64) of these plus a separate "current patch" edit buffer, and tracks a modified/"[edited]" state independent of the stored program.
    • JaySynthMidiCC/MidiNrpn/MidiCC_PopUp implement per-parameter MIDI-learn: any synth parameter can be bound to a CC or NRPN controller, with absolute/relative modes.
    • JaySynthAudioProcessorEditor + graph_window/ButtonLED/VelKey_PopUp are the custom GUI (no Projucer-managed component files beyond the .jucer's single editor group — most UI is hand-written).
    • Notifications from the audio/synth layer to the GUI go through JaySynthActionListener (a JUCE::ActionBroadcaster wrapping typed events like JAYSYNTH_CHANGED_PARAM, JAYSYNTH_CHANGED_MIDICC, JAYSYNTH_CHANGED_PROGRAM as encoded strings), not direct callbacks — needed because it crosses the audio-thread/message-thread boundary.
  • extras/waves/ — binary wavetable sets (Microwave/PPG format, 8/12-bit) that must be installed as waves.bin next to the plugin for the wavetable oscillator feature to activate (see extras/waves.readme).
  • extras/sounds/ — example patches/banks in both VST2 native format (.fxp/.fxb) and a custom XML format (.xmp/.xmb, patch/bank respectively) — these are the two import/export paths implemented in PluginProcessor.cpp (patchImportXml/bankImportXml vs. setCurrentProgramStateInformation/setStateInformation).
  • matlab/ — MATLAB/Octave prototyping scripts for the DSP algorithms (oscillator BLEP/BLIT, filter, envelope, LFO, limiter, param curves) — reference models for the C implementation, not part of the build.
  • doc/papers/ — background papers on the DSP techniques used (band-limited synthesis, wavetables).
  • history.txt — the changelog (German/English mixed); check here before assuming a described bug is still open.
  • todo.txt — open design/feature notes, also mixed German/English.

Conventions to be aware of

  • Per-voice DSP parameters are plain enums (VOICE_PARAM_*), not named struct fields — when adding a synth parameter, it must be threaded through the enum in voice.h, param_scale.c's scaling table, and the plugin-side parameter name/XML (de)serialization in PluginProcessor.cpp.
  • Modulation source enums are duplicated per destination (VOICE_MOD_SRC_0_* vs VOICE_MOD_SRC_1_* vs the general VOICE_MOD_SRC_*) because not all destinations can be modulated by all sources (e.g. VCO0 can't modulate itself) — keep this distinction when touching mod routing.
  • Comments and log/history text are a German/English mix; don't assume English-only when grepping for TODOs or history entries.