JUCE Audio Plugin Review
JUCE abstractions hide thread boundaries — always trace which thread each piece of code runs on before declaring it safe.
Step 0 — Run universal checks first
Invoke audio-dsp-review and audio-numerics-review before this skill. This skill adds JUCE-specific checks on top; it does not replace realtime-safety or numerics reviews.
Step 1 — Identify plugin structure
Locate the three core classes and their thread ownership:
AudioProcessor— audio thread ownsprocessBlock; message thread owns everything elseAudioProcessorEditor— message thread only; never call from audio threadAudioProcessorValueTreeState— parameter tree lives on message thread; audio thread must usegetRawParameterValue()raw pointers only
Step 2 — Scan for JUCE violations
| Violation | Where to look | Risk |
|---|---|---|
juce::String construction/concatenation | processBlock, DSP helpers | May allocate and may touch shared string/logging machinery; realtime unsafe |
DBG() macro | Any audio-thread code | Logger I/O in debug builds; blocks indefinitely |
MessageManager::getInstance() from audio thread | Audio callbacks, DSP helpers | Not thread-safe; undefined behaviour |
apvts.getParameter(...) or apvts.state from audio thread | processBlock and DSP helpers | APVTS/ValueTree access is not an audio-thread data path; use raw pointer cache instead |
ValueTree listener callbacks assumed on audio thread | Listener overrides | Dispatched via MessageManager; runs on message thread |
AudioBuffer::setSize() inside processBlock | Buffer management code | Triggers allocation; causes xrun |
| Modifying MIDI buffer while iterating it | MIDI event loops | Iterator invalidation; undefined behaviour |
prepareToPlay not resetting all state (e.g. filters, envelopes, delay lines) | prepareToPlay body | Double-call leaves stale state; causes audio artefacts |
juce::CriticalSection::tryEnter() or enter() on audio thread | processBlock, DSP helpers | exit()/destructor does a syscall to wake waiting threads — not realtime-safe even with tryEnter() |
juce::SpinLock::enter() on audio thread | processBlock, DSP helpers | Busy-waits on audio thread — use tryEnter() + fallback only; non-audio thread should use progressive back-off |
Step 3 — Write the review
## JUCE 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 thread / correctness risk]
Fix: [concrete JUCE-idiomatic suggestion]
### Warnings
[same format]
### What's Done Well
[correct patterns observed]
### Recommended Fixes (priority order)
1. ...
Quick fix table
| Violation | Fix |
|---|---|
juce::String in processBlock | Pre-format on message thread; pass IDs/scalars via AbstractFifo or atomics |
DBG() in audio code | Remove entirely or gate behind a lock-free ring buffer drained on message thread |
MessageManager from audio thread | Use juce::MessageManager::callAsync from message thread only |
apvts.getParameter(id) in audio thread | Cache getRawParameterValue(id) once after APVTS construction; read atomically in processBlock |
ValueTree listener on audio thread | Handle listener callbacks on message thread; pass state to audio thread via atomics |
AudioBuffer::setSize() in processBlock | Call only in prepareToPlay; never resize during playback |
| Mutating MIDI buffer mid-iteration | Collect into a preallocated member buffer or bounded event array, then swap/write back after iteration |
Missing state reset in prepareToPlay | Reset filters, envelopes, delay lines, and position counters unconditionally |
CriticalSection::tryEnter() in audio thread | Replace with std::atomic<T> or lock-free SPSC queue; tryEnter() + exit() is not safe because exit() does a syscall |
SpinLock::enter() on audio thread | Use tryEnter() + fallback only; non-audio thread should use progressive back-off (see references/juce-violations.md Section 4) |
For full code examples (BAD/GOOD patterns, AbstractFifo usage, async dispatch) see
references/juce-violations.md.