HuXMPlay
XM module player for the PC-Engine/TurboGrafx-16 game machine
Install / Use
/learn @Turboxray/HuXMPlayREADME
HuPCM: PC Engine PCM playback driver
What is it?
==========
This is a six channel PCM driver that will stream PCM data to any of the six channels
via DDA mode. Channels 0 to 3 can be frequency scaled via note, octave, and finestep
parameters.
Channels 4 & 5 are fixed frequency and only playback at roughly 7khz. The PCM format
is PCE DDA native 5bit. Included with the driver is a conversion utility: wav2sixbit.
This is ~not~ a music engine. It's a sound driver. If you want to use it in a music
engine: you'll have to interface with the driver with your own engine.
How does it work?
================
The driver uses a small chunk of ram in order to utilize self modifying code to
provide the fastest possible frequency scaling routines. Since the driver is accessing
the audio regs every Timer interrupt call, a service routine is attached to the vblank
interrupt to provide an updating window. This window uses buffered software registers.
A series of macros have been created to help facilitate accessing the internal software
registers. Any channel can be individually turned off or stopped.
How much CPU resource does it use?
=================================
It all depends on how many channels you intend to use as once. The base overhead with
all channels disabled is ~10440 cycles or ~8.7% cpu resource. Each frequency scaled
channel adds ~5.3% per channel enabled. Each fixed frequency channel adds ~3.0% per
channel enabled.
The interrupt is setup so that the interrupt flag is immediately cleared after entering
the routine, allowing for higher priority interrupts to without delay (VDC).
A secondary mechanism is in place just in case the original Timer interrupt takes too
long and/or is delayed too long by the VDC interrupt; a sample will be delayed in this
case. If a second Timer interrupt is called while the first has finished operations, it
will exit until the busy flag is cleared.
The driver shouldn't use more than 368cycles max per call under normal circumstances,
for all channels in use, but if a sample crosses page, or hits a loop-point, or reaches
an end, a small amount of overhead for particular channel will occur. Probably add
another 3.8% cpu resource if all channels are enabled.
How do I use it?
===============
Here's a breakdown of how to use it:
[] The driver needs to be copied to ram. Basically define a segment in ram with the player
size.
TIMER_PLAYER = the driver address to be copied from.
PCMDriverSize = the size of the driver to be copied.
ram_driver = the ram segment define in BSS, to be copied to.
[] The driver needs to be copied into its BSS define. A set of three macros are provided:
InitializeRamDriver (macro, three args)
Arguments in order: TIMER_PLAYER, ram_driver, PCMDriverSize
InitializeRamDriver_FarSingle (macro, four args)
Arguments in order: TIMER_PLAYER, <address block>, ram_driver, PCMDriverSize
InitializeRamDriver_FarDouble (macro, four args)
Arguments in order: TIMER_PLAYER, <address block>, ram_driver, PCMDriverSize
* Note:
1) Address block is the MPR range to be mapped to: $4000,$6000,$8000, etc.
2) "_FarSingle" assumes the data to be copied does not cross a bank boundary.
3) "_FarDouble" assumes the data to be copied does cross a bank boundary and
maps in two banks to cover this scenario. Warning: don't do this for block
$C000 as it will map out MPR 7 ($E000) and likely crash your code.
[] After the driver is copied, some internal variables/states need to be initialized.
InitialRegs (macro, no arguments)
[] The internal working parts of the driver interface need two ZP bytes to work with.
You can create your own specific set for this or repurpose another already defined
set using this macro. Either way, pass the ZP label to the macro:
AssignInternalPointer <zp label>
[] The driver is automatically created in a disabled state. Once
the internal parts have been initialized, the driver itself
can be activated.
EnablePCMDriver (macro, no arguments)
A list of the following control/interface macros
------------------------------------------------
[] Non channel dependent control/interface macros.
{{ Sets the global pan volume register }}
SetGlobalVol left_vol, right_ol (two arguments)
SetGlobalVol left_right_vol (one argument: byte)
{{ Pauses the whole playback driver }}
PausePCMDriver (no args): pause the driver. No effect on update processor.
{{ Resumes the playback driver }}
ResumePCMDriver (no args): Resume the driver. No effect on update processor.
[] Channel dependent macros: channel <number> prefixes macro name.
{{ Sets the channel pan volume register }}
SetChannelPanVol_0 left_vol, right_ol (two arguments; range 0-15)
SetChannelPanVol_0 left_right_vol (one argument: byte)
{{ Sets the global pan volume register }}
SetChannelVol_0 vol (one argument; range 0-31)
{{ Needed when loading a new "instrument" }}
RestartChannel_0 (no args. Resets phase accumulator state) (*2)
{{ Loads a sample to an individual channel }}
SetSampleChannel_0 (arg; sample label)
Info: Setting a sample to a channel doesn't reset the frequency,
volume, phase accumulator, or any other related attributes.
{{ Stops a channel from outputting to DDA port }}
StopChannel_0 (no args. channel stopped/paused)
Info: Does not affect any of the volume registers.
{{ Stops the internal updating process for specific channel }}
HaltUpdateChan_0 (no args): Halts the internal update processor.
{{ Resumes the internal updating process for specific channel }}
ResumeUpdateChan_0 (no args): Resumes the internal update processor.
{{ Resumes playback of specific channel }}
ResumeChannel_0 (no args. channel resume)
{{ Sets the frequency playback of specific channel }}
SetChannelNote_0 (3 args; note, octave, finestep) (*2)
Info: Note ranges from 0 to 11
Octave ranges from 0 to 7
Finestep ranges from 0 to 31
Special macros:
---------------
These macros are to be used inside a vblank routine, preferably as
close to the interrupt call as possible, because these routines are
what keep the Timer interrupt in sync - so each frame has the same
timing of TIRQ. This also runs the "update processor".
There are two versions of each set. The 7khz version manually calls
the TIRQ/driver routine inside the vblank INT. This gives an even
117 samples per frame. The 6.9kz (6.97khz) does not call inside the
vblank int routine and thus results in 116 samples per frame.
It's *highly* recommended to use the 6.9khz version for timing reasons.
The local versions are for the update processor being located locally
in a fixed bank (something like MPR).
ProcessPCM7_0khz_local
ProcessPCM6_9khz_local
The far versions are for the update processor being located in another
bank that isn't fixed or necessarily mapped at the time of the call.
ProcessPCM7_0khz_far
ProcessPCM6_9khz_far
Note: Make sure the update processor routine does not cross a bank
boundary for the "far" versions. It only maps in one back for
the call.
Files and Package
=================
The driver package consists of the following files:
driver.asm
driver_macros.asm
driver_interface.asm
driver_vars.asm
Break down of files
-------------------
driver.asm:
This contains the driver itself. No other relatable code is
located here. The location of this file in rom is irrelevant;
it's only purpose is to be copied into ram. So in other words
don't bother wasting fixed bank space with this file include.
driver_macros.asm:
As the name implies, only macros are stored in this file.
The specific reason for this, is that PCEAS doesn't accurately
resolve macro defines in the two-pass processing like it does
for equates and other label resolving processing. So it's
important that macros be defined at the very start of the main
source file, before any code. See the example test rom source
code for further visual reference.
driver_interface
