An .s3p is an Akai S1000 or S3000 program. It is not a file layout: it is a recording of a conversation. The sampler had no program file format, only a MIDI System Exclusive dump, so the program was captured message by message and each message written to disk with its length in front of it. Everything strange about the format follows from that.
Two layers, written by two different things. The outer layer is a file: a magic, a count, then a length in front of each message, all big-endian. The inner layer is MIDI, and the multi-byte values inside it are little-endian, because that is the byte order of the processor inside the sampler. A reader that picks one endianness for the whole file gets one of the two layers wrong.
Twelve bytes of header, then the first message with its length in front. There is no index and no total size: the messages are walked, not looked up.
Every message is a complete MIDI System Exclusive frame, exactly as it came down the cable.
A program is one function-07 message followed by one function-09 message per keygroup, in that order. The selector bytes between the model byte and the payload say which program and which keygroup, which is what a sampler needs and a file does not.
A System Exclusive message may not contain a byte with bit 7 set, because that is how MIDI marks the start of a new message. So a data byte cannot travel as itself. Each one is sent as two bytes, low nibble first:
This is the format's one real trap. A reader that skips the step does not crash and does not read zeros: it reads a stream of small numbers that look like plausible parameters, and names that decode to strings of the digit zero. The failure is quiet, which is the worst kind.
A block is 192 bytes once rejoined and the sampler fills the first 150; the rest is padding on the wire.
One per program. It opens with 01, the block identifier, and
carries what applies to the whole program rather than to one key range.
GROUPS is the honest check on the whole format. It sits inside a block, and the number of keygroup messages is a property of the container around it. The two are written by different parts of the sampler and have no reason to agree unless the block layout is being read correctly.
One per key range. It opens with 02 and holds the key span,
the filter, two envelopes, and then four velocity zones.
A zone opens with a 12-character sample name and then its velocity range and offsets. The name is all there is: the sample data lives elsewhere entirely, so a program without its bank is a set of instructions referring to things that are not there. An unused zone is a name of twelve spaces.
The commonest use of two zones is not velocity layering at all. It is stereo: one zone panned hard left and one hard right, over the same velocity range, naming the two halves of a stereo sample.
The sampler has its own character set, and it is not a superset of anything. There are forty-one characters and no lower case.
Read as ASCII the codes are control characters, so a name comes out as
invisible junk rather than as wrong text. Read correctly, code 11 is A: the
set is an index, not an encoding.
Three fields look like they point at something and none of them point at
anything in the file. KGRP1@, NXTKG@ and SBADD are
addresses in the sampler's own memory, captured along with everything else because
the dump is a memory dump.
KGRP1@ reads as 150 in ordinary files, which is exactly
the block size and looks convincingly like an offset to the next block. It is not. Following
it lands on nothing, and the only correct use of these fields is to notice they are there.
The container's lengths and counts are big-endian. The words inside a block, once the nibbles are rejoined, are little-endian. The two layers were written by different machines and neither was asked to agree with the other.