Added audio engine architecture
This commit is contained in:
parent
a12c77354b
commit
8bb24b097a
BIN
FCB1010.pdf
BIN
FCB1010.pdf
Binary file not shown.
240
FCB1010_SysEx.md
240
FCB1010_SysEx.md
@ -1,240 +0,0 @@
|
|||||||
You're absolutely right - the current document lacks the specific technical details needed for implementation. Let me examine the repositories more closely for implementation details.
|
|
||||||
|
|
||||||
I'll need to search for the specific Python file in the repository.
|
|
||||||
|
|
||||||
I need to look at the actual code to get the detailed specification. Let me search for the source code directly.
|
|
||||||
|
|
||||||
Let me try to find a specific PDF that might contain detailed SysEx specifications:
|
|
||||||
|
|
||||||
Now I have the detailed specification we need! Let me update our documentation with this complete information:
|
|
||||||
|
|
||||||
# FCB1010 SysEx Protocol Documentation
|
|
||||||
|
|
||||||
## 1. Introduction
|
|
||||||
|
|
||||||
This document describes the System Exclusive (SysEx) protocol used by the Behringer FCB1010 MIDI Foot Controller. The protocol allows configuration of the device's global settings and individual presets through MIDI SysEx messages. This documentation is based on official Behringer documentation and reverse-engineered implementations.
|
|
||||||
|
|
||||||
## 2. SysEx Message Structure
|
|
||||||
|
|
||||||
### 2.1 General Format
|
|
||||||
|
|
||||||
FCB1010 SysEx messages follow this general structure:
|
|
||||||
|
|
||||||
```
|
|
||||||
F0 00 20 32 <global-channel> 0C <data...> F7
|
|
||||||
```
|
|
||||||
|
|
||||||
Where:
|
|
||||||
- `F0`: SysEx Start byte
|
|
||||||
- `00 20 32`: Behringer's manufacturer ID
|
|
||||||
- `<global-channel>`: Global MIDI channel
|
|
||||||
- `0C`: Device ID for FCB1010
|
|
||||||
- `<data...>`: Message payload (in 7-bit format)
|
|
||||||
- `F7`: SysEx End byte
|
|
||||||
|
|
||||||
### 2.2 Data Encoding Scheme
|
|
||||||
|
|
||||||
Due to MIDI's 7-bit transmission limitation, the FCB1010 uses a special encoding scheme for 8-bit data:
|
|
||||||
|
|
||||||
- Data is transmitted in packets of 8 bytes
|
|
||||||
- The first 7 bytes contain the lower 7 bits of each data byte
|
|
||||||
- The 8th byte contains the most significant bits (MSBs) of the preceding 7 bytes
|
|
||||||
- This allows reconstructing the full 8-bit values from the 7-bit MIDI data
|
|
||||||
|
|
||||||
For example, to transmit 7 bytes with the following MSBs: 0,1,0,0,1,1,0:
|
|
||||||
- The 8th byte would be: `01001100` (binary) or `4C` (hex)
|
|
||||||
- Each bit position in this byte corresponds to the MSB of one of the preceding 7 bytes
|
|
||||||
|
|
||||||
## 3. Memory Map
|
|
||||||
|
|
||||||
The FCB1010 memory is organized as follows:
|
|
||||||
|
|
||||||
### 3.1 Preset Data
|
|
||||||
|
|
||||||
- **Addresses**: `0x000` to `0x640`
|
|
||||||
- **Structure**: 100 presets (10 banks of 10 presets each)
|
|
||||||
- **Preset Size**: 16 bytes per preset
|
|
||||||
|
|
||||||
### 3.2 Global Configuration
|
|
||||||
|
|
||||||
- **Addresses**: `0x7E0` to `0x7E9`
|
|
||||||
- Contains global MIDI channel assignments for each function
|
|
||||||
|
|
||||||
## 4. Preset Structure
|
|
||||||
|
|
||||||
Each preset occupies 16 bytes and contains the following data:
|
|
||||||
|
|
||||||
| Offset | Description | Notes |
|
|
||||||
|--------|-------------|-------|
|
|
||||||
| 0x0 | Program Change 1 | Program number |
|
|
||||||
| 0x1 | Program Change 2 | Program number |
|
|
||||||
| 0x2 | Program Change 3 | Program number |
|
|
||||||
| 0x3 | Program Change 4 | Program number |
|
|
||||||
| 0x4 | Program Change 5 | Program number |
|
|
||||||
| 0x5 | Controller 1 | Controller number |
|
|
||||||
| 0x6 | Controller 1 Value | Value with Relay 1 status in MSB |
|
|
||||||
| 0x7 | Controller 2 | Controller number |
|
|
||||||
| 0x8 | Controller 2 Value | Value with Relay 2 status in MSB |
|
|
||||||
| 0x9 | Expression Pedal A | Controller number |
|
|
||||||
| 0xA | Expression Pedal A Lower Value | Minimum value |
|
|
||||||
| 0xB | Expression Pedal A Upper Value | Maximum value |
|
|
||||||
| 0xC | Expression Pedal B | Controller number |
|
|
||||||
| 0xD | Expression Pedal B Lower Value | Minimum value |
|
|
||||||
| 0xE | Expression Pedal B Upper Value | Maximum value |
|
|
||||||
| 0xF | Note | MIDI note number |
|
|
||||||
|
|
||||||
### 4.1 Special Fields
|
|
||||||
|
|
||||||
- **Switch Relay Status**: The MSB of Controller 1 Value contains Relay 1 status; the MSB of Controller 2 Value contains Relay 2 status
|
|
||||||
- **Program Change Values**: Range 0-127, correspond to MIDI program changes 1-128
|
|
||||||
- **Controller Numbers**: Range 0-127, standard MIDI CC values
|
|
||||||
- **Note Values**: Range 0-127, standard MIDI note numbers
|
|
||||||
|
|
||||||
## 5. Memory Addressing
|
|
||||||
|
|
||||||
Each preset's starting address is calculated as:
|
|
||||||
```
|
|
||||||
Address = 0x000 + (preset_number * 0x10)
|
|
||||||
```
|
|
||||||
|
|
||||||
Where `preset_number` ranges from 0 to 99:
|
|
||||||
- Bank 0, Preset 1 = Preset 0 = Address 0x000
|
|
||||||
- Bank 0, Preset 2 = Preset 1 = Address 0x010
|
|
||||||
- Bank 0, Preset 10 = Preset 9 = Address 0x090
|
|
||||||
- Bank 1, Preset 1 = Preset 10 = Address 0x100
|
|
||||||
- ...and so on
|
|
||||||
|
|
||||||
## 6. Global MIDI Channel Configuration
|
|
||||||
|
|
||||||
The MIDI channels for each function are stored at addresses 0x7E0 to 0x7E9:
|
|
||||||
|
|
||||||
| Address | Function |
|
|
||||||
|---------|----------|
|
|
||||||
| 0x7E0 | Program Change 1 Channel |
|
|
||||||
| 0x7E1 | Program Change 2 Channel |
|
|
||||||
| 0x7E2 | Program Change 3 Channel |
|
|
||||||
| 0x7E3 | Program Change 4 Channel |
|
|
||||||
| 0x7E4 | Program Change 5 Channel |
|
|
||||||
| 0x7E5 | Controller 1 Channel |
|
|
||||||
| 0x7E6 | Controller 2 Channel |
|
|
||||||
| 0x7E7 | Expression Pedal A Channel |
|
|
||||||
| 0x7E8 | Expression Pedal B Channel |
|
|
||||||
| 0x7E9 | Note Channel |
|
|
||||||
|
|
||||||
Values range from 0-15, corresponding to MIDI channels 1-16.
|
|
||||||
|
|
||||||
## 7. Sending SysEx to FCB1010
|
|
||||||
|
|
||||||
To send configuration data to the FCB1010:
|
|
||||||
|
|
||||||
1. Put the FCB1010 in SysEx receive mode:
|
|
||||||
- Hold DOWN button while powering on
|
|
||||||
- Press DOWN again to enter CONFIG mode
|
|
||||||
- Press footswitch 7 (SYSEX RCV)
|
|
||||||
|
|
||||||
2. Send the SysEx message with the following structure:
|
|
||||||
```
|
|
||||||
F0 00 20 32 <global-channel> 0C <encoded-data> F7
|
|
||||||
```
|
|
||||||
|
|
||||||
3. The FCB1010 will automatically exit receive mode once data is received successfully
|
|
||||||
|
|
||||||
## 8. Receiving SysEx from FCB1010
|
|
||||||
|
|
||||||
To retrieve the current configuration from the FCB1010:
|
|
||||||
|
|
||||||
1. Put the FCB1010 in SysEx send mode:
|
|
||||||
- Hold DOWN button while powering on
|
|
||||||
- Press UP until CONFIG LED lights
|
|
||||||
- Press footswitch 6 (SYSEX SEND)
|
|
||||||
|
|
||||||
2. Capture the SysEx data sent from the FCB1010
|
|
||||||
|
|
||||||
3. Decode the received data using the 7-bit to 8-bit conversion method described above
|
|
||||||
|
|
||||||
## 9. Implementation Example (Pseudocode)
|
|
||||||
|
|
||||||
### 9.1 Encoding Data for SysEx Transmission
|
|
||||||
|
|
||||||
```
|
|
||||||
function encode_data_for_sysex(data):
|
|
||||||
encoded_data = []
|
|
||||||
for i in range(0, len(data), 7):
|
|
||||||
chunk = data[i:i+7]
|
|
||||||
while len(chunk) < 7:
|
|
||||||
chunk.append(0) # Pad last chunk if needed
|
|
||||||
|
|
||||||
msb_byte = 0
|
|
||||||
for j in range(7):
|
|
||||||
if j < len(chunk):
|
|
||||||
# Extract MSB from each byte and put in msb_byte
|
|
||||||
msb_byte |= ((chunk[j] & 0x80) >> 7) << j
|
|
||||||
# Clear MSB from original byte
|
|
||||||
encoded_data.append(chunk[j] & 0x7F)
|
|
||||||
|
|
||||||
encoded_data.append(msb_byte)
|
|
||||||
|
|
||||||
return encoded_data
|
|
||||||
```
|
|
||||||
|
|
||||||
### 9.2 Decoding Received SysEx Data
|
|
||||||
|
|
||||||
```
|
|
||||||
function decode_sysex_data(encoded_data):
|
|
||||||
decoded_data = []
|
|
||||||
for i in range(0, len(encoded_data), 8):
|
|
||||||
if i + 7 >= len(encoded_data):
|
|
||||||
break # Not enough data for a complete chunk
|
|
||||||
|
|
||||||
msb_byte = encoded_data[i+7]
|
|
||||||
for j in range(7):
|
|
||||||
if i + j < len(encoded_data) - 1: # Skip the MSB byte
|
|
||||||
byte_value = encoded_data[i+j]
|
|
||||||
# Apply MSB from msb_byte
|
|
||||||
if (msb_byte & (1 << j)) != 0:
|
|
||||||
byte_value |= 0x80
|
|
||||||
decoded_data.append(byte_value)
|
|
||||||
|
|
||||||
return decoded_data
|
|
||||||
```
|
|
||||||
|
|
||||||
### 9.3 Creating a Preset Configuration
|
|
||||||
|
|
||||||
```
|
|
||||||
function create_preset(bank, preset_number, config):
|
|
||||||
preset_index = (bank * 10) + preset_number - 1 # Convert to 0-99 index
|
|
||||||
preset_address = 0x000 + (preset_index * 0x10)
|
|
||||||
|
|
||||||
preset_data = [
|
|
||||||
config.program_change_1,
|
|
||||||
config.program_change_2,
|
|
||||||
config.program_change_3,
|
|
||||||
config.program_change_4,
|
|
||||||
config.program_change_5,
|
|
||||||
config.controller_1_number,
|
|
||||||
config.controller_1_value | (config.relay_1_status << 7),
|
|
||||||
config.controller_2_number,
|
|
||||||
config.controller_2_value | (config.relay_2_status << 7),
|
|
||||||
config.expression_pedal_a_controller,
|
|
||||||
config.expression_pedal_a_min,
|
|
||||||
config.expression_pedal_a_max,
|
|
||||||
config.expression_pedal_b_controller,
|
|
||||||
config.expression_pedal_b_min,
|
|
||||||
config.expression_pedal_b_max,
|
|
||||||
config.note
|
|
||||||
]
|
|
||||||
|
|
||||||
return (preset_address, preset_data)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 10. Compatibility Notes
|
|
||||||
|
|
||||||
- This protocol specification is based on FCB1010 firmware version 2.5
|
|
||||||
- The protocol may differ for other firmware versions or third-party firmware
|
|
||||||
- Large SysEx dumps may not work reliably with some MIDI-USB interfaces due to buffer limitations
|
|
||||||
|
|
||||||
## 11. References
|
|
||||||
|
|
||||||
1. Behringer FCB1010 SysEx File Structure (2003), Behringer Spezielle Studiotechnik GmbH
|
|
||||||
2. riban-bw/fcb1010 GitHub repository - Python implementation for FCB1010 SysEx communication
|
|
||||||
3. trafficpest/fcbtool GitHub repository - C-based tool for managing FCB1010 presets
|
|
||||||
161
audio_engine.md
Normal file
161
audio_engine.md
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
# Audio Engine Architecture
|
||||||
|
|
||||||
|
This document defines the high-level architectural decisions for the real-time audio engine.
|
||||||
|
The engine must coordinate real-time audio processing with file I/O and network operations without introducing timing glitches,
|
||||||
|
while managing unlimited-length recordings without real-time memory allocation.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The architecture uses two threads to maintain real-time performance guarantees.
|
||||||
|
The real-time thread handles all audio processing including capture, playback, mixing, and state machine updates.
|
||||||
|
This thread cannot allocate memory, perform file operations, or make blocking system calls.
|
||||||
|
|
||||||
|
The I/O thread runs tokio and manages async tasks for file operations and the OSC server.
|
||||||
|
These subsystems communicate with the real-time thread through lock-free ring buffers.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
RT[Real-Time Thread<br/>JACK Process<br/>Audio Processing]
|
||||||
|
IO[I/O Thread<br/>Tokio Runtime<br/>File Operations<br/>OSC Server]
|
||||||
|
|
||||||
|
IO -->|Pre-allocated Buffers| RT
|
||||||
|
IO -->|Loaded / Saved Audio| RT
|
||||||
|
|
||||||
|
RT -->|Tracks for Saving| IO
|
||||||
|
RT -->|Status Updates| IO
|
||||||
|
```
|
||||||
|
|
||||||
|
The system maintains audio state across three levels of organization.
|
||||||
|
Individual tracks contain the actual recorded audio data organized as linked lists of pre-allocated buffers.
|
||||||
|
Columns group tracks that share common timing behavior.
|
||||||
|
Global state coordinates overall system behavior including volume and timing synchronization.
|
||||||
|
|
||||||
|
Buffer management employs a pre-allocated pool strategy to eliminate real-time memory allocation.
|
||||||
|
Track ownership is shared between threads after buffers are written.
|
||||||
|
This enables file operations without copying audio data,
|
||||||
|
ensuring commands and data transfers never block the critical audio processing path.
|
||||||
|
|
||||||
|
## Audio Buffer Management and Ownership
|
||||||
|
|
||||||
|
Audio buffers use Arc-wrapped chunks to enable safe sharing between threads without sacrificing RT performance.
|
||||||
|
The IO thread pre-allocates all `Arc<AudioChunk>` instances and stocks the buffer pool, moving allocation costs away from the RT thread.
|
||||||
|
|
||||||
|
During recording, the RT thread builds linked lists of these Arc-wrapped chunks.
|
||||||
|
When recording completes, the RT thread clones the root Arc and sends it to the IO thread for saving while retaining the original reference for immediate playback.
|
||||||
|
This allows seamless transition from recording to playback without waiting for file operations.
|
||||||
|
|
||||||
|
The RT thread can replace track data at any time by swapping the root pointer, even during active save operations.
|
||||||
|
If recording over existing data, the old root reference can be retained in case the recording is cancelled, enabling undo functionality.
|
||||||
|
|
||||||
|
The IO thread optimizes data layout for performance.
|
||||||
|
When loading files, it creates single long buffers rather than linked lists.
|
||||||
|
After saving, it consolidates linked lists into single buffers.
|
||||||
|
This means only unsaved or actively-recording tracks have linked-list overhead,
|
||||||
|
while frequently-played loops benefit from optimized layout.
|
||||||
|
|
||||||
|
Arc destruction happens in the IO thread via the Tracks channel as a Delete message,
|
||||||
|
keeping deallocation costs out of the RT thread.
|
||||||
|
The RT thread's buffer pool operations involve only taking and returning Arc references, with minimal atomic operations.
|
||||||
|
|
||||||
|
```
|
||||||
|
System State Hierarchy:
|
||||||
|
|
||||||
|
GlobalState
|
||||||
|
├── samples_per_beat: f32
|
||||||
|
├── sample_rate: u32
|
||||||
|
├── selected_cell: (usize, usize)
|
||||||
|
├── click_track_samples: [f32]
|
||||||
|
└── columns: ColumnState[]
|
||||||
|
│
|
||||||
|
├── columns[0]
|
||||||
|
│ ├── beats: usize
|
||||||
|
│ └── tracks: TrackState[]
|
||||||
|
│ │
|
||||||
|
│ ├── tracks[0]
|
||||||
|
│ │ ├── current_state: Idle | Recording | Playing | Solo
|
||||||
|
│ │ ├── next_state: Idle | Recording | Playing | Solo
|
||||||
|
│ │ ├── volume: f32
|
||||||
|
│ │ └── audio: Option<Arc<AudioChunk>>
|
||||||
|
│ │ │
|
||||||
|
│ │ └── AudioChunk
|
||||||
|
│ │ ├── samples: [f32]
|
||||||
|
│ │ ├── sample_count: usize
|
||||||
|
│ │ └── next: Option<Arc<AudioChunk>>
|
||||||
|
│ │ │
|
||||||
|
│ │ └── AudioChunk (next in linked list)
|
||||||
|
│ │
|
||||||
|
│ ├── TrackState[1] ...
|
||||||
|
│ └── TrackState[2] ...
|
||||||
|
│
|
||||||
|
├── ColumnState[1] ...
|
||||||
|
└── ColumnState[2] ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ring buffers
|
||||||
|
|
||||||
|
Communication between real-time and non-real-time threads requires lock-free data structures to avoid blocking the RT thread.
|
||||||
|
We selected thingbuf because it provides both synchronous and asynchronous interfaces,
|
||||||
|
making it ideal for bridging real-time and async-based systems.
|
||||||
|
|
||||||
|
The RT thread uses the synchronous, non-blocking interface for immediate data transfer.
|
||||||
|
The async I/O thread uses the asynchronous interface,
|
||||||
|
allowing it to efficiently wait for data availability without busy-wait loops.
|
||||||
|
Thingbuf also provides strong memory ordering guarantees and allocation-free operation in the RT context.
|
||||||
|
|
||||||
|
## Beat quantization and command execution
|
||||||
|
|
||||||
|
Musical timing relies on beat-quantized state changes rather than immediate command execution.
|
||||||
|
Each track maintains both current state (playing, recording, solo, idle) and next beat state.
|
||||||
|
Commands update the next beat state, which becomes active at the next beat boundary.
|
||||||
|
|
||||||
|
Audio processing follows these major steps:
|
||||||
|
|
||||||
|
- **Process updated audio buffers**: Store loaded and optimized audio data.
|
||||||
|
|
||||||
|
- **Process MIDI commands**: Update track volumes (immediate) and next beat states.
|
||||||
|
Last command wins if multiple commands arrive for the same track during a buffer.
|
||||||
|
|
||||||
|
- **Beat detection**: Check if a beat occurs during the current buffer and calculate the exact sample index.
|
||||||
|
|
||||||
|
- **Process audio**:
|
||||||
|
- Process samples up to beat index
|
||||||
|
- Copy next beat state to current state for all tracks at beat boundary
|
||||||
|
- Handle saving and deleting
|
||||||
|
- Process remaining samples with new states
|
||||||
|
|
||||||
|
- **MIDI output**: Send transport control and song position pointer messages.
|
||||||
|
|
||||||
|
This approach avoids per-sample state checking while maintaining beat-accurate timing.
|
||||||
|
Commands arriving near a beat boundary apply at that beat,
|
||||||
|
providing musically appropriate timing even if not sample-accurate.
|
||||||
|
|
||||||
|
## Click track generation and routing
|
||||||
|
|
||||||
|
The click track provides audible timing reference using a pre-computed sine wave tone at beat boundaries.
|
||||||
|
JACK port configuration includes a separate mono click output,
|
||||||
|
allowing users to route the click independently of the main program audio.
|
||||||
|
Click generation operates alongside the beat detection system,
|
||||||
|
triggering the pre-computed waveform when beats occur.
|
||||||
|
Click volume and enable/disable control operate through the standard command system for real-time adjustment.
|
||||||
|
|
||||||
|
### Xrun detection and recovery
|
||||||
|
|
||||||
|
Audio buffer underruns and overruns (xruns) disrupt the continuous flow of audio data and can break musical timing if not handled properly.
|
||||||
|
JACK provides xrun detection only through a callback mechanism that runs in a separate thread.
|
||||||
|
So the RT thread must monitor for xruns and recover from them by monitoring the process scope.
|
||||||
|
|
||||||
|
Xrun recovery happens in stage 0 of audio processing, before the normal stages:
|
||||||
|
|
||||||
|
**Stage 0 - Xrun Recovery:**
|
||||||
|
- **Check for time glitches**: Detect if an xrun occurred and calculate missed sample count
|
||||||
|
- **Calculate sample positions**: Update sample index for every column based on missed time
|
||||||
|
- **Maintain recording continuity**: Add empty buffers to tracks currently recording to preserve timeline integrity
|
||||||
|
- **Trigger saves**: Send completed tracks to the I/O thread for saving
|
||||||
|
|
||||||
|
This approach maintains musical timing relationships across all tracks while accepting the temporal disruption.
|
||||||
|
Recording tracks receive silent buffers for the missed duration, preventing timeline gaps.
|
||||||
|
Playback positions advance by the missed sample count, wrapping at loop boundaries as needed.
|
||||||
|
|
||||||
|
If beats were missed during the xrun,
|
||||||
|
the normal beat detection in subsequent stages will handle state transitions appropriately,
|
||||||
|
since the missed time has already been accounted for in the position calculations.
|
||||||
@ -12,7 +12,7 @@ graph TD
|
|||||||
AudioEngine[Audio Engine Process]
|
AudioEngine[Audio Engine Process]
|
||||||
Display[Display Process]
|
Display[Display Process]
|
||||||
|
|
||||||
FCB1010 -->|MIDI DIN| UMC404HD
|
FCB1010 <-->|MIDI DIN| UMC404HD
|
||||||
|
|
||||||
UMC404HD <-->|MIDI USB| JACK_MIDI
|
UMC404HD <-->|MIDI USB| JACK_MIDI
|
||||||
UMC404HD <-->|Audio USB| JACK_AUDIO
|
UMC404HD <-->|Audio USB| JACK_AUDIO
|
||||||
@ -55,7 +55,6 @@ Button 9: CC #28 (Column 4 Select)
|
|||||||
Button 10: CC #29 (Column 5 Select)
|
Button 10: CC #29 (Column 5 Select)
|
||||||
UP: CC #30 (Row Up / Mode Switch)
|
UP: CC #30 (Row Up / Mode Switch)
|
||||||
DOWN: CC #31 (Row Down / Mode Switch)
|
DOWN: CC #31 (Row Down / Mode Switch)
|
||||||
SysEx Test: CC #127 (Trigger FCB1010 reconfiguration)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Expression Pedals
|
#### Expression Pedals
|
||||||
@ -137,7 +136,7 @@ SPP (F2H): Reset to 0 on Start message
|
|||||||
```osc
|
```osc
|
||||||
/looper/cell/<column>/<row>/state <string>
|
/looper/cell/<column>/<row>/state <string>
|
||||||
Column: 1-5, Row: 1-5
|
Column: 1-5, Row: 1-5
|
||||||
Values: "empty" | "ready" | "recording" | "playing" | "solo"
|
Values: "empty" | "loading" | "ready" | "recording" | "playing" | "solo"
|
||||||
Update frequency: On change only
|
Update frequency: On change only
|
||||||
|
|
||||||
/looper/cell/<column>/<row>/volume <float>
|
/looper/cell/<column>/<row>/volume <float>
|
||||||
@ -149,14 +148,14 @@ SPP (F2H): Reset to 0 on Start message
|
|||||||
#### 4. Column State Messages
|
#### 4. Column State Messages
|
||||||
|
|
||||||
```osc
|
```osc
|
||||||
/looper/column/<column>/bars <int>
|
/looper/column/<column>/beats <int>
|
||||||
Column: 1-5
|
Column: 1-5
|
||||||
Value: Number of bars in column (set by first recording, 0 = not set)
|
Value: Number of beats in column (set by first recording, 0 = not set)
|
||||||
Update frequency: On first recording in column
|
Update frequency: On first recording in column
|
||||||
|
|
||||||
/looper/column/<column>/bar <int>
|
/looper/column/<column>/beat <int>
|
||||||
Column: 1-5
|
Column: 1-5
|
||||||
Value: Current bar (1 = start of loop, 1+ = bar N)
|
Value: Current beat (1 = start of loop, 1+ = beat N)
|
||||||
Update frequency: On change
|
Update frequency: On change
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -164,7 +163,7 @@ SPP (F2H): Reset to 0 on Start message
|
|||||||
|
|
||||||
```osc
|
```osc
|
||||||
/looper/metronome/position <float>
|
/looper/metronome/position <float>
|
||||||
Range: 0.0 - 1.0 (position within current bar)
|
Range: 0.0 - 1.0 (position within current beat)
|
||||||
Update frequency: 60 FPS
|
Update frequency: 60 FPS
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -172,27 +171,28 @@ SPP (F2H): Reset to 0 on Start message
|
|||||||
|
|
||||||
#### Initial State Synchronization
|
#### Initial State Synchronization
|
||||||
|
|
||||||
OSC is stateless - there's no "subscription" mechanism. When the display process connects:
|
OSC is stateless - there's no "subscription" mechanism.
|
||||||
|
When the display process connects:
|
||||||
|
|
||||||
1. **Display connects** to Unix socket
|
1. **Display connects** to Unix socket
|
||||||
2. **Audio engine detects** new connection
|
2. **Audio engine detects** new connection
|
||||||
3. **Complete state dump** sent immediately:
|
3. **Complete state dump** sent immediately:
|
||||||
4. **Regular updates** begin (position updates, state changes)
|
4. **Regular updates** begin (position updates, state changes)
|
||||||
|
|
||||||
### OSC Message Bundling
|
#### OSC Message Bundling
|
||||||
|
|
||||||
For efficiency, column-bar messages are bundled:
|
For efficiency, column-beat messages are bundled:
|
||||||
|
|
||||||
## Session Persistence
|
## Session Persistence
|
||||||
|
|
||||||
**Single Source of Truth**
|
**Single Source of Truth**
|
||||||
|
|
||||||
- **Timing authority**: `samples_per_bar` in state.json
|
- **Timing authority**: `samples_per_beat` in state.json
|
||||||
- **Track existence**: WAV file presence in filesystem
|
- **Track existence**: WAV file presence in filesystem
|
||||||
- **Tempo**: Derived from `(sample_rate × 60) ÷ (samples_per_bar × beats_per_bar)`
|
- **Tempo**: Derived from `(sample_rate × 60) ÷ samples_per_beat`
|
||||||
- **Column bars**: Derived from `wav_length_samples ÷ samples_per_bar`
|
- **Column beats**: Derived from `wav_length_samples ÷ samples_per_beat`
|
||||||
- **Delete files**:
|
- **Delete files**:
|
||||||
- If it's not a multiple of `samples_per_bar`
|
- If it's not a multiple of `samples_per_beat`
|
||||||
- If it doesn't match the sample rate
|
- If it doesn't match the sample rate
|
||||||
- When a track is cleared
|
- When a track is cleared
|
||||||
|
|
||||||
@ -233,8 +233,7 @@ For efficiency, column-bar messages are bundled:
|
|||||||
},
|
},
|
||||||
"timing": {
|
"timing": {
|
||||||
"sample_rate": 44100,
|
"sample_rate": 44100,
|
||||||
"samples_per_bar": 105840,
|
"samples_per_beat": 105840,
|
||||||
"time_signature": [4, 4]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## System Overview
|
## System Overview
|
||||||
|
|
||||||
A looper pedal system using a Behringer FCB1010 MIDI foot controller, Behringer UMC404HD audio interface, Raspberry Pi with TFT display (215x135mm).
|
A looper pedal system using a modified Behringer FCB1010 MIDI foot controller, Behringer UMC404HD audio interface, Raspberry Pi with TFT display (215x135mm).
|
||||||
The system provides a 5x5 matrix of loop tracks organized in columns and rows, with real-time visual feedback.
|
The system provides a 5x5 matrix of loop tracks organized in columns and rows, with real-time visual feedback.
|
||||||
|
|
||||||
## Hardware Components
|
## Hardware Components
|
||||||
@ -23,15 +23,15 @@ The system provides a 5x5 matrix of loop tracks organized in columns and rows, w
|
|||||||
- **5 Columns**: Synchronized recording and playback for each column
|
- **5 Columns**: Synchronized recording and playback for each column
|
||||||
- **5 Rows**: Individual tracks within each column
|
- **5 Rows**: Individual tracks within each column
|
||||||
- **25 Total Cells**: Each cell can contain one audio loop
|
- **25 Total Cells**: Each cell can contain one audio loop
|
||||||
- **Column-based Timing**: First recording in a column sets the bar count for all tracks in that column
|
- **Column-based Timing**: First recording in a column sets the beat count for all tracks in that column
|
||||||
|
|
||||||
## Interface Design
|
## Interface Design
|
||||||
- **Metronome**: Moving indicator that pulses when in center
|
- **Metronome**: Moving indicator that pulses when in center
|
||||||
- **Progress Bars**: Show current position in each column with bar divisions
|
- **Progress Bars**: Show current position in each column with beat divisions
|
||||||
|
|
||||||
First recording behavior:
|
First recording behavior:
|
||||||
- Progress bar fills during bar 1, then stays full
|
- Progress bar fills during beat 1, then stays full
|
||||||
- Additional bars appear as sliding segments (right-to-left)
|
- Additional beats appear as sliding segments (right-to-left)
|
||||||
- **Active Row**: Increased border with
|
- **Active Row**: Increased border with
|
||||||
- **Selected Cell**: Switched border and background color
|
- **Selected Cell**: Switched border and background color
|
||||||
- **Track States**:
|
- **Track States**:
|
||||||
@ -69,7 +69,7 @@ The system provides a 5x5 matrix of loop tracks organized in columns and rows, w
|
|||||||
## Operational Behavior
|
## Operational Behavior
|
||||||
|
|
||||||
### Recording Workflow
|
### Recording Workflow
|
||||||
- **First Recording in Column**: Sets bar count for entire column, records until stopped
|
- **First Recording in Column**: Sets beat count for entire column, records until stopped
|
||||||
- **Subsequent Recordings**: Auto-sync to established column length
|
- **Subsequent Recordings**: Auto-sync to established column length
|
||||||
- **Record Button Stop**: Recording stops, cell starts playing
|
- **Record Button Stop**: Recording stops, cell starts playing
|
||||||
- **Stop Button Stop**: Recording cancelled, previous state restored
|
- **Stop Button Stop**: Recording cancelled, previous state restored
|
||||||
@ -79,7 +79,7 @@ The system provides a 5x5 matrix of loop tracks organized in columns and rows, w
|
|||||||
- **All actions sync to the beat**: Start/stop occurs at metronome center position
|
- **All actions sync to the beat**: Start/stop occurs at metronome center position
|
||||||
- **Column Switching**: Only when not recording, immediate switching to selected column
|
- **Column Switching**: Only when not recording, immediate switching to selected column
|
||||||
- **Tempo Changes**: Only allowed when all tracks are cleared
|
- **Tempo Changes**: Only allowed when all tracks are cleared
|
||||||
- **Recording Start**: Synced to column start if column playing, otherwise next bar
|
- **Recording Start**: Synced to column start if column playing, otherwise next beat
|
||||||
|
|
||||||
### Solo Functionality
|
### Solo Functionality
|
||||||
- **Solo Activation**: Press Solo button on any track
|
- **Solo Activation**: Press Solo button on any track
|
||||||
@ -98,5 +98,5 @@ The system provides a 5x5 matrix of loop tracks organized in columns and rows, w
|
|||||||
### State Management
|
### State Management
|
||||||
|
|
||||||
- **Startup Behavior**: Load last audio tracks and configure FCB1010
|
- **Startup Behavior**: Load last audio tracks and configure FCB1010
|
||||||
- **Column Consistency**: Recordings automatically match column bar count
|
- **Column Consistency**: Recordings automatically match column beat count
|
||||||
- **State Validation**: Prevent invalid operations (e.g., switching columns while recording)
|
- **State Validation**: Prevent invalid operations (e.g., switching columns while recording)
|
||||||
Loading…
x
Reference in New Issue
Block a user