The Flapi protocol is a simple protocol for communication between the Flapi server and Flapi clients.
Version 2.0
Messages are all constructed in the following format:
Section | Request/Response | Meaning |
---|---|---|
Sysex header | Both | Begin MIDI system-exclusive message and identify the message type. |
Message origin | Both | Shows the origin of the message. |
Client ID | Both | Unique identifier for client. |
Continuation byte | Both | Whether this is message contains a continuation (ie is split across multiple messages). |
Message type | Both | Type of message being sent. |
Status code | Both | Status info about response. |
Additional data (optional) | Depends | Data is dependent by message type, but often uses a base-64 encoded string. |
Sysex end byte (0xF7 ) |
Both | End of MIDI system-exclusive message. |
The following values are sent at the start of all messages in order to identify that the message is associated with Flapi. Messages without this header are ignored.
Byte | Meaning |
---|---|
0xF0 |
System exclusive message start |
0x7D |
Non-commercial use byte number (see the MIDI 1.0 core specification) |
0x46 |
'F' |
0x6C |
'l' |
0x61 |
'a' |
0x70 |
'p' |
0x69 |
'i' |
One of the listed values is used.
Value | Meaning |
---|---|
0x00 |
Message originates from client. |
0x01 |
Message originates from server. |
0x02 |
Message is internal to server (client should disregard). |
The ID of the client. This should be randomly generated by the client at the start of a session, and allows multiple clients to connect simultaneously. Clients should ignore responses targeting a different client ID.
This should be a random byte, with a value between 0x01
and 0x7F
.
The ID 0x00
is reserved for universal messages, which target all clients.
Upon receiving a request with a given client ID, the server will give a response with the same client ID.
In Windows, system-exclusive MIDI messages can't have an arbitrary length.
Applications must instead pre-allocate a buffer, which the Windows API writes
into. If the buffer is too small, the message may be discarded. Most
applications only support a buffer size of 1024
bytes.
To account for this, Flapi messages may be split across multiple MIDI messages.
If a message is too long, it is split at 1000
bytes, and the continuation
byte is set to 1
. The remaining message is sent as a separate message, which
only contains the sysex header,
message origin, client ID and continuation
byte. These split messages must be continuous for a single client, and cannot
be interleaved with messages of other types.
The final MIDI message of a Flapi message should have its continuation byte set
to 0
to indicate the end of the message.
The message type byte describes the type of message. The value can be one of the following values. Other values are reserved for future Flapi versions.
Value | Meaning |
---|---|
0x00 |
Client hello |
0x01 |
Client goodbye |
0x02 |
Server goodbye |
0x03 |
Version query |
0x04 |
Register message type |
0x05 |
Exec |
0x06 |
Stdout |
Later sections describe each of these message types.
The final byte is a status code, which is used to indicate the status of a response. In requests, this should be set to zero.
Code | Meaning | Typical additional data |
---|---|---|
0x00 |
Success | Determined by message type. |
0x01 |
An exception was raised during the operation. | Base-64 encoded string of the error message. |
0x02 |
The server failed to process the request | Base-64 encoded string of the error message. Default client raises this message as a FlapiServerError . |
This message is sent when a client wants to connect to the Flapi server. For client hello messages, no additional data is present.
This message type is the only message type to which the server may not respond. In particular, the server will not respond if that client ID is already in use by another active client. If a client does not receive a response, it should generate a new client ID and try again. If the client repeatedly doesn't receive a response, it is likely that FL Studio isn't running (or all device IDs are taken).
If the connection is accepted, the server will give an empty response targeted to the newly connected client. This means the server has registered that client ID session.
This message is sent when a client is exiting, and wants to disconnect from the
Flapi server. As additional data, it contains an int
exit code, represented
as as base-64 string to prevent illegal values for high exit codes.
from base64 import b64encode, b64decode
code = 130
# Encode
encoded = b64encode(str(code).encode())
# Decode
decoded = int(b64decode(data).decode())
assert code == decoded
The server will respond with an identical reply. The default Flapi client
(provided by this library) raises a FlapiClientExit
exception when this
happens, which will (by default) cause the program to exit with the given exit
code.
This message should never be sent by clients. Instead, it is sent by the server
when FL Studio is exiting. The default Flapi client raises a FlapiServerExit
exception when this happens, which (by default) is not handled.
This message is sent by clients to query the version of the Flapi server.
The server will respond with 3 bytes as the message data:
(major, minor, revision)
.
This message is sent by clients to register a new type of message. This message type is handled by the server by setting up a new handler for a message.
This can be used to inject code into the Flapi server, to allow for clients to gain deeper control over FL Studio, and to save on repeated operations.
The default client uses this functionality to register a pickle-eval
message to
evaluate data and encode the results using Pickle.
Note that the server should not share message handlers between clients.
Message handler functions should match the given interface:
def message_handler(
client_id: int,
status_code: int,
msg_data: Optional[bytes],
scope: dict[str, Any],
) -> int | tuple[int, bytes]:
...
Where:
client_id
is the ID of the client that sent the requeststatus_code
is the incoming status code.msg_data
is the full message data, if provided. Otherwise, the message data isNone
. Note that if the data was split across multiple messages, it will be joined before calling this function.scope
is a dictionary containing a local scope that should be used when executing arbitrary code.- Return type is
int
for status code, and optionallybytes
for response data. Note that if the response needs to be split across multiple messages, this will be handled internally by the server.
A base-64 string containing the definition of the message handler function.
A single byte, the message type number that should be used to communicate with this message hander.
This message is sent by clients, and instructs the server to exec
code inside
FL Studio.
A base-64 string containing the code to execute.
No data is given on success. In order to get return data, clients should
register an eval
message type handler.
This message is sent from the server to the client to notify it of stdout
originating from FL Studio's console. This can be sent at any time, and so
clients should be prepared to handle it at any point when receiving a message.
Additional data is a base-64 string of the content written to stdout
.
When sent from the client, it is printed to FL Studio's console, and no response is given.
To help clarify, here is an example demonstrating the basic functionality of the protocol.
Messages are shown as a list of bytes in hexadecimal, with spaces used to show different sections of the message.
Origin | Message | Meaning |
---|---|---|
Client | F07D466C617069 00 01 00 00 |
Client requests to connect using client ID 01 |
Server | F07D466C617069 01 01 00 00 |
Server responds, accepting the connection |
Client | F07D466C617069 00 01 00 03 |
Client requests server version information |
Server | F07D466C617069 01 01 00 03 010000 |
Server responds, saying it is using version 1.0.0 |
Client | F07D466C617069 00 01 00 04 61573...97964413D3D |
Client requests to execute import transport |
Server | F07D466C617069 01 01 00 04 00 |
Server responds, indicating success |
Client | F07D466C617069 00 01 00 04 64484...A304B436B3D |
Client requests to execute transport.start() |
Server | F07D466C617069 01 01 00 04 00 |
Server responds, indicating success |
Client | F07D466C617069 00 01 00 01 6741524C4143343D |
Client disconnects with a code of 0 |
Server | F07D466C617069 01 01 00 01 6741524C4143343D |
Server acknowledges disconnect |