Completion Date: January 7, 2026
Estimated Time: 4-6 hours
Actual Time: ~1.5 hours
Phase 3 successfully implemented the complete MIDI Router infrastructure for bidirectional communication between the plugin and D&R Cinemix console. The MidiRouter class encapsulates all MIDI protocol translation logic and provides a clean interface for parameter-to-MIDI and MIDI-to-parameter conversion.
Key Features:
- Clean class interface inheriting from
juce::MidiInputCallback - Device enumeration and management methods
- Comprehensive MIDI output methods (send CC, faders, parameters, commands)
- Callback mechanism for incoming MIDI handling
- Complete mapping data structures for all 161 parameters
Public API:
// Device Management
juce::StringArray getAvailableMidiInputs() const;
juce::StringArray getAvailableMidiOutputs() const;
bool openMidiPorts(int in1, int in2, int out1, int out2);
void closeMidiPorts();
bool arePortsOpen() const;
juce::String getPortStatusString() const;
// MIDI Output (DAW → Console)
void sendMidiCC(int channel, int cc, int value, int port);
void sendFaderValue(int channel, int ccPair, float value, int port);
void sendParameterUpdate(int paramIndex, float value);
void sendActivateCommand();
void sendDeactivateCommand();
void sendFullSnapshot(const std::array<float, 161>& params);
// MIDI Input (Console → DAW)
void handleIncomingMidiMessage(juce::MidiInput*, const juce::MidiMessage&) override;
std::function<void(int, float)> onParameterChangeFromConsole;
// Utility
void clearMidiCache();Data Structures:
struct MidiMapping {
int midiChannel; // MIDI channel (1-16)
int ccNumber; // Primary CC number
int ccNumber2; // Secondary CC for 14-bit (or -1)
int portIndex; // Which port: 1 or 2
bool isFader; // true = 14-bit fader, false = button/switch
};Implementation Highlights:
- Uses JUCE's
MidiInput::getAvailableDevices()andMidiOutput::getAvailableDevices() - Returns device names as StringArray for UI display
- Proper handling of device availability checks
- Opens/closes 4 MIDI ports (2 inputs, 2 outputs)
- Graceful handling of missing or unavailable devices
- Automatic cleanup on destruction
- Status reporting for UI feedback
- Single CC: Direct CC message sending with channel and port routing
- Dual CC (Faders): 14-bit resolution using CC pairs (MSB + LSB)
- Parameter Updates: Automatic routing based on parameter index
- Special Encoding:
- Mutes: 2=OFF, 3=ON (not 0/127!)
- AUX Mutes: Special values on CC#96 (2-21 encoding)
- Joystick Mutes: 2=OFF, 3=ON
- Duplicate Prevention: Caches previous MIDI values, only sends on change
- Activation/Deactivation Commands:
- Activate: CC#127, Val 127, Ch 5 (both ports)
- Deactivate: 0xFF System Reset byte
- Implements
handleIncomingMidiMessage()callback - Routes incoming MIDI to parameter update callback
- Prepared for Phase 4 reverse lookup implementation
Faders (0-71): 72 parameters, 14-bit dual CC
- Channels 1-24 Upper (0-23): Port 1, Ch 1, CC pairs 0-47
- Channels 25-36 Upper (24-35): Port 2, Ch 2, CC pairs 0-23
- Channels 1-24 Lower (36-59): Port 1, Ch 1, CC pairs 48-95
- Channels 25-36 Lower (60-71): Port 2, Ch 2, CC pairs 24-47
Mutes (72-143): 72 parameters, 7-bit, values 2/3
- Channels 1-24 Upper (72-95): Port 1, Ch 3, CC 0-23
- Channels 25-36 Upper (96-107): Port 2, Ch 4, CC 0-11
- Channels 1-24 Lower (108-131): Port 1, Ch 3, CC 24-47
- Channels 25-36 Lower (132-143): Port 2, Ch 4, CC 12-23
AUX Mutes (144-153): 10 parameters
- All: Port 2, Ch 5, CC 96 (special value encoding)
Master Section (154-160):
- Joy1_X (154): Port 2, Ch 2, CC 48
- Joy1_Y (155): Port 2, Ch 2, CC 50
- Joy1_Mute (156): Port 2, Ch 4, CC 24 (val 2/3)
- Joy2_X (157): Port 2, Ch 2, CC 52
- Joy2_Y (158): Port 2, Ch 2, CC 54
- Joy2_Mute (159): Port 2, Ch 4, CC 26 (val 2/3)
- Master_Fader (160): Port 2, Ch 5, CC 0+1 (14-bit)
floatToMidi14Bit(): Converts 0.0-1.0 → 0-16383midi14BitToFloat(): Converts 0-16383 → 0.0-1.0floatToMidi7Bit(): Converts 0.0-1.0 → 0-127midi7BitToFloat(): Converts 0-127 → 0.0-1.0
Changes to PluginProcessor.h:
#include "MidiRouter.h"
// Public interface
MidiRouter& getMidiRouter() { return midiRouter; }
void syncParameterToMidi(int paramIndex, float value);
// Private member
MidiRouter midiRouter;Changes to PluginProcessor.cpp:
// Constructor: Setup bidirectional callback
midiRouter.onParameterChangeFromConsole = [this](int paramIndex, float value)
{
// Convert paramIndex → paramID string
// Update parameter via APVTS
};
// Console control methods now use MidiRouter
void activateConsole() {
consoleActive = true;
midiRouter.sendActivateCommand();
}
void deactivateConsole() {
consoleActive = false;
midiRouter.sendDeactivateCommand();
}
void sendSnapshot() {
// Collect all 161 parameter values
// Send via midiRouter.sendFullSnapshot()
}
void syncParameterToMidi(int paramIndex, float value) {
if (consoleActive)
midiRouter.sendParameterUpdate(paramIndex, value);
}Build Integration:
- Included
MidiRouter.cppdirectly inPluginProcessor.cpp(line 17) - This approach ensures single compilation unit for JUCE projects
- Avoids need for manual Makefile modifications
All 161 parameters correctly mapped according to original protocol:
✅ Port Assignment
- Port 1: Channels 1-24 (LOW), MIDI Ch 1 & 3
- Port 2: Channels 25-36 (HIGH) + Master, MIDI Ch 2, 4, 5
✅ Fader Encoding
- 14-bit resolution (dual CC)
- Proper CC pair calculation
- Smooth 0-16383 value range
✅ Mute Encoding
- Values 2=OFF, 3=ON (not standard 0/127)
- AUX special encoding: 2-21 on single CC
- Joystick mutes: 2/3 encoding
✅ Special Commands
- Activation: CC#127, Val 127, Ch 5
- Deactivation: 0xFF System Reset
- Snapshot: All parameters sent in sequence
✅ Duplicate Prevention
- Caches previous MIDI values
- Only sends on actual parameter change
- Reduces MIDI traffic significantly
Platform: Linux (Ubuntu)
Build System: Makefile (auto-generated by Projucer)
Compiler: GCC with C++17
Build Output:
✅ CinemixAutomationBridge (Standalone) - 8.1 MB
✅ CinemixAutomationBridge.vst3 - VST3 plugin
Build Time: ~15 seconds (clean build)
No warnings or errors
Source/
├── MidiRouter.h 6,539 bytes (new)
├── MidiRouter.cpp 15,354 bytes (new)
├── PluginProcessor.h 4,925 bytes (modified)
├── PluginProcessor.cpp 15,251 bytes (modified)
├── PluginEditor.h 1,350 bytes (unchanged)
└── PluginEditor.cpp 2,735 bytes (unchanged)
Decision: Include MidiRouter.cpp in PluginProcessor.cpp instead of separate compilation
Rationale:
- Simplifies build process (no Makefile modifications needed)
- Common JUCE pattern for helper classes
- Avoids linking issues across platforms
- Single compilation unit approach
Decision: Use std::function callback for MIDI → parameter updates
Rationale:
- Decouples MidiRouter from PluginProcessor
- Allows flexible parameter update routing
- Easier to test independently
- Prepared for future bidirectional scenarios
Decision: Store previous MIDI values in std::array<int, 161>
Rationale:
- Prevents redundant MIDI sends (reduces bus traffic)
- Essential for test mode and automation
- Minimal memory overhead (644 bytes)
- Fast lookup (O(1))
Decision: Use dual CC for all faders (not single 7-bit)
Rationale:
- Matches original protocol specification
- Smooth fader movement (16384 steps vs 128)
- Professional console requirement
- Standard practice for high-end MIDI controllers
Decision: Direct array index = parameter index (0-160)
Rationale:
- O(1) lookup performance
- Simple and predictable
- Matches APVTS parameter order
- Easy to debug and verify
✅ Compilation Test: Clean build on Linux
✅ API Verification: All methods properly declared and defined
✅ Mapping Table Verification: All 161 parameters mapped correctly
✅ Integration Test: PluginProcessor successfully uses MidiRouter
✅ Build Product Verification: Standalone + VST3 generated
- MIDI Input Processing: Reverse lookup (MIDI → param index) stubbed for Phase 4
- Error Handling: Device connection errors need UI feedback (Phase 6)
- MIDI Port Selection: UI for port selection deferred to Phase 6
- Test Verification: Requires actual console hardware (Phase 9)
- Performance Testing: Real-time latency testing with hardware (Phase 9)
Phase 4: MIDI Protocol Implementation (Est. 3-4 hours)
Objectives:
- Implement reverse MIDI-to-parameter lookup
- Add parameter change listeners to auto-sync with console
- Implement console initialization sequence
- Add MIDI port state management
- Create MIDI activity indicators
- Test bidirectional parameter sync
Priority Features:
- Parameter listeners trigger MIDI sends
- Incoming MIDI updates DAW parameters
- Console activation sends initialization sequence
- Test mode triggers MIDI output
- Snapshot command sends all parameters
- New Files: 2 (MidiRouter.h, MidiRouter.cpp)
- Modified Files: 2 (PluginProcessor.h, PluginProcessor.cpp)
- Lines of Code Added: ~650
- MIDI Mappings Implemented: 161
- MIDI Channels Used: 5 (Ch 1-5)
- MIDI Ports Managed: 4 (2 in, 2 out)
- CC Controllers Mapped: ~110 unique CCs
Phase 3 Status: COMPLETE ✅
Ready for Phase 4: MIDI Protocol Implementation