261 lines
9.9 KiB
Markdown
261 lines
9.9 KiB
Markdown
# FCB1010 MIDI Integration Technical Reference
|
|
|
|
## 1. MIDI Controller Configuration
|
|
|
|
The FCB1010 will be configured with a consistent set of Control Change (CC) messages for all buttons and pedals:
|
|
|
|
| Control | CC Number | Value | Description |
|
|
|---------|-----------|-------|-------------|
|
|
| **Button 1** | CC 1 | 127/0 | Value 127 when pressed, 0 when released |
|
|
| **Button 2** | CC 2 | 127/0 | Value 127 when pressed, 0 when released |
|
|
| **Button 3** | CC 3 | 127/0 | Value 127 when pressed, 0 when released |
|
|
| **Button 4** | CC 4 | 127/0 | Value 127 when pressed, 0 when released |
|
|
| **Button 5** | CC 5 | 127/0 | Value 127 when pressed, 0 when released |
|
|
| **Button 6** | CC 6 | 127/0 | Value 127 when pressed, 0 when released |
|
|
| **Button 7** | CC 7 | 127/0 | Value 127 when pressed, 0 when released |
|
|
| **Button 8** | CC 8 | 127/0 | Value 127 when pressed, 0 when released |
|
|
| **Button 9** | CC 9 | 127/0 | Value 127 when pressed, 0 when released |
|
|
| **Button 10** | CC 10 | 127/0 | Value 127 when pressed, 0 when released |
|
|
| **UP** | CC 11 | 127/0 | Value 127 when pressed, 0 when released |
|
|
| **DOWN** | CC 12 | 127/0 | Value 127 when pressed, 0 when released |
|
|
| **Expression A** | CC 16 | 0-127 | Continuous controller |
|
|
| **Expression B** | CC 17 | 0-127 | Continuous controller |
|
|
| **MIDI Channel** | All on Channel 1 | | Single channel for simplicity |
|
|
|
|
## 2. System Architecture
|
|
|
|
### 2.1 Integration Diagram
|
|
|
|
```
|
|
+-------------+ +----------------+ +-------------+ +--------------+
|
|
| JACK Thread | --> | Lock-free MIDI | --> | MIDI Thread | --> | Application |
|
|
| (real-time) | | Input Queue | | | | Logic Thread |
|
|
+-------------+ +----------------+ +-------------+ +--------------+
|
|
^ |
|
|
| v
|
|
+----------------+ +----------------+
|
|
| Lock-free MIDI | | Configuration |
|
|
| Output Queue | <----------------------| Commands |
|
|
+----------------+ +----------------+
|
|
```
|
|
|
|
### 2.2 Communication Channels
|
|
|
|
The system uses Crossbeam channels for thread communication:
|
|
|
|
1. **MIDI Input Channel**: Transfers raw MIDI data from JACK thread to MIDI processing thread
|
|
2. **MIDI Output Channel**: Transfers SysEx configuration messages from MIDI thread to JACK thread
|
|
3. **Event Channel**: Delivers controller events from MIDI thread to application logic
|
|
4. **Command Channel**: Sends configuration commands from application to MIDI thread
|
|
|
|
### 2.3 MIDI Message Flow
|
|
|
|
1. **JACK Callback (Real-time Thread)**:
|
|
- Receives MIDI data from FCB1010
|
|
- Copies data to `midi_input_sender`
|
|
- Reads from `midi_output_receiver` for SysEx configuration
|
|
|
|
2. **MIDI Processing Thread**:
|
|
- Reads from `midi_input_receiver`
|
|
- Translates MIDI messages to `ControllerEvent` instances
|
|
- Sends events via `event_sender`
|
|
- Processes configuration commands from `command_receiver`
|
|
- Sends SysEx messages via `midi_output_sender`
|
|
|
|
3. **Application Logic Thread**:
|
|
- Reads from `event_receiver`
|
|
- Sends configuration commands via `command_sender`
|
|
|
|
## 3. Message Translation
|
|
|
|
### 3.1 MIDI to ControllerEvent Translation
|
|
|
|
The MIDI processing thread translates FCB1010 MIDI messages to controller-independent events according to the following mapping:
|
|
|
|
| MIDI Message | Condition | Translated Event |
|
|
|--------------|-----------|------------------|
|
|
| CC 1, Value 127 | Channel 1 | ButtonPress(1) |
|
|
| CC 2, Value 127 | Channel 1 | ButtonPress(2) |
|
|
| CC 3, Value 127 | Channel 1 | ButtonPress(3) |
|
|
| CC 4, Value 127 | Channel 1 | ButtonPress(4) |
|
|
| CC 5, Value 127 | Channel 1 | ButtonPress(5) |
|
|
| CC 6, Value 127 | Channel 1 | ButtonPress(6) |
|
|
| CC 7, Value 127 | Channel 1 | ButtonPress(7) |
|
|
| CC 8, Value 127 | Channel 1 | ButtonPress(8) |
|
|
| CC 9, Value 127 | Channel 1 | ButtonPress(9) |
|
|
| CC 10, Value 127 | Channel 1 | ButtonPress(10) |
|
|
| CC 11, Value 127 | Channel 1 | Navigation(NavigationButton::Up) |
|
|
| CC 12, Value 127 | Channel 1 | Navigation(NavigationButton::Down) |
|
|
| CC 16, Any Value | Channel 1 | ExpressionPedal(ExpressionPedal::A, value) |
|
|
| CC 17, Any Value | Channel 1 | ExpressionPedal(ExpressionPedal::B, value) |
|
|
|
|
Notes on translation:
|
|
- Only button press events (value 127) are translated for footswitches and navigation buttons
|
|
- Button release events (value 0) are ignored
|
|
- All expression pedal value changes (0-127) are translated
|
|
- Messages on MIDI channels other than Channel 1 are ignored
|
|
- Other MIDI message types (Program Change, Note On/Off, etc.) are ignored
|
|
|
|
### 3.2 Event Types
|
|
|
|
```rust
|
|
pub enum ControllerEvent {
|
|
/// Footswitch button press (1-10)
|
|
ButtonPress(u8),
|
|
|
|
/// Navigation button press
|
|
Navigation(NavigationButton),
|
|
|
|
/// Expression pedal value change
|
|
ExpressionPedal(ExpressionPedal, u8), // value 0-127
|
|
}
|
|
|
|
pub enum NavigationButton {
|
|
Up,
|
|
Down,
|
|
}
|
|
|
|
pub enum ExpressionPedal {
|
|
A,
|
|
B,
|
|
}
|
|
|
|
pub enum FcbCommand {
|
|
/// Configure the FCB1010 for our application
|
|
Initialize,
|
|
}
|
|
```
|
|
|
|
## 4. FCB1010 Initialization
|
|
|
|
The FCB1010 is configured at startup using SysEx messages:
|
|
|
|
```rust
|
|
fn initialize_fcb1010(command_sender: &crossbeam::channel::Sender<FcbCommand>) {
|
|
// Send initialization command to MIDI processing thread
|
|
command_sender.send(FcbCommand::Initialize).unwrap_or_else(|e| {
|
|
error!("Failed to send FCB1010 initialization command: {}", e);
|
|
});
|
|
}
|
|
```
|
|
|
|
The MIDI processing thread handles the initialization by sending SysEx messages:
|
|
|
|
```rust
|
|
fn handle_fcb_command(
|
|
command: FcbCommand,
|
|
midi_output_sender: &crossbeam::channel::Sender<Vec<u8>>
|
|
) {
|
|
match command {
|
|
FcbCommand::Initialize => {
|
|
info!("Initializing FCB1010");
|
|
|
|
// Get SysEx messages from FCB1010 SysEx generator
|
|
let sysex_messages = generate_fcb1010_configuration();
|
|
|
|
// Send each SysEx message with a delay to avoid buffer overruns
|
|
for message in sysex_messages {
|
|
midi_output_sender.send(message).unwrap_or_else(|e| {
|
|
error!("Failed to send SysEx message: {}", e);
|
|
});
|
|
|
|
// Allow time for the message to be processed
|
|
std::thread::sleep(std::time::Duration::from_millis(50));
|
|
}
|
|
|
|
info!("FCB1010 initialization complete");
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## 5. JACK Integration
|
|
|
|
### 5.1 JACK Process Callback
|
|
|
|
The JACK process callback runs in a real-time thread with strict timing constraints. To maintain real-time safety:
|
|
|
|
1. **No Memory Allocations**: The callback must never allocate memory (no `Vec`, `String`, `Box`, etc.)
|
|
2. **No Blocking Operations**: The callback must not block (no file I/O, mutex locks, etc.)
|
|
3. **Minimal Processing**: Only the bare minimum work should be done in this thread
|
|
|
|
For FCB1010 integration, the JACK callback should:
|
|
|
|
1. **Read MIDI Input**: Copy raw MIDI data bytes to a pre-allocated lock-free ring buffer
|
|
2. **Write MIDI Output**: Read from a lock-free buffer and send to the MIDI output port
|
|
|
|
#### Avoiding Allocations
|
|
|
|
To avoid allocations while handling MIDI data:
|
|
|
|
1. **Pre-allocated Buffers**: Use fixed-size arrays or pre-allocated ring buffers
|
|
2. **Zero-Copy Access**: Access MIDI data directly without copying when possible
|
|
3. **Message Passing**: Use lock-free data structures that don't require allocation
|
|
4. **Byte References**: Pass references to MIDI data rather than copying the data
|
|
|
|
### 5.2 MIDI Processing Thread
|
|
|
|
The MIDI processing thread runs continuously outside of JACK's real-time constraints, allowing for:
|
|
|
|
1. Receiving MIDI data from the lock-free input buffer
|
|
2. Translating MIDI messages to controller events
|
|
3. Sending events to the application logic
|
|
4. Processing configuration commands
|
|
5. Sending SysEx messages to the FCB1010
|
|
|
|
This thread can safely perform memory allocations and more complex processing since it's not subject to real-time constraints.
|
|
|
|
## 6. Testing Approach
|
|
|
|
### 6.1 Mock FCB Implementation
|
|
|
|
For testing without hardware, a mock FCB implementation will use the same MIDI message format as the physical FCB1010:
|
|
|
|
1. **JACK-Based MIDI Device**: The mock will appear as a standard MIDI device in the JACK system
|
|
2. **Message Format Consistency**: It will generate identical CC messages following the table in section 1
|
|
3. **Bidirectional Communication**: It will receive and verify SysEx configuration messages
|
|
4. **Programmatic Control**: It can be triggered by test code to simulate button presses and pedal movements
|
|
|
|
This approach allows testing the complete integration without requiring the physical FCB1010 hardware, while ensuring message format compatibility.
|
|
|
|
The mock FCB will translate controller-independent events back to MIDI messages, effectively doing the reverse of the translation described in section 3.1. This symmetrical translation enables comprehensive testing of the entire message pipeline.
|
|
|
|
### 6.2 Integration Testing
|
|
|
|
Integration tests will verify the complete flow from MIDI messages to application events:
|
|
|
|
1. Create a mock FCB instance
|
|
2. Start the MIDI processing thread
|
|
3. Generate MIDI messages from the mock FCB
|
|
4. Verify correct `ControllerEvent` instances are produced
|
|
5. Test error conditions and recovery
|
|
|
|
### 6.3 SysEx Testing
|
|
|
|
SysEx testing will verify the FCB1010 configuration messages:
|
|
|
|
1. Create a mock FCB that captures SysEx messages
|
|
2. Send the initialization command
|
|
3. Verify the SysEx messages match the expected format
|
|
4. Test with corrupted or incomplete SysEx responses
|
|
|
|
## 7. Logging
|
|
|
|
The integration will use the Rust `log` crate for detailed logging:
|
|
|
|
```rust
|
|
// MIDI message logging
|
|
debug!("Received MIDI message: {:?}", data);
|
|
|
|
// Controller event logging
|
|
info!("Translated to controller event: {:?}", event);
|
|
|
|
// SysEx logging
|
|
info!("Sending FCB1010 configuration SysEx");
|
|
debug!("SysEx message: {:?}", sysex_message);
|
|
|
|
// Error logging
|
|
error!("Failed to process MIDI message: {}", error);
|
|
```
|
|
|
|
Initial logs will go to standard output, with a future extension to log to a UI window. |