YUP Audio Plugin Review
YUP looks JUCE-like in places, but its plugin API has its own parameter handles, bus layout, editor contract, and wrapper behavior. Review those directly instead of applying APVTS rules.
Step 0 - Run universal checks first
Invoke audio-dsp-review and audio-numerics-review before this skill. This skill adds YUP-specific checks on top; it does not replace realtime-safety or numerics reviews.
Step 1 - Identify plugin structure
Locate the core classes and boundaries:
yup::AudioProcessor - audio thread owns processBlock; wrapper/host code calls lifecycle, state, and preset methods
yup::AudioProcessorEditor - UI/message thread only; never call from audio thread
yup::AudioParameter - atomic scalar value plus listeners for host/UI notification
yup::AudioParameterHandle - audio-thread smoothing/read path; initialise in prepareToPlay
yup::AudioBusLayout - immutable I/O contract exposed to plugin wrappers
extern "C" yup::AudioProcessor* createPluginProcessor() - plugin entry point used by wrappers
Step 2 - Scan for YUP violations
| Violation |
Where to look |
Risk |
AudioParameterHandle default-constructed and used before prepareToPlay |
Members and processBlock |
Null parameter assertion/crash; wrong smoothing state |
Missing handle.updateNextAudioBlock() |
processBlock |
Automation and smoothing lag or never update |
Recreating AudioParameterHandle in processBlock |
Audio callback |
Reinitialises smoothing and may add avoidable work/glitches |
setValueNotifyingHost(), beginChangeGesture(), or endChangeGesture() from audio code |
processBlock, MIDI handlers, DSP helpers |
Listener/host notification path is not a realtime data path |
Allocating voices, buffers, MemoryBlock, String, std::vector, or smart-pointer objects in processBlock |
MIDI note-on, scratch buffers, metering |
Heap traffic causes xruns |
YUP_DBG, Logger::writeToLog, File, URL, JSON/XML, or string formatting in audio code |
Debug and error paths |
Logging/I/O/allocation on audio thread |
| Hard-coded channel pointers without layout checks |
getWritePointer(0/1) and bus setup |
Mono, sidechain, or unusual host layout crashes/corrupts audio |
Mutating MidiBuffer while iterating it |
MIDI loops |
Iterator invalidation or unbounded allocation |
prepareToPlay not resetting all DSP and note state |
Lifecycle methods |
Host reactivation leaves stale filter history, held notes, or ramps |
flush() missing for synths/effects with tails or voices |
CLAP reset handling |
Host reset leaves hanging notes or stale delay/reverb tails |
| State load/save left unimplemented |
loadStateFromMemory, saveStateIntoMemory |
DAW session recall and presets fail |
Long work under getProcessLock() or state swaps with no handoff |
UI/state/background code |
Processing may skip under CLAP try-lock or race under other wrappers |
| Editor stores raw processor-owned UI pointers or processor stores editor pointer |
Editor/processor members |
Dangling pointer when host closes editor |
Step 3 - Check YUP API contracts
- Constructor: parameters are added once; IDs are unique and stable; bus layout matches
PLUGIN_IS_SYNTH / PLUGIN_IS_MONO intent.
prepareToPlay: allocates and resets all state; constructs handles with current sample rate; tolerates repeated calls.
processBlock: reads smoothed parameters through handles; respects audioBuffer.getNumSamples() and actual channel count; clears or writes every output sample it owns.
- MIDI: consumes events in sample order; uses bounded voice/event storage; clears or rewrites outgoing MIDI deliberately.
- Editor: uses gesture begin/end around drags; calls
setValueNotifyingHost() for user changes; polls parameter values with a timer using dontSendNotification.
- State/presets: serialises all host-visible parameters and preset data; validates memory before applying; does not assume state callbacks are the audio thread.
- Format wrappers: CLAP and VST3 builds are enabled only as needed; CLAP/VST3/standalone targets all link the same
${target_name}_shared code.
Step 4 - Write the review
## YUP Plugin Review: `[file / class]`
### Verdict
[Safe | Has critical violations | Warnings only] - [one sentence summary]
### Critical Violations
**[Category]: [description]**
`file:line` - `offending code`
Why: [one sentence on YUP lifecycle/thread/API risk]
Fix: [concrete YUP-idiomatic suggestion]
### Warnings
[same format]
### What's Done Well
[correct patterns observed]
### Recommended Fixes (priority order)
1. ...
Quick fix table
| Violation |
Fix |
| Direct unsmoothed parameter read for DSP |
Keep AudioParameter::Ptr, initialise AudioParameterHandle in prepareToPlay, read via the handle |
| Missing gesture pairing |
Call beginChangeGesture() on drag start and endChangeGesture() on drag end |
UI value changes with setValue() |
Use setValueNotifyingHost() for user edits so automation/host state is notified |
| Allocating voices on note-on |
Preallocate voice slots and reuse them; reject/steal voices when full |
| Hard-coded stereo |
Declare stereo in AudioBusLayout and guard getNumChannels() before channel access |
| Missing reset |
Clear voices, delay lines, filters, smoothers, and pending MIDI in both prepareToPlay and flush() where relevant |
| Unimplemented state recall |
Serialise parameters and presets into MemoryBlock; validate and apply from non-audio state callbacks |
| Audio-to-UI updates |
Write scalars to atomics or a preallocated lock-free queue; have editor Timer poll them |
For full code examples and YUP-specific BAD/GOOD patterns, see references/yup-violations.md.
1---2name: yup-review3description: Reviews YUP audio plugin code for YUP-specific correctness issues: AudioProcessor lifecycle, AudioParameterBuilder and AudioParameterHandle usage, editor gestures, AudioBusLayout handling, CLAP/VST3 wrapper contracts, state recall, and MIDI safety. Use when the user asks to review a YUP plugin, check a processBlock, audit parameter smoothing, or asks "is this YUP code safe?". Trigger when you see yup::AudioProcessor, yup_audio_plugin, AudioParameterBuilder, AudioParameterHandle, AudioProcessorEditor, or createPluginProcessor in the code.4---56# YUP Audio Plugin Review78YUP looks JUCE-like in places, but its plugin API has its own parameter handles, bus layout, editor contract, and wrapper behavior. Review those directly instead of applying APVTS rules.910## Step 0 - Run universal checks first1112Invoke `audio-dsp-review` and `audio-numerics-review` before this skill. This skill adds YUP-specific checks on top; it does not replace realtime-safety or numerics reviews.1314## Step 1 - Identify plugin structure1516Locate the core classes and boundaries:17- `yup::AudioProcessor` - audio thread owns `processBlock`; wrapper/host code calls lifecycle, state, and preset methods18- `yup::AudioProcessorEditor` - UI/message thread only; never call from audio thread19- `yup::AudioParameter` - atomic scalar value plus listeners for host/UI notification20- `yup::AudioParameterHandle` - audio-thread smoothing/read path; initialise in `prepareToPlay`21- `yup::AudioBusLayout` - immutable I/O contract exposed to plugin wrappers22- `extern "C" yup::AudioProcessor* createPluginProcessor()` - plugin entry point used by wrappers2324## Step 2 - Scan for YUP violations2526| Violation | Where to look | Risk |27|-----------|--------------|------|28| `AudioParameterHandle` default-constructed and used before `prepareToPlay` | Members and `processBlock` | Null parameter assertion/crash; wrong smoothing state |29| Missing `handle.updateNextAudioBlock()` | `processBlock` | Automation and smoothing lag or never update |30| Recreating `AudioParameterHandle` in `processBlock` | Audio callback | Reinitialises smoothing and may add avoidable work/glitches |31| `setValueNotifyingHost()`, `beginChangeGesture()`, or `endChangeGesture()` from audio code | `processBlock`, MIDI handlers, DSP helpers | Listener/host notification path is not a realtime data path |32| Allocating voices, buffers, `MemoryBlock`, `String`, `std::vector`, or smart-pointer objects in `processBlock` | MIDI note-on, scratch buffers, metering | Heap traffic causes xruns |33| `YUP_DBG`, `Logger::writeToLog`, `File`, `URL`, JSON/XML, or string formatting in audio code | Debug and error paths | Logging/I/O/allocation on audio thread |34| Hard-coded channel pointers without layout checks | `getWritePointer(0/1)` and bus setup | Mono, sidechain, or unusual host layout crashes/corrupts audio |35| Mutating `MidiBuffer` while iterating it | MIDI loops | Iterator invalidation or unbounded allocation |36| `prepareToPlay` not resetting all DSP and note state | Lifecycle methods | Host reactivation leaves stale filter history, held notes, or ramps |37| `flush()` missing for synths/effects with tails or voices | CLAP reset handling | Host reset leaves hanging notes or stale delay/reverb tails |38| State load/save left unimplemented | `loadStateFromMemory`, `saveStateIntoMemory` | DAW session recall and presets fail |39| Long work under `getProcessLock()` or state swaps with no handoff | UI/state/background code | Processing may skip under CLAP try-lock or race under other wrappers |40| Editor stores raw processor-owned UI pointers or processor stores editor pointer | Editor/processor members | Dangling pointer when host closes editor |4142## Step 3 - Check YUP API contracts4344- Constructor: parameters are added once; IDs are unique and stable; bus layout matches `PLUGIN_IS_SYNTH` / `PLUGIN_IS_MONO` intent.45- `prepareToPlay`: allocates and resets all state; constructs handles with current sample rate; tolerates repeated calls.46- `processBlock`: reads smoothed parameters through handles; respects `audioBuffer.getNumSamples()` and actual channel count; clears or writes every output sample it owns.47- MIDI: consumes events in sample order; uses bounded voice/event storage; clears or rewrites outgoing MIDI deliberately.48- Editor: uses gesture begin/end around drags; calls `setValueNotifyingHost()` for user changes; polls parameter values with a timer using `dontSendNotification`.49- State/presets: serialises all host-visible parameters and preset data; validates memory before applying; does not assume state callbacks are the audio thread.50- Format wrappers: CLAP and VST3 builds are enabled only as needed; CLAP/VST3/standalone targets all link the same `${target_name}_shared` code.5152## Step 4 - Write the review5354```markdown55## YUP Plugin Review: `[file / class]`5657### Verdict58[Safe | Has critical violations | Warnings only] - [one sentence summary]5960### Critical Violations61**[Category]: [description]**62`file:line` - `offending code`63Why: [one sentence on YUP lifecycle/thread/API risk]64Fix: [concrete YUP-idiomatic suggestion]6566### Warnings67[same format]6869### What's Done Well70[correct patterns observed]7172### Recommended Fixes (priority order)731. ...74```7576## Quick fix table7778| Violation | Fix |79|-----------|-----|80| Direct unsmoothed parameter read for DSP | Keep `AudioParameter::Ptr`, initialise `AudioParameterHandle` in `prepareToPlay`, read via the handle |81| Missing gesture pairing | Call `beginChangeGesture()` on drag start and `endChangeGesture()` on drag end |82| UI value changes with `setValue()` | Use `setValueNotifyingHost()` for user edits so automation/host state is notified |83| Allocating voices on note-on | Preallocate voice slots and reuse them; reject/steal voices when full |84| Hard-coded stereo | Declare stereo in `AudioBusLayout` and guard `getNumChannels()` before channel access |85| Missing reset | Clear voices, delay lines, filters, smoothers, and pending MIDI in both `prepareToPlay` and `flush()` where relevant |86| Unimplemented state recall | Serialise parameters and presets into `MemoryBlock`; validate and apply from non-audio state callbacks |87| Audio-to-UI updates | Write scalars to atomics or a preallocated lock-free queue; have editor `Timer` poll them |8889For full code examples and YUP-specific BAD/GOOD patterns, see `references/yup-violations.md`.