Skip to content

Safety fixes, random voice generator, resizable UI, 4-op sysex import#517

Open
wellsst wants to merge 9 commits intoasb2m10:masterfrom
wellsst:fix/phase1-safety-critical
Open

Safety fixes, random voice generator, resizable UI, 4-op sysex import#517
wellsst wants to merge 9 commits intoasb2m10:masterfrom
wellsst:fix/phase1-safety-critical

Conversation

@wellsst
Copy link

@wellsst wellsst commented Mar 14, 2026

Summary

  • Fix 12 safety-critical bugs: null derefs, out-of-bounds array access, undefined behavior from invalid shift amounts, division by zero, buffer overflows, and data corruption in sysex handling
  • Fix CI/build issues: script injection in GitHub Actions, codesign typo in macOS installer, wrong CMake flag on Windows CI, remove obsolete MSVC macros, add missing include guards
  • Fix EngineMkI audio quality bug: gain interpolation in compute_fb2/compute_fb3 was always zero, causing zipper noise on algorithms 4 and 6
  • Add random voice generator (RND button): generates musically-biased random DX7 patches with sensible envelope shapes, harmonic frequency ratios, and subtle LFO
  • Add resizable window: corner drag handle with aspect ratio constraint, scales from 50% to screen-height-fitted maximum
  • Add 4-operator sysex import: automatically detects and converts DX21/DX27/DX100/TX81Z bulk sysex files to DX7-compatible 6-operator format

Details

Safety-Critical Fixes

Fix File
Add missing return after null file error PluginData.cpp
Mask algorithm index to 0-31 dx7note.cc
Change sysex offset from uint8 to int PluginProcessor.cpp
Guard PluginFx::process against zero-length buffers PluginFx.cpp
Add default case to filter switch PluginFx.cpp
Clamp shift amounts in Exp2::lookup and Freqlut::lookup exp2.h, freqlut.cc
Null-terminate opSwitch after strncpy PluginData.cpp
Add null check in setupStartupCart PluginData.cpp
Remove memcpy overwriting normalized sysex data PluginData.cpp
Clamp portamento_cc from XML to 0-127 PluginData.cpp
Guard pitchStep division against zero dx7note.cc

4-Op Import

Converts 4-op patches by mapping algorithms (1-8 → DX7 equivalents), scaling envelope rates (0-31 → 0-99), converting DX100 frequency ratio indices to DX7 coarse+fine, and silencing unused operators 5-6. Tested with DX100 factory patches.

Test plan

  • Build succeeds on Windows (VS 2022 Build Tools, MSVC 14.44)
  • Standalone app launches and plays audio correctly
  • RND button generates varied, musical random patches
  • Window resizes with corner drag, maintains aspect ratio
  • DX100 .syx files load and play through 4-op converter
  • Build on macOS
  • Build on Linux
  • Test as VST3 plugin in a DAW

🤖 Generated with Claude Code

wellsst and others added 9 commits March 14, 2026 13:19
- Add missing return after null file error in sendSysexCartridge (crash)
- Mask algorithm index to 0-31 range in dx7note init/update (OOB crash)
- Change sysex offset from uint8 to int to prevent truncation (wrong memory write)
- Guard PluginFx::process against zero-length buffers (crash in some DAWs)
- Add default case to filter switch to prevent uninitialized variable
- Clamp shift amounts in Exp2::lookup and Freqlut::lookup (undefined behavior)
- Null-terminate opSwitch after strncpy (buffer overread)
- Add null check for InputStream in setupStartupCart (null deref)
- Remove memcpy that overwrote normalized sysex data (dead code / data corruption)
- Clamp portamento_cc loaded from XML to 0-127 (OOB array access)
- Guard pitchStep division against zero and out-of-range values (division by zero)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add RND button to GlobalEditor between STORE and the program selector
- Implement randomizeVoice() that generates musically-biased random DX7 patches
- Operator frequencies use harmonically useful ratios (0,1,2,3,4,5,6,7,8,10,12,16)
- Envelope shapes favor pad-like characteristics: slower decay, high sustain levels
- Pitch EG held flat to prevent unwanted pitch shifts
- LFO kept subtle: low pitch/amp depth, slow speed, favors sine/triangle waveforms
- Feedback capped at 0-4 to avoid harsh distortion
- Random voice names generated as "RND-" + 6 random alphanumeric chars

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add ResizableCornerComponent for drag-to-resize
- Scale UI proportionally using existing AffineTransform zoom mechanism
- Maintain aspect ratio via ComponentBoundsConstrainer
- Limit max size to fit screen height (prevents overflow on ultrawide monitors)
- Minimum size is 50% of base, maximum fits available screen
- Configure standalone window to respect constrainer
- Zoom factor persists via existing preference system

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tion

