Technical Breakdown of Standard MIDI 1.0 Messages

MIDI Message Structure (Bytes and Bits)

MIDI 1.0 messages are transmitted as a sequence of 8-bit bytes. Each message begins with a status byte followed by zero, one, or two data bytes (depending on the message type). The most significant bit (MSB) of a status byte is always 1, whereas the MSB of a data byte is 0. This bit convention allows the receiver to distinguish status bytes from data bytes in the serial stream. In binary, a byte is b7 b6 b5 b4 b3 b2 b1 b0 (b7 = MSB). For status bytes, b7 = 1; for data bytes, b7 = 0.

A status byte encodes the message type and, for channel-specific messages, the MIDI channel number. In a Channel status byte, bits 3–0 (the lower 4 bits) specify the channel number (0–15, often referred to as channels 1–16) and bits 6–4 (the next 3 bits) identify the message type. For example, a binary status byte 1001nnnn indicates a "Note On" message (binary 1001 = 0x9) on channel nnnn. A status byte of 1111xxxx (0xF0–0xFF in hex), with bits 7–4 all 1, denotes a System message that is not channel-specific. Each data byte that follows carries a 7-bit value (0–127) with b7=0.

In summary: a MIDI message = [Status Byte] + [Data Byte(s)]. The status byte’s MSB is 1 and encodes what the message is (and possibly which channel), and the data bytes’ MSB is 0 and encode the values (like note number, velocity, etc.). The table below outlines this structure:

  • Status byte (1 byte): MSB = 1. Encodes message type and possibly channel.
  • Data byte(s) (0–2 bytes): MSB = 0. Carry the parameters for the message (e.g., note number, velocity).

For example, the hex bytes 0x90 0x3C 0x64 could form a MIDI message. 0x90 is a status byte in binary 1001 0000: 1001 indicates a Note On message and 0000 indicates channel 1 (channel numbers 0–15 correspond to channels 1–16). It is followed by two data bytes: 0x3C (0011 1100 in binary, a data byte representing note number 60) and 0x64 (0110 0100 in binary, a data byte for velocity 100). This message would be interpreted as “Note On, channel 1, note 60, velocity 100.”

MIDI Channel Messages (Channel Voice Messages)

Channel messages are those directed to a specific MIDI channel (1–16). These include musical performance messages like turning notes on/off and adjusting controllers. The status byte of a channel message contains a 4-bit channel number and a 4-bit message type code, as described above. The table below lists the standard Channel Voice messages, their status byte, and data byte format:

Channel Voice MessageStatus Byte (Binary & Hex)Data Byte 1Data Byte 2
Note Off1000nnnn (0x8n)Key Number (0–127)Release Velocity (0–127)
Note On1001nnnn (0x9n)Key Number (0–127)Attack Velocity (0–127)
Polyphonic Aftertouch1010nnnn (0xAn)Key Number (0–127)Pressure Value (0–127)
Control Change1011nnnn (0xBn)Controller Number (0–127)Controller Value (0–127)
Program Change1100nnnn (0xCn)Program Number (0–127)(no second data byte)
Channel Aftertouch1101nnnn (0xDn)Pressure Value (0–127)(no second data byte)
Pitch Bend Change1110nnnn (0xEn)LSB (0–127)MSB (0–127)

Note: In the status byte notation above, n is the 4-bit channel number (0x0–0xF for channels 1–16). For example, a Note On for channel 13 would have status 0x9C (since 0xC = 12 in the low nibble, representing channel 13).

