"USING GAPCM"

Using GAPCM

Supplementary guide.

A consumer PCM file is headerless (raw) with unsigned 8-bit samples.[1] A game PCM file is formatted for the in-game PCM driver. The decoder transcodes game into consumer, whereas the encoder does the opposite. PCM samples should be played back at 16276 Hz.

Of the 230 in-game PCM files, only two are stereo[2] with the rest being mono. The decoder will inform you on stereo outputs. Echo, fade, and gain features are not supported; get their parameters with the prober and apply them elsewhere.

You may want to look at the usages first back in the [landing page].

  1. Developers can switch to signed representation as instructed in src/gapcm/gapcm.h.
  2. rp025.pcm and rp039.pcm.

PCM2WAV

Interoperation with audio software.

A consumer PCM file should be headered to be conventionally usable. This can be done in many ways.

FFmpeg

You can pipe the decoder output to [FFmpeg] and save it as a WAVE file:

$ ./gamdec -o - lunar2/rp040.pcm | ffmpeg -hide_banner -f u8 -ar 16276 -ac 1 -i - -c:a pcm_u8 rp040.wav

For stereo, set -ac to 2. Details can be found in [their wiki].

SoX

If FFmpeg is too noisy, then you can do the same with [SoX]:

$ ./gamdec -o - lunar2/rp041.pcm | sox -b 8 -e un -r 16276 -t raw -c 1 - rp041.wav

For stereo, set -c to 2.

Tenacity

You can also import the decoder output files into [Tenacity]: FileImportRaw Data…, pick your file, set the appropriate values, then import away. From here, you can do whatever you want to.

The same goes for [Audacity].

Advanced

Tricks from the Magic Guild.

These can be made convenient by using the prober with command substitution and shell variables.

Endless Playback

You can play a game PCM file virtually endlessly by piping the decoder output to a raw PCM player. One such is FFplay, part of FFmpeg:

$ ./gamdec -l -1 -o - lunar2/rp040.pcm | ffplay -autoexit -hide_banner -f u8 -ar 16276 -ac 1 -

This plays its loop 65535 times. For stereo, set -ac to 2. Use -nodisp to disable the graphical display (and thus controls).

At the time of writing, FFplay has issues playing raw stereo planar PCM at the above rate, while an -ar of 16274 works fine. This does not occur in FFmpeg.

Original Sound

You can simulate the in-game characteristics with this FFmpeg audio filter chain:

aresample=32552:filter_size=0:phase_shift=0,channelmap=0,lowpass=5000:p=1,lowpass=5000:p=1

channelmap is required for lowpass to work. For stereo, set it to 0|1 (mind the pipe). lowpass is repeated for a 12 dB per octave rolloff. Adjust its cutoff to preference.

Note that 32252 is twice of 16276 and is the native rate of the RF5C164.

Fade-in

Some if not all game PCM files fade in in-game. With FFmpeg, you can recreate this with the afade filter:

afade=t=in:d=0.0795

The above duration is actually 16276 / 1024 / 2 seconds or half a sector. This is enough to smoothen the start of the PCM stream, and does not follow the in-game counterparts.

No Lead

To skip the lead, get the loop start position (mark) with the prober and multiply it by 1024 samples. With FFmpeg, you can pass this to the atrim filter:

atrim=start_sample=$(./gaminfo -m lunar2/rp040.pcm)*1024

For stereo, multiply the mark by 512 samples instead.

VGM Rip

The standard sequence of playing twice followed by a 10-second fade-out can be done with FFmpeg. Shell variables are much preferred this time:

$ file=lunar2/rp040.pcm \
      ; output=${file##*/}.wav \
      ; fade=10 \
      ; loop=2 \
      ; rate=16276 \
      ; channels=$(./gaminfo -c ${file}) \
      ; mark=$(( $(./gaminfo -m ${file}) * (1024 / channels) )) \
      ; end=$(( ($(./gaminfo -n ${file}) - mark) * loop + mark )) \
      ; ./gamdec -p 0 -l $(( loop + 6 )) -o - ${file} \
      | ffmpeg -hide_banner -f u8 -ar ${rate} -ac ${channels} -i - -c:a pcm_s16le \
          -af "afade=t=out:start_sample=${end}:d=${fade},atrim=end_sample=${end} + ${rate} * ${fade}" \
          ${output}

Adjust the parameters to preference.

Reproducibility Test

To verify the correctness of GAPCM, you can decode then encode a game PCM file and compare its checksum against that of its clone. This example requires shell support for arrays.

Given a file file:

$ file=lunar2/rp041.pcm

Store its header fields into an array fields:

$ IFS=$'\n ' && for field in $(./gaminfo ${file} | cut -d : -f 2 -s); do fields+=(${field}); done

Then pass them to the encoder within this long chain of commands:

$ {
  ./gamdec -p 0 -l 1 -t -o - ${file} \
      | ./gamenc -c ${fields[0]} -m ${fields[1]} -n ${fields[2]} \
          -ea 0x${fields[3]} 0x${fields[4]} 0x${fields[5]} 0x${fields[6]} 0x${fields[7]} 0x${fields[8]} \
          -ep ${fields[9]} -ed ${fields[10]} \
          -el ${fields[11]} ${fields[12]} ${fields[13]} -p ${fields[14]} \
          -t -o - -
} 2> /dev/null | md5sum -b ${file} -

The checksum for both files should be printed for you to match by eye.

If you want to run this test again, then clear fields first.

$ unset fields[@]

Alternatively, you can also run res/repro-test.sh with a directory of game PCM files. This compares files by their bytes instead of checksums.