CI/Build fixes:
- Fix script injection: quote github.ref in CI pipeline
- Fix codesign typo: --stric -> --strict in macOS installer
- Fix Windows CI: COPY_PLUGIN_AFTER_BUILD -> JUCE_COPY_PLUGIN_AFTER_BUILD
- Remove obsolete MSVC int32_t/uint32_t typedefs (unnecessary since VS 2010+)
- Remove deprecated OSMemoryBarrier() on macOS (unused with GCC fence fallback)
- Remove #define snprintf _snprintf (unnecessary since VS 2015+)

Header safety:
- Add #pragma once to lfo.h, freqlut.h, sin.h, exp2.h

Audio quality fix:
- Fix EngineMkI compute_fb2/compute_fb3 gain interpolation: dgain for
  non-feedback operators was always zero (gain - gain = 0), causing zipper
  noise and clicks on fast envelopes with algorithms 4 and 6

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Automatically detects 4-op bulk sysex files (format number 0x04) and
converts them to DX7-compatible 6-operator patches on load.

Conversion details:
- Maps 4-op algorithms 1-8 to DX7 algorithms 1,14,8,7,5,22,31,32
- Scales envelope rates from 4-op range (0-31) to DX7 range (0-99)
- Converts DX100 frequency ratio indices (64-entry lookup table) to
  DX7 coarse + fine frequency parameters
- Maps detune from 4-op range (0-6, center=3) to DX7 (0-14, center=7)
- Sets DX7 operators 5 and 6 to silent (output level 0)
- Preserves LFO, feedback, transpose, and voice names directly
- Works with any .syx file containing the standard 4104-byte bulk format

Tested with DX100 factory patches - loads and plays correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Thread safety:
- Make vuSignal, forceRefreshUI, refreshVoice std::atomic to prevent
  data races between audio and UI threads (critical on ARM/Apple Silicon)
- Move lastCCUsed.setValue() off audio thread: store to atomic pendingCCValue
  on audio thread, poll and forward to Value on UI timer (prevents deadlocks
  from JUCE Value listener callbacks on audio thread)
- Use local float for VU meter loop, store once at end (avoids atomic
  operations per sample)

Drag-and-drop:
- Accept .syx files dropped onto main window (not just cart manager)
- Works with both DX7 and 4-op sysex files

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Persist zoom factor immediately when corner-drag resizing (savePreference)
- Remove constrainer from standalone DocumentWindow — keep only on editor
  to prevent feedback loop where window title bar height caused progressive
  shrinking on each move

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Random patch history:
- RND button now saves each generated patch to a 32-slot ring buffer
- PREV button steps back through random patch history
- Allows recalling previously generated sounds without losing them

Async dialogs:
- Convert all synchronous AlertWindow::showMessageBox calls to
  showMessageBoxAsync in PluginProcessor.cpp and PluginEditor.cpp
- Prevents potential UI freezing and DAW deadlocks from blocking
  modal dialogs on the message thread (deprecated in JUCE 6+)
- Two remaining synchronous dialogs (showOkCancelBox, showYesNoCancelBox)
  left for now as they require callback refactoring

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Keyboard shortcuts:
- Ctrl+R: randomize voice (same as RND button)
- Ctrl+Z: undo randomize (same as PREV button)

Null safety fixes:
- AlgoDisplay: initialize opStatus and algo to nullptr, guard access
- ProgramListBox: initialize listener to nullptr
- VUMeter: guard against null image pointer in paint()
- OperatorEditor: null-check getParentComponent() before chaining
- Env: initialize all members in constructor (staticcount_, level_,
  targetlevel_, rising_, ix_, inc_, down_)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant