# 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.

```sh
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.
