All notable changes to the reSIDue project will be documented in this file.
The format is based on Keep a Changelog,
and this project adheres to Semantic Versioning.
[v0.9.51] – 2025-10-13
Reference: Shared Wavetable Storage Architecture Fix
Summary: Fixed critical bug where voices 2 and 3 didn’t share wavetable data with voice 1, and enhanced table selector input behavior
Fixed
- Voice 2/3 Wavetable Data Isolation: Fixed critical bug where wavetables applied to voices 2 or 3 sounded different from voice 1 due to each WavetablePlayer instance having separate empty storage
- Voice Table Selector Immediate Updates: Fixed voice wavetable table selectors in WavetableSelectorComponent updating on every keystroke instead of only on Enter or focus loss
- Filter/Vol Selector Immediate Updates: Fixed global Filter/Vol table selector updating on every keystroke preventing complete hex value entry
Changed
- WavetablePlayer.h (lines 31-35): Added type aliases WavetableStorage and ControlFlagsStorage, changed constructor to take references to shared storage
- WavetablePlayer.h (lines 86-95): Changed storage from per-instance arrays to references (allWavetables&, allWavetablesControl&) pointing to PluginProcessor-owned storage
- WavetablePlayer.cpp (lines 23-31): Updated constructor to accept references and initialize reference members instead of creating per-instance storage
- PluginProcessor.h (lines 212-214): Added sharedWavetables and sharedControlFlags members to own wavetable storage for all 3 voices within plugin instance
- PluginProcessor.cpp (lines 34-41): Updated constructor initializer list to create all 3 wavetablePlayers and globalFilterVolPlayer with references to shared storage
- WavetableSelectorComponent.cpp (lines 102-132, 183-214): Replaced onTextChange with onReturnKey and onFocusLost callbacks for deferred table switching
Technical Implementation
- Shared Storage Pattern: PluginProcessor owns ONE copy of wavetable storage (sharedWavetables, sharedControlFlags) passed as references to all 3 WavetablePlayer instances
- Per-Instance Isolation: Each plugin instance in DAW has its own wavetable storage preventing cross-track data contamination that would occur with static storage
- Reference Member Architecture: WavetablePlayer uses reference members (WavetableStorage&, ControlFlagsStorage&) binding to PluginProcessor storage at construction time
- 1D Array Simplification: Internal WavetablePlayer arrays remain 1D [regType] since each instance represents one voice, not all three voices
- Deferred Input Updates: Table selector text fields buffer typed input until Enter or focus loss preventing incomplete hex value interpretation
Architecture Benefits
- Consistent Voice Behavior: All 3 voices within plugin instance share same wavetable data pool ensuring voices 2 and 3 sound identical to voice 1
- Multi-Instance Safety: Multiple plugin instances in DAW maintain separate wavetable storage preventing unintended cross-track data sharing
- Memory Efficiency: Single shared storage pool per plugin instance eliminates data duplication across voices while maintaining proper isolation
- Better UX: Users can type complete hex table numbers without plugin attempting to switch on partial values during typing
Root Cause Resolution
- Per-Instance Empty Storage: Original architecture had 3 separate WavetablePlayer instances each with independent wavetable storage, but GUI only wrote to instance 0 leaving instances 1 and 2 with empty data
- Text Callback Timing: WavetableSelectorComponent used onTextChange callback triggering table switches on every character typed instead of waiting for complete user input
- Static Storage Consideration: Briefly considered static storage but rejected due to cross-instance contamination issue in multi-track DAW scenarios
Testing Recommendations
- Load same wavetable on all 3 voices – verify they sound identical
- Open multiple plugin instances in DAW – verify wavetable edits don’t affect other tracks
- Type multi-digit hex values in table selectors – verify no intermediate table switching
- Check that table switches occur only on Enter key or field focus loss
[v0.9.50] – 2025-10-12
Reference: Wavetable Index Input UX Improvements
Summary: Enhanced wavetable index field behavior for better user experience when entering table numbers manually
Fixed
- Table Index Field Immediate Switching: Fixed wavetable table index editor switching tables on every keystroke – now only switches on Enter key press or field focus loss allowing complete hex value entry
- Font Size Growth on Input: Fixed wavetable selector components increasing font size to 1.25x normal when typing in index fields – now maintains consistent font size across all operations
Changed
- WavetableTableComponent.cpp (lines 109-135): Replaced onTextChange callback with onReturnKey and onFocusLost callbacks for deferred table switching
- WavetableSelectorComponent.cpp (line 123): Changed font size from 1.25x multiplier to consistent C64FontForEditors() in onTextChange callback
Technical Implementation
- Deferred Table Switching: Table index field now buffers typed input allowing multi-digit hex values without intermediate table changes
- Event-Driven Updates: Table switching triggered by explicit user actions (Enter key, focus change) instead of every character input
- Consistent Typography: All table selector font operations now use FontManager::getC64FontForEditors() eliminating visual size inconsistencies
Architecture Benefits
- Better UX: Users can type complete hex values like “05A” without plugin attempting to switch to incomplete values like “0” then “05” then “05A”
- Visual Consistency: Table selectors maintain uniform appearance across all user interactions and operations
- Predictable Behavior: Table switching occurs at well-defined points matching standard text input conventions
[v0.9.49] – 2025-10-11
Reference: Advanced Sandbox Stability and Initialization Improvements
Summary: Enhanced singleton thread safety, state save/load validation, and embedded factory patch loading system for professional plugin initialization
Fixed
- FontManager Singleton Initialization: Fixed potential crashes during plugin scanning by implementing lazy initialization with double-checked locking pattern instead of eager static initialization
- State Save Memory Spikes: Added 20MB size validation to getStateInformation preventing watchdog timeouts from excessive memory allocations during project saves
- State Load Memory Validation: Added comprehensive null pointer, size, and bounds checking to setStateInformation preventing crashes from corrupted or oversized state data
- Missing Factory Patches Error: Fixed “File does not exist” error on first launch by implementing three-tier fallback system loading from embedded binary data when user file missing
- Font Loading Error Reporting: Replaced catch-all exception handler with specific exception types providing detailed logging of font loading failures
Added
- Thread-Safe Font Loading: Implemented std::mutex and std::atomic with acquire/release memory ordering for multi-instance plugin safety
- State Truncation Marker System: Added ‘TRUC’ marker for oversized state data enabling graceful recovery when state exceeds size limits
- Exception-Safe State Operations: Comprehensive try-catch blocks with bad_alloc handling ensuring plugin continues operating even if state operations fail
- Embedded Factory Patches: Implemented BinaryData loading system automatically loading factory patches from embedded patches/patches.reSIDue when user file not found
- Detailed State Logging: Added comprehensive logging of state save/load operations with byte counts and success/failure messages for debugging
Changed
- FontManager.h (lines 27-135): Complete rewrite of singleton with lazy initialization, double-checked locking, thread safety, and specific exception handling with detailed logging
- PluginProcessor.cpp (lines 270-320): Enhanced getStateInformation with size validation (20MB limit), truncation marker system, and comprehensive exception handling
- PluginProcessor.cpp (lines 323-400): Enhanced setStateInformation with null checks, size validation, truncation marker detection, reserve-before-assign pattern, and graceful error handling
- PatchManagerComponent.cpp (line 31): Added BinaryData.h include for embedded resource access
- PatchManagerComponent.cpp (lines 548-613): Completely rewrote initializePatches() with three-tier fallback: user file → factory patches → default patches
Technical Implementation
- Lazy Singleton Pattern: FontManager uses double-checked locking with atomic flag and mutex ensuring thread-safe initialization only when first accessed
- Memory Ordering: std::atomic operations use acquire/release semantics preventing compiler/CPU reordering issues in multi-threaded environments
- State Size Validation Flow: Check size → validate < 20MB → allocate with reserve → catch bad_alloc → log and degrade gracefully
- Embedded Data Loading:
BinaryData::getNamedResource("patches_reSIDue", dataSize)provides access to compiled-in factory patches from patches/patches.reSIDue - Three-Tier Fallback System: File exists check → load user file OR load embedded data OR create programmatic defaults with appropriate logging at each tier
Architecture Benefits
- Plugin Scanner Safety: Lazy font loading prevents crashes during DAW plugin scanning phase when full initialization not needed
- Memory Spike Prevention: 20MB limits prevent watchdog timeouts during project save operations in restrictive sandbox environments
- Professional First Launch: Users see complete factory patch library instead of errors or minimal defaults improving initial experience
- Multi-Instance Support: Thread-safe singleton allows multiple plugin instances to safely load fonts simultaneously without race conditions
- Graceful State Recovery: Plugin continues operating even when state save/load fails, maintaining stability under all error conditions
Root Cause Resolution
- Static Initialization Order Fiasco: FontManager eagerly loaded font during static initialization causing crashes when binary data not yet available during plugin scanning
- Unbounded Memory Allocation: State save/load performed unbounded vector allocations potentially triggering sandbox watchdog timers with large projects
- Missing Factory Patches: Plugin logged errors and created minimal defaults on first run instead of loading comprehensive factory patch library from embedded resources
- Silent Font Failures: Catch-all exception handler masked specific font loading errors preventing diagnosis of sandbox permission issues
Testing Recommendations
- Launch multiple plugin instances simultaneously – verify no font loading race conditions
- Save/load large projects – verify no watchdog timeouts during state operations
- Delete user patch file and launch – verify factory patches load without errors
- Check logs for font loading success message and state operation byte counts
- Test state restore with intentionally corrupted data – verify graceful continuation
[v0.9.48] – 2025-10-11
Reference: VST Sandbox Crash Prevention
Summary: Comprehensive fixes for VST host sandbox mode crashes including GUI thread safety, exception handling, memory management, and lifetime safety improvements
Fixed
- GUI Thread Safety: Fixed AlertWindow operations being called from non-GUI threads causing Cocoa/Windows assertion failures in sandboxed environments
- Exception Propagation: Replaced catch-all exception handlers with specific exception types preventing unhandled exception crashes
- Memory Safety: Added bounds checking and error handling around const_cast operations preventing undefined behavior and segmentation faults
- Use-After-Free: Fixed FileChooser lambda lifetime issues where component could be destroyed while file dialog was open
- Unbounded Log Growth: Fixed FileLogger using unlimited size (0) which could exhaust disk space in long-running sessions
- Memory Allocation Failures: Added guards against excessive memory allocation during binary serialization preventing watchdog timer crashes
Added
- MessageManager::callAsync Wrapping: All AlertWindow operations now wrapped in MessageManager::callAsync ensuring GUI operations only happen on message thread
- Exception Detail Logging: Added exception message capture and logging before displaying user alerts for better diagnostics
- SafePointer Pattern: Implemented juce::Component::SafePointer in all FileChooser async lambdas with null checks preventing use-after-free
- Memory Usage Validation: Added 5MB reserve with bad_alloc handling and 10MB absolute maximum size check for binary serialization
- FileLogger Size Limit: Set 10MB maximum log file size with automatic rotation by JUCE framework
- Try-Catch Protection: Added try-catch blocks around all const_cast operations with specific error logging
Changed
- FileManager.cpp (lines 1426-1451): Split catch-all into specific std::exception handler and unknown exception handler with MessageManager::callAsync wrapping
- PatchManagerComponent.cpp (lines 698-731): Applied same exception handling pattern to patch loading operations
- PatchManagerComponent.cpp (lines 167-192, 212-242, 591-638, 659-678): Replaced raw
thiscaptures with SafePointer in all FileChooser lambdas - FileManager.cpp (lines 589-631): Added try-catch blocks around const_cast operations in wavetable loading with error logging and early return
- FileManager.cpp (lines 645-676): Protected wavetable control flag loading with try-catch and error handling
- LogManager.cpp (line 71): Changed FileLogger from unlimited size to 10MB limit with automatic rotation
- FileManager.cpp (lines 141-178): Added memory allocation validation with reserve, bad_alloc handling, and size checking in appendData lambda
Technical Implementation
- Thread Safety Pattern:
juce::MessageManager::callAsync([message]() { AlertWindow::showMessageBoxAsync(...); })ensures GUI operations on correct thread - Exception Handling Flow: Catch specific exception → log details → schedule async GUI alert → catch unknown → log generic → schedule async alert
- SafePointer Pattern:
[safeThis = juce::Component::SafePointer<T>(this)]withif (safeThis == nullptr) return;check at lambda entry - Memory Guard Flow: Reserve 5MB → catch bad_alloc → return empty. Then appendData checks size < 10MB before each insertion
- const_cast Safety:
try { auto& ref = const_cast<T&>(...); } catch (const std::exception& e) { log error; return false; }
Architecture Benefits
- Sandbox Compatibility: Plugin now stable in restrictive VST sandbox environments (Logic Pro, Ableton, etc.)
- Crash Prevention: Eliminated primary crash vectors: GUI thread violations, unhandled exceptions, memory errors, use-after-free
- Resource Limits: Prevents unbounded resource consumption (logs, memory) that could trigger watchdog timers
- Graceful Degradation: All error paths log diagnostic information and fail gracefully instead of crashing
- Thread Safety: Proper separation between audio thread, message thread, and async file operations
Root Cause Resolution
- GUI Thread Violations: Direct AlertWindow calls from file I/O contexts could execute on audio thread in sandboxed environments
- Exception Masking: Catch-all handlers silently swallowed exceptions preventing proper error diagnosis and recovery
- Lifetime Issues: Raw
thiscaptures in async lambdas created dangling pointers when components destroyed during file operations - Memory Spikes: Unbounded allocations during binary serialization could trigger sandbox watchdog timers
- Log Accumulation: Unlimited log growth in long sessions could exhaust disk quotas in sandboxed file systems
Testing Recommendations
- Load/save patches while audio playing – verify no dropouts
- Close plugin GUI while file dialog open – verify no crash
- Trigger errors during file operations – verify graceful handling
- Long session with heavy logging – verify log rotation works
- Low memory conditions – verify graceful degradation
- Sandbox mode in Logic Pro / Ableton – verify stable operation
[v0.9.47] – 2025-10-11
Reference: EXEC Jump Destination Row Processing
Summary: Implemented second-pass EXEC parsing after EXEC=0x00/0x01 jump commands to ensure jump destination rows are fully processed
Fixed
- Jump Destination Row Skipped: Fixed critical bug where EXEC=0x00 and EXEC=0x01 jump commands would update row pointer but immediately return without processing the destination row
- Missing Register Updates: Jump destination row register values (FREQ/PULS/CTRL/A/D/S/R) are now applied instead of being skipped
- Unprocessed EXEC Commands: EXEC commands at jump destination rows are now parsed enabling chained jump operations
- Loop Logic Bypass: Loop logic (LOOP=0 row pointer establishment and LOOP>0 counter operations) at jump destinations now executes properly
Added
- Loop-Based EXEC Processing: Implemented
do-whileloop around EXEC command parsing allowing up to 2 parses (initial + 1 reparse after jump) - Jump Detection System: Added
bool didJumpflag to detect when EXEC=0x00/0x01 triggers a jump operation - Parse Counter: Added
int execParseCountcounter limiting EXEC parsing to maximum 2 iterations preventing infinite loops - Chained Jump Support: One level of jump chaining now supported – jump destination can itself contain EXEC jump command
Changed
- WavetablePlayer.cpp (lines 128-183): Wrapped EXEC processing in loop that continues while
didJump && execParseCount < 2 - Jump Return Logic: Replaced immediate
returnstatements after jump withdidJump = true; execParseCount++; continue;pattern - Processing Flow: After loop exits, normal register processing continues with final resolved row ensuring complete row execution
Technical Implementation
- EXEC=0x00 Jump Flow: Updates
state.currentRow, sets flags, continues loop to reparse EXEC at new row ifexecParseCount < 2 - EXEC=0x01 Conditional Jump: Same flow as 0x00 but only when
noteOn && loop.has_value()conditions met - Loop Termination:
while (didJump && execParseCount < 2)ensures maximum 2 EXEC parses preventing endless loop scenarios - Complete Row Processing: Register values, remaining EXEC commands (0x02/0x03), loop logic, and row advancement all execute after final EXEC resolution
Architecture Benefits
- Jump Destination Execution: Jump destination rows now fully processed including all register updates and control logic
- Chained Operations: Enables complex wavetable sequences with jump-to-jump patterns within safety limits
- Infinite Loop Prevention: Hard limit of 2 EXEC parses provides safety against circular jump references
- Maintained State Consistency: All wavetable state operations (loop counters, row pointers) now execute correctly at jump destinations
Root Cause Resolution
- Early Return Problem: Previous implementation used
returnimmediately after updatingstate.currentRowin lines 140 and 155 - Incomplete Execution: Early return skipped register processing (lines 176-193), loop logic (lines 195-233), and row advancement (lines 235-254)
- Single-Pass Limitation: EXEC commands were only parsed once per
processRegisterType()call, ignoring destination row EXEC values
[v0.9.46] – 2025-10-10
Reference: File Picker Path Respect Fix
Summary: Fixed binary and JSON save/load operations to respect user-selected file paths from file picker dialogs instead of redirecting to hardcoded directories
Fixed
- File Picker Path Ignored: Fixed critical bug where file picker dialog selections were ignored and files were always saved to
~/.config/reSIDuedirectory - Binary Save Path Respect: Binary
.reSIDuefiles now save to user-selected directory from file chooser instead of being redirected to hardcoded location - JSON Export Path Respect: JSON export now respects user-selected path from “Export to JSON” file chooser dialog
- Load Path Handling: File loading now accepts full paths from file chooser enabling loading from any user-selected location
Changed
- PatchManagerComponent.cpp (lines 183, 223, 653): Changed from passing filename-only to passing full path using
selectedFile.getFullPathName() - FileManager::savePatchSetBinary(): Now accepts full filepath parameter and uses JUCE File class to handle complete path directly
- FileManager::loadPatchSetBinary(): Now accepts full filepath parameter enabling loading from user-selected locations
- FileManager::savePatchSetToFileV2(): Renamed parameter from
overrideFilenametooverrideFilepathand implemented full path handling with parent directory creation
Technical Implementation
- FileManager.cpp Binary Save: Removed hardcoded
~/.config/reSIDuereconstruction, now uses provided path with JUCE File class for cross-platform compatibility - FileManager.cpp Binary Load: Eliminated directory path reconstruction logic, uses full path from file chooser directly
- FileManager.cpp JSON Export: Added path handling branch – uses full path when provided, falls back to
~/.config/reSIDue/patches.jsonfor automatic saves - Parent Directory Creation: Enhanced file save operations to create parent directories if they don’t exist for user-selected paths
- Extension Handling: Automatic
.reSIDueand.jsonextension enforcement using JUCEhasFileExtension()andwithFileExtension()methods
Architecture Benefits
- User Control Restored: Users can now save/load patch files to any location they choose respecting standard OS file picker behavior
- Cross-Platform Path Handling: JUCE File class ensures proper path handling across Windows, macOS, and Linux
- Backward Compatibility: Default automatic save/load operations still use
~/.config/reSIDuewhen no explicit path provided - Consistent Behavior: File operations now behave as users expect – saved files appear where file picker dialog specified
Root Cause Resolution
- Filename Extraction Bug: Previous implementation extracted only filename from file chooser then reconstructed path to hardcoded directory
- Path Reconstruction Logic: Old code used
getFileNameWithoutExtension()losing user’s directory selection and forcing saves to fixed location - Missing Full Path Usage: File chooser returned complete path via
getFullPathName()but code discarded directory information
[v0.9.45] – 2025-10-08
Reference: Relative Note Scaling System Implementation
Summary: Added EXEC command-controlled relative note scaling system for precise fractional semitone frequency modulation
Added
- Relative Note Scaling Mode: New
relativeNoteScalingboolean flag in WavetableState structure controlling frequency scaling behavior - EXEC 0x02 Command: Enable relative note scaling mode for current wavetable
- EXEC 0x03 Command: Disable relative note scaling mode for current wavetable
- Fractional Semitone Scaling: FrqR values now scale frequency by 1/32768th of a semitone when scaling mode is enabled
- Filter/Vol Initialization: Enhanced
initializeStates()to include Filter/Vol wavetable (index 5) in state initialization
Enhanced
- Dual FrqR Processing Modes: FrqR column now supports both absolute frequency offset (default) and relative note scaling (when enabled)
- Mathematical Precision: Relative scaling uses
2^(1/(12*32768))ratio raised to FrqR value for accurate fractional semitone adjustment - State Parameter Integration: Added WavetableState parameter to
processFreqStep()method for access to scaling mode flag - Complete State Reset:
initializeStates()now properly resetsrelativeNoteScalingto false for all 6 wavetable types
Technical Implementation
- Wavetable.h:79: Added
bool relativeNoteScaling = falseto WavetableState structure with default initialization - WavetablePlayer.cpp:159-163: EXEC 0x02 command sets
state.relativeNoteScaling = true - WavetablePlayer.cpp:165-169: EXEC 0x03 command sets
state.relativeNoteScaling = false - WavetablePlayer.cpp:283-300: Implemented fractional semitone scaling in FrqR processing when
state.relativeNoteScalingis true - WavetablePlayer.cpp:493: Extended initialization loop from 5 to 6 register types including Filter/Vol wavetable
- WavetablePlayer.h:100: Added WavetableState parameter to
processFreqStep()method signature - WavetablePlayer.cpp:176: Updated
processFreqStep()call to pass state reference
Mathematical Formula
- Scaling Calculation:
newFreq = currentFreq * (2^(1/12))^(frqR/32768) - Semitone Ratio:
2^(1/12)represents one semitone frequency ratio - Fractional Division: 1/32768 provides extremely fine-grained pitch control (±15-bit signed range)
- Multiplicative Scaling: Preserves harmonic relationships across frequency range unlike additive offset
Architecture Benefits
- Per-Wavetable Control: Each wavetable can independently enable/disable relative note scaling via EXEC commands
- Backward Compatibility: Default disabled state preserves existing FrqR behavior as absolute frequency offset
- Dynamic Mode Switching: EXEC commands allow real-time switching between scaling modes within same wavetable
- State Initialization: Proper reset to false ensures predictable behavior on patch loading and note triggering
[v0.9.44] – 2025-10-05
Reference: UI Layout and Visual Consistency Improvements
Summary: Enhanced visual consistency across UI components with rounded borders, circuit background, and oscilloscope sizing improvements
Enhanced
- Rounded Border Consistency: Updated WavetableSelectorComponent border to match patch manager styling with 2px thickness and proper corner rounding using
bounds.reduced(1) - Circuit Component Background: Added dark green PCB-style background (0xff003300) to circuit area between wavetable selector and oscilloscope with matching rounded corners and border styling
- Oscilloscope Height Alignment: Removed inner 5px margin from oscilloscope component making it same height as wavetable selector for consistent visual alignment
- Seamless Component Integration: Eliminated gaps between wavetable selector, circuit component, and oscilloscope for unified visual appearance
Technical Implementation
- WavetableSelectorComponent.cpp:331: Changed border from 4.0f to 2.0f thickness and added
bounds.reduced(1)for proper rounded corner rendering - CircuitComponent.cpp:16-35: Implemented dark green background painting in area between wavetable selector and oscilloscope with automatic layout calculation
- CircuitComponent.cpp:20-21: Background spans directly from wavetable selector right edge to oscilloscope left edge with no gaps
- OscilloscopeComponent.cpp:21: Removed
bounds.reduce(5.0f, 5.0f)inner margin for full-height display - OscilloscopeComponent.cpp:127: Updated border to use
borderBounds.reduced(1)matching wavetable selector styling
Visual Improvements
- Consistent Border Styling: All major UI components now use matching 2px border with 8px rounded corners and 1px inset
- PCB Aesthetic Enhancement: Circuit background adds authentic hardware appearance bridging wavetable controls and audio visualization
- Unified Component Heights: Wavetable selector, circuit area, and oscilloscope now align perfectly at same height eliminating visual discontinuity
- Professional Polish: Cohesive visual design with consistent rounded borders and seamless component integration
Architecture Benefits
- Dynamic Layout Calculation: Circuit background automatically adapts to component positions through area-based rendering
- Consistent Styling Pattern: All components follow same border and corner radius conventions for unified visual language
- Clean Component Boundaries: Proper use of
reduced()ensures borders render cleanly within rounded corner areas
[v0.9.43] – 2025-10-03
Reference: Custom Icon Buttons for Patch Management
Summary: Enhanced Load and Save buttons with custom icons and improved visual styling
Added
- LoadBankButton Class: Custom button component displaying Load1.png icon with “Load Bank” text label
- SaveBankButton Class: Custom button component displaying Save1.png icon with “Save Bank” text label
- Icon Integration: Added Load1.png and Save1.png to binary resources in CMakeLists.txt
Enhanced
- Rounded Corners: Increased button corner radius from 4px to 8px for more polished appearance
- Mouse-Over Highlighting: Added hover effect that brightens button background by 20% when mouse enters
- Icon-Text Layout: Icons positioned on left (16px size, 8px padding) with text labels to the right
- Consistent Styling: Both buttons use matching C64 colour scheme with BackgroundMedium base colour
- Professional Polish: Custom painted buttons with proper state visualization (normal, hover, pressed)
Technical Implementation
- PatchManagerComponent.h: Added LoadBankButton and SaveBankButton class definitions with custom paintButton() methods
- Custom Paint Logic: Implemented icon rendering with proper scaling and text positioning using FontManager::getC64FontForEditors()
- Colour Integration: Used C64Colors constants (Button::BackgroundMedium, Button::BackgroundDark, Button::Text) for consistent theming
- Binary Resources: Load1.png (1212 bytes) and Save1.png (1248 bytes) embedded as BinaryData resources
- Simplified Initialization: Removed redundant TextButton styling code, custom buttons handle all rendering internally
Architecture Benefits
- Visual Consistency: Icon buttons match the professional appearance of other custom UI elements
- Maintainable Design: Self-contained button classes with clear paintButton() implementations
- Resource Efficiency: Embedded PNG icons loaded once and cached by JUCE ImageCache
- Code Simplification: Custom buttons require minimal initialization compared to styled TextButtons
User Experience
- Intuitive Interface: Icons provide immediate visual recognition of Load/Save functionality
- Professional Appearance: Rounded corners and hover effects create polished, modern UI feel
- Visual Feedback: Clear hover highlighting provides immediate feedback when interacting with buttons
- Consistent Design: Buttons maintain C64 theme while adding modern visual polish
[v0.9.42] – 2025-10-02
Reference: Windows Compatibility Fix
Summary: Added pre-generated libsidplayfp headers for Windows builds eliminating autogen/configure dependency
Fixed
- Windows Build Compatibility: Fixed missing
siddefs-fpII.hheader that prevented Windows builds without autotools - Cross-Platform Headers: Copied auto-generated headers to
Source/libsidplayfp_compat/directory for universal access - CMake Include Priority: Modified CMakeLists.txt to prioritize compatibility directory ensuring Windows finds pre-generated headers
Technical Implementation
- Pre-Generated Headers: Copied
siddefs-fpII.hfrom Linux build toSource/libsidplayfp_compat/directory - CMakeLists.txt Enhancement: Added
${CMAKE_CURRENT_SOURCE_DIR}/Source/libsidplayfp_compatas first include directory for residfp target (line 119) - Include Path Priority: Compiler checks compatibility directory first, finding pre-generated headers before falling back to libsidplayfp source tree
- No Fork Required: Avoided forking libsidplayfp by shipping generated headers in main project source tree
Architecture Benefits
- Windows Native Support: Windows builds work out-of-the-box with MSVC/MinGW without requiring MSYS2/Cygwin autotools
- Clean Submodule: Maintains upstream libsidplayfp as clean submodule without modifications or fork maintenance
- Minimal Overhead: Small generated header files require minimal maintenance and rarely change between libsidplayfp versions
- Universal Compatibility: Single codebase builds on Linux (with autotools) and Windows (without autotools) using same CMake configuration
User Experience
- Simplified Windows Setup: Windows developers can build directly without installing MSYS2 or running autogen/configure
- Consistent Build Process: Same CMake build process works identically on all platforms
- Reduced Dependencies: Eliminates autotools dependency for Windows builds streamlining development workflow
[v0.9.41] – 2025-10-02
Reference: Build System Integration
Summary: Automated libsidplayfp submodule initialization and configuration for streamlined build process
Enhanced
- Automated Submodule Setup: Updated
build.shto automatically configure libsidplayfp submodule before building - CMake Configuration: Added proper C++14 support and include paths for libsidplayfp headers
- Build Script Integration: Integrated autoreconf and configure steps for libsidplayfp into build workflow
Technical Implementation
- libsidplayfp Initialization: Added autoreconf -fi and ./configure steps to build.sh (Step 2)
- Header File Generation: Automated generation of
siddefs-fp.hvia configure script before CMake runs - C++ Standard Compliance: Added
HAVE_CXX14=1compile definition and C++14 standard requirement for residfp library - Include Path Fix: Added
libsidplayfp/srcto include directories forsidcxx11.hheader access - CMake Build Type: Updated build script to use RelWithDebInfo configuration matching VSCode CMake Tools settings
Fixed
- Missing Header Files: Resolved “siddefs-fp.h: No such file or directory” compilation errors
- C++11 Compiler Check: Fixed “This is not a C++11 compiler” errors by defining HAVE_CXX14 macro
- Submodule Dependencies: Initialized exsid-driver submodule required by libsidplayfp configure script
- Build Process Order: Ensured libsidplayfp configuration runs before CMake configuration step
Build Process
- Step 1: Clean existing build directory
- Step 2: Configure libsidplayfp (autoreconf + ./configure)
- Step 3: Create build directory
- Step 4: Run CMake with RelWithDebInfo
- Step 5: Build with make -j$(nproc)
[v0.9.40] – 2025-10-02
Reference: libsidplayfp Edition
Summary: Major emulation upgrade replacing reSID with reSIDfp for improved filter accuracy, combined waveform modeling, and authentic SID chip behavior
Enhanced
- reSIDfp Integration: Replaced original reSID library with libsidplayfp’s reSIDfp for significantly improved SID emulation accuracy
- Floating-Point Filter Emulation: Advanced filter modeling using floating-point calculations instead of fixed-point for more accurate frequency response
- Combined Waveforms: Enabled STRONG combined waveforms mode using parametrized model based on Kevtris samples for authentic pulse+sawtooth+triangle combinations
- Improved Envelope Generator: Shift register-based envelope implementation matching real MOS 6581/8580 hardware behavior
- Two-Pass Resampling: High-quality audio resampling with computational optimizations for better sound quality
- 2024 Accuracy Updates: Benefits from active libsidplayfp development including oscillator leakage emulation and voice sync fixes
Fixed
- Startup Click Elimination: Implemented 100ms linear fade-in preventing DC offset clicks from reSIDfp filter initialization
- Patch Change Gate Triggers: Removed register backup/restore from chip model switching eliminating unwanted note triggers during patch loading
- Audio Graininess: Removed 1.1x cycle multiplier causing resampler timing issues, ensuring clean audio output
- Chip Model Switching: Simplified setChipModel() to only change chip model without reset or register manipulation
Technical Implementation
- Git Submodule Migration: Replaced
reSIDsubmodule withlibsidplayfpfrom https://github.com/libsidplayfp/libsidplayfp - CMakeLists.txt Refactoring: Updated build configuration for 17 reSIDfp source files including filters, integrators, waveform generators, and resamplers
- SIDEngine.h/cpp Updates: Migrated from
reSIDnamespace toreSIDfpnamespace with API method name changes: set_chip_model()→setChipModel()set_sampling_parameters()→setSamplingParameters()enable_filter()→enableFilter()SAMPLE_INTERPOLATE→RESAMPLEchip_model→ChipModelcycle_count→unsigned intclock(delta_t, buf, n)→clock(cycles, buf)with return value- Configuration Files: Created
siddefs-fp.handsidcxx11.hwith proper C++20 configuration and compiler feature detection - Combined Waveforms Setup: Added
sid->setCombinedWaveforms(STRONG)in SIDEngine constructor for maximum accuracy - Fade-In Architecture: Added
fadeInSamplesRemainingcounter with linear gain ramp (0.0 → 1.0) over 4410 samples preventing startup transients
Architecture Benefits
- Active Development: libsidplayfp receives regular updates (12+ improvements in 2024) vs. older reSID codebase
- Better Filter Accuracy: Floating-point calculations provide more precise filter modeling especially for 6581 chip characteristics
- Authentic Combined Waveforms: Parametrized waveform model based on real hardware sampling provides accurate mixed waveform output
- Improved Hardware Fidelity: Shift register envelope generator and oscillator leakage emulation match real chip behavior more closely
- Professional Audio Quality: Two-pass resampling and optimised filters deliver cleaner output suitable for professional production
User Experience
- Improved Sound Quality: More authentic SID chip emulation with better filter response and combined waveform accuracy
- Click-Free Operation: Smooth fade-in on startup eliminates jarring DC offset clicks from filter initialization
- Clean Patch Switching: No unwanted note triggers when changing patches or chip models during performance
- Professional Reliability: Stable emulation based on actively maintained library with ongoing accuracy improvements
[v0.9.36] – 2025-10-01
Reference: Sandbox Compatibility & Security Hardening
Summary: Fixed file I/O sandbox violations and added comprehensive error handling for sandboxed DAW environments
Fixed
- Sandbox Violations: Changed all file I/O from
userDocumentsDirectorytouserApplicationDataDirectoryfor sandbox compatibility - DAW Compatibility: Resolved file access issues in Logic Pro X, Ableton Live, Pro Tools AAX, and other sandboxed DAWs
- File Operation Errors: Added comprehensive error handling with logging for failed directory creation and file operations
Changed
- File Locations (All Platforms):
- Windows:
C:\Users\<username>\AppData\Roaming\reSIDue\(was: Documents\reSIDue) - macOS:
~/Library/reSIDue/(was: ~/Documents/reSIDue/) - Linux:
~/.config/reSIDue/(was: ~/Documents/reSIDue/)
Enhanced
- Error Logging: All file operations now log errors when directory creation or file I/O fails
- Graceful Degradation: LogManager falls back to stderr output if log directory creation fails
- User Feedback: File operations provide clear error messages through logging system
Technical Implementation
- FileManager.cpp: Updated 5 file operations to use
userApplicationDataDirectorywith error handling savePatchSetBinary(): Added directory creation validation and write error loggingloadPatchSetBinary(): Added file existence checks and read error loggingsaveConfig(): Added directory creation error handlingloadConfig(): Added empty file warningsavePatchSetToFileV2(): Added JSON export error handling- PatchManagerComponent.cpp: Updated 5 file chooser locations to use
userApplicationDataDirectory - Save/Load binary patch set file choosers
- Import/Export JSON file choosers
getPatchSetFile(): Added directory creation error handling- LogManager.cpp: Updated log file location with stderr fallback
ensureInitialized(): Added directory creation validation with stderr fallback
Architecture Benefits
- Sandbox Compliance: Plugin now works correctly in all major DAW sandbox environments
- Security: Uses platform-standard application data directories following OS guidelines
- Reliability: Comprehensive error handling prevents silent failures
- User Experience: Clear error messages help diagnose file access issues
- Cross-Platform: Proper platform-specific paths for Windows, macOS, and Linux
[v0.9.35] – 2025-09-30
Reference: Spring Cleaning Edition #3
Summary: Code cleanup removing unused methods and variables to improve codebase maintainability
Removed
- Unused Variable: Removed
loopIterationsvariable from PluginProcessor that was incremented but never read - Unused Methods: Removed
getVoiceState()method from PluginProcessor (never called) - WavetableTableComponent Cleanup: Removed unused
getRow(),setRow(), and constgetTableData()methods
Enhanced
- UI Text Consistency: Capitalized voice channel dropdown “off” to “Off” for consistent UI presentation
Technical Implementation
- PluginProcessor.h:274: Removed unused
loopIterationsmember variable declaration - PluginProcessor.cpp:137,237-245: Removed all
loopIterationsinitialization, increment, and reset operations - PluginProcessor.h:93: Removed unused
getVoiceState()method providing direct voice state access - WavetableTableComponent.h:405: Removed unused const
getTableData()method (only mutable version used) - WavetableTableComponent.cpp:333-351: Removed unused
getRow()andsetRow()methods (19 lines of dead code) - WavetableSelectorComponent.cpp:44,57,589,606: Capitalized “off” to “Off” in voice channel dropdown
Architecture Benefits
- Cleaner Codebase: Eliminated approximately 25 lines of dead code that served no purpose
- Reduced Maintenance Burden: Fewer unused methods means less code to maintain during future development
- Simplified API Surface: Removed redundant methods that duplicated existing functionality without adding value
- Code Quality Improvement: Systematic cleanup improves overall codebase maintainability
[v0.9.34] – 2025-09-30
Reference: JUCE framework upgrade to version 7.0.6 and build system restoration
Summary: Updated JUCE framework to version 7.0.6 and resolved build errors by restoring missing modules and OpenGL functionality
Fixed
- JUCE Submodule Corruption: Resolved corrupted JUCE submodule by performing clean checkout to JUCE 7.0.6 tag with proper file structure
- Missing Module Dependencies: Restored missing
juce_openglandjuce_gui_extramodules that were causing build failures in CMake configuration - OpenGL Functionality: Re-enabled OpenGL hardware acceleration support in main CMakeLists.txt after module restoration
- Build System Integrity: Fixed broken module references in JUCE modules CMakeLists.txt ensuring only available modules are included
Technical Implementation
- Framework Upgrade: Updated from JUCE 8.0.10 to stable JUCE 7.0.6 for better compatibility and feature support
- Module Availability: Verified all required JUCE modules exist and are properly configured for audio plugin development
- CMake Configuration: Cleaned up module references removing non-existent modules while preserving essential functionality
- Build Verification: Confirmed successful compilation of both Standalone and VST3 plugin formats
Architecture Benefits
- Stable Foundation: JUCE 7.0.6 provides stable framework foundation for continued development and deployment
- OpenGL Acceleration: Hardware acceleration capabilities fully restored for improved GUI performance
- Build Reliability: Eliminated CMake configuration errors ensuring consistent build success across development environments
- Module Integrity: All JUCE modules properly aligned with framework version preventing future build conflicts
[v0.9.33] – 2025-09-29
Reference: JUCE Path-based circuit rendering system and code cleanup
Summary: Converted CircuitComponent to use JUCE’s Path class for optimised line rendering and removed unused methods for cleaner codebase
Enhanced
- JUCE Path Integration: Replaced manual line-by-line drawing with JUCE’s Path class using
path.lineTo()for optimised rendering performance - Cached Path System: Added cached
juce::Pathto CircuitPath struct with automatic regeneration when waypoints change for improved rendering efficiency - Path Stroke Optimisation: Single
g.strokePath()call replaces multiple individual line draws reducing graphics API overhead - Connection Dot Rendering: Enhanced connection dots with square boxes instead of circular ellipses for more authentic PCB aesthetic
Technical Implementation
- CircuitComponent.h: Added
cachedPathandpathNeedsUpdatemembers to CircuitPath struct with constructor initialization - CircuitComponent.cpp: Implemented
generatePathForCircuit()method usingpath.startNewSubPath()andpath.lineTo()for efficient path creation - Rendering Architecture: Modified
drawCircuitPath()to usePathStrokeTypewith single stroke operation instead of loop-based line drawing - Path Caching: Automatic path regeneration only when waypoints change via
pathNeedsUpdateflag preventing unnecessary recalculation
Code Quality
- Dead Code Removal: Eliminated unused
updateCircuitColors()method that was never called from anywhere in codebase - API Cleanup: Removed unused
drawCircuitPath(size_t pathIndex)overload method that had no callers - Method Consolidation: Streamlined CircuitComponent API by removing redundant methods improving maintainability
Performance Benefits
- Single Stroke Operations: JUCE Path rendering uses optimised graphics pipeline instead of multiple individual line draw calls
- Reduced Graphics Overhead: Path-based rendering leverages GPU acceleration when available for smoother visual performance
- Smart Caching: Paths only regenerated when geometry changes eliminating redundant path calculations during paint cycles
- Future Flexibility: Path-based architecture enables easy addition of curves, rounded corners, and complex shapes
Architecture Benefits
- JUCE Framework Alignment: Uses JUCE’s native path rendering system following framework best practices for graphics operations
- Memory Efficient Caching: Cached paths stored per-circuit reducing computation while maintaining reasonable memory usage
- Visual Consistency: JUCE’s path stroking provides consistent line rendering across different platforms and graphics backends
- Maintainable Code: Cleaner codebase with fewer unused methods and streamlined rendering architecture
[v0.9.32] – 2025-09-29
Reference: Circuit line animation system with 50Hz timer integration
Summary: Added dynamic circuit line colour animation using 50Hz timer for realistic PCB trace flickering effects
Added
- Random Line Animation: Implemented
animateRandomLine()method in CircuitComponent for dynamic colour variations at 50Hz intervals - Timer Integration: Connected circuit animation to existing GuiUpdateTimer system eliminating need for separate animation timer
- Callback Architecture: Added
triggerCircuitAnimation()andsetCircuitAnimationCallback()methods to PluginProcessor following established pattern - Performance Optimisation: Separated geometry initialization from colour updates preventing unnecessary path rebuilding during animation
Enhanced
- ±40% Green Variation: Circuit lines randomly vary green component by ±40% from base colour creating realistic PCB trace flickering
- Base Colour Preservation: Added
baseColormember to CircuitPath struct ensuring variations always calculated from original colour preventing drift - Individual Line Updates: Optimised repainting to only refresh affected path area instead of entire component during colour changes
- Full Opacity Animation: All animated lines maintain full opacity (alpha = 255) for maximum visual impact
Technical Implementation
- CircuitComponent.h: Added
animateRandomLine()method andbaseColormember to CircuitPath struct for consistent variation calculations - CircuitComponent.cpp: Implemented random path selection with ±40% green variation using
juce::Random::getSystemRandom()and proper colour clamping - PluginProcessor.h/cpp: Added circuit animation callback system with
triggerCircuitAnimation()method andcircuitAnimationCallbackmember - PluginEditor.cpp: Connected circuit animation to GuiUpdateTimer with
MessageManager::callAsync()for thread-safe GUI updates - Performance Architecture: Geometry rebuilding only occurs when component areas change via
geometryInitializedflag preventing unnecessary recalculation
Architecture Benefits
- Stable Geometry: Circuit paths maintain fixed waypoints while allowing dynamic colour changes for optimal performance
- Thread-Safe Animation: All GUI updates properly marshaled to message thread preventing JUCE assertion failures
- Efficient Rendering: Individual path repainting with calculated bounds minimizes drawing overhead during animation
- Consistent Pattern: Follows established callback architecture used by oscilloscope and wavetable updates
User Experience
- Live Circuit Effect: PCB traces now flicker with realistic green variations simulating active circuit board appearance
- Authentic Hardware Feel: Dynamic animation enhances the authentic C64 hardware emulation experience
- Non-Intrusive Animation: Circuit animation provides visual enhancement without interfering with existing UI functionality
- Performance Optimised: Animation runs smoothly at 50Hz without impacting audio processing or other UI responsiveness
[v0.9.31] – 2025-09-29
Reference: PCB-style circuit visualization component implementation
Summary: Added visual circuit board component drawing PCB-style traces connecting SID chip to UI components for enhanced hardware authenticity
Added
- CircuitComponent Class: New visual component implementing PCB-style circuit traces between SID chip and interface elements
- SID Chip Pin Mapping: Authentic MOS 6581 pin layout with address pins (A0-A4), data pins (D0-D7), and audio output pin positioning
- Dynamic Circuit Routing: Automatic circuit path generation based on component positions with complex waypoint routing to avoid visual overlap
- PCB Aesthetic Elements: Green circuit traces with connection dots at waypoints for authentic circuit board appearance
Enhanced
- Component Integration: Circuit paths dynamically update when chip area, wavetable selector area, or oscilloscope area positions change
- Visual Data Flow: Circuit traces visually represent how data flows from SID chip to wavetable controls and audio output to oscilloscope
- Hardware Authenticity: Visual representation matches real circuit board connections enhancing the authentic C64 hardware emulation experience
- Professional Visual Polish: Circuit board aesthetic adds professional hardware appearance to the plugin interface
Technical Implementation
- CircuitComponent.cpp: Complete implementation with pin positioning based on MOS 6581 layout using relative coordinates within chip area
- Address/Data Pin Connections: A0-A4 and D0-D7 pins connect to wavetable selector with sophisticated multi-waypoint routing using 3-pixel trace spacing
- Audio Pin Connection: Audio output pin connects to oscilloscope input with simplified waypoint routing for clean visual appearance
- Dynamic Path Updates:
updateCircuitPaths()method automatically recalculates circuit routing when component areas change viasetChipArea(),setWavetableSelectorArea(), andsetOscilloscopeArea() - Rendering System:
drawCircuitPath()method renders circuit segments with specified line width and connection dots for authentic PCB appearance
Architecture Benefits
- Component Isolation: CircuitComponent operates independently with mouse events passing through via
setInterceptsMouseClicks(false, false) - Scalable Design: Circuit paths automatically adapt to different component sizes and positions for flexible UI layout
- Visual Consistency: Green trace colour (#004000) and standardized connection dots provide consistent circuit board aesthetic
- Performance Optimised: Efficient path rendering with pre-calculated waypoints minimizing drawing overhead
User Experience
- Enhanced Visual Appeal: Circuit board traces add professional hardware appearance making the plugin more visually engaging
- Educational Value: Visual representation helps users understand how SID chip connects to various interface controls
- Authentic Hardware Feel: PCB-style visualization reinforces the authentic C64 hardware emulation experience
- Non-Intrusive Design: Circuit traces provide visual enhancement without interfering with existing UI functionality
[v0.9.30] – 2025-09-28
Reference: ReTrig functionality implementation and wavetable activation optimisation
Summary: Implemented ReTrig behavior in wavetable start() method and modified activation logic to always enable all wavetables on note-on events
Enhanced
- ReTrig Implementation: Added ReTrig functionality to
WavetablePlayer::start()method – wavetables with ReTrig enabled (flag[1]) now reset their state to position 0 on note-on events - Always-Active Wavetables: Modified wavetable activation logic to always activate all wavetables on start() regardless of Loop setting, ensuring immediate responsiveness
- State Reset Control: ReTrig flag now properly controls whether wavetable state (currentRow, execCount, etc.) resets on new note events
Technical Implementation
- WavetablePlayer.cpp:80-83: Added ReTrig check using
allWavetablesControl[i][currentTable][1]to conditionally reset wavetable state - WavetablePlayer.cpp:84: Changed activation logic to always set
states[i].isActive = trueinstead of conditional activation based on Loop settings - WavetablePlayer.cpp:86-95: Preserved original Loop-based activation logic in comments for reference while implementing new always-active approach
- Flag Integration: ReTrig functionality leverages existing control flag system (flag[1] = ReTrig) for consistent wavetable behavior control
User Experience Benefits
- Responsive Wavetables: All wavetables now immediately respond to note-on events providing instant feedback regardless of Loop configuration
- Controlled Reset Behavior: Users can enable/disable ReTrig per wavetable to control whether note-on events reset wavetable position or continue from current position
- Predictable Operation: Simplified activation logic eliminates confusion about when wavetables will respond to MIDI input
Architecture Benefits
- Consistent Activation: Uniform wavetable activation ensures all wavetables are ready for processing when notes are triggered
- Flexible Reset Control: ReTrig flag provides granular control over wavetable reset behavior without affecting activation status
- Simplified Logic: Removed complex conditional activation reducing potential edge cases and improving reliability
[v0.9.29] – 2025-09-27
Reference: CH10 Voice Channel Re-evaluation Fix
Summary: Fixed MIDI channel 10 patch changes not applying correct sound by adding voice channel re-evaluation after patch loading
Fixed
- CH10 Sound Application: Fixed issue where MIDI channel 10 patch changes would update patch index but not apply correct sound to playing voices
- Voice Channel Re-evaluation: Added voice MIDI channel compatibility check after patch loading to ensure voice processes with correct settings
- Patch Change Effectiveness: MIDI channel 10 notes now properly trigger both patch change and correct audio output matching the new patch
Technical Implementation
- PluginProcessor.cpp:345: Enhanced CH10 patch change logging with note number and target patch index for debugging
- PluginProcessor.cpp:48-53: Added voice channel re-evaluation logic after
setCurrentProgram()call in MIDI channel 10 handling - Channel Compatibility Check: After patch change, voice MIDI channel settings are re-checked against incoming MIDI channel to ensure voice remains eligible for processing
- Skip Logic Enhancement: Voice processing skips if new patch assigns incompatible MIDI channel to the voice, preventing incorrect sound output
Root Cause Resolution
- Mid-Loop Patch Changes: Patch loading via
setCurrentProgram()was changing voice MIDI channel settings while voice processing loop was active - Voice Assignment Mismatch: Original voice selection criteria became invalid after patch changed voice channel assignments, causing voice to process with wrong channel mapping
- Architectural Timing: Fixed timing issue where patch change affected global settings but voice processing continued with pre-change voice selection
User Experience Benefits
- Accurate CH10 Response: MIDI channel 10 percussion mapping now produces correct sounds matching the intended patch for each MIDI note
- Immediate Audio Feedback: Patch changes via MIDI channel 10 notes now immediately affect audio output as expected
- Reliable Percussion Workflow: Drum-style MIDI channel 10 operation now works consistently with proper patch-to-sound mapping
[v0.9.28] – 2025-09-26
Reference: MIDI Channel 10 Percussion Patch Mapping
Summary: Added special percussion mapping for MIDI channel 10 enabling automatic patch switching based on MIDI note numbers
Added
- MIDI Channel 10 Handler: Special processing for MIDI channel 10 (standard percussion channel) in handleNoteOn method
- Note-to-Patch Mapping Array: MIDI notes on channel 10 trigger specific patch changes via midiNoteToPatchMap array lookup
- Automatic Patch Switching: When mapped MIDI note received on channel 10, plugin automatically calls setCurrentProgram() for corresponding patch
- Drum Kit Style Operation: Enables drum-kit workflow where different MIDI notes (kick, snare, hi-hat) trigger different instrument patches
- Dynamic Performance Control: Real-time patch changes during MIDI performance for percussion-style patch management
Technical Implementation
- PluginProcessor.cpp:339-347: Added MIDI channel 10 check with midiNoteToPatchMap array lookup in handleNoteOn method
- Patch Triggering Logic: If midiNoteToPatchMap[midiNote] != -1, calls setCurrentProgram(patchIndex) for automatic patch switching
- Channel-Specific Processing: Only processes note-to-patch mapping on channel 10, preserving normal operation for other channels
- Integration with Existing System: Works alongside existing MIDI channel filtering and voice processing logic
User Experience Benefits
- Percussion Workflow: MIDI channel 10 can now trigger different patches automatically based on note number (C-1 → Patch 5, D-1 → Patch 12, etc.)
- Live Performance Enhancement: Enables dynamic patch switching during performance using standard drum channel conventions
- Hardware Controller Integration: Works with drum pads and percussion controllers that send on MIDI channel 10
- Professional Workflow: Follows established MIDI percussion mapping conventions for intuitive operation
Architecture Benefits
- Backward Compatibility: Existing MIDI processing preserved – only adds new functionality for channel 10
- Existing Data Integration: Uses pre-existing midiNoteToPatchMap array that was added in v0.9.20
- Minimal Code Impact: Simple addition to existing handleNoteOn method without affecting other MIDI processing
- Standard MIDI Compliance: Follows MIDI specification where channel 10 is reserved for percussion instruments
[v0.9.27] – 2025-09-26
Reference: MIDI Channel Filtering Implementation
Summary: Enhanced handleNoteOn and handleNoteOff methods to respect per-voice MIDI channel settings for proper voice filtering
Added
- MIDI Channel Parameter: Added midiChannel parameter to handleNoteOn() and handleNoteOff() method signatures
- Voice Channel Filtering: Implemented MIDI channel checking logic in both note on/off handlers
- Channel Validation Logic: Added comprehensive channel filtering (-1=off, 0=Any, 1-16=specific channel)
- Message Processing Enhancement: Updated MIDI message processing to pass msg.getChannel() to note handlers
Enhanced
- Per-Voice Channel Control: Each voice now only responds to MIDI notes on its configured channel
- Channel Setting Integration: Leverages existing getVoiceMidiChannel() method for channel configuration retrieval
- Skip Logic Implementation: Voices skip processing when channel doesn’t match incoming MIDI channel
- Consistent Behavior: Both note-on and note-off events respect identical channel filtering logic
Technical Details
- PluginProcessor.h:208-209: Updated method signatures to include int midiChannel parameter
- PluginProcessor.cpp:180,184: Updated MIDI message processing to pass msg.getChannel() to handlers
- PluginProcessor.cpp:329-337: Added channel filtering logic in handleNoteOn() before voice processing
- PluginProcessor.cpp:396-404: Added identical channel filtering logic in handleNoteOff() for consistency
- Channel Logic: -1 (off) = skip voice, 0 (Any) = process all channels, 1-16 = process specific channel only
Architecture Benefits
- Proper Voice Isolation: Voices configured for specific channels no longer respond to other channels
- Existing UI Integration: Works seamlessly with current channel selection dropdowns in WavetableSelectorComponent
- Patch Data Compatibility: Uses existing voiceMidiChannel field from PatchData structure
- Backward Compatibility: Maintains existing API while adding channel awareness to MIDI processing
[v0.9.26] – 2025-09-26
Reference: Binary Format MIDI Note Mapping Completion
Summary: Added complete MIDI note to patch mapping serialization to binary .reSIDue format for consistency with JSON format
Added
- Binary MIDI Note Mapping: Added complete 128-entry MIDI note to patch mapping array serialization to
createBinaryV3()method - Binary MIDI Note Loading: Added corresponding deserialization in
parseBinaryV3()method to restore MIDI note mappings from binary files - Format Consistency: Binary .reSIDue format now includes same MIDI note mapping data as JSON format ensuring feature parity
- Embedded Patch Data: Added patches.reSIDue file as embedded binary resource in CMakeLists.txt for future default patch system integration
Technical Details
- FileManager.cpp:283-288: Added MIDI note to patch mapping write operation as final step in
createBinaryV3() - FileManager.cpp:636-652: Added MIDI note to patch mapping read operation in
parseBinaryV3()with proper bounds checking - Complete Array Serialization: All 128 MIDI note slots written/read as integers using existing
appendData/readDatahelpers - Error Handling: Added safe reading with early exit if data is incomplete during MIDI mapping restoration
- CMakeLists.txt:74: Added patches/patches.reSIDue to juce_add_binary_data for embedded resource access
Architecture Benefits
- Format Equivalence: Binary and JSON formats now preserve identical data sets eliminating format-specific feature limitations
- Data Completeness: MIDI note to patch mappings properly preserved in binary saves ensuring no data loss during file operations
- Consistent Implementation: Uses same patterns as existing binary serialization with proper error handling and bounds checking
[v0.9.25] – 2025-09-25
Reference: MIDI Note to Patch Mapping Interface Implementation
Summary: Added CH10 dropdown for MIDI note to patch mapping with complete UI integration
Added
- CH10 MIDI->Patch Dropdown: Added dropdown in WavetableSelectorComponent for assigning MIDI notes to patches
- Voice Label Optimisation: Changed voice labels from “Voice 1/2/3” to compact “V1:/V2:/V3:” format
- Layout Enhancement: Repositioned voice labels to the left of channel selector dropdowns for horizontal layout
- 98 MIDI Note Support: Dropdown populated with full MIDI note range from “KT” (-1) to “C8” (108) using existing midiNoteArray
- Patch Data Integration: Connected to existing PatchData.assignedMidiNote field with real-time synchronization
Changed
- WavetableSelectorComponent Layout: Expanded from 6 to 7 columns to accommodate CH10 dropdown
- Voice Control Area Width: Increased voice control section from 70px to 90px for better spacing
- Dropdown Dimensions: Unified all dropdown heights to match CH10 dropdown dimensions
- Label Positioning: Moved voice labels from vertical (above) to horizontal (left) layout
- Font Sizing: Applied smaller font (7.0f) to CH10 column header for better fit
Technical Details
- WavetableSelectorComponent.h: Added midiToPatchComboBox, midiToPatchLabel, and callback support
- WavetableSelectorComponent.cpp:273-290: Dropdown initialization with midiNoteArray population
- WavetableSelectorComponent.cpp:640-660: ComboBox change handler for MIDI note selection
- WavetableSelectorComponent.cpp:689-730: setAssignedMidiNote() method with validation and fallback
- WavetableSelectorComponent.cpp:535-550: refreshFromPatchData() integration for patch switching
- PatchManagerComponent.cpp:507-509: Added refreshFromPatchData() call to refreshUIAfterPatchLoad()
- Layout Architecture: 7-column header with V1:/V2:/V3: labels positioned left of dropdowns
- Data Flow: Direct patch data updates ensure synchronization across all patch management operations
[v0.9.24] – 2025-09-25
Reference: Spring Cleaning Edition #2
Summary: Code cleanup – removed unused methods from PatchManagerComponent
Removed
- Dead Code Elimination: Removed unused methods from PatchManagerComponent to clean up codebase
- PatchManagerComponent.h:50: Removed unused
getLoadedFileName()declaration that was never implemented - PatchManagerComponent.cpp:512-539: Removed unused
PatchData::toJSON()method (28 lines of dead code) - PatchManagerComponent.cpp:541-602: Removed unused
PatchData::fromJSON()method (62 lines of dead code)
Technical Details
- Code Analysis: Systematic analysis identified 3 unused methods totaling 90+ lines of dead code
- JSON Serialization Cleanup: Removed obsolete JSON serialization methods since project migrated to binary .reSIDue format
- Header Cleanup: Removed method declaration that was never implemented
- Codebase Optimisation: Improved maintainability by eliminating unused functionality
[v0.9.23] – 2025-09-25
Reference: Filter/Volume Patch Index Persistence Fix
Summary: Fixed Filter/Volume wavetable index not persisting when switching between patches
Fixed
- Filter/Volume Index Persistence: Fixed Filter/Volume wavetable index changes being lost when switching patches by saving changes directly to patch data
- WavetableSelectorComponent Data Consistency: Filter/Volume index changes now saved to
patches[currentPatchIndex].globalFilterVolIndexlike voice-specific wavetable indexes - Patch Data Architecture Alignment: Eliminated inconsistency where voice-specific indexes were saved to patch data but Filter/Volume index was only stored locally
Technical Details
- WavetableSelectorComponent.cpp:173-182: Added patch data update in
filterVolSelector.onTextChangecallback - WavetableSelectorComponent.cpp:445-454: Added patch data update in
setFilterVolTableNumber()method - Root Cause: Filter/Volume changes were only stored in local
filterVolTableNumbervariable, causingrefreshFromPatchData()to restore original values when switching patches
[v0.9.22] – 2025-09-24
Reference: JUCE Thread Safety Fix
Summary: Fixed JUCE Component.cpp:1609 assertion failures during MIDI note playback
Fixed
- GUI Thread Safety: Fixed JUCE Component.cpp:1609 assertion failures occurring during MIDI note playback by wrapping GUI callback operations with
MessageManager::callAsync() - Wavetable Row Updates: Fixed
updateCurrentPlaybackRow()calls being executed from non-message thread - Oscilloscope Updates: Fixed
updateDisplay()calls triggeringrepaint()from timer thread - MIDI Indicator Updates: Fixed
triggerMidiActivity(),setGateOn(), andsetGateOff()methods callingrepaint()directly from MIDI processing thread
Technical Details
- PluginEditor.cpp:100-107: Wrapped wavetable row update callback with
MessageManager::callAsync() - PluginEditor.cpp:118-125: Wrapped oscilloscope update callback with
MessageManager::callAsync() - PluginEditor.cpp:45-52: Wrapped MIDI indicator activity callback with
MessageManager::callAsync() - PluginEditor.cpp:79-86: Wrapped MIDI gate on callback with
MessageManager::callAsync() - PluginEditor.cpp:88-95: Wrapped MIDI gate off callback with
MessageManager::callAsync()
Root Cause
- GUI components were being updated directly from non-message threads (timer threads and MIDI processing callbacks)
- JUCE requires all GUI operations (including
repaint()calls) to occur on the message thread - The assertions were triggered by
Component::repaint()calls from wrong thread context
[v0.9.21] – 2025-09-24
Reference: Spring Cleaning Edition #1
Summary: Code cleanup and maintenance improvements
Changed
- Version update to v0.9.21 “Spring Cleaning Edition #1”
[v0.9.20] – 2025-09-23
Reference: MIDI Note to Patch Mapping System Implementation
Summary: Added complete MIDI note to patch mapping system with JSON serialization for persistent note-to-patch assignments
Added
- MIDI Note to Patch Mapping: New
std::array<int, 128> midiNoteToPatchMapin PluginProcessor for mapping MIDI notes (0-127) to patch indexes - Array Initialization: All mapping slots initialized to -1 (no mapping) in constructor for consistent default state
- Public Accessor Methods: Added
getMidiNoteToPatchMap()const and non-const methods for external access to mapping array - JSON Serialization: Complete array serialization in
savePatchSetToFileV2()preserving all 128 mapping slots - JSON Deserialization: Loading support in
loadAllPatchesFromPatchSet()with bounds checking and safe array restoration - File Format Extension: New
"midiNoteToPatchMap"property at JSON root level containing complete mapping array
Technical Implementation
- PluginProcessor.h:190: Added
std::array<int, 128> midiNoteToPatchMapmember variable with -1 sentinel values - PluginProcessor.h:158-160: Added public getter methods following existing
getPatches()pattern - PluginProcessor.cpp:73: Constructor initialization using
midiNoteToPatchMap.fill(-1)for default state - FileManager.cpp:1317-1324: Save implementation dumping complete 128-element array to JSON
- FileManager.cpp:1666-1674: Load implementation with array bounds checking and safe restoration
Data Format
- Array Structure: Direct index mapping where
midiNoteToPatchMap[midiNote] = patchIndex - Sentinel Value: -1 indicates no mapping assigned for that MIDI note
- Complete Persistence: All 128 slots saved/loaded regardless of assignment status
- JSON Format:
"midiNoteToPatchMap": [-1, -1, 5, -1, 12, ...]with array index as MIDI note number
[v0.9.19] – 2025-09-23
Reference: Single SID Instance Architecture – Critical Memory Safety and Authenticity Fix
Summary: Major architectural transformation from three separate SID instances to single shared SID instance, eliminating Valgrind memory errors and providing authentic C64 hardware emulation
Fixed
- Critical Memory Safety: Eliminated uninitialized filter state conflicts causing Valgrind “Use of uninitialised value” errors in reSID filter processing
- Architecture Flaw: Replaced incorrect three-SID design with authentic single-SID architecture matching real C64 hardware
- Filter State Issues: Fixed filter register conflicts between multiple SID instances that caused unstable audio behavior
- Performance Overhead: Eliminated 3× redundant filter processing and multiple render calls with audio mixing
Added
- SIDEngine Class: New core engine wrapping single reSID instance with shared filter state for all voices
- Voice Register Mapping: Automatic translation of voice-relative addresses to absolute SID register addresses
- Voice 1: 0x00-0x06 (FREQ_LO, FREQ_HI, PW_LO, PW_HI, CTRL, ATTACK_DECAY, SUSTAIN_RELEASE)
- Voice 2: 0x07-0x0D (same register types, different addresses)
- Voice 3: 0x0E-0x14 (same register types, different addresses)
- Shared: 0x15-0x18 (Filter Cutoff Low/High, Resonance/Filter Routing, Mode/Volume)
- SIDVoice Wrapper System: Backwards-compatible interface maintaining existing API while delegating to shared engine
- Address Translation:
mapRegisterAddress()method handles voice-relative to absolute address mapping automatically
Changed
- Rendering Architecture: Single unified render call outputs all voices mixed together instead of separate voice rendering
- Filter Behavior: Filter/volume registers now correctly affect all voices simultaneously (authentic C64 behavior)
- Memory Management: Single SID instance eliminates memory duplication and state conflicts
- PluginProcessor Structure:
- Added
SIDEngine sidEngine(single shared instance) - Added
SIDVoice sidVoice0/1/2(wrapper instances pointing to shared engine) - Added
getSIDVoice(int index)helper method for backwards compatibility
Technical Implementation
- SIDEngine.h/cpp: Complete rewrite implementing single SID instance with proper voice delegation
- PluginProcessor.h:610-613: Updated member variables to use single SID engine with voice wrappers
- PluginProcessor.cpp:14-16: Constructor initialization using dependency injection pattern
- PluginProcessor.cpp:280-289: Added
getSIDVoice()helper method for index-based voice access - PluginProcessor.cpp:100-101: Unified SID reset and sample rate setting on single engine
- PluginProcessor.cpp:485: Optimised
renderAllActiveVoices()to use single SID render call - Constructor Pattern:
SIDVoice(SIDEngine* engine, int voiceIndex)linking wrappers to shared engine - Address Mapping: Voice registers automatically mapped to correct SID chip addresses
- Delegation Pattern: All SIDVoice operations delegate to shared SIDEngine with proper voice context
Architecture Benefits
- Authentic Emulation: Single SID chip with three voices exactly matching real C64 hardware architecture
- Memory Safety: Eliminates all uninitialized filter state errors detected by Valgrind static analysis
- Performance: Single render call instead of three separate calls with significantly reduced CPU overhead
- Correctness: Shared filter registers properly affect all voices as in authentic C64 behavior
- Maintainability: Simplified architecture with clear separation between shared engine and voice interfaces
- API Compatibility: Existing wavetable and MIDI code continues to work without modification
[v0.9.18] – 2025-09-22
Reference: Patch MIDI note assignment support
Summary: Added assignedMidiNote field to PatchData structure for MIDI note-to-patch mapping functionality
Added
- MIDI Note Assignment: Added
assignedMidiNotefield toPatchDatastruct for mapping specific MIDI notes to patches (-1 = None, 0-127 = specific note) - File Format Support: Integrated
assignedMidiNoteinto both JSON and binary patch file formats ensuring complete persistence - Backward Compatibility: New field defaults to -1 (None) maintaining compatibility with existing patch sets
Technical Implementation
- PluginProcessor.h:18: Added
int assignedMidiNote = -1field to PatchData struct with proper initialization - FileManager.cpp:1229: Added
assignedMidiNoteto JSON patch serialization insavePatchSetToFileV2() - FileManager.cpp:1618: Added
assignedMidiNoteto JSON patch deserialization inloadAllPatchesFromPatchSet() - FileManager.cpp:143: Added
assignedMidiNoteto binary format increateBinaryV3() - FileManager.cpp:358: Added
assignedMidiNoteto binary parsing inparseBinaryV3()
Data Structure
- Field Type: Integer field supporting values -1 (None/off), 0-127 (specific MIDI note assignment)
- Default Value: -1 (None) ensuring backward compatibility with existing patches
- File Persistence: Fully supported in both JSON v3.0 and binary v3.0 patch formats
[v0.9.17] – 2025-09-18
Reference: JUCE architecture compliance – GUI timer separation fix
Summary: Fixed JUCE separation of concerns violation by moving GuiUpdateTimer from PluginProcessor to PluginEditor
Fixed
- JUCE Architecture Violation: Moved
GuiUpdateTimerfromPluginProcessortoPluginEditorfollowing proper JUCE separation of concerns principles - GUI Timer Lifecycle: Timer now properly starts/stops with editor window instead of running continuously in audio processor
- Private Member Access: Added public trigger methods
triggerWavetableRowUpdate()andtriggerOscilloscopeUpdate()for clean GUI-to-processor communication - Resource Management: GUI timer now stops when editor closes in VST environments, preventing unnecessary background processing
Technical Implementation
- PluginEditor.h:44: Added
std::unique_ptr<GuiUpdateTimer> guiUpdateTimermember to editor class - PluginEditor.cpp:7-38: Moved
GuiUpdateTimerclass definition from PluginProcessor.cpp to PluginEditor.cpp - PluginEditor.cpp:447-448: Initialize and start timer in editor constructor (50Hz = 20ms intervals)
- PluginEditor.cpp:231-235: Stop and reset timer in editor destructor before component destruction
- PluginProcessor.h:147-148: Added public trigger methods for GUI timer communication
- PluginProcessor.cpp:773-787: Implemented trigger methods that safely call private callbacks
- Removed: Timer member, initialization, cleanup, and friend class declaration from PluginProcessor
Architecture Improvements
- Proper Separation of Concerns: Audio processor handles audio, editor handles GUI updates as per JUCE best practices
- Correct Lifecycle Management: Timer only runs when GUI is open, eliminating unnecessary processing when editor is closed
- Clean API Design: Public trigger methods provide controlled access to processor callbacks without exposing private members
- Thread Safety: Maintained existing thread safety while improving architectural compliance
[v0.9.16] – 2025-09-18
Reference: VST multi-instance safety and static variable fixes
Summary: Fixed critical static variable issues preventing safe multi-instance VST operation and eliminated compilation errors
Fixed
- Static Variable Multi-Instance Issues: Moved static audio processing variables from global scope to PluginProcessor instance members preventing state sharing between plugin instances
- Cache Initialization Threading: Fixed static cache initialization flags in wavetable components to be per-instance preventing race conditions when multiple plugin instances load simultaneously
- Logging Thread Safety: Created thread-safe LogManager singleton with proper std::once_flag and std::mutex protection replacing problematic static logging variables
- Compilation Error: Fixed LogManager.h compilation failure by replacing non-existent
<JuceHeader.h>with proper<juce_data_structures/juce_data_structures.h>include
Technical Implementation
- PluginProcessor.h:250-254: Added instance member variables
filterVolSamplesUntilNext50Hz,debugCounter, andloopIterationsto replace static variables - LogManager.h/cpp: Implemented thread-safe singleton pattern with
std::once_flag initFlagandstd::mutex logMutexfor safe multi-instance logging - FreqWavetableComponent.h:99, SingleValueWavetableComponent.h:95, PulsWavetableComponent.h:93: Added per-instance
mutable bool cacheInitialized = falseflags - JUCE Include Fix: Replaced incorrect
<JuceHeader.h>with project-standard<juce_data_structures/juce_data_structures.h>include
Multi-Instance Safety
- Audio Processing Isolation: Each plugin instance now maintains separate audio processing state preventing cross-instance interference
- Thread-Safe Logging: LogManager singleton ensures safe concurrent logging from multiple plugin instances without data corruption
- Cache Independence: Wavetable cache initialization is now per-component eliminating shared state between plugin instances
- Compilation Stability: All files now compile correctly with proper JUCE module includes matching project architecture
[v0.9.15] – 2025-09-16
Reference: VST UI close crash fix
Summary: Fixed critical crash when closing VST plugin UI by resolving race condition between GUI timer and editor destruction
Fixed
- VST UI Close Crash: Fixed critical bug where closing VST plugin UI crashed host due to GUI timer continuing to execute callbacks after UI components were destroyed
- Race Condition Resolution: Added proper timer cleanup in
PluginProcessordestructor to stopGuiUpdateTimerbefore destruction preventing callback execution on destroyed UI - Missing Callback Cleanup: Added missing
setOscilloscopeUpdateCallback(nullptr)to editor destructor ensuring all GUI callbacks are properly cleared - Timer Lifecycle Management: Enhanced destructor sequence to properly stop and reset GUI update timer preventing post-destruction callback access
Technical Implementation
- PluginProcessor.cpp:106-114: Enhanced processor destructor with proper
guiUpdateTimercleanup includingstopTimer()andreset()calls - PluginEditor.cpp:216: Added missing
processorRef.setOscilloscopeUpdateCallback(nullptr)to complete callback cleanup in editor destructor - Timer Safety: Ensured
GuiUpdateTimerstops before processor destruction preventing callback access to destroyed UI components
Stability Improvements
- Universal DAW Compatibility: VST plugin UI can now be safely closed in all DAW environments without causing host crashes
- Proper Resource Cleanup: All GUI-related timers and callbacks are properly cleaned up during component destruction
- Race Condition Prevention: Eliminated timing-dependent crashes between background timer execution and UI component destruction
[v0.9.14] – 2025-09-16
Reference: Oscilloscope performance optimizations
Summary: Optimised oscilloscope rendering performance by adapting sample count to display width and pre-calculating coordinate transformations
Enhanced
- Adaptive Sample Rendering: Changed oscilloscope to render samples equal to display width instead of fixed 1024 samples, creating optimal 1:1 pixel-to-sample ratio
- Pre-calculated Transformations: Eliminated repeated coordinate calculations in render loop by pre-calculating X and Y scaling factors
- Reduced Processing Overhead: Narrow oscilloscope displays now process fewer samples, improving performance when oscilloscope width is less than 1024 pixels
- Optimised Coordinate Calculations: Combined Y-coordinate calculation with bounds clamping in single operation, removing redundant arithmetic
Technical Implementation
- OscilloscopeComponent.cpp:86: Changed
samplesToRenderfrom fixed 1024 tostd::min(static_cast<size_t>(width), audioBuffer.size()) - Coordinate Pre-calculation: Added
xScaleandyScalevariables calculated once before render loop - Loop Optimisation: Replaced division and multiple multiplications with simple multiplication using pre-calculated scale factors
Performance Benefits
- Variable Processing Load: Oscilloscope CPU usage now scales with display width – smaller displays use less processing power
- Eliminated Redundant Math: Pre-calculated scaling factors remove repeated arithmetic operations from per-sample calculations
- Better Cache Efficiency: Reduced calculations per sample improve CPU cache utilization during waveform rendering
[v0.9.13] – 2025-09-16
Reference: Critical audio processing performance optimizations
Summary: Eliminated memory allocations in audio thread and optimised voice rendering for real-time audio performance
Enhanced
- Real-Time Audio Safety: Fixed critical buffer resize bug in
SIDEngine.cpp:84-87that caused memory allocations during audio processing, replaced with pre-allocated buffer clamping - Branch-Free Voice Rendering: Optimised
renderAllActiveVoices()inPluginProcessor.cpp:476-505by building active voice pointer array eliminating conditional branches in rendering loop - Eliminated Audio Thread Allocations: Replaced runtime
tempBuffer.resize()with compile-time buffer size validation ensuring zero memory allocations in real-time audio path - Performance-Critical Optimizations: Focused on hot path audio processing code eliminating function call overhead and branch prediction penalties
Technical Implementation
- SIDEngine.cpp: Changed dynamic buffer resizing to fixed buffer size validation with sample count clamping preventing audio thread memory allocation
- PluginProcessor.cpp: Replaced boolean array iteration with direct active voice pointer array for branch-free rendering with improved cache efficiency
- Voice Processing Architecture: Streamlined active voice detection and rendering pipeline eliminating redundant conditional checks in audio callback
Performance Benefits
- Zero Audio Thread Allocations: Eliminated all potential memory allocations during real-time audio processing ensuring glitch-free audio output
- Improved Branch Prediction: Removed conditional branches from voice rendering hot path improving CPU pipeline efficiency and reducing audio latency
- Better Cache Utilization: Active voice pointer array provides sequential memory access pattern improving CPU cache performance during voice rendering
- Reduced Function Call Overhead: Eliminated unnecessary function calls and conditional logic in audio processing pipeline
Real-Time Audio Compliance
- Memory Allocation Safety: All audio processing now operates within pre-allocated memory bounds eliminating potential audio dropouts from memory allocations
- Deterministic Processing: Fixed buffer sizes and branch-free loops provide predictable audio processing timing critical for professional audio applications
- Optimised Hot Paths: Critical audio rendering code optimised for minimal CPU overhead and maximum throughput ensuring stable real-time performance
[v0.9.12] – 2025-09-16
Reference: Fixed 256-slot patch array system conversion
Summary: Converted patch storage from dynamic vector to fixed 256-slot array system for simplified architecture and predictable memory usage
Enhanced
- Fixed Array Architecture: Converted
std::vector<PatchData> patchestostd::array<PatchData, WavetablePlayer::MAX_PATCHES> patchesfor predictable memory usage and simplified bounds checking - Eliminated Dynamic Allocation: Removed all
.resize(),.clear(),.push_back(), and.size()operations from patch management eliminating vector reallocation overhead - Simplified Bounds Checking: Replaced
patches.size()comparisons with constantWavetablePlayer::MAX_PATCHES(256) throughout codebase for cleaner logic - Streamlined Serialization: Simplified binary and JSON serialization logic by always handling exactly 256 patch slots eliminating conditional empty/existing patch handling
Technical Implementation
- PluginProcessor.h/cpp: Changed core data structure declaration and updated all size-related method calls and initialization logic
- PatchManagerComponent.h/cpp: Updated return types, replaced vector operations with direct array indexing, simplified patch creation logic
- WavetableSelectorComponent.cpp: Updated bounds checking from dynamic size to fixed constant for patch access validation
- FileManager.cpp: Simplified serialization by removing vector-specific operations and implementing element-wise copying for array compatibility
- PluginEditor.cpp: Updated patch access bounds checking to use fixed array size constant
Architecture Benefits
- Predictable Memory Usage: Always exactly 256 patch slots allocated regardless of actual patch count eliminating memory fragmentation
- Simplified Code Logic: Removed complex dynamic allocation, bounds checking, and size management code paths throughout patch system
- Better Performance: Eliminated vector reallocation overhead and memory management complexity during patch operations
- Cleaner Initialization: All 256 patches always exist with default constructor initialization eliminating edge cases for empty patch arrays
User Experience
- Consistent Patch Access: All 256 patch slots (0-255) always available for use matching intended design and user expectations
- Improved Stability: Eliminated potential memory allocation failures and bounds checking edge cases in patch management system
- Faster Patch Operations: Removed dynamic memory management overhead during patch loading, saving, and switching operations
- Professional Reliability: Fixed array system provides deterministic behavior and eliminates vector-related memory management issues
Code Quality Improvements
- Type Safety: Consistent array types throughout codebase eliminating vector/array type conversion issues and compilation errors
- Reduced Complexity: Simplified patch management logic by removing dynamic allocation concerns and size-dependent code paths
- Better Maintainability: Fixed array approach eliminates entire class of bugs related to dynamic vector management and memory allocation
- Framework Alignment: Fixed-size array matches the intended 256-patch design explicitly documented in MAX_PATCHES constant
[v0.9.11] – 2025-09-16
Reference: Audio processing performance optimizations
Summary: Implemented critical audio buffer optimizations eliminating memory allocations and enhancing stereo processing efficiency
Enhanced
- Pre-Allocated Audio Buffers: Eliminated repeated memory allocations in SIDVoice::render() by pre-allocating tempBuffer as member variable with 8192-sample capacity
- Batch Voice Processing: Reorganized audio processing into separate phases – wavetable tick updates followed by optimised batch voice rendering with early exit for inactive voices
- Optimised Stereo Handling: Integrated stereo channel copying into batch voice rendering using JUCE’s SIMD-optimised copyFrom() method eliminating redundant buffer operations
- Smart Voice Counting: Batch renderer counts active voices upfront to optimise single-voice scenarios and minimize unnecessary processing overhead
Technical Implementation
- SIDEngine.h Enhancement: Added
std::vector<short> tempBuffermember variable to SIDVoice class for persistent audio buffer storage - SIDEngine.cpp Optimisation: Modified constructor to pre-allocate 8192-sample buffer and updated render() method to use resize-only-if-needed logic
- PluginProcessor.cpp Refactoring: Separated voice processing into wavetable tick phase and batch rendering phase through new renderAllActiveVoices() method
- Stereo Processing Integration: Moved stereo channel copying from main processBlock() into batch renderer with JUCE’s optimised buffer operations
Performance Benefits
- Eliminated Memory Allocation Overhead: Removed ~777 allocations/second (3 voices × 259 callbacks/second) that occurred during sustained audio generation
- Reduced Function Call Overhead: Batch processing minimizes repeated active/enabled checks and optimizes voice rendering pipeline
- SIMD Acceleration: JUCE’s copyFrom() automatically uses vectorized instructions for stereo channel copying when available
- Improved Cache Locality: Stereo copying occurs immediately after voice rendering while audio data is hot in cache
User Experience
- Smoother Audio Performance: Significantly reduced audio processing overhead during sustained multi-voice operation
- Lower CPU Usage: Optimised audio pipeline reduces system load especially during complex wavetable processing scenarios
- Professional Reliability: Enhanced audio processing stability through elimination of repeated memory operations in critical audio path
- Maintained Audio Quality: All optimizations preserve identical audio output while improving underlying performance characteristics
Architecture Benefits
- Memory-Efficient Design: Pre-allocated buffers eliminate garbage collection pressure and memory fragmentation in audio thread
- Scalable Performance: Performance improvements become more significant with increased voice count and buffer sizes
- Framework Integration: Leverages JUCE’s highly optimised internal audio buffer handling methods for maximum efficiency
- Maintainable Code: Clean separation between voice processing phases and optimised stereo handling for better code organization
[v0.9.10] – 2025-09-16
Reference: Oscilloscope timer consolidation and CPU optimisation for wavetable processing
Summary: Consolidated oscilloscope timer with GUI timer, fixed wavetable Loop defaults causing excessive CPU usage, and optimised rendering performance
Enhanced
- Timer Consolidation: Merged oscilloscope timer with existing 50Hz GUI timer eliminating separate 60 FPS timer and reducing timer overhead
- CPU Usage Fix: Changed default Loop behavior from enabled to disabled for all wavetables preventing 800+ processing calls per second that caused high CPU usage
- Oscilloscope Performance: Reduced glow effect from triple-pass to dual-pass rendering (commented out middle glow layer) for improved drawing performance
- Wavetable Activation Control: Wavetables now only activate when users explicitly enable Loop, eliminating automatic continuous processing of all 15 voice wavetables
Technical Implementation
- OscilloscopeComponent Timer Removal: Removed separate
juce::Timerinheritance and 60 FPSstartTimer(16)from oscilloscope component - GUI Timer Integration: Added
oscilloscopeUpdateCallbacktoGuiUpdateTimercallingupdateDisplay()at 50Hz instead of separate 60 FPS timer - WavetablePlayer Default Fix: Modified constructor to set
allWavetablesControl[regType][table][0] = false(Loop disabled) by default instead oftrue - Wavetable Start Method: Enhanced
start()method to only activate wavetables that have Loop enabled in control flags preventing mass activation - Debug Logging Added: Implemented comprehensive logging system to track filter/vol processing frequency and active wavetable counts for performance analysis
Performance Benefits
- Reduced Timer Overhead: Eliminated redundant 60 FPS timer while maintaining smooth oscilloscope updates at 50Hz
- Dramatic CPU Reduction: Fixed CPU bottleneck where all 15 voice wavetables (3 voices × 5 register types) were processing continuously at 50Hz = 750 calls/second
- User-Controlled Processing: Wavetables only run when explicitly enabled by users through Loop buttons instead of running by default
- Optimised Rendering: Simplified oscilloscope glow effect reduces path stroking operations while maintaining visual quality
User Experience
- Responsive Performance: Plugin now operates with significantly lower CPU usage eliminating performance issues during normal operation
- Intentional Wavetable Animation: Users must explicitly enable Loop on wavetables they want to animate instead of all wavetables running automatically
- Maintained Visual Quality: Oscilloscope retains smooth animation and visual appeal while using fewer system resources
- Professional Operation: Plugin performs efficiently in professional DAW environments without excessive CPU overhead
Root Cause Resolution
- Default Wavetable Processing: All wavetables were enabled by default causing continuous 50Hz processing even when not needed
- Timer Redundancy: Separate oscilloscope timer created unnecessary overhead when GUI timer could handle all UI updates
- Mass Activation Bug:
start()method was force-activating all wavetables on MIDI note-on regardless of Loop control flag settings - Performance Analysis: Added logging revealed 15 active wavetables + 1 filter/vol = 800+ processing calls per second causing CPU spikes
[v0.9.9] – 2025-09-15
Reference: MIDI program change independence from GUI components
Summary: Fixed MIDI program change functionality to work without GUI resources, enabling patch switching when plugin window is closed
Enhanced
- GUI-Independent Program Changes: MIDI program changes now work reliably when VST GUI is closed by calling
setCurrentProgram()directly in audio processor - Complete Patch Loading:
setCurrentProgram()properly loads all patch settings including wavetable indexes, chip model, and MIDI channel configurations - DAW Host Notification: Added
refreshParameterList()call insetStateInformation()to notify DAW when program names change after patch restoration - Early Patch Initialization: Audio processor constructor initializes 256 patches ensuring
getNumPrograms()returns correct value during plugin instantiation
Technical Implementation
- PluginProcessor.cpp Enhancement: Added direct
setCurrentProgram(programNumber)call in MIDI program change handling (line 220) bypassing GUI callback dependency - Patch Loading Method: Implemented comprehensive
loadPatch()method applying wavetable indexes, chip model, MIDI channels, and global filter/volume settings from patch data - Binary Format Restoration: Enhanced
parseBinaryV3()to restore patches directly to processor when patch manager unavailable during DAW state restoration - Program Name Display: Modified
getProgramName()to show meaningful patch names with fallback to “Patch N” for empty patches
Architecture Benefits
- Audio-First Design: Audio processor maintains complete independence from GUI components for all MIDI program change functionality
- DAW Compatibility: Plugin works correctly in professional DAW workflows where GUI may be closed while audio processing continues
- Single Source of Truth: Audio processor owns all patch data ensuring availability regardless of GUI state following proper VST3 architecture patterns
- Host Integration: Proper DAW notification system ensures program lists refresh when patch data changes
User Experience
- Seamless MIDI Control: MIDI program changes work identically whether plugin GUI is open or closed eliminating workflow disruptions
- Professional Reliability: Plugin responds to MIDI program changes from external controllers and DAW automation regardless of GUI state
- Consistent Behavior: All patch settings including wavetable assignments and chip model properly loaded via MIDI program change
- DAW Integration: Program names display correctly in DAW program change menus after plugin state restoration
Root Cause Resolution
- GUI Dependency: Previously MIDI program changes only worked through GUI callback preventing operation when plugin window closed
- Incomplete Loading: Original implementation only set patch index without applying actual patch settings to audio processing
- Host Communication: Missing DAW notification prevented program name updates after patch data restoration from saved state
[v0.9.8] – 2025-09-15
Reference: 1-based patch indexing for DAW/MIDI compatibility
Summary: Converted patch display and MIDI program change handling from 0-based to 1-based indexing for improved DAW and MIDI controller compatibility
Enhanced
- Industry Standard Compatibility: MIDI Program Change 0-127 now maps to user-visible Patches 1-128 matching DAW and MIDI hardware expectations
- Professional User Experience: Patch list displays 1-256 instead of 0-255, eliminating confusion where “first patch” appeared as “Patch 0”
- MIDI Controller Integration: Program Change 0 now loads “Patch 1”, Program Change 1 loads “Patch 2”, etc., matching industry conventions
- DAW Workflow Alignment: Patch numbering now matches professional DAW and music hardware standards where programs start at 1
Technical Implementation
- MIDI Program Change Translation: Enhanced handleMidiProgramChange() to maintain internal 0-based arrays while presenting 1-based numbers to users
- UI Display Updates: Modified PatchListItem display formatting to show (patchIndex + 1) in patch list (line 953)
- Current Patch Display: Updated updateCurrentPatchDisplay() to show 1-based patch numbers in “Playing: XXX” status (line 842)
- Documentation Updates: Revised User Manual examples to show Program Change 0-127 → Patches 1-128 mapping with clear explanations
- Backward Compatibility: Maintained internal 0-based data structures and file formats ensuring no breaking changes to existing patch files
User Experience Benefits
- Intuitive Numbering: “Patch 1” is now the first patch, not “Patch 0”, matching user expectations and industry standards
- MIDI Hardware Compatibility: MIDI controllers displaying “Program 1” now correctly load the first patch instead of causing confusion
- Professional Workflow: Eliminates cognitive load of converting between 0-based internal numbering and 1-based industry conventions
- DAW Integration: Seamless integration with DAWs that display program changes as 1-128 in automation lanes and MIDI editors
Architecture Benefits
- Clean Translation Layer: Internal 0-based arrays maintained for efficiency while providing 1-based user interface consistently
- No File Format Changes: Existing .reSIDue and .json patch files continue working unchanged with automatic index translation
- Consistent Implementation: Applied 1-based display conversion uniformly across all UI components and user-facing documentation
- Industry Alignment: Plugin now follows established audio industry conventions reducing user confusion and improving professional adoption
[v0.9.7] – 2025-09-15
Reference: Centralized patch limit constant refactoring
Summary: Replaced hardcoded 256 patch limit values with centralized WavetablePlayer::MAX_PATCHES constant for improved maintainability and consistency
Enhanced
- Centralized Patch Limit: Updated MAX_PATCHES constant from 128 to 256 in WavetablePlayer.h to match actual usage throughout codebase
- Eliminated Hardcoded Values: Replaced all hardcoded 256 values in PatchManagerComponent.cpp and FileManager.cpp with WavetablePlayer::MAX_PATCHES references
- Consistent Architecture: Applied same centralized constant pattern used for MAX_TABLES to patch management for unified architecture approach
- Maintainable Code: All patch-related size checks and loops now use single source of truth eliminating maintenance burden of scattered hardcoded values
Technical Implementation
- WavetablePlayer.h Enhancement: Updated MAX_PATCHES constant from 128 to 256 (line 12) to match current codebase usage
- PatchManagerComponent.cpp Updates: Replaced 4 hardcoded 256 values with WavetablePlayer::MAX_PATCHES in getNumRows(), listBoxItemClicked(), loadPatch(), and patch vector sizing
- FileManager.cpp Updates: Replaced 4 hardcoded 256 values with WavetablePlayer::MAX_PATCHES in binary patch serialization, patch count validation, and patch vector allocation
- Consistent Pattern: Applied same centralized constant approach used successfully for MAX_TABLES to patch management architecture
Architecture Benefits
- Single Source of Truth: All patch limit references now use centralized constant eliminating inconsistencies and making future changes trivial
- Scalable Design: Changing patch limit now requires single constant update instead of hunting through multiple files for hardcoded values
- Code Consistency: Follows established MAX_TABLES pattern providing uniform approach to configurable limits throughout codebase
- Maintainable Codebase: Reduced maintenance burden with centralized configuration and eliminated risk of missing hardcoded values during future updates
User Experience
- Invisible Changes: All existing patch management functionality preserved with no workflow changes or visible differences to users
- Future Flexibility: Architecture now supports easy adjustment of patch limits through single constant modification if needed
- Consistent Behavior: All patch-related operations use same limit ensuring consistent behavior across all plugin components
[v0.9.6] – 2025-09-15
Reference: Enhanced patch application with wavetable table switching
Summary: Improved applyPatchToUI() to switch wavetable editor tables to match loaded patch indexes for better visual synchronization
Enhanced
- Complete Table Switching: When applying a patch, all wavetable editors now switch to display the tables referenced by the patch indexes
- Voice 0 Synchronization: FREQ, PULS, CTRL, A/D, and S/R editors automatically switch to tables used by voice 0 in the loaded patch
- Global Filter/Vol Switching: Filter/Vol editor switches to the global table index specified in the patch
- Visual State Consistency: UI immediately reflects which wavetables are actually being used by the loaded patch
Technical Implementation
- PatchManagerComponent.cpp Enhancement: Updated
applyPatchToUI()method (lines 477-515) to include table switching for all wavetable editor components - Safe Component Access: Added proper null checking for
wavetableEditorComponentand individual wavetable components before switching tables - Index Retrieval: Extract wavetable indexes from
patch.wavetableIndexes[voice][registerType]andpatch.globalFilterVolIndexfor accurate table selection - Component Method Calls: Uses
switchToTable(index)method on each wavetable component type (FreqWavetableComponent, PulsWavetableComponent, etc.)
User Experience
- Immediate Visual Feedback: Loading a patch immediately shows the correct wavetable data in all editors eliminating confusion about which tables are active
- Consistent Interface State: All wavetable editors display data that matches the patch’s actual wavetable references
- Better Workflow Understanding: Users can immediately see which wavetables a patch uses without having to navigate through table indexes manually
- Professional Polish: Patch loading provides complete visual synchronization between patch data and editor display state
Architecture Benefits
- State Synchronization: Eliminates discrepancy between loaded patch data and displayed wavetable content
- Component Integration: Leverages existing
switchToTable()methods across all wavetable editor types for consistent behavior - Robust Implementation: Comprehensive null checking ensures stable operation during all plugin lifecycle stages
- Maintainable Code: Clean separation of patch loading logic with proper component method delegation
[v0.9.5] – 2025-09-14
Reference: Legacy method cleanup and code simplification
Summary: Removed unused loadPatchFromFile() method that became redundant after file handling architecture consolidation
Removed
- Unused Method Elimination: Removed
loadPatchFromFile()method from both PatchManagerComponent.h and PatchManagerComponent.cpp since it had no callers in the current codebase - Legacy Code Cleanup: Eliminated method that was previously used by Load buttons but became obsolete after v0.8.3 file handling consolidation
- Redundant Wrapper Removal: Method only called
importFromJSON()which is already directly accessible through hamburger menu system
Technical Implementation
- Header Cleanup: Removed
void loadPatchFromFile();declaration from PatchManagerComponent.h line 121 - Implementation Removal: Removed complete method implementation from PatchManagerComponent.cpp lines 661-664
- API Simplification: Eliminated unnecessary indirection layer that provided no additional functionality over direct
importFromJSON()access
Architecture Benefits
- Cleaner Codebase: Removed approximately 4 lines of dead code that served no purpose in current architecture
- Reduced Maintenance Burden: Fewer unused methods means less code to maintain and review during future development
- Simplified API Surface: Eliminated redundant method that duplicated existing functionality without adding value
- Code Consistency: Aligns with architectural changes where Load buttons use file choosers directly instead of wrapper methods
Root Cause Analysis
- Historical Context: Method was originally used by Load buttons before v0.8.3 file handling consolidation changed to use direct file chooser implementations
- Architectural Evolution: File handling architecture evolved to use FileManager methods directly, making this wrapper method obsolete
- Unused Code Accumulation: Method remained in codebase after Load button behavior changed, creating dead code that served no function
User Experience
- No Impact: Completely invisible change to users as the method was never called in current codebase
- Maintained Functionality: All existing JSON import functionality preserved through direct
importFromJSON()access via hamburger menu - Code Quality Improvement: Cleaner codebase contributes to overall plugin reliability and maintainability
[v0.9.4] – 2025-09-14
Reference: Binary format conversion for loadPatchSetFromFile method
Summary: Refactored loadPatchSetFromFile() to use binary .reSIDue format instead of JSON, consolidated file handling architecture, and cleaned up unused methods
Enhanced
- Binary Format Loading: Converted
loadPatchSetFromFile()from JSON parsing toFileManager::loadPatchSetBinary()calls for consistent binary format usage - File Reference Update: Changed from
getCurrentPatchSetFile()togetPatchSetFile()to usepatches.reSIDuebinary files instead ofpatches.json - Simplified Loading Logic: Removed complex JSON parsing, version checking, and legacy format support since binary format handles all data uniformly
- Consistent File Architecture: All automatic patch loading now uses binary format matching the file chooser operations for Load/Save buttons
Removed
- Unused Method Cleanup: Eliminated
getCurrentPatchSetFile()method from both header and implementation files since it’s no longer called anywhere in codebase - JSON Parsing Logic: Removed 42 lines of JSON parsing code including version detection, legacy format support, and complex nested data handling
- Redundant File Operations: Simplified file existence checking by removing JSON string loading and validation since binary format is self-validating
Technical Implementation
- PatchManagerComponent.cpp Enhancement: Replaced lines 815-857 JSON parsing logic with single
fileManager->loadPatchSetBinary()call - File Path Simplification: Updated lines 795-813 to use
getPatchSetFile()returningpatches.reSIDueand removed JSON string loading operations - Method Elimination: Removed
getCurrentPatchSetFile()declaration from PatchManagerComponent.h line 117 and complete implementation from .cpp lines 640-659 - Error Handling Preservation: Maintained file existence checking and default patch creation while using binary format for all successful loads
Architecture Benefits
- Unified File Format: Plugin now consistently uses binary .reSIDue format for both automatic loading and user-initiated file operations
- Reduced Complexity: Eliminated dual format support complexity by standardizing on binary format throughout the loading architecture
- Cleaner Codebase: Removed approximately 50 lines of unused code including complex JSON parsing logic and redundant file path methods
- Performance Improvement: Binary format loading is faster than JSON parsing and provides more reliable data validation
User Experience
- Invisible Changes: All existing patch loading functionality preserved with no workflow changes or visible differences to users
- Improved Reliability: Binary format provides more robust loading with built-in data validation and error recovery
- Consistent File Handling: Automatic startup loading now uses same binary format as Load/Save buttons for unified file management experience
- Better Performance: Faster plugin initialization due to more efficient binary loading compared to JSON parsing
Root Cause Resolution
- Format Inconsistency: Resolved mismatch where automatic loading used JSON while user operations used binary format
- Code Duplication: Eliminated separate file handling paths by consolidating on single binary format approach
- Unused Code Accumulation: Cleaned up methods that became obsolete after previous architectural changes
[v0.9.3] – 2025-09-13
Reference: WavetableSelectorComponent architecture cleanup and chip model patch integration
Summary: Removed duplicate tableNumbers cache array from WavetableSelectorComponent and fixed chip model selector to update current patch data
Enhanced
- Eliminated Data Duplication: Removed
std::array<std::array<int, 5>, 3> tableNumberscache array that duplicated wavetable indexes from patch data - Single Source of Truth: WavetableSelectorComponent now reads/writes directly from
audioProcessor->patches[currentPatchIndex].wavetableIndexesensuring data consistency - Direct Access Architecture: Implemented direct patch data access through audioProcessor pointer eliminating sync issues between cache and authoritative data
- Maintained API Compatibility: Preserved existing
getTableNumber()andsetTableNumber()method signatures ensuring all existing callers work unchanged - Chip Model Patch Integration: Fixed chip model selector to update current patch’s chipModel field ensuring patch-specific chip model persistence
Technical Implementation
- PluginProcessor Integration: Added
setAudioProcessor()method to WavetableSelectorComponent with proper forward declaration and include structure - Method Reimplemantation: Updated
getTableNumber()andsetTableNumber()to access patch data directly instead of local cache with comprehensive null safety checks - Initialization Enhancement: Added
refreshFromPatchData()method to update UI from current patch data eliminating hardcoded initialization values - Memory Safety: Added comprehensive null pointer validation and bounds checking for robust operation during all plugin lifecycle stages
- Chip Model Persistence Fix: Enhanced
PluginProcessor::setChipModel()to update current patch’s chipModel field alongside global processor state and SID voices
Architecture Benefits
- No Sync Issues: Eliminated possibility of cache getting out of sync with authoritative patch data by removing intermediate storage layer
- Cleaner Code: Removed cache management complexity, initialization logic, and redundant synchronization code, simplifying component architecture and reducing maintenance burden
- Performance Consistency: Direct access eliminates memory overhead of duplicate storage while maintaining same performance characteristics
- Framework Compliance: Follows “lightweight patch system” design principles with patches containing only indexes, not data copies
- Eliminated Redundant Operations: Removed approximately 50 lines of unnecessary wavetable index synchronization code across multiple methods
Fixed
- Redundant Patch Switching Code: Removed legacy wavetable index synchronization code (lines 288-310) in PatchManagerComponent that was redundant with new direct access architecture
- Redundant Save Synchronization: Removed unnecessary wavetable index copying in
saveAllPatchesToFile()method (lines 341-361) since WavetableSelectorComponent now writes directly to patch data - Chip Model Patch Persistence: Fixed chip model button not updating current patch’s chipModel field, ensuring chip model changes are saved with patch data
User Experience
- Invisible Changes: All existing wavetable selector functionality preserved with no workflow changes or visible differences to users
- Improved Reliability: Eliminates potential for UI display not matching actual patch data due to cache synchronization issues
- Consistent Behavior: Wavetable index changes immediately reflected in patch data ensuring proper save/load and patch switching behavior
- Proper Chip Model Persistence: Chip model selection now properly saved with patches and restored during patch loading
[v0.9.2] – 2025-09-13
Reference: Chip model selector relocation and MIDI channel data architecture
Summary: Moved chip model selector to wavetable selector component, enhanced oscilloscope size, and restructured MIDI channel data to be patch-specific
Added
- Patch-Specific MIDI Channel Storage: MIDI channel settings now stored in PatchData structure ensuring proper save/load with patches
- Callback System for MIDI Channels: Implemented getter/setter callbacks to connect WavetableSelectorComponent to patch data
- Processor MIDI Channel Methods: Added setVoiceMidiChannel() and getVoiceMidiChannel() methods to PluginProcessor
- Enhanced Patch Loading: MIDI channel dropdowns properly update when switching between patches
- File Format MIDI Channel Support: Added voiceMidiChannel array serialization to both JSON and binary file formats
Enhanced
- Chip Model Selector Relocation: Moved from WavetableEditorComponent to WavetableSelectorComponent for logical grouping (affects all voices)
- Optimised Button Positioning: Chip selector positioned inline with Voice 2 row and F/V selector column for better layout organization
- Transparent Button Background: Chip model button background made transparent when no border is enabled for cleaner visual integration
- Oscilloscope Size Increase: Increased preferred width from 384px to 480px (25% wider) for better waveform visualization
- MIDI Indicator Updates: Fixed MIDI indicator shape changes during patch loading to reflect chip model stored in patches
- Complete File Format Integration: MIDI channel settings fully integrated into patch save/load system with proper defaults
Fixed
- Patch Loading MIDI Channels: MIDI channel dropdowns now correctly display loaded patch values instead of always showing defaults
- Callback Chain Integration: Proper callback setup ensures MIDI channel changes are saved to current patch data
- Data Architecture: MIDI channel data properly integrated into patch structure instead of being component-specific
- File Persistence: MIDI channel configurations now properly saved and restored across plugin sessions
[v0.9.1] – 2025-09-12
Reference: Centralized colour scheme management system
Summary: Replaced all hardcoded hex ARGB colours with organised, maintainable colour constants system for improved code maintainability
Added
- C64ColorScheme.h: Comprehensive colour constants file organizing all UI colours by component type and usage
- Structured Colour Organization: Colours grouped by UI element (TextEditor, Button, ComboBox, PopupMenu, Label, ListBox, Table, MIDI, Oscilloscope, Component)
- Semantic Colour Names: Self-documenting colour constants like
C64Colors::Button::BackgroundDarkandC64Colors::Label::TextCyan - Centralized Maintenance: Single source of truth for all colour definitions enabling easy theme modifications
Enhanced
- Code Maintainability: Eliminated 100+ hardcoded colour instances across 9 source files for centralized management
- Consistency: Unified colour usage prevents accidental colour variations and ensures visual consistency
- Developer Experience: Clear, readable colour references improve code understanding and modification workflow
- Theme Flexibility: Structured approach enables future theme variations and dynamic colour switching
Technical Implementation
- Namespace Organization: Colours organised in logical namespaces (TextEditor, Button, ComboBox, etc.) for clear categorization
- Type Safety: All colours defined as
const juce::Colourconstants preventing runtime modifications - Include Integration: Added
#include "C64ColorScheme.h"to all relevant source files for colour access - Complete Coverage: Replaced all hex colour codes (0xff000000 through 0xffffffff) with named constants
Refactored Files
- WavetableSelectorComponent.cpp: 37 colour replacements for combo boxes, text editors, buttons, and labels
- WavetableTableComponent.cpp: 33 colour replacements for table components, editors, and UI elements
- WavetableTableComponent.h: 4 colour replacements for toggle button states and backgrounds
- MidiIndicatorComponent.cpp: 7 colour replacements for MIDI status indicator colours
- WavetableEditorComponent.cpp: 3 colour replacements for component backgrounds
- PatchManagerComponent.cpp: 21 colour replacements for patch management interface elements
- OscilloscopeComponent.cpp: 9 colour replacements for waveform visualization and grid colours
Colour Categories Organised
- Background Colours: Black, DarkBlue, MediumBlue, LightBlue variations for different component depths
- Text Colours: White, LightGrey, Cyan, Yellow, Green for different text roles and voice indicators
- Accent Colours: Purple, DarkPurple, LightGreen for highlights, focus states, and special effects
- State Colours: Red, DarkRed, VeryDarkRed, DarkGreen for active/inactive states and current row indicators
- Control Colours: Various grey shades for buttons, backgrounds, and UI element differentiation
Architecture Benefits
- Single Source of Truth: All colours defined once in header file eliminating duplication and inconsistencies
- Easy Modifications: Change entire colour scheme by modifying constants in one file
- Scalable Design: Organised structure supports future additions of new colours or theme variants
- Professional Standards: Follows established patterns for colour management in large codebases
[v0.9.0] – 2025-01-15
Summary: Major UI layout redesign with full-width top bar containing logo, MIDI indicator, and hamburger menu
Pre-v0.9.0 Version Summary
[v0.8.18-v0.8.0] – Major Architectural Improvements
- v0.8.18: Fixed VST3/Standalone pitch discrepancy with dynamic sample rate configuration
- v0.8.17: Fixed binary patch file compatibility with proper data distribution
- v0.8.16: Fixed UI scaling issues and font consistency
- v0.8.15: Replaced text labels with custom image components
- v0.8.14: Code cleanup – removed unused methods and dead code
- v0.8.13: Applied C64 font consistency and text field centering
- v0.8.12: Added patch index bounds validation for stability
- v0.8.11: Fixed patch list selection synchronization
- v0.8.10: Moved patch storage to processor for VST compatibility
- v0.8.9: Reorganized patch data architecture for headless VST operation
- v0.8.8: Added comprehensive JUCE-based logging system
- v0.8.7: Fixed 5x UI rendering performance overhead
- v0.8.6: Enhanced patch manager visual styling with borders and layout
- v0.8.5: Optimised UI performance by removing excessive repaints
- v0.8.4: Added ListBox patch navigation with inline editing
- v0.8.3: Consolidated file handling with binary format and JSON import/export
- v0.8.2: Fixed patch filename display during initialization
- v0.8.1: Fixed custom button state refresh when switching wavetable tables
- v0.8.0: Implemented shared wavetable architecture eliminating memory duplication
[v0.7.9-v0.7.0] – Configuration and UI Enhancements
- v0.7.9: Global configuration system for OpenGL and UI scaling
- v0.7.8: Binary patch file system with DAW state integration
- v0.7.7: VST3 focus handling fix for wavetable navigation
- v0.7.6: Save-on-close implementation for patch persistence
- v0.7.5: 2x UI scaling implementation with state persistence
- v0.7.4: C64 font consistency and interface cleanup
- v0.7.3: Custom image toggle button for chip model selection
- v0.7.2: VST3 MIDI input compatibility fix
- v0.7.1: Critical VST3 stability fix with callback lifecycle management
- v0.7.0: Interactive tooltip system for wavetable cells
[v0.6.23-v0.6.0] – Wavetable System Development
- MIDI Program Change Support: Real-time patch switching and DAW automation
- Gate-Conditional Jump Commands: Enhanced EXEC operations with SID gate checking
- Decimal Input Support: A/R column input with validation and templates
- Scalable Architecture: MAX_TABLES constant for centralized wavetable limits
- Custom UI Components: Hamburger menu, rename/save buttons, patch management
- Wavetable Control System: Loop/ReTrig/AutoGate flags with JSON persistence
- Complete Editor Integration: FREQ/PULS/CTRL/AD/SR wavetable GUI system
- Template-Based Architecture: Code deduplication and type-safe operations
[v0.5.17-v0.5.0] – Interface Polish and Plugin Rename
- C64-Themed Interface: Consistent styling with popup menus and ComboBox theming
- Professional Visual Elements: Embedded logo, oscilloscope enhancements, CRT glow effects
- Advanced Wavetable Editing: Context menus, copy/paste, optimised serialization
- Hardware Acceleration: Optional OpenGL rendering with state persistence
- Complete Plugin Rename: “SID6581” to “reSIDue” throughout entire codebase
[v0.4.2-v0.4.0] – Filter/Volume System and Core Features
- Complete Filter/Volume Implementation: Global wavetable system with 6-column editor
- Authentic SID Processing: 11-bit filter cutoff control and independent 50Hz timing
- Logo Integration: PNG display with proper positioning and scaling
- Button Visibility Fixes: Proper layout for Loop/Retrig controls
- Expanded GUI: 2100×1000 pixel interface with enhanced patch management
[v0.3.2-v0.1.0] – Foundation Development (Aug 2025)
Major Architectural Achievements:
- Complete SID Emulation Pipeline: JUCE + reSID integration to audio output
- Advanced Wavetable System: 32-row step automation for all SID registers (1000 tables per type)
- Multi-Voice Architecture: 3 independent voices with monophonic MIDI and per-voice control
- Professional GUI Framework: C64-themed interface with hex input and real-time visualization
- Sample-Accurate Timing: Authentic 50Hz processing matching C64 hardware (882 samples @ 44.1kHz)
- Robust Patch Management: JSON save/load with thread-safe operations and comprehensive persistence
Critical Fixes Resolved:
- Timing Bug Resolution: Fixed unsigned wraparound preventing continuous processing
- Voice Control Integration: GUI wavetable selector properly controlling per-voice audio
- Race Condition Elimination: Thread-safe patch loading with audio pause/resume
- WAIT/LOOP Processing: Fixed infinite loops with proper 3-variable state machine
- Direct Access Architecture: Zero-latency GUI-audio communication
Technical Foundation:
- VST3 Build Support: Complete plugin format compatibility with VST2 stub headers
- Cross-Platform Compatibility: JUCE File class integration and proper path handling
- Real-Time Visualization: Working oscilloscope with trigger detection and optimised refresh
- Professional Workflow: Streamlined patch management and comprehensive error handling