This document provides an overview of the KeyMaster application architecture for AI assistance and future development reference.
KeyMaster is a JUCE-based MIDI router and processor that manages MIDI connections, songs, patches, and real-time MIDI routing. It uses a document-based architecture with JSON file storage.
KeyMasterApplication (Main.cpp)
└─ MainWindow
└─ MainComponent (FileBasedDocument)
├─ MenuManager (ApplicationCommandTarget)
├─ SetListSongsListBox
├─ SongPatchesListBox
├─ ConnectionsTable
├─ SetListsListBox
├─ MessagesListBox
├─ TriggersTable
├─ Clock UI (Clock light, BPM, button)
└─ SongNotes
Files:
Source/gui/menu_manager.h(38 lines)Source/gui/menu_manager.cpp(457 lines)
Architecture:
Uses JUCE's ApplicationCommandTarget and MenuBarModel pattern.
Command Categories:
- File: new_project, open_project, save_project, save_project_as
- Edit: undo, redo, cut, copy, paste, new_song, new_patch, new_connection, new_set_list, new_message, new_trigger, delete operations
- Go: next_song, prev_song, next_patch, prev_patch, find_song, find_set_list
- MIDI: toggle_clock, all_notes_off, super_panic, midi_monitor
- Help: about (macOS only)
Command Flow:
- User clicks menu item
- MenuManager routes command via
perform()method (lines 288-396) - Delegates to MainComponent methods
- Changes trigger ActionBroadcaster notifications
- UI components update via ActionListener callbacks
Files:
Source/Main.cpp(153 lines) - Application lifecycle and settings initSource/MainComponent.h(154 lines)Source/MainComponent.cpp(558 lines)
Storage Mechanism:
Uses JUCE's ApplicationProperties with XML format.
Initialization: Main.cpp lines 24-35
- Application name: "KeyMaster"
- File suffix: ".props"
- macOS location:
~/Library/Application Support/KeyMaster/
Current Settings Keys:
"window.state"- Window position and size"window.width"- Window width"window.height"- Window height"km.file"- Last opened project file path (constant at line 20)
Shutdown: Main.cpp lines 40-50
- Saves window state on exit
- Closes property files to flush to disk
Files:
Source/km/storage.h(61 lines)Source/km/storage.cpp(555 lines)Source/MainComponent.cpp(558 lines)
File Format:
- Extension:
.kmst - Format: JSON (using JUCE's JSON parser)
Key Operations:
Initial Load (MainComponent.cpp lines 160-186):
- Checks if
"km.file"property exists - Verifies file still exists
- Calls
loadDocument()if valid - Falls back to new project if file missing
New Project (lines 215-220):
- Prompts to save current project first (async)
- Creates new KeyMaster instance
- Removes old file from settings
Open Project (lines 222-231):
- Prompts to save current project first
- Opens file browser (via JUCE's loadFromUserSpecifiedFileAsync)
- Calls loadDocument with selected file
Save Project (lines 233-240):
- Calls saveAsync with async callback
- Updates settings with file path on success
Save As (lines 242-244):
- Calls saveAsInteractiveAsync
- Opens file dialog for new location
Storage Class (storage.cpp):
load()method: Parses JSON, reconstructs KeyMaster object hierarchysave()method: Serializes KeyMaster to JSON, writes to file- Returns error strings for UI display
Location: Source/km/
Main Classes:
keymaster.h/cpp- Main application state holder, root of data hierarchycursor.h/cpp- Navigation state (set_list, song, patch, connection, message, trigger indices)set_list.h/cpp- Contains array of Song objectssong.h/cpp- Contains patches, notes, BPM, clock settingspatch.h/cpp- Container for connectionsconnection.h/cpp- Maps MIDI input to output with filtering/transformationtrigger.h/cpp- Custom keyboard/MIDI action bindingsmessage_block.h/cpp- MIDI message definitionscurve.h/cpp- CC value transformation curvesdevice_manager.h/cpp- MIDI I/O device management
Data Hierarchy:
KeyMaster
├─ Cursor (current selections)
├─ SetList[]
│ └─ Song[]
│ └─ Patch[]
│ └─ Connection[]
├─ MessageBlock[]
├─ Trigger[]
└─ DeviceManager
Location: Source/gui/
Base Classes:
KmEditor- Dialog-based editors with Apply/Ok/Cancel buttons
Specific Editors:
SongEditor- Edit song propertiesPatchEditor- Edit patch propertiesConnectionEditor- Edit MIDI connection routingSetListEditor- Edit set list properties
List Components:
SetListSongsListBox- Songs in current set listSongPatchesListBox- Patches in current songConnectionsTable- Connections in current patchSetListsListBox- All set listsMessagesListBox- Message blocksTriggersTable- Trigger definitions
Special Components:
MidiMonitor- Separate window for MIDI message monitoringClockLight- Visual MIDI clock indicatorClockBpm- BPM display and editorClockButton- Start/stop clock button
- User interacts with GUI (menu, button, list box)
- Component calls MainComponent method or KeyMaster method
- Data model updates
- Model calls
KeyMaster::changed()to mark document dirty - Model sends ActionBroadcaster notification
- UI components receive ActionListener callbacks
- Components refresh their display
Source/
├── Main.cpp # Application entry point, settings init
├── MainComponent.h/cpp # Main window, document management
├── km/ # Core data model
│ ├── keymaster.h/cpp # Root data object
│ ├── cursor.h/cpp # Navigation state
│ ├── set_list.h/cpp # Set list container
│ ├── song.h/cpp # Song with patches
│ ├── patch.h/cpp # Patch with connections
│ ├── connection.h/cpp # MIDI routing
│ ├── message_block.h/cpp # MIDI messages
│ ├── trigger.h/cpp # Key/MIDI bindings
│ ├── storage.h/cpp # JSON serialization
│ └── device_manager.h/cpp # MIDI devices
└── gui/ # UI components
├── menu_manager.h/cpp # Application menus
├── *_editor.h/cpp # Dialog editors
├── *_list_box.h/cpp # List components
└── clock_*.h/cpp # Clock UI components
All menu operations use JUCE's ApplicationCommandTarget pattern:
- Commands defined in enum
getAllCommands()returns available command IDsgetCommandInfo()provides command metadataperform()executes commands
MainComponent extends JUCE's FileBasedDocument:
- Tracks dirty state
- Handles save confirmations
- Manages file associations
Model changes broadcast via JUCE's ActionBroadcaster/ActionListener:
- Data objects inherit ActionBroadcaster
- UI components implement ActionListener
- Automatic UI updates on data changes
- Always use JUCE's
Fileclass for cross-platform compatibility - File operations are async (use callbacks)
- Validate file existence before operations
- Update settings after successful file operations
- Add commands to CommandIDs enum
- Register in getAllCommands()
- Add info in getCommandInfo()
- Implement in perform()
- Delegate to MainComponent methods
- Use ApplicationProperties for persistence
- Define string constants for keys
- Save on successful operations
- Load on startup with validation
- Inherit ActionBroadcaster for data models
- Implement ActionListener for UI components
- Use
actionListenerCallback()to refresh UI - Keep UI logic separate from business logic
KeyMaster uses JUCE's Projucer for project management. The project files
are not checked into git; instead, developers generate them from the
.jucer file.
When implementing new features:
- Test cross-platform file path handling
- Verify settings persistence across restarts
- Test with missing/moved files
- Verify undo/redo compatibility
- Check memory management with JUCE smart pointers
- Test async operations and callbacks
When adding new features, you'll likely interact with:
- MenuManager: Add new menu items and commands
- MainComponent: Add document-level operations
- ApplicationProperties: Store user preferences
- KeyMaster: Access/modify core data model
- Storage: Serialize new data to/from JSON