fcb_looper/FCB1010_integration.md
2025-05-25 23:53:46 +02:00

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.