Processing Raw MIDI Arrays in Max/MSP
Modular software environments like Max/MSP provide a flexible framework for intercepting, manipulating, and retransmitting MIDI data at the lowest programmatic level. By treating MIDI events as structured byte arrays or sequential numerical lists, users can bypass standard musical interface constraints to implement custom algorithmic modifications, generative routing, and non-linear data mapping. This overview outlines how raw incoming MIDI messages are parsed into accessible lists, manipulated through array-processing objects, and reconstructed for real-time performance.
Ingesting and Parsing Raw Streams
Standard MIDI communication operates via multi-byte serial streams, typically consisting of a status byte (indicating message type and channel) followed by one or two data bytes (such as pitch and velocity).
In Max/MSP, raw MIDI enters through objects like midiin
or sysexin. The midiin object outputs raw,
unformatted decimal bytes. To structure this continuous stream into
intelligible arrays:
- The
midiparseobject automatically groups raw bytes into specific message categories (pitch bend, controller changes, polyphonic pressure, note on/off). - The
threshobject groups consecutive bytes arriving within a specified millisecond window into discrete arrays (lists), turning raw byte streams into intact 2-byte or 3-byte lists. - Low-level routing uses
midiselectto filter streams based on channel, message type, or numerical thresholds before any processing takes place.
Array Deconstruction and List Processing
Once an incoming message is captured as a list, it can be parsed
using native list-handling objects, primarily the zl object
family.
- Separation: The
unpackorzl.sliceobjects split arrays into individual values. For example, a 3-byte note event[144, 60, 100](Note-On on Channel 1, Middle C, Velocity 100) is broken into individual integer streams for discrete analysis. - Stream Reordering: Objects such as
zl.rot(rotate) andzl.rev(reverse) alter the positional order of elements within arrays, allowing for structural permutations of polyphonic chords or sequencer patterns. - Mathematical Filtering: Arrays can be processed en
masse using
zl.mth(extract specific indices),zl.scramble(stochastic reordering), orzl.sortto reorder multi-pitch arrays from lowest to highest.
Algorithmic Transformations
After decomposing raw arrays, mathematical transformations are applied to pitch, velocity, or timing vectors.
- Pitch Inversion and Scaling: Mathematical operators
(
+,-,*,%) apply matrix transformations across pitch arrays. Modulo operators (% 12) isolate pitch-class sets, enabling real-time quantization to custom microtonal or non-Western scales. - Dynamic Modification: Velocity values within lists
can be mapped non-linearly using objects like
scaleor function tables (function), converting standard linear response curves into logarithmic or inverted dynamic responses. - Lookup Tables and Collections: Arrays can be stored
and cross-referenced in memory structures such as
coll,table, ordict. A raw input pitch array can serve as an index address that retrieves an entirely new array of harmonic or control data.
Procedural Scripting with JavaScript and Gen
For complex multi-dimensional array operations that exceed standard visual patching, Max provides procedural programming layers:
- The
jsObject: Embedded JavaScript allows developers to handle MIDI packets as native JavaScript arrays. Functions such as.map(),.filter(), and.reduce()can process complex System Exclusive (SysEx) dumps or polyphonic chord arrays within a few lines of code. - Gen Environments: When handling timing-critical,
sample-accurate MIDI event schedules,
gen~or specialized code-box operators process integer streams at audio rate, eliminating the timing jitter inherent in lower-priority visual messaging threads.
Message Reconstruction and Dispatch
Once transformations are computed, the modified arrays must be re-serialized into valid MIDI specifications to interface with hardware or digital audio workstations (DAWs).
- Re-Packing: Objects like
packorzl.joinassemble the transformed discrete variables back into standardized 2-byte or 3-byte array formats. - Formatting: The
midiformatobject takes separate control, note, and channel streams and packages them into a fully compliant MIDI data packet. - Transmission: The completed array is passed to
midioutto transmit raw serial bytes directly to physical hardware ports, or tonoteout/ctloutto route data to internal software instruments and virtual MIDI buses.