Created
November 23, 2020 01:27
-
-
Save offlinemark/bc3ec0f21370a9f6da4404c637ff8244 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The FruityLoops FLP file format explained | |
(v 1.7.x) | |
(BETA) | |
Introduction: | |
Here I will try to explain the FLP file format, which is the FruityLoops loop / | |
song format. Visit www.fruityloops.com to get more info about FruityLoops. | |
First you've got to understand that FruityLoops keeps growing, so does the FLP | |
format. Fortunately it has been designed for it... While I made some mistakes | |
during its evolution, the format still remains updatable. Normally anything | |
made out of this document will be able to process future loop files, although | |
of course you will miss newly added features. | |
Not sure what this document could be useful for, but if you ever use it to make | |
something, send it to me! Also if you'd like me to update this file (it's not | |
complete yet), feel free to mail me. | |
(2/09/99) Didier Dambrin (gol) [email protected] | |
How it works: | |
Don't expect the FLP format to be a big chunk full of ordered parameters, like | |
most trackers file formats. It had to evolute, so I chose the 'events' way. | |
You should see it a bit like MIDI / AIFF files. It's just a succession of | |
events. Once you've understand how to process the file to retrieve these | |
events, the only thing you'll need is the list of events available! | |
It's better, since once you've made the piece of code to get the events, you | |
won't bother with the format anymore. Also you will just ignore any event you | |
don't know (yet) about. | |
Please note that the format *does not* respect the AIFF standard, although I | |
tried to keep the chunks system similar. | |
Although the program has been coded in Pascal, I'll do my best to use C++ | |
declarations, so everyone will understand. DWORD is 4 bytes, WORD is 2 bytes. | |
Retrieving the events: | |
I said it looks like a MIDI file, but it's not a MIDI file. | |
First, you'll have to get & check the HEADER chunk, to be sure it's a FLP file. | |
The header is similar to the format of a MIDI file header: | |
DWORD ChunkID 4 chars which are the letters 'FLhd' for 'FruityLoops header' | |
DWORD Length The length of this chunk, like in MIDI files. Should be 6 | |
because of the 3 WORDS below... WORD Format Set to 0 for full songs. WORD | |
nChannels The total number of channels (not really used). WORD BeatDiv | |
Pulses per quarter of the song. | |
Most of this chunk is not used, it's just that I tried (as a start) to respect | |
the proper MIDI header :) | |
Then you'll encounter the DATA chunk, which is in fact the last chunk, the one | |
containing all the events. | |
DWORD ChunkID 4 chars which are the letters 'FLdt' for 'FruityLoops data' | |
DWORD Length The length of this chunk WITHOUT these 2 DWORDS (that is minus | |
4*2 bytes), like in MIDI files. | |
The whole data chunk is a succession of EVENTS, which I'm going to explain... | |
To retrieve an event, first you read a byte (the event ID). According to this | |
byte, the size of the event data varies: 0..63 The data after this byte is a | |
BYTE (signed or unsigned, depending on the ID). 64..127 The data after this | |
byte is a WORD. 128..192 The data after this byte is a DWORD. 192..255 The | |
data after this byte is a variable-length block of data (a text for example). | |
That makes 64 BYTE events, 64 WORD events, 64 DWORD events & 64 TEXT events. | |
The purpose of this split is of course to keep the file size small. So you get | |
the event ID & then you read the number of bytes according to this ID. Whether | |
you process the event or not isn't important. What is important is that you can | |
jump correctly to the next event if you skip it. | |
For TEXT (variable-length) events, you still have to read the size of the | |
event, which is coded in the next byte(s) a bit like in MIDI files (but not | |
stupidly inverted). After the size is the actual data, which you can process or | |
skip. To get the size of the event, you've got to read bytes until the last | |
one, which has bit 7 off (the purpose of this compression is to reduce the file | |
size again). | |
Start with a DWORD Size = 0. You're going to reconstruct the size by getting | |
packs of 7 bits: 1. Get a byte. 2. Add the first 7 bits of this byte to Size. | |
3. Check bit 7 (the last bit) of this byte. If it's on, go back to 1. to | |
process the next byte. | |
To resume, if Size < 128 then it will occupy only 1 byte, else if Size < 16384 | |
it will occupy only 2 bytes & so on... | |
So globally, you open the file, check the header, point to the data chunk & | |
retrieve / filter all the events. Easy(?) Now let's get to the events... | |
The events list: | |
Some events are obvious. Some others will have to be explained in details. But | |
to understand how to process the file, you've got to know how the FLP is stored | |
because the order of most of these events *IS* important. For example, when | |
storing a channel, an FLP_NewChan event is added. Then any next 'channel' event | |
will affect this newly created channel. To spare some space, some events are | |
stored only if they differ from their default value. | |
// BYTE EVENTS | |
FLP_Byte =0; | |
FLP_Enabled =0; | |
FLP_NoteOn =1; //+pos (byte) | |
FLP_Vol =2; | |
FLP_Pan =3; | |
FLP_MIDIChan =4; | |
FLP_MIDINote =5; | |
FLP_MIDIPatch =6; | |
FLP_MIDIBank =7; | |
FLP_LoopActive =9; | |
FLP_ShowInfo =10; | |
FLP_Shuffle =11; | |
FLP_MainVol =12; | |
FLP_Stretch =13; // old byte version | |
FLP_Pitchable =14; | |
FLP_Zipped =15; | |
FLP_Delay_Flags =16; | |
FLP_PatLength =17; | |
FLP_BlockLength =18; | |
FLP_UseLoopPoints =19; | |
FLP_LoopType =20; | |
FLP_ChanType =21; | |
FLP_MixSliceNum =22; | |
// WORD EVENTS | |
FLP_Word =64; | |
FLP_NewChan =FLP_Word; | |
FLP_NewPat =FLP_Word+1; //+PatNum (word) | |
FLP_Tempo =FLP_Word+2; | |
FLP_CurrentPatNum =FLP_Word+3; | |
FLP_PatData =FLP_Word+4; | |
FLP_FX =FLP_Word+5; | |
FLP_Fade_Stereo =FLP_Word+6; | |
FLP_CutOff =FLP_Word+7; | |
FLP_DotVol =FLP_Word+8; | |
FLP_DotPan =FLP_Word+9; | |
FLP_PreAmp =FLP_Word+10; | |
FLP_Decay =FLP_Word+11; | |
FLP_Attack =FLP_Word+12; | |
FLP_DotNote =FLP_Word+13; | |
FLP_DotPitch =FLP_Word+14; | |
FLP_DotMix =FLP_Word+15; | |
FLP_MainPitch =FLP_Word+16; | |
FLP_RandChan =FLP_Word+17; | |
FLP_MixChan =FLP_Word+18; | |
FLP_Resonance =FLP_Word+19; | |
FLP_LoopBar =FLP_Word+20; | |
FLP_StDel =FLP_Word+21; | |
FLP_FX3 =FLP_Word+22; | |
FLP_DotReso =FLP_Word+23; | |
FLP_DotCutOff =FLP_Word+24; | |
FLP_ShiftDelay =FLP_Word+25; | |
FLP_LoopEndBar =FLP_Word+26; | |
FLP_Dot =FLP_Word+27; | |
FLP_DotShift =FLP_Word+28; | |
// DWORD EVENTS | |
FLP_Int =128; | |
FLP_Color =FLP_Int; | |
FLP_PlayListItem =FLP_Int+1; //+Pos (word) +PatNum (word) | |
FLP_Echo =FLP_Int+2; | |
FLP_FXSine =FLP_Int+3; | |
FLP_CutCutBy =FLP_Int+4; | |
FLP_WindowH =FLP_Int+5; | |
FLP_MiddleNote =FLP_Int+7; | |
FLP_Reserved =FLP_Int+8; // may contain an invalid version info | |
FLP_MainResoCutOff =FLP_Int+9; | |
FLP_DelayReso =FLP_Int+10; | |
FLP_Reverb =FLP_Int+11; | |
FLP_IntStretch =FLP_Int+12; | |
FLP_SSNote =FLP_Int+13; | |
FLP_FineTune =FLP_Int+14; | |
// TEXT EVENTS | |
FLP_Undef =192; //+Size (var length) | |
FLP_Text =FLP_Undef; //+Size (var length)+Text (Null Term. String) | |
FLP_Text_ChanName =FLP_Text; // name for the current channel | |
FLP_Text_PatName =FLP_Text+1; // name for the current pattern | |
FLP_Text_Title =FLP_Text+2; // title of the loop | |
FLP_Text_Comment =FLP_Text+3; // old comments in text format. Not used anymore | |
FLP_Text_SampleFileName =FLP_Text+4; // filename for the sample in the current channel, stored as relative path | |
FLP_Text_URL =FLP_Text+5; | |
FLP_Text_CommentRTF =FLP_Text+6; // new comments in Rich Text format | |
FLP_Version =FLP_Text+7; | |
FLP_Text_PluginName =FLP_Text+9; // plugin file name (without path) | |
FLP_MIDICtrls =FLP_Text+16; | |
FLP_Delay =FLP_Text+17; | |
FLP_TS404Params =FLP_Text+18; | |
FLP_DelayLine =FLP_Text+19; | |
FLP_NewPlugin =FLP_Text+20; | |
FLP_PluginParams =FLP_Text+21; | |
FLP_ChanParams =FLP_Text+23; // block of various channel params (can grow) | |
More details to come, if anyone is interested... If not, let's not spend too | |
much time into this file :) | |
---------------------------------------------------------------- | |
Fruity Wrapper | |
There are two formats, the old and the new :) | |
Both start with a version number (signed long). | |
The old format (version <= 4): | |
- optional "extra block" size (dword) | |
- midi port (dword) | |
- synth saved or not (dword) | |
- plugin type (dword) - vst (0 or 1), dx (1 or 5), vst 3 (7 or 8) | |
- plugin specific block size (dword) | |
- plugin specific block: | |
(vst): name (1 byte length + data) | |
(dx): name (1 byte length + data) + guid (16 bytes) | |
- ... (since you want the name, I'll leave this out) | |
The new format (version > 4) is based on chunks. A chunk is defined by an ID (dword) and a size (int64). After that comes chunk specific data. You can skip chunks based on the size. | |
The plugin name is stored in a chunk with ID = 54 (cidPluginName). I think you can just skip all other chunks. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment