PC Engine Hardware : PSG
by Paul Clifford (firstname.lastname@example.org)
The PSG provides 6 sound channels, which can be conveniently paired according to the functionality they provide :
Frequency modulation (channel 1 muted)
|2-3||-||Waveform playback only|
White noise generation
Waveform playback is the most common and allows a 32-byte, 5-bit unsigned linear sample to be played back at selected frequencies. Frequency modulation takes this one step further, allowing the playback frequency to be dynamically adjusted according to a specified pattern. White noise is used to simulate percussion instruments and effects such as explosions, by means of a pseudo random square wave.
Alternatively, each channel can be individually switched to "Direct D/A" mode whereby the programmer can send data directly to the sound mixer, allowing more complex sound patterns to be generated, such as speech. Inevitably this requires more programming effort and CPU time.
PSG registers :
|Channel number (only 0-5 valid)|
This selects the channel used for operations on registers
Be aware that the noise register is only
valid for channels 4 and 5, and that the LFO registers
will affect channels 0 and 1 only, irrespective of what
values you set here.
|Global sound balance|
||Volume from left output|
|Volume from right output|
This alters the overall sound volume once all the channels have been mixed together.
|Fine frequency adjust|
||The lower 8 bits of the 12 bit channel frequency|
The channel frequency is a 12 bit value used as a divider into the 3.58MHz clock. The exact use depends on what type of sound the channel is being used for at the time. For waveform output, a copy of this value is, in effect, decremented 3,580,000 times a second until zero is reached. When this happens the PSG advances an internal pointer into the channel's waveform buffer by one. Since the buffer is 32 bytes long, this frequency value can be converted into more familiar units as follows :
3580000 frequency (Hz) = --------------------- 32 x (12 bit value)
Hence, in the opposite direction we would have :
3580000 12 bit value = --------------------- 32 x frequency (Hz)
|Rough frequency adjust|
|The upper 4 bits of the 12 bit channel frequency|
See above for explanation.
|Channel on/off, DDA on/off, channel volume|
||Channel on (1) or off (0)|
|DDA output on (1) or off (0)|
|Overall channel volume|
Depending on the setting of bit 6 and 7, a channel can be in one of four states:
The waveform write index is reset to 0,
so that further writes to
Waveform output is enabled. The channel
output is generated from the waveform buffer,
advancing an internal read index at the rate
specified by the channel frequency registers
Direct D/A mode enabled. Any value written
The overall channel volume can be used to fade in and out
individual channels (see
$0805 for altering channel balance).
||Volume to left output|
|Volume to right output|
This allows you to alter the balance of an individual channel.
|Channel sound data|
|5 bit unsigned linear sample data|
A write to this location will have different effects depending
on the settings of bit 6 and 7 of register
$0804. When DDA
is enabled, data written here is mixed in directly with the
outputs from the other channels (you also need to turn the
channel on if you actually want to hear it though!).
This is useful for handling things like speech which are
not easy (possible?) to do with the waveform buffer.
When the channel is turned off and DDA disabled, data written here is copied into the channel's waveform buffer and the internal write index incremented by one, wrapping round at 32 to 0. For example, to make a sine wave you could write the following hex values into the waveform buffer (shamelessly plundered from the PSG patent) :
0f, 12, 15, 17, 19, 1b, 1d, 1e, 1e, 1e, 1d, 1b, 19, 17, 15, 12, 0f, 0c, 09, 07, 05, 03, 01, 00, 00, 00, 01, 03, 05, 07, 09, 0c
|Noise enable, noise frequency|
||Noise on (1) or off (0)|
This register is only effective for channels 4 and 5. The output of white noise is enabled by setting bit 7, at which point the PSG will stop reading data from the channel's waveform buffer and begin generating a random rectangular waveform with a frequency defined by the lower 5 bits of the register.
After a little experimenting I found that it is easiest to imagine you are dealing with a 64-byte waveform buffer divided into two parts. The first half randomly contains either all 0 or all 31, and likewise with the second half, so that you have either a complete or a partial cycle of a square wave, ie one of the following :
----+ +---- --------- | | +---- ----+ --------- 0 32 64 0 32 64 0 32 64 0 32 64
(A new "waveform" is automatically generated each time the read index wraps back around to zero).
Contrarily to the channel frequency, the noise frequency field
is bit inverted, so it is necessary to XOR it by 31 (
for correct results.
The formula for the actual noise frequency is therefore :
3580000 frequency (Hz) = --------------------------- 64 x (5 bit value XOR 31)
Hence, in the opposite direction we would have :
3580000 5 bit value = --------------------- XOR 31 64 x frequency (Hz)
When the LFO is active (see
$0809), the channel 1 waveform
buffer is assumed to contain frequency modulation data rather
than samples (and hence channel 1 is automatically muted).
The frequency at which this buffer is read from is defined
by multiplying the LFO frequency by the 12 bit frequency
$0803) from channel 1.
This leads to the following formula :
3580000 freq (Hz) = --------------------------------- 32 x (12-bit freq) x (LFO freq)
The values in channel 1's waveform buffer are treated as
5-bit signed integers which are shifted left as necessary
$0809) and added to channel 0's frequency. This result
then becomes the new frequency for channel 0 until a new
byte of FM data is read, at which point the frequency
is recalculated again.
|LFO trigger, LFO control|
(0 = LFO on, 1 = LFO off/reset)
Bit 7 controls whether the low frequency oscillator (LFO) is active or not. When set it causes the LFO to switch off and resets the channel 1 waveform read position to 0. When clear, the LFO is activated, causing the data in channel 1's waveform buffer to be used to frequency modulate the output from channel 0. The process by which this is achieved depends on the value in "LFO control" (the lower two bits) :
|00||-||No frequency modulation is performed, so the output from channel 0 is unchanged.|
|01||-||The FM data is added directly to channel 0's frequency.|
|10||-||The FM data is shifted left by four places then added to the frequency.|
|11||-||The FM data is shifted left by eight places then added to the frequency.|
For example, if "LFO control" was set to 2 and the value read
from the channel 1 waveform buffer was binary