Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011dhtwRLARk4eiPngcQykLJ
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 inblit.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_tis a build-time typedef (doubleon Linux,floaton Windows — set via-Dsynth_float_t=...in the Makefiles).voice.h/voice.cis the center of this layer: avoice_t(per-voice state: 2 VCOs, 4 LFOs, 4 envelopes, 1 VCF) driven by a sharedvoice_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 flatVOICE_PARAM_*enum invoice.h; modulation routing (FM/AM/PWM sources for oscillators, filter cutoff/Q sources) is expressed as source/amount/op triples pointing intoVOICE_MOD_SRC_*.- Voice audio rendering can run multi-threaded:
JaySynthThread(insrc/plug/JaySynth.h) splits the voice array across CPU cores, each thread callingVoiceProcessDataV.
src/plug/— the JUCEAudioProcessor/Synthesiserplugin 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/.fxbimport/export.JaySynth.{h,cpp}(class JaySynth : public Synthesiser) owns the array ofvoice_t(viaJaySynthVoice), MIDI note/CC/NRPN handling, humanize/unison/monophonic modes, and MIDI clock sync (JK_MidiClock).JaySynthSoundis a patch: a flatparameter[SYNTH_NUM_PARAMS]array plus aJaySynthMidiCC(per-parameter MIDI CC/NRPN assignment table).JaySynthAudioProcessorholdsNUM_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_PopUpimplement 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_PopUpare 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(aJUCE::ActionBroadcasterwrapping typed events likeJAYSYNTH_CHANGED_PARAM,JAYSYNTH_CHANGED_MIDICC,JAYSYNTH_CHANGED_PROGRAMas 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 aswaves.binnext to the plugin for the wavetable oscillator feature to activate (seeextras/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 inPluginProcessor.cpp(patchImportXml/bankImportXmlvs.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 invoice.h,param_scale.c's scaling table, and the plugin-side parameter name/XML (de)serialization inPluginProcessor.cpp. - Modulation source enums are duplicated per destination (
VOICE_MOD_SRC_0_*vsVOICE_MOD_SRC_1_*vs the generalVOICE_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.