VGM replay.

Song format

The VGM data used by the following replay routine is a stripped down version of the VGM v1.61 format.

First the 512 bytes header is removed.  Only the following commands are kepts.

$b9 rr ddHuC6280 command.  rr is the index of the PSG register, dd the byte to be written.
$60TODO
$62End of frame (wait for next vsync).
$66End of data.

As there are only 10 PSG registers ($0800-$0809), the HuC6280 command byte is omitted, and the register index and data are directly output.

So if we read a value less to 10, we know that this is a register index and the next byte is the data to be written to this register.  Otherwise it is either a “end of frame” or “end of data” special command.  The format is then.

rr ddrr is the index of the PSG register.  Its value is between 0 and 9.  dd is the data byte to be written to the register.
$e0Wait for more than 2 frames (up to 17).
$f0End of frame.
$ffEnd of data.

Translated into pseudo-code, we will have :

do
{
    A = vgm_read_byte
    if (A < 10)
    {   // PSG register index.
        data = vgm_read_byte
        psgport[A] = data
    }
} while (A < 10);

if (A == 0xf0)
{   // Nothing else to do for current frame.
}
else if (A == 0xff)
{   // The song is finished.
}

Tool

A small command line utility to strip down a standard VGM file is available in the tools directory (tools/vgmrip.c).

vgmrip -b bb -o hhll input.vgm out

The command line arguments are,

-b, --bankSpecify the first ROM bank of the VGM data.
-o, --orgAddress of the VGM data.
input.vgmVGM (at least 1.61) song.
outBasename for output files.

vgmrip generates an assembly file containing the include directive and the start bank and base address of the VGM data, as long as the bank and address for song looping.  The song data is split into files which size is at most 8192 bytes.

Example

Let’s suppose that the song data was generated using the vgmrip tool with song as the output basename.  The output assembly file will contain the following values,

song_bankThe first ROM bank of the VGM data.
song_base_addressThe base address of the VGM data.
song_loop_bankROM bank of the loop address.
song_loopLoop address.

First the following ZP variables must be inialized,

vgm_baseVGM data logical address.
vgm_bankCurrent VGM data ROM bank.
vgm_ptrCurrent VGM pointer.
vgm_endSentinal for the current VGM pointer.  It is the MSB of ((vgm_mpr+1)<<13).
vgm_loop_bankROM bank of the loop.
vgm_loop_ptrLogical address of the song loop.

Once these variables has been setup, the song is played by calling the vgm_update routine at each VSYNC.

   lda    #low(song_base_address)
   sta    <vgm_base
   sta    <vgm_ptr

   lda    #high(song_base_address)
   sta    <vgm_base+1
   sta    <vgm_ptr+1

   lda    #song_bank
   sta    <vgm_bank

   lda    <vgm_base+1
   clc
   adc    #$20
   sta    <vgm_end

   lda    #song_loop_bank
   sta    <vgm_loop_bank
   stw    #song_loop, <vgm_loop_ptr

; [...]

vsync_hook:
   jsr    vgm_update
   ; [...]
   rts
Summary
VGM replay.The VGM data used by the following replay routine is a stripped down version of the VGM v1.61 format.
Variables
vgm_mprMPR used to map VGM data.
vgm_bankFirst ROM bank of the VGM data.
vgm_endVGM data upper bound.
vgm_loop_bankBank of the VGM loop address.
vgm_loop_ptrVGM loop address.
vgm_waitFrame delay.
Macros
vgm_mapMap VGM data to MPR 6.
vgm_unmapRestore the value of MPR 6.
Functions
vgm_setupSetup VGM player
vgm_next_byteIncrement VGM data pointer.
vgm_updateRead VGM frame data.

Variables

vgm_mpr

vgm_mpr = 6

MPR used to map VGM data.

vgm_bank

vgm_bank .ds 1

First ROM bank of the VGM data.

vgm_end

vgm_end .ds 1

VGM data upper bound.

vgm_loop_bank

vgm_loop_bank .ds 1

Bank of the VGM loop address.

vgm_loop_ptr

vgm_loop_ptr .ds 2

VGM loop address.

vgm_wait

vgm_wait .ds 1

Frame delay.

Macros

vgm_map

Map VGM data to MPR 6.

vgm_unmap

Restore the value of MPR 6.

Functions

vgm_setup

Setup VGM player

Parameters

ASong address MSB
XSong address LSB
_blSong bank
_siLoop offset
_bhLoop bank

vgm_next_byte

Increment VGM data pointer.

If the VGM data pointer crosses the MPR boundary, the address is reseted and the next bank is mapped.

This routine may be called after each VSYNC.

vgm_update

Read VGM frame data.

vgm_mpr = 6
MPR used to map VGM data.
vgm_bank .ds 1
First ROM bank of the VGM data.
vgm_end .ds 1
VGM data upper bound.
vgm_loop_bank .ds 1
Bank of the VGM loop address.
vgm_loop_ptr .ds 2
VGM loop address.
vgm_wait .ds 1
Frame delay.
Close