PC Engine Hardware : PSG
by Paul Clifford (paul@plasma.demon.co.uk)
Introduction :
The PSG provides 6 sound channels, which can be conveniently paired according to the functionality they provide :
0-1 | - | Waveform playback Frequency modulation (channel 1 muted) |
2-3 | - | Waveform playback only |
4-5 | - | Waveform playback 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 select | |||
$0800 |
/W |
(unused) | |
Channel number (only 0-5 valid) |
This selects the channel used for operations on registers
$0802
to $0807
.
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 | |||
$0801 |
/W |
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 | |||
$0802 |
/W |
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 | |||
$0803 |
/W |
(unused) | |
The upper 4 bits of the 12 bit channel frequency |
See above for explanation.
Channel on/off, DDA on/off, channel volume | |||
$0804 |
/W |
Channel on (1) or off (0) | |
DDA output on (1) or off (0) | |||
(unused) | |||
Overall channel volume |
Depending on the setting of bit 6 and 7, a channel can be in one of four states:
ch on | dda | state |
0 | 0 |
Writes to $0806 will store the value in the
waveform buffer and advance the waveform
write index.
|
0 | 1 |
The waveform write index is reset to 0,
so that further writes to $0806 will start
from the beginning of the waveform buffer.
|
1 | 0 |
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
($0802 and $0803 ).
|
1 | 1 |
Direct D/A mode enabled. Any value written
to $0806 is immediately mixed into the
overall sound output.
|
The overall channel volume can be used to fade in and out
individual channels (see $0805
for altering channel balance).
Channel balance | |||
$0805 |
/W |
Volume to left output | |
Volume to right output |
This allows you to alter the balance of an individual channel.
Channel sound data | |||
$0806 |
/W |
(unused) | |
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 | |||
$0807 |
/W |
Noise on (1) or off (0) | |
(unused) | |||
Noise frequency |
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 (%11111
)
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) |
LFO frequency | |||
$0808 |
/W |
LFO frequency |
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
($0802
and $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
(see $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 | |||
$0809 |
/W |
LFO trigger (0 = LFO on, 1 = LFO off/reset) |
|
(unused) | |||
LFO control |
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 %10111
then