TR123e - EMCT Computing Final Project Version 1.0
Research Project Translation from gen~ to embedded Moog synth
Loading...
Searching...
No Matches
MidiHandler Class Reference

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< MidiNoteMessageincomingMessages
 Queue for incoming MIDI messages awaiting delay processing.
std::queue< MidiNoteMessagedelayedMessages
 Queue for processed messages ready for audio consumption.

Detailed Description

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

  • Producer-Consumer: Separates MIDI input from audio processing
  • Temporal Buffer: Implements time-based event scheduling
  • Queue-based Architecture: Lockless FIFO for real-time safety

@real_time_considerations

  • No dynamic allocation in processing path
  • Bounded execution time for all operations
  • Lock-free queue operations prevent priority inversion
  • Deterministic memory access patterns for cache efficiency

@usage_example

MidiHandler handler(44100.0f, 2.0f); // 2ms delay compensation
// In MIDI callback:
handler.processMidiMessage(60, 100, currentTime);
// In audio callback:
handler.update(currentTime);
while (handler.hasDelayedMessage()) {
MidiNoteMessage msg = handler.popDelayedMessage();
// Process note event...
}
MidiHandler(float sampleRate, float delayMs=1.0f)
Construct MidiHandler with timing parameters.
Definition MidiHandler.cpp:29
Encapsulates MIDI note event data with high-precision timing.
Definition MidiHandler.h:72

Constructor & Destructor Documentation

◆ MidiHandler()

MidiHandler::MidiHandler ( float sampleRate,
float delayMs = 1.0f )

Construct MidiHandler with timing parameters.

Initialize MidiHandler with audio system parameters.

Parameters
sampleRateAudio system sample rate in Hz Used for time/sample conversion calculations Typical values: 44100, 48000, 96000 Hz
delayMsTemporal 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.

Parameters
sampleRateAudio processing sample rate
delayMsTemporal delay compensation period

@implementation_details Uses member initializer list for optimal construction performance. No dynamic allocation or complex initialization required.

Member Function Documentation

◆ hasDelayedMessage()

bool MidiHandler::hasDelayedMessage ( )

Check availability of processed MIDI messages.

Check for available processed messages.

Returns
true if delayed messages are available for processing false if no messages ready

@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:

while (handler.hasDelayedMessage()) {
MidiNoteMessage msg = handler.popDelayedMessage();
// Process message...
}
Returns
Availability status of delayed messages

@implementation_notes Simple wrapper around std::queue::empty() for consistent API. Provides boolean logic inversion for intuitive usage patterns.

◆ msToSamples()

int MidiHandler::msToSamples ( float milliseconds) const

Convert milliseconds to sample count at current sample rate.

Convert time duration to sample count.

Parameters
millisecondsTime duration in milliseconds
Returns
Equivalent number of audio samples (rounded to nearest integer)

@complexity O(1) - Single arithmetic operation @precision Limited by floating-point arithmetic and integer rounding

@formula samples = (milliseconds * sampleRate) / 1000

@use_cases

  • Converting delay times to sample-based counters
  • Calculating envelope timing in samples
  • Sample-accurate event scheduling calculations
Parameters
millisecondsInput time duration
Returns
Equivalent sample count (rounded)

@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.

◆ popDelayedMessage()

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.

Returns
MidiNoteMessage containing note data and timestamp Returns {-1, -1, -1.0f} if queue is empty

@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.

Note
Caller should verify message validity by checking for sentinel values before processing note data
Returns
Next available message or sentinel values if empty

@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

◆ processMidiMessage()

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.

Parameters
noteNumberMIDI note number [0-127]
velocityMIDI velocity [0-127] 0 = note-off, 1-127 = note-on with velocity
currentTimeMsCurrent 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

Note
This method should be called from MIDI input thread/callback

@implementation_details

  • Creates MidiNoteMessage structure with current timestamp
  • Pushes to incoming message queue for temporal analysis
  • Queue automatically manages memory with bounded growth
  • No validation performed for maximum real-time performance
Parameters
noteNumberMIDI note number
velocityMIDI velocity value
currentTimeMsCurrent system timestamp

@performance_analysis

  • Queue insertion: O(1) amortized complexity
  • Memory allocation: Bounded by queue implementation
  • Cache behavior: Sequential access pattern optimal for modern CPUs

@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

◆ samplesToMs()

float MidiHandler::samplesToMs ( int samples) const

Convert sample count to milliseconds at current sample rate.

Convert sample count to time duration.

Parameters
samplesNumber of audio samples
Returns
Equivalent time duration in milliseconds

@complexity O(1) - Single arithmetic operation @precision Full floating-point precision maintained

@formula milliseconds = (samples * 1000) / sampleRate

@use_cases

  • Converting buffer sizes to timing information
  • Latency calculations and reporting
  • Temporal analysis of processing delays
Parameters
samplesInput sample count
Returns
Equivalent time duration in milliseconds

@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:

  • 23-bit mantissa provides ~7 decimal digits of precision
  • At 44.1kHz: theoretical resolution ~0.00001ms (10ns)
  • Practical resolution limited by system clock accuracy

@use_case_examples

  • Buffer latency calculations: samplesToMs(bufferSize)
  • Processing delay measurement: samplesToMs(processingCycles)
  • Timing analysis: samplesToMs(eventSeparation)

◆ update()

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.

Parameters
currentTimeMsCurrent 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

  1. Iterate through incoming message queue (FIFO order)
  2. Check if (currentTime - messageTime) >= delayPeriod
  3. Transfer qualified messages to delayed queue
  4. Stop at first non-qualified message (temporal ordering preserved)

@temporal_accuracy Timing accuracy depends on update() call frequency. For sample-accurate processing, call once per audio buffer with buffer-aligned timing.

Note
This method should be called from audio processing thread at the beginning of each audio buffer processing cycle
Parameters
currentTimeMsCurrent 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

Member Data Documentation

◆ delayedMessages

std::queue<MidiNoteMessage> MidiHandler::delayedMessages
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)

◆ delayTimeMs

float MidiHandler::delayTimeMs
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)

◆ incomingMessages

std::queue<MidiNoteMessage> MidiHandler::incomingMessages
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)

◆ sampleRate

float MidiHandler::sampleRate
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


The documentation for this class was generated from the following files: