MIDI 1.0
Timing & Sync
MIDI handles time in two ways. Files store musical time as ticks, converted to seconds through a tempo map. Live systems sync through a stream of clock messages (tempo-relative) or MIDI Time Code (absolute, SMPTE-based).
Ticks, PPQ and tempo
A Standard MIDI File's header sets the division, usually as PPQ (pulses or ticks per quarter note). Common values are 96, 480, and 960. Every event's delta-time counts ticks. Tempo is stored separately, in microseconds per quarter note, by the Set Tempo meta event FF 51 03. With no tempo event, assume 500,000 µs (120 BPM).
| Quantity | Formula |
|---|---|
| BPM | 60,000,000 / µsPerQuarter |
| µs per quarter | 60,000,000 / BPM |
| ms per tick | µsPerQuarter / PPQ / 1000 |
| seconds for n ticks | n × µsPerQuarter / PPQ / 1,000,000 |
Example: at 120 BPM (500,000 µs) and 480 PPQ, one tick lasts 500,000 / 480 / 1000 ≈ 1.042 ms. A tempo change affects only the ticks after it, so finding absolute time means walking the tempo map and summing each segment.
BPM means quarter notes
MIDI tempo is always defined per quarter note, whatever the time signature. In 6/8, a "dotted-quarter = 80" feel is a MIDI tempo of 120 quarter notes per minute.
SMPTE-based division
A file can use absolute time instead of PPQ. When bit 15 of the division word is set, the high byte is a negative
frame rate in two's complement (E8 = −24, E7 = −25, E3 = −29 for 29.97 drop
frame, E2 = −30) and the low byte is ticks per frame. E7 28 (25 fps × 40 ticks) gives
exactly 1 ms per tick. Tempo events do not affect timing in this mode.
MIDI Clock
MIDI Clock is a tempo-following pulse. A leader sends F8 (Timing Clock) at 24 pulses per quarter note. Followers derive tempo from the spacing between pulses. There is no tempo value in the stream, so tempo changes appear as changes in pulse spacing.
| Status | Name | Meaning |
|---|---|---|
| F8 | Timing Clock | 24 per quarter note while the leader runs. Many devices send it even while stopped, so followers can show tempo. |
| FA | Start | Start from the beginning (song position 0). The first F8 after Start marks the first beat. |
| FB | Continue | Resume from the current song position, for example one just set by SPP. Also begins at the next clock. |
| FC | Stop | Stop. The follower keeps its position so a later Continue resumes there. |
| F2 ll mm | Song Position Pointer | Sets the position in MIDI beats. Send while stopped. |
These are System Real-Time messages. They are a single byte and may appear anywhere, even between the bytes of another message.
Song Position Pointer
SPP is a 14-bit value, LSB first, counting MIDI beats. One MIDI beat is a sixteenth note, which is 6 clocks. The maximum, 16,383 sixteenths, is just under 1,024 bars of 4/4.
position (16ths) = ll | (mm << 7)
clocks from start = position × 6
F2 20 00 → 32 sixteenths → bar 3, beat 1 in 4/4
The usual way to locate: FC (Stop), F2 ll mm, then FB (Continue). Allow the
follower time to seek before sending clocks.
Jitter and latency
At 120 BPM, clocks arrive every 20.8 ms. Followers usually smooth the incoming intervals (moving average or a phase-locked loop) instead of reacting to each pulse, because USB scheduling, OS timers and merges add jitter. If tight sync matters, use a dedicated hardware clock source, avoid long chains of MIDI Thru, and compensate for fixed latency on the follower. On a network, Ableton Link is a common alternative.
MIDI Time Code
MIDI Time Code (MTC) carries absolute SMPTE time (hours:minutes:seconds:frames), used to lock MIDI devices to video, tape and other timecode-driven systems. It is independent of tempo.
Quarter-frame messages
While running, the leader sends F1 0nnn dddd four times per
frame. nnn is the piece number (0–7) and dddd a 4-bit nibble:
| Piece | Data byte | Carries |
|---|---|---|
| 0 | 0000 ffff | Frames, low nibble |
| 1 | 0001 000f | Frames, high bit |
| 2 | 0010 ssss | Seconds, low nibble |
| 3 | 0011 00ss | Seconds, high 2 bits |
| 4 | 0100 mmmm | Minutes, low nibble |
| 5 | 0101 00mm | Minutes, high 2 bits |
| 6 | 0110 hhhh | Hours, low nibble |
| 7 | 0111 0rrh | Hours, high bit (h) and frame rate (rr) |
rr | Frame rate |
|---|---|
00 | 24 fps (film) |
01 | 25 fps (PAL/EBU) |
10 | 29.97 fps drop-frame (NTSC) |
11 | 30 fps non-drop |
Eight quarter frames, so two frames, make one complete time value. When piece 7 arrives, the assembled time is the time at which piece 0 was sent. A receiver must add 2 frames to get current time. In reverse playback the pieces arrive 7→0.
Full-frame message
When locating, rewinding, or jumping, a full time stamp is sent at once as Universal Real-Time SysEx:
F0 7F 7F 01 01 hh mm ss ff F7
hh is 0rrhhhhh, with the rate bits as above and the hour in the low 5 bits. The other bytes
hold minutes (0–59), seconds (0–59), and frames.
Drop-frame timecode
NTSC video runs at 30000/1001 ≈ 29.97 fps. Drop-frame timecode keeps the displayed time close to wall-clock time by skipping frame numbers 00 and 01 at the start of every minute, except minutes divisible by 10. No video frames are dropped, only labels. 29.97 non-drop also exists in video, but MTC has no code for it. It is usually sent with the 30 fps code and runs 0.1% slow.
Aside: Active Sensing
FE Active Sensing is not a timing message, but it belongs to the same Real-Time family.
It is optional. Once a receiver has seen one, it expects a message of some kind at least every 300 ms. If none
arrives, it assumes the cable was pulled and turns off all voices. Most software filters FE out of
its monitors.
Spotted an error? Edit this page on GitHub.