PPM audio follows the encoded frame payload and the per-frame sound-effect flags. A 0x20-byte audio header starts with four 32-bit track lengths in this order: BGM, SE1, SE2, SE3. The next two bytes carry animation speed and BGM recording speed. The track bytes then follow in the same order with no separate section index. [1][2]
Compute the audio start
The sound flags occupy one byte per frame, rounded to a four-byte boundary by the original serialiser. The audio header begins after those flags. Compute the boundary from the validated animation extent and frame count; never search for a guessed audio signature in arbitrary frame bytes. A nonzero declared track length must fit within the actual file before decoding. [1][2]
Track encoding
The published PPM description identifies mono IMA ADPCM tracks. Each starts with a four-byte initial decoder state, followed by packed four-bit samples in the format's nibble order. A decoder must reset its predictor and step index for each track. Test silence and high-amplitude transitions as well as ordinary speech, because wrong nibble order may sound vaguely recognisable while distorting pitch and noise. [2]
Scheduling and speed
The three effect triggers are separate bits in each frame's flag byte. BGM is continuous. If current animation speed differs from the speed used when BGM was recorded, the playback rate needs adjustment; applying that same adjustment to the three effect tracks is incorrect. Seeking and looping should reset frame and audio positions together. [1][2]
Original version difference
V2 rounds emitted audio lengths to even bytes; Japanese V1 does not. The original serialiser appends existing backing-buffer bytes for padding rather than explicitly writing zeroes. This matters to byte comparisons and malformed-file analysis, even though a normal player should respect the declared safe extents. [1]
Track sizes and playback rates
The four 32-bit track lengths appear as BGM, SE1, SE2 and SE3. The wiki records a BGM maximum of 245,408 bytes and an 8,192-byte maximum for each effect. The next two header bytes are playback speed and the speed used when recording the BGM; fourteen unused bytes complete the 32-byte header. Each effect flag byte has bit 0 for SE1, bit 1 for SE2 and bit 2 for SE3. [3]
| Displayed speed | Frames per second |
|---|---|
| 1 | 0.5 |
| 2 | 1 |
| 3 | 2 |
| 4 | 4 |
| 5 | 6 |
| 6 | 12 |
| 7 | 20 |
| 8 | 30 |
The stored speed field is reversed: subtract its value from eight to obtain the displayed speed index before consulting the table. A track begins with a signed 16-bit ADPCM predictor, an 8-bit step index and one additional byte whose use is not established. Packed IMA ADPCM samples follow at a nominal 8,180 Hz, with the nibble ordering documented by the wiki. Its observed output path up-samples to about 32,720 Hz. If the BGM recording and current playback speeds differ, scale BGM sample rate by current FPS divided by recording FPS; do not apply that correction to the effect tracks. [3]
All sound-header byte positions
If N is the actual frame count and A the animation length, sound-effect flag byte i is at 0x06A0 + A + i. The sound header starts at align_up(0x06A0 + A + N, 4). Its offsets below are relative to that computed header. [2]
| Relative offset | Width | Field |
|---|---|---|
| 0x00 | u32 | BGM length, including its four-byte state header; documented maximum 245,408 bytes |
| 0x04 | u32 | SE1 length, including its state header; maximum 8,192 bytes |
| 0x08 | u32 | SE2 length, including its state header; maximum 8,192 bytes |
| 0x0C | u32 | SE3 length, including its state header; maximum 8,192 bytes |
| 0x10 | u8 | Reversed current frame-speed value |
| 0x11 | u8 | Reversed frame-speed value used when BGM was recorded |
| 0x12–0x1F | 14 bytes | Null padding in the documented layout |
The BGM limit is described as about sixty seconds and each effect limit as about two seconds. A zero-length track has no state header to read; before decoding a non-empty track, require enough bytes for its initial state as well as its advertised extent. [2]
Initial state, nibble order and undecoded observations
| Track-relative offset | Type | Interpretation |
|---|---|---|
| 0x00 | i16 | Initial IMA predictor; the wiki tentatively suggests it is normally zero |
| 0x02 | u8 | Initial IMA step-table index |
| 0x03 | u8 | Unresolved; sometimes 0x02 in BGM tracks |
| 0x04 onward | Packed four-bit samples | Mono IMA ADPCM; earlier sample uses the low nibble |
The wiki suggests that a BGM state byte of 0x02 might relate to effects having been mixed into the music. That interpretation has not been confirmed. Preserve the byte without relying on it as a flag. Nominal 8,180 Hz four-bit audio uses 4,090 payload bytes per second; the four-byte state header is included separately in the track length. [2]
BGM adjustment and output resampling are separate
First convert both reversed speed bytes to displayed speed indices with 8 - stored_value, then obtain their FPS values from the speed table. The BGM playback rate is 8180 × current_FPS / recording_FPS. A recording made at displayed speed 6 (12 FPS), played at speed 7 (20 FPS), therefore uses approximately 13,633.33 Hz. The three effects retain their own base rate; changing the animation speed does not apply the BGM ratio to them. [2]
The wiki describes nearest-neighbour output upsampling to about 32,720 Hz. flipnote.js uses a 32,768 Hz default and notes the approximate alternative in its source. These are separate values: the reported approximate hardware rate and the software mixer's selected output rate. [2] [4]
Reserved sound-flag bits and boundary padding
Only bits 0, 1 and 2 of each per-frame sound-effect byte select effects. DSiBrew describes the upper five bits, masks 0x08, 0x10, 0x20, 0x40 and 0x80, as padding. Those bits do not select additional tracks. After the frame-flag array, zero to three additional bytes align the actual sound header to a four-byte boundary. The article describes these alignment bytes as zero-filled. They are separate from the reserved bits inside each flag byte and the sound header's fourteen-byte padding field. [5]
The old SoX example is a raw-audio reference
The historical page gives sox -t ima -N [input] [output] as an example for decoding raw audio. A complete PPM also contains metadata, images, effect flags and four tracks with their own lengths, so it cannot be passed directly to that command. The page rounds the sound rate to “8 kHz”; use the documented 8,180 Hz base rate and apply the BGM speed adjustment. [5]
SoX's raw IMA handler also starts its predictor and step index at zero. It does not read the initial state from a PPM track's four-byte header, and stripping the header loses that state when it is nonzero. Decode each track with a PPM-aware implementation that applies its stored predictor, step index, nibble order and playback rate, then pass PCM or WAV to general audio tools. [6] [7] [8]
