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].
- Developers can switch to signed representation as
instructed in
src/gapcm/gapcm.h
. rp025.pcm
andrp039.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]: File→Import→Raw 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.