How the SysEx protocol works
Overview
The SysEx File System API provides remote file system access to the Deluge device through MIDI System Exclusive (SysEx) messages. The protocol uses JSON-encoded commands sent over MIDI SysEx to perform file operations like reading, writing, directory listing, and file management.
Protocol Fundamentals
Key Specifications
- Transport: MIDI System Exclusive (SysEx) messages
- Data Format: JSON
- Session Management: Session-based with message sequence numbers
- File Handle System: File descriptor-based operations
- Maximum Open Files: 4 concurrent files
- Block Buffer Size: 1024 bytes maximum per operation
Message Format
Messages consist of a SysEx “packet” which has a header, a sequence number, a JSON body, an optional 0 separator and packed binary part.
All messages follow the standard SysEx format:
F0 00 21 7B 01 <command> <sequence_number> <json_data> F7
F0
: SysEx start00 21 7B
: Synthstrom Audible (Deluge manufacturer ID)01
: Product ID<command>
: Command type (Json=0x06, JsonReply=0x07)<sequence_number>
: Message sequence number for session tracking (1-127)<json_data>
: JSON payloadF7
: SysEx end
Data Encoding
Packed binary is used to transfer file content to or from the Deluge. So far only the read and write requests use it.
The packed binary part, if present, is encoded in 7 byte to 8 byte format. In 7 to 8 format, we send the information about a 7 byte-long block as 8 bytes, with the first byte in the group holding the sign bits of the following 7 bytes. Those following 7 bytes have their high-order bit masked-off, resulting in valid SysEx data.
Binary data in read/write operations uses 7-bit MIDI-safe encoding:
- Every 7 bytes of data are encoded as 8 bytes
- The first byte contains the high bits of the following 7 bytes
- This ensures all bytes are ≤ 0x7F (MIDI-safe)
JSON Structure
The JSON body is always a JSON object with one key/value pair. The key is the request code and the value contains the parameters for the request. We did things this way so that the order-of-appearance of keys does not matter.
An example request:
{ "open": { "path": "/SONGS/SONG004.XML", "write": 1 } }
And a response:
{ "^open": { "fid": 2, "size": 0, "err": 0 } }
The caret in front of the response is used as a convention, but is not required. The way responses are matched up with requests is by using a sequence number that wraps from 1 to 127. The web side keeps track of sequence numbers and stores a callback function for each outgoing message to handle the reply.
The Deluge can initiate a message exchange by sending a request to the web-side using a sequence number of 0. This is not actually being used yet.
Implementation Philosophy: Maximize processing on the web side, minimize state on the Deluge. The device maintains only a small table of open file handles and directory positions.
Block-based Processing: Operations transfer data in small blocks. The client tracks offsets and reassembles complete files or directory listings.
Session Management
Assign Session
Assigns a session ID for client tracking and message correlation.
Request:
{ "session": { "tag": "string" }}
Parameters:
tag
(string, optional): Client-specified tag for session identification
Response:
{ "^session": { "sid": 1, "tag": "string", "midBase": 8, "midMin": 9, "midMax": 15 }}
Response Fields:
sid
: Session ID (1-15)tag
: Echo of request tagmidBase
: Base message ID for this sessionmidMin
: Minimum message ID for this sessionmidMax
: Maximum message ID for this session
File Operations
Basic file I/O operations using file descriptors (max 4 concurrent files)
Open File
Opens a file for reading or writing and returns a file descriptor.
Request:
{ "open": { "path": "/path/to/file.txt", "write": 0, "date": 0, "time": 0 }}
Parameters:
path
(string, required): Full path to the filewrite
(integer, required): 0=read, 1=write (create new), 2=write (append)date
(integer, optional): Date for directory creation (FAT format)time
(integer, optional): Time for directory creation (FAT format)
Response:
{ "^open": { "fid": 1, "size": 1024, "err": 0 }}
Response Fields:
fid
: File descriptor ID (0 if error)size
: File size in byteserr
: Error code (FRESULT)
Close File
Closes an open file descriptor.
Request:
{ "close": { "fid": 1 }}
Parameters:
fid
(integer, required): File descriptor ID to close
Response:
{ "^close": { "fid": 1, "err": 0 }}
Response Fields:
fid
: Echo of file descriptor IDerr
: Error code (FRESULT)
Read Block
Reads a block of data from an open file.
Request:
{ "read": { "fid": 1, "addr": 0, "size": 1024 }}
Parameters:
fid
(integer, required): File descriptor IDaddr
(integer, required): File offset to read fromsize
(integer, optional): Number of bytes to read (max 1024)
Response:
{ "^read": { "fid": 1, "addr": 0, "size": 1024, "err": 0 }}
Response Fields:
fid
: Echo of file descriptor IDaddr
: Echo of file offsetsize
: Actual bytes readerr
: Error code (FRESULT)
Note: Binary data follows the JSON header, encoded using 7-bit MIDI-safe encoding.
Write Block
Writes a block of data to an open file.
Request:
{ "write": { "fid": 1, "addr": 0, "size": 1024 }}
Parameters:
fid
(integer, required): File descriptor IDaddr
(integer, required): File offset to write tosize
(integer, required): Number of bytes to write (max 1024)
Note: Binary data follows the JSON header, encoded using 7-bit MIDI-safe encoding.
Response:
{ "^write": { "fid": 1, "addr": 0, "size": 1024, "err": 0 }}
Response Fields:
fid
: Echo of file descriptor IDaddr
: Echo of file offsetsize
: Actual bytes writtenerr
: Error code (FRESULT)
Directory Operations
Browse and manage directories (max 25 entries per request)
Get Directory Entries
Lists files and directories in a specified path.
Request:
{ "dir": { "path": "/path/to/directory", "offset": 0, "lines": 20 }}
Parameters:
path
(string, optional): Directory path (default: ”/”)offset
(integer, optional): Starting entry offset (default: 0)lines
(integer, optional): Number of entries to return (max 25, default: 20)
Response:
{ "^dir": { "list": [ { "name": "filename.txt", "size": 1024, "date": 21156, "time": 34816, "attr": 32 } ], "err": 0 }}
Response Fields:
list
: Array of directory entriesname
: File/directory namesize
: File size in bytesdate
: Last modified date (FAT format)time
: Last modified time (FAT format)attr
: File attributes (0x10=dir, 0x20=archive, 0x01=readonly, 0x02=hidden, 0x04=system)
err
: Error code (FRESULT)
Create Directory
Creates a new directory.
Request:
{ "mkdir": { "path": "/path/to/new/directory", "date": 0, "time": 0 }}
Parameters:
path
(string, required): Directory path to createdate
(integer, optional): Creation date (FAT format)time
(integer, optional): Creation time (FAT format)
Response:
{ "^mkdir": { "path": "/path/to/new/directory", "err": 0 }}
Response Fields:
path
: Echo of directory patherr
: Error code (FRESULT)
File Management
Advanced file operations: delete, rename, copy, move, timestamps
Delete File
Deletes a file or directory.
Request:
{ "delete": { "path": "/path/to/file.txt" }}
Parameters:
path
(string, required): Path to file/directory to delete
Response:
{ "^delete": { "err": 0 }}
Response Fields:
err
: Error code (FRESULT)
Rename/Move
Renames or moves a file/directory.
Request:
{ "rename": { "from": "/old/path/file.txt", "to": "/new/path/file.txt" }}
Parameters:
from
(string, required): Source pathto
(string, required): Destination path
Response:
{ "^rename": { "from": "/old/path/file.txt", "to": "/new/path/file.txt", "err": 0 }}
Response Fields:
from
: Echo of source pathto
: Echo of destination patherr
: Error code (FRESULT)
Copy File
Copies a file to a new location.
Request:
{ "copy": { "from": "/source/file.txt", "to": "/destination/file.txt", "date": 0, "time": 0 }}
Parameters:
from
(string, required): Source file pathto
(string, required): Destination file pathdate
(integer, optional): Timestamp for destination file (FAT format)time
(integer, optional): Timestamp for destination file (FAT format)
Response:
{ "^copy": { "from": "/source/file.txt", "to": "/destination/file.txt", "err": 0 }}
Response Fields:
from
: Echo of source pathto
: Echo of destination patherr
: Error code (FRESULT)
Move File
Moves a file to a new location (rename or copy+delete for cross-filesystem moves).
Request:
{ "move": { "from": "/source/file.txt", "to": "/destination/file.txt", "date": 0, "time": 0 }}
Parameters:
from
(string, required): Source file pathto
(string, required): Destination file pathdate
(integer, optional): Timestamp for destination file (FAT format)time
(integer, optional): Timestamp for destination file (FAT format)
Response:
{ "^move": { "from": "/source/file.txt", "to": "/destination/file.txt", "err": 0 }}
Response Fields:
from
: Echo of source pathto
: Echo of destination patherr
: Error code (FRESULT)
Update Timestamp
Updates the timestamp of a file or directory.
Request:
{ "utime": { "path": "/path/to/file.txt", "date": 21156, "time": 34816 }}
Parameters:
path
(string, required): Path to file/directorydate
(integer, required): New date (FAT format)time
(integer, required): New time (FAT format)
Response:
{ "^utime": { "err": 0 }}
Response Fields:
err
: Error code (FRESULT)
Utility Operations
Simple connectivity and status operations
Ping
Simple connectivity test.
Request:
{ "ping": {}}
Response:
{ "^ping": {}}
Implementation Details
Technical specifications, error codes, and system constraints
Operations Summary
So far, the file protocol has:
- session - Assign session ID
- open - Open file for reading/writing
- close - Close file descriptor
- dir - Get directory entries
- read - Read file block
- write - Write file block
- delete - Delete file/directory
- mkdir - Create directory
- rename - Rename/move file/directory
- copy - Copy file
- move - Move file (with fallback to copy+delete)
- utime - Update file timestamp
- ping - Connectivity test
High-Level Routines
The file routines include:
- readFile - Complete file reading
- writeFile - Complete file writing
- recursiveDelete - Delete directory tree
- downloadOneItem - Download single file/directory
- recursiveDownload - Download directory tree
- getDirInfo - Get directory information
The file routines work with Uint8Array objects. The assumption is that we have an enormous amount of free memory on the web side. Downloading uses the web File System API and can handle nested directories.
Error Codes
Error codes follow the FatFs FRESULT enumeration:
0
: FR_OK - Success1
: FR_DISK_ERR - Disk error2
: FR_INT_ERR - Internal error3
: FR_NOT_READY - Drive not ready4
: FR_NO_FILE - File not found5
: FR_NO_PATH - Path not found6
: FR_INVALID_NAME - Invalid filename7
: FR_DENIED - Access denied8
: FR_EXIST - File exists9
: FR_INVALID_OBJECT - Invalid object10
: FR_WRITE_PROTECTED - Write protected11
: FR_INVALID_DRIVE - Invalid drive12
: FR_NOT_ENABLED - Volume not enabled13
: FR_NO_FILESYSTEM - No filesystem14
: FR_MKFS_ABORTED - Format aborted15
: FR_TIMEOUT - Timeout16
: FR_LOCKED - File locked17
: FR_NOT_ENOUGH_CORE - Out of memory18
: FR_TOO_MANY_OPEN_FILES - Too many open files
System Limitations
- Maximum 4 concurrent open files
- Maximum 1024 bytes per read/write operation
- Maximum 25 directory entries per request
- Session IDs limited to 1-15
- Message sequence numbers limited to 1-7 per session