Each Channel Voice message has a fixed size and meaning for its data bytes:

  • Note On (0x9n): Triggers a note. Data bytes: Key number (note pitch 0–127 where 60 is middle C) and Velocity (attack intensity 0–127). A velocity of 0 is often interpreted as a Note Off (a method to optimize running status usage) in many devices.
  • Note Off (0x8n): Releases a note. Data: Key number and Release velocity. (Release velocity is seldom used by older gear, but it’s part of the MIDI spec.)
  • Polyphonic Key Pressure (Aftertouch) (0xAn): Indicates pressure applied to an individual note (key) that is already held down. Data: Key number and Pressure value.
  • Control Change (0xBn): Adjusts a continuous controller or switches a mode. Data: Controller number and value. Standard controllers include things like Modulation wheel (controller #1), Volume (#7), Pan (#10), etc. Controller numbers 120–127 are reserved for Channel Mode messages (special commands affecting the channel’s mode) – for example, All Notes Off (123), Omni On/Off (125/124), Mono/Poly mode (126/127), etc. These mode messages share the 0xBn status but use those high controller numbers for specific channel-wide functions.
  • Program Change (0xCn): Changes the instrument program (patch) on the channel. Data: one byte indicating the program number (0–127). There is only one data byte for Program Change (no second data byte).
  • Channel Pressure (Channel Aftertouch) (0xDn): Similar to polyphonic aftertouch but applied to the whole channel (often the highest pressure of all currently held notes). Data: one byte for the pressure amount. This message is sent by devices that only measure a single overall aftertouch (like many synth keyboards).
  • Pitch Bend Change (0xEn): Indicates a change in the pitch bender wheel or lever. This message uses two data bytes, which together form a 14-bit value (0–16383). The first data byte is the Least Significant 7 bits and the second is the Most Significant 7 bits of a 14-bit unsigned value. The pitch bend range is centered at 8192 (0x2000) meaning no pitch deviation at center position. Values below 8192 bend downward, above 8192 bend upward. (For example, a Pitch Bend message 0xE0 0x00 0x40 would set pitch bend to 0x4000 = 16384 decimal, which is actually 8192 + 8192, the maximum upward bend if the device’s range is ±2 semitones by default.)

Example – Note On: To illustrate a channel message at the bit level, consider the Note On example from above. Suppose we want to play MIDI note number 80 (0x50) with maximum velocity 127 (0x7F) on channel 13. The device would send the three bytes: 0x9C 0x50 0x7F. In binary, this is: 1001 1100 0101 0000 0111 1111. Breaking that down:

  • 1001 1100 = 0x9C is the status byte. 1001 indicates Note On, and 1100 (binary for 12) indicates channel 13 (since channel numbers are 0-indexed in the byte). Bit7 is 1, confirming it’s a status byte.
  • 0101 0000 = 0x50 is the first data byte (note number 80, with bit7=0).
  • 0111 1111 = 0x7F is the second data byte (velocity 127, bit7=0).

The receiver interprets this as “Note On, Channel 13, Note 80, Velocity 127.” If another Note On for the same channel were to follow immediately, the status byte 0x9C could optionally be omitted due to running status (explained later), since the receiver already knows subsequent data bytes refer to the last status.

System Common Messages

System Common messages are MIDI messages that are not specific to any one channel; they carry information relevant to the system or sequence as a whole. These messages also have status bytes (0xF1–0xF7, excluding real-time 0xF8–0xFF) and a defined number of data bytes, but they apply globally rather than to a single channel. The standard System Common messages in MIDI 1.0 are:

  • MIDI Time Code Quarter Frame (0xF1): 1 data byte. Used for synchronizing to external time code (such as SMPTE). It sends a piece of the time code (the data byte is split into a 3-bit message type and a 4-bit value, hence “quarter frame”). This allows distribution of a full time code message in 8 sequential Quarter Frame messages. 
  • Song Position Pointer (0xF2): 2 data bytes. This is a 14-bit value indicating the position in the song (sequence) in MIDI beats (ticks) from the start. It typically counts MIDI beats where 1 beat = 6 MIDI Clocks (since 24 clocks = one quarter note). The first data byte is the least significant 7 bits and the second is the most significant 7 bits of the position. For example, a Song Position Pointer message F2 00 10 in hex would set the song position to 0x1000 (LSB=0x00, MSB=0x10) = 4096 in decimal. This message allows devices like sequencers to jump to a point in the song.
  • Song Select (0xF3): 1 data byte. Selects a song or sequence number to play. The data byte corresponds to the song number (0–127). This is typically used with devices that have multiple preset songs or sequences.
  • Undefined (0xF4 & 0xF5): These status codes are reserved in MIDI 1.0 and do not currently have an assigned message. Devices should ignore these if encountered.
  • Tune Request (0xF6): 0 data bytes. A message intended for analog synthesizers, instructing them to tune their oscillators. Upon receiving this, an analog synth will usually perform an internal tuning routine.
  • End of Exclusive (EOX, 0xF7): 0 data bytes (technically a status byte used as a terminator). This is not a standalone message by itself, but rather a flag marking the end of a System Exclusive message. It is sent at the conclusion of a SysEx message (detailed below) to signal that the exclusive message is complete. If a device receives an unexpected 0xF7 by itself, it generally means the end of a SysEx with no start, and is usually ignored unless it was in the middle of parsing a SysEx.

System Exclusive (SysEx) Messages

System Exclusive messages (status 0xF0) are a special class of system message that allow manufacturers or organizations to define their own custom data and commands. A SysEx message can be of arbitrary length. It begins with the SysEx start byte 0xF0 and is terminated by the End of Exclusive byte 0xF7. Everything in between is the message data and uses only bytes with MSB=0 (so that no byte in the middle looks like a status byte).

The data format of a SysEx usually starts with a Manufacturer ID to indicate the intended recipient of the message. Manufacturer IDs are assigned by the MIDI Manufacturers Association and can be one byte (for large manufacturers) or three bytes (for extended or smaller manufacturer IDs). For example, 0x41 is the ID for Roland, 0x43 for Yamaha, etc., and 0x7E and 0x7F are special universal IDs used for non-manufacturer-specific SysEx (like MIDI standard messages for all devices). After the manufacturer ID(s), the message may include a sub-ID or command type and then a sequence of data bytes specific to that command. Since all bytes until the terminating 0xF7 have bit7 = 0, the entire SysEx content is effectively a stream of 7-bit data bytes. When 0xF7 is received, the SysEx message is considered ended.

Example structure of a SysEx message:

F0 <Manuf. ID> <sub-ID/command> <data byte 1> <data byte 2> ... <data byte N> F7

All bytes from the Manufacturer ID through the last data byte have their MSB = 0. A concrete example is the General MIDI System On message defined in the MIDI spec:

F0 7E 7F 09 01 F7

Here, 0x7E is the universal non-real-time ID, 0x7F is a special device ID (meaning “to all devices”), 0x09 is the sub-ID for "General MIDI On", and 0x01 is the value turning it on. The message ends with F7. Any device that recognizes this universal message will respond by enabling General MIDI mode.

Devices that do not recognize or support the particular SysEx ID or command will simply ignore the rest of the message until the 0xF7 terminator. Importantly, SysEx messages can be very long (for example, dumping entire memory banks of a synthesizer) so they are the only MIDI message that can span an arbitrary number of bytes. Only System Real-Time messages (described next) are allowed to interleave within a SysEx stream – meaning a real-time status byte (like MIDI Clock) can appear in the middle of a SysEx without terminating it. Aside from those, any other status byte (with MSB=1) will be interpreted as the end of the SysEx (if it’s not the 0xF7 terminator, it actually signifies some error or a new message starting).

System Real-Time Messages

System Real-Time messages are a set of single-byte messages (status bytes 0xF8 through 0xFF) that are used for timing and control of the entire system. They are called “real-time” because they can be sent at any time, even in the middle of other message sequences, and are intended to be acted upon immediately (often for synchronization).These messages do not include any data bytes – the single status byte itself carries the meaning. The standard real-time messages in MIDI 1.0 are:

  • Timing Clock (0xF8): No data. This is a timing tick used to synchronize sequencers and arpeggiators. It is typically sent at a rate of 24 clocks per quarter note (PPQN = 24) when a device is serving as the master clock. For example, at a tempo of 120 BPM (2 beats per second), the master will send 48 Timing Clock messages per second. Receivers use this to advance their playback position in perfect sync. (Clock messages can be sent whether music is playing or not, as long as a master is providing tempo.)
  • Start (0xFA): No data. Signals all connected devices to start playback of their sequences or songs from the beginning. Commonly used by a master sequencer or drum machine to tell slave devices to start at beat 0. After a Start, the master will usually continue sending Timing Clocks to keep slaves in sync.
  • Continue (0xFB): No data. Tells devices to continue playback from where they were last stopped (resume from the current song position). This message is used after a Stop; devices should resume at the point they left off. Timing Clocks typically continue after this message as well.
  • Stop (0xFC): No data. Commands all devices to stop playback/sequencing. A Stop message indicates the sequencer or controller has halted playback. (Slaves will stop their sequences until a Start or Continue is received.)
  • Undefined (0xFD): No data. This status is reserved/undefined in the MIDI 1.0 spec and is not used. Like the other undefined statuses (0xF4, 0xF5), it should be ignored if encountered.
  • Active Sensing (0xFE): No data. This is a special heartbeat message intended to indicate an “active connection.” A device that enables Active Sensing will send 0xFE messages repeatedly (at least one every 300 milliseconds or faster) when there is otherwise no other MIDI data being sent. If a receiver that supports Active Sensing stops receiving any MIDI data for over 300 ms, it will assume the connection is lost (for example, a cable got unplugged) and it will turn off all notes and return to a safe state. Active Sensing is optional; if not used, devices rely on MIDI Note Off messages or other means to silence notes. If used, it helps prevent stuck notes when a MIDI cable is disconnected unexpectedly by prompting the receiver to silence everything when the signal drops out.
  • System Reset (0xFF): No data. This message resets all receivers to their power-up state. It will stop any playing notes and clear out controllers, effectively as if the device was just turned on. System Reset is a “panic” message – it should be used sparingly (and is usually only triggered manually by a user in an emergency) because it can abruptly silence and reinitialize devices in the middle of a performance. Notably, devices should not automatically send System Reset on their own startup; it’s meant to be sent only when explicitly needed.

All the real-time messages above are single-byte (their whole message is just the status byte). They can appear interspersed with other MIDI messages without waiting for those messages to finish. For instance, a Timing Clock (0xF8) might be sent in between the two data bytes of a long SysEx or between the status and data of a Note On. MIDI receivers are designed to recognize these real-time bytes instantly and handle them immediately, without confusing them with the surrounding message. Real-time messages do not use running status and do not disrupt running status of channel messages.

MIDI Transmission Details (Serial Transport of MIDI 1.0)

Standard MIDI 1.0 hardware communication occurs over a serial UART connection (the 5-pin DIN MIDI cable) and is often referred to as “MIDI DIN” transport. The transmission is asynchronous and byte-oriented, very similar to an RS-232 serial line in format. The characteristics of the MIDI physical layer are:

  • Baud Rate: 31,250 bits per second (31.25 Kbaud). This rate is a fixed standard for MIDI over DIN cables. (It equates to a bit time of about 32 microseconds.) A tolerance of ±1% is allowed for MIDI devices’ internal clocks to still communicate reliably.
  • Frame Format: 1 start bit, 8 data bits, no parity bit, 1 stop bit (often notated as 8-N-1). The start bit is always 0 (which marks the beginning of a byte), and the stop bit is 1 (marking the end of the byte). Each byte on the wire is encapsulated with these start/stop bits, so it requires 10 serial bits in total to send one 8-bit MIDI byte.
  • Throughput: ~3125 bytes per second maximum. Because of the 10-bit framing, the effective rate of MIDI bytes is 31250/10 = 3125 bytes/s. In time terms, sending one byte takes 320 microseconds. For example, a 3-byte MIDI message (like a Note On) takes about 960 microseconds to transmit, and a long SysEx of 100 bytes would take about 32 milliseconds. This bandwidth is modest, which is why running status (below) and other optimizations are useful to make the most of it.
  • Transmission Medium: MIDI DIN cables use a current-loop scheme (5 mA current at ~5V) for noise immunity, and an opto-isolator at the receiver for ground isolation. (While not the focus of this discussion, it’s worth noting the electrical aspect: a logic 0 is represented by current flow, and logic 1 by no current, which helps avoid false start bits on an unconnected line. The important point is that MIDI is a unidirectional serial stream from the OUT of one device to the IN of another, running at the fixed rate and format above.

Parsing MIDI Bytes: MIDI devices process the incoming serial data in real time using the MSB of each byte to separate status bytes from data bytes. A simple state machine in a MIDI receiver might work as follows: when a status byte (MSB=1) arrives, the device identifies the message type and channel (if applicable) and knows how many data bytes should follow. It then gathers the next one or two bytes as data (since those will have MSB=0). Once the required number of data bytes have been received, the message is complete and can be executed (e.g., play a note or change a control). The device then expects either another status byte or, under some conditions, more data bytes continuing the stream via running status (if the same status is repeated).

Running Status: To maximize efficiency, MIDI employs a feature called running status. This means that if the same status byte (for a channel message) would be repeated for subsequent messages, it can be omitted and the receiver will assume the last status continues to apply. For example, if a synth needs to send many Note On messages in a row on the same channel, it can send the status byte 0x9n once, and then just send pairs of note and velocity data bytes for each additional note without re-sending 0x9n every time. The receiver remembers the “running” status from the previous message and applies it to the new data bytes. Running status is only applicable to channel messages (status bytes 0x80–0xEF) – system messages are not eligible for running status and always transmit their full status byte when they occur.
MIDI spec: *“Running status is used: status bytes of MIDI channel messages may be omitted if the preceding event is a MIDI channel message with the same status.”* ([smf - Unrecognized status byte in Midi file - Stack Overflow](https://stackoverflow.com/questions/42689052/unrecognized-status-byte-in-midi-file#:~:text=,message%20with%20the%20same%20status)). This greatly reduces overhead in dense MIDI streams (such as rapid sequences of notes).

A receiver implementing running status will treat any incoming data byte (MSB=0) as belonging to the last known status, if the expected number of data bytes for that message type hasn’t been fulfilled or if no new status has arrived. If a new status byte comes (MSB=1), it resets the running status to that new message. Certain status bytes will reset or cancel running status: for instance, any System Common or SysEx message (0xF0–0xF7) will cancel the running status, meaning the next channel message must resend its status byte.

Example of running status: Suppose a keyboard sends three Note On messages in a row on channel 1: Note 60 on, Note 64 on, Note 67 on (all with velocity 100). Normally this would be: 0x90 0x3C 0x64, 0x90 0x40 0x64, 0x90 0x43 0x64. With running status, the keyboard can send: 0x90 0x3C 0x64, 0x40 0x64, 0x43 0x64. The first Note On includes the status 0x90. The next two omit the status byte and just send the data bytes, which the receiver knows still correspond to the last received status (0x90). This saves two bytes in the transmission. The receiver will still interpret the stream correctly as three Note On events in succession. If a different status byte (say, a Control Change 0xB0) appears, it would break the running status chain and establish a new running status (until changed again).