PPM sound data

Find the four tracks, frame triggers and two playback speed fields.

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 speedFrames per second
10.5
21
32
44
56
612
720
830

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 offsetWidthField
0x00u32BGM length, including its four-byte state header; documented maximum 245,408 bytes
0x04u32SE1 length, including its state header; maximum 8,192 bytes
0x08u32SE2 length, including its state header; maximum 8,192 bytes
0x0Cu32SE3 length, including its state header; maximum 8,192 bytes
0x10u8Reversed current frame-speed value
0x11u8Reversed frame-speed value used when BGM was recorded
0x12–0x1F14 bytesNull 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 offsetTypeInterpretation
0x00i16Initial IMA predictor; the wiki tentatively suggests it is normally zero
0x02u8Initial IMA step-table index
0x03u8Unresolved; sometimes 0x02 in BGM tracks
0x04 onwardPacked four-bit samplesMono 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]

Sources

  1. [1] Flipnote Decompilation Project — PPM container and original serialisation
  2. [2] Flipnote Studio Docs — PPM format
  3. [3] Flipnote Studio Docs wiki — PPM format
  4. [4] flipnote.js — PpmParser sample-rate definitions
  5. [5] DSiBrew — Flipnote Files/PPM
  6. [6] SoX — raw IMA format handler
  7. [7] SoX — raw IMA startup wrapper
  8. [8] SoX — ADPCM initialisation and reset

← All documentation