The data chunk is a list of events, one after another with nothing between them. Each event is one ID byte and a value. The ID says what the value means and also how big it is, so a reader can step over any event, including ones it does not recognise.
Size classes
The ID byte falls into one of five ranges. The range sets the size of the value.
| IDs | Class | Value |
|---|---|---|
| 0x00 (0) to 0x3F (63) | Byte | 1 byte |
| 0x40 (64) to 0x7F (127) | Word | 2 bytes, little-endian |
| 0x80 (128) to 0xBF (191) | Dword | 4 bytes, little-endian |
| 0xC0 (192) to 0xCF (207) | Text | a variable-length size, then that many bytes of text |
| 0xD0 (208) to 0xFF (255) | Data | a variable-length size, then that many bytes of data |
Some IDs in the data range hold text, because they were added after the text range was full: 231 (display group name, from version 3.4) and, in later files, 239 (track name). Whether these versions write 239 is not recorded. They are stored exactly like data events, so the size rule still holds, and their text is UTF-16LE like the rest.
Fixed-size events
Byte, word and dword events are the ID followed straight away by the value.
09 01 loop active: 1
40 00 00 new channel: index 0
9C E0 22 02 00 tempo: 140000, which is 140 BPM
Variable-length size
Text and data events give their size as a variable-length number between the ID and the bytes. Each byte carries seven bits of the number, lowest bits first. The top bit of a byte is set when another byte follows. Sizes under 128 take one byte.
300 in binary = 10 0101100
low seven bits = 0101100 = 0x2C, more follows → 0x2C + 0x80 = 0xAC
next seven bits = 10 = 0x02, last byte → 0x02
stored as = AC 02
To read a size, start at zero and a shift of zero. Take a byte, add its low seven bits shifted left by the shift, and add seven to the shift. Stop after a byte whose top bit is clear.
Text
Text events hold a string followed by a terminating zero. Versions 11.5 to 12 are the first to use two encodings:
- The FL Studio version string (event 199) is ASCII, ending in one zero byte, so a reader can always find out which version saved the file.
- Every other string is UTF-16LE, two bytes per character, ending in two zero bytes.
C7 0C 32 30 2E 38 2E 33 2E 32 33 30 34 00 20.8.3.2304
C2 0A 53 00 6F 00 6E 00 67 00 00 00 Song
Where exactly the switch happened is not documented. PyFLP first placed it at 12.0, then moved it to 11.5 after finding UTF-16 text in plugin presets from that version; the libflp reader still switches at 12.0. For files that say 11.5 to 11.x, check the text itself: UTF-16LE Latin text has a zero byte after every character.
How events are grouped
The stream has no nesting. Items such as channels and patterns are built from runs of events: a New event opens an item, and the events after it set that item’s properties until the next New event of the same kind.
| Event | ID | Opens |
|---|---|---|
| Channel New | 0x40 (64) | a channel in the channel rack; the value is its index |
| Pattern New | 0x41 (65) | a pattern; the value is its index |
| Slot Index | 0x62 (98) | an effect slot in a mixer insert |
Each pattern’s New event appears twice in a project, so one pattern’s events can come in two separate runs. Join them by the pattern index. Mixer inserts have no New event; their events appear insert by insert, in mixer order. Plugin events follow the channel or slot that holds the plugin.
Project events, such as the tempo and the title, can appear anywhere in the stream and always belong to the project as a whole. The event reference lists every known event by the item it belongs to.