Fix High-severity real-time audio-thread safety issues from TODO.md
- synthChanged() no longer builds JaySynthActionListener Strings and calls sendActionMessage directly on the audio thread. It now pushes a small POD event into a lock-free SPSC queue (JUCE's AbstractFifo), drained by the existing 10ms UI timer on the message thread, which does the String-building/posting there instead. - VoiceProcessDataV's ~450KB-900KB of SYNTH_MAX_BUFSIZE-sized local arrays are now persistent per-voice buffers allocated once in VoiceSetBufsize (freed in VoiceFree), matching the pattern already used for pBuf_Q and by vcf.c/env.c/lfo.c, instead of being reallocated on the stack on every audio block. - SynthDebug uses vsnprintf instead of vsprintf, bounding writes to its 1024-byte buffer. Verified with clean debug and release builds (no new warnings) and a live playback smoke test in Reaper. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011dhtwRLARk4eiPngcQykLJ
This commit is contained in:
@@ -12,11 +12,11 @@ Findings from a code-review pass over `src/plug` and `src/synth` (2026-07-27, br
|
||||
- [x] **Unchecked file stream could be null, crashing on plugin construction.** `JaySynth::JaySynth` (`src/plug/JaySynth.cpp:55-57`) now guards the wavetable file read against `createInputStream()` returning null.
|
||||
- [x] **Parameter-changing calls raced with real-time audio rendering — no lock protected them.** `JaySynth::setParameter`/`setControl`/`setPerVoiceControl`/`setParam`/`ClearControls` (`src/plug/JaySynth.cpp`) now take `ScopedLock sl(lock)`, matching `renderNextBlock` and the note/controller handlers.
|
||||
|
||||
## High — real-time audio-thread safety
|
||||
## High — real-time audio-thread safety (fixed)
|
||||
|
||||
- [ ] **Every note/CC/voice-count change allocates Strings and posts a message from the audio thread.** `JaySynthActionListener::call*` (`src/plug/PluginProcessor.h:41-127`) build messages via chained `String` concatenation, invoked synchronously from `synthChanged` inside `noteOn`/`noteOff`/`handleController`/`Voicestart`/`renderNextBlock` — all on the audio thread whenever the GUI is open. Fix: pass typed structs instead of encoding into Strings, or hop to the message thread before allocating.
|
||||
- [ ] **~450KB–900KB of stack arrays allocated per voice per audio block.** `VoiceProcessDataV` (`src/synth/voice.c:836-858`) declares ~11 `SYNTH_MAX_BUFSIZE`-sized locals, called once per active voice per block from the worker threads. Fix: hoist these into `voice_common_t`/`voice_t` as pre-allocated buffers (same pattern already used by `vcf.c`/`env.c`/`lfo.c`/`blit.c`).
|
||||
- [ ] **`SynthDebug` uses unbounded `vsprintf` into a fixed 1024-byte buffer plus blocking I/O**, called from `handleMidiEvent` inside the audio-thread render path (`src/synth/synth_debug.c:27-38`). Fix: use `vsnprintf` with the buffer size, and consider a lock-free ring buffer instead of blocking I/O for debug builds.
|
||||
- [x] **Every note/CC/voice-count change allocated Strings and posted a message from the audio thread.** `synthChanged()` (`src/plug/PluginProcessor.cpp`) no longer calls `JaySynthActionListener::call*` directly from the audio thread. It now pushes a small POD event into a lock-free single-producer/single-consumer queue (JUCE's `AbstractFifo`, added to `PluginProcessor.h`), and the existing 10ms UI `Timer` (`timerCallback`/`dispatchUiEvents`) drains it on the message thread, where the String-building and `sendActionMessage` now safely happen.
|
||||
- [x] **~450KB–900KB of stack arrays allocated per voice per audio block.** `VoiceProcessDataV`'s 11 `SYNTH_MAX_BUFSIZE`-sized locals (`src/synth/voice.c`) are now persistent per-voice buffers (`pBuf_VCO_fmout`, `pBuf_vco_pwm`, etc., declared in `voice.h`), allocated once in `VoiceSetBufsize`/freed in `VoiceFree` — same lifecycle/pattern already used by `pBuf_Q` and by `vcf.c`/`env.c`/`lfo.c`.
|
||||
- [x] **`SynthDebug` used unbounded `vsprintf` into a fixed 1024-byte buffer.** `src/synth/synth_debug.c` now uses `vsnprintf` with the buffer size. (Left the blocking `puts()`/`OutputDebugString` I/O as-is — it's compiled out entirely unless `SYNTH_DEBUG` is defined, so release builds are unaffected, and a lock-free logging redesign felt like overreach for a debug-only path.)
|
||||
|
||||
## Memory management
|
||||
|
||||
|
||||
Reference in New Issue
Block a user