|
TR123e - EMCT Computing Final Project Version 1.0
Research Project Translation from gen~ to embedded Moog synth
|
Real-time MIDI message processor with temporal delay compensation. More...
#include <MidiHandler.h>
Public Member Functions | |
| MidiHandler (float sampleRate, float delayMs=1.0f) | |
| Construct MidiHandler with timing parameters. | |
| void | processMidiMessage (int noteNumber, int velocity, float currentTimeMs) |
| Process incoming MIDI note message with timestamping. | |
| void | update (float currentTimeMs) |
| Update temporal processing and transfer delayed messages. | |
| bool | hasDelayedMessage () |
| Check availability of processed MIDI messages. | |
| MidiNoteMessage | popDelayedMessage () |
| Retrieve and remove next processed MIDI message. | |
| int | msToSamples (float milliseconds) const |
| Convert milliseconds to sample count at current sample rate. | |
| float | samplesToMs (int samples) const |
| Convert sample count to milliseconds at current sample rate. | |
Private Attributes | |
| float | sampleRate |
| Audio system sample rate in Hz. | |
| float | delayTimeMs |
| Delay compensation period in milliseconds. | |
| std::queue< MidiNoteMessage > | incomingMessages |
| Queue for incoming MIDI messages awaiting delay processing. | |
| std::queue< MidiNoteMessage > | delayedMessages |
| Queue for processed messages ready for audio consumption. | |
Real-time MIDI message processor with temporal delay compensation.
The MidiHandler class implements sophisticated MIDI event processing designed for professional audio applications requiring sample-accurate timing. It addresses inherent MIDI protocol limitations and hardware interface jitter through temporal buffering and delay compensation algorithms.
@design_patterns
@real_time_considerations
@usage_example
| MidiHandler::MidiHandler | ( | float | sampleRate, |
| float | delayMs = 1.0f ) |
Construct MidiHandler with timing parameters.
Initialize MidiHandler with audio system parameters.
| sampleRate | Audio system sample rate in Hz Used for time/sample conversion calculations Typical values: 44100, 48000, 96000 Hz |
| delayMs | Temporal delay compensation in milliseconds Allows jitter analysis and timing stabilization Default: 1.0ms (suitable for most hardware) Range: [0.1-10.0]ms typical |
@complexity O(1) - Constant time initialization @memory Minimal fixed overhead plus queue storage
@design_rationale Default 1ms delay provides balance between timing accuracy and responsiveness. Longer delays improve jitter compensation but increase perceived latency. Shorter delays reduce latency but may not fully compensate for hardware timing variations.
| sampleRate | Audio processing sample rate |
| delayMs | Temporal delay compensation period |
@implementation_details Uses member initializer list for optimal construction performance. No dynamic allocation or complex initialization required.
| bool MidiHandler::hasDelayedMessage | ( | ) |
Check availability of processed MIDI messages.
Check for available processed messages.
@complexity O(1) - Constant time queue size check @thread_safety Safe for concurrent read access @realtime_safety Real-time safe (no blocking operations)
@usage_pattern Typically used in conditional loop for message processing:
@implementation_notes Simple wrapper around std::queue::empty() for consistent API. Provides boolean logic inversion for intuitive usage patterns.
| int MidiHandler::msToSamples | ( | float | milliseconds | ) | const |
Convert milliseconds to sample count at current sample rate.
Convert time duration to sample count.
| milliseconds | Time duration in milliseconds |
@complexity O(1) - Single arithmetic operation @precision Limited by floating-point arithmetic and integer rounding
@formula samples = (milliseconds * sampleRate) / 1000
@use_cases
| milliseconds | Input time duration |
@mathematical_precision Formula: samples = (ms * sampleRate) / 1000 Rounding to nearest integer minimizes quantization errors for typical audio timing calculations.
@performance_characteristics Single floating-point multiplication and division operation. Modern CPUs execute this in 1-2 clock cycles with pipelining.
@precision_considerations Integer rounding may introduce ±0.5 sample timing error, which corresponds to ±11.3μs at 44.1kHz sample rate - negligible for most musical applications but may be significant for scientific measurement applications.
| MidiNoteMessage MidiHandler::popDelayedMessage | ( | ) |
Retrieve and remove next processed MIDI message.
Extract next processed MIDI message.
Returns the oldest delayed message from the processing queue and removes it from the internal buffer. Messages are returned in temporal order (FIFO) to preserve musical timing relationships.
@complexity O(1) - Constant time queue extraction @thread_safety Safe for single consumer thread @realtime_safety Real-time safe (no dynamic allocation)
@error_handling Empty queue condition returns sentinel values (-1) that can be checked by caller for proper error handling without exceptions.
@memory_management Message data is copied by value; no memory management required by caller. Internal queue storage is automatically managed.
@error_handling_strategy Returns sentinel values {-1, -1, -1.0f} for empty queue condition rather than throwing exceptions. This approach maintains real-time safety by avoiding exception handling overhead in audio threads.
@memory_efficiency Message data copied by value eliminates pointer management and potential memory leaks. Queue automatically manages internal storage.
Handle empty queue condition gracefully Sentinel values indicate invalid message to caller
Extract message data before queue modification Copy-by-value ensures data integrity across queue operations
Remove message from queue after data extraction Maintains queue state consistency and prevents memory growth
| void MidiHandler::processMidiMessage | ( | int | noteNumber, |
| int | velocity, | ||
| float | currentTimeMs ) |
Process incoming MIDI note message with timestamping.
Buffer incoming MIDI message with timestamp.
Accepts raw MIDI note data and creates timestamped message for temporal processing. Messages are buffered in arrival order for subsequent delay analysis and compensation.
| noteNumber | MIDI note number [0-127] |
| velocity | MIDI velocity [0-127] 0 = note-off, 1-127 = note-on with velocity |
| currentTimeMs | Current system time in milliseconds Must be monotonically increasing for proper operation |
@complexity O(1) amortized - Queue insertion @memory O(n) where n = number of buffered messages @realtime_safety Real-time safe (no dynamic allocation)
@thread_safety Safe for single-threaded MIDI input processing
@implementation_details
| noteNumber | MIDI note number |
| velocity | MIDI velocity value |
| currentTimeMs | Current system timestamp |
@performance_analysis
@debug_instrumentation Commented rt_printf() statement available for real-time debugging. When enabled, provides detailed message logging with timing information. Use sparingly in production due to potential timing impact.
Construct message structure with input parameters Uses direct initialization for optimal performance
Debug logging (disabled for production performance) Uncomment for development/debugging scenarios: rt_printf("MIDI: Note %d, Velocity %d at %.2f ms\n", msg.noteNumber, msg.velocity, msg.timestamp);
Queue message for temporal processing Queue automatically handles memory management and growth
| float MidiHandler::samplesToMs | ( | int | samples | ) | const |
Convert sample count to milliseconds at current sample rate.
Convert sample count to time duration.
| samples | Number of audio samples |
@complexity O(1) - Single arithmetic operation @precision Full floating-point precision maintained
@formula milliseconds = (samples * 1000) / sampleRate
@use_cases
| samples | Input sample count |
@mathematical_precision Formula: ms = (samples * 1000) / sampleRate Maintains full floating-point precision for accurate timing calculations. No rounding applied to preserve maximum temporal resolution.
@performance_characteristics Single floating-point multiplication and division operation. Identical computational cost to msToSamples() but with float return type.
@accuracy_analysis Precision limited only by IEEE 754 floating-point representation:
@use_case_examples
| void MidiHandler::update | ( | float | currentTimeMs | ) |
Update temporal processing and transfer delayed messages.
Process temporal delays and transfer ready messages.
Analyzes incoming message timestamps against current time to determine which messages have completed their delay period. Qualified messages are transferred to the delayed queue for audio processing consumption.
| currentTimeMs | Current system time in milliseconds Must match timeline used in processMidiMessage() |
@complexity O(k) where k = number of messages ready for processing @realtime_safety Real-time safe (bounded execution time) @determinism Execution time bounded by queue size and delay period
@algorithm_details
@temporal_accuracy Timing accuracy depends on update() call frequency. For sample-accurate processing, call once per audio buffer with buffer-aligned timing.
| currentTimeMs | Current system time reference |
@algorithm_efficiency The algorithm leverages temporal ordering of incoming messages to minimize processing overhead. Since messages arrive in chronological order, processing can stop at the first non-ready message, avoiding unnecessary queue traversal.
@temporal_accuracy Delay calculation: (currentTime - messageTime) >= delayPeriod Provides sub-millisecond timing accuracy limited only by system clock resolution and update() call frequency.
Process all messages that have completed delay period FIFO ordering ensures temporal relationships are preserved
Examine oldest message without removing from queue Reference avoids unnecessary copying for performance
Check if message has completed its delay period Temporal comparison determines readiness for audio processing
Transfer message to delayed queue for consumption Message is ready for immediate audio processing
Remove processed message from incoming queue Maintains queue state consistency
Stop processing at first non-ready message Temporal ordering guarantees all subsequent messages are also not ready, enabling early termination
|
private |
Queue for processed messages ready for audio consumption.
Contains MIDI messages that have completed their delay period and are ready for immediate processing by the audio synthesis engine. Messages are consumed by audio thread in temporal order.
@container std::queue<MidiNoteMessage> @ordering First-In-First-Out (maintains musical timing) @consumption Audio processing thread via popDelayedMessage() @thread_safety Not thread-safe (single consumer assumed)
|
private |
Delay compensation period in milliseconds.
Configurable delay period for temporal jitter compensation and timing stabilization. Longer delays improve timing accuracy but increase system latency. Shorter delays reduce latency but may not adequately compensate for hardware timing variations.
@units Milliseconds @range [0.1-10.0]ms typical for audio applications @default 1.0ms (good balance of accuracy and responsiveness)
|
private |
Queue for incoming MIDI messages awaiting delay processing.
FIFO queue storing raw MIDI messages with timestamps for temporal analysis. Messages remain in this queue until their delay period expires, at which point they're transferred to delayedMessages queue.
@container std::queue<MidiNoteMessage> @ordering First-In-First-Out (preserves temporal relationships) @growth_behavior Dynamic allocation with exponential growth @thread_safety Not thread-safe (requires external synchronization)
|
private |
Audio system sample rate in Hz.
Cached sample rate for time/sample conversion calculations. Eliminates repeated parameter passing and improves computational efficiency.
@units Hertz (samples per second) @range Typically [8000-192000] Hz for audio applications @precision Full floating-point precision for accurate conversions