Sound in Basic I - Programmable Sound Generator PSG
At the time of writing this post, it is planned for Commander X16 has three main ways to make sound:
- Programmable Sounds Generator (PSG) in VERA
- Pulse-Code Modulation (PCM) playback system in VERA
- Yamaha YM2151 sound chip
PCM playback is not very useful for BASIC programmers but both PSG and YM2151 can be used effectively in BASIC programs. This post is about PSG.
! Important Update !
Before we go too deep into this post it is important to emphasize that ROM version R42 introduced dedicated BASIC commands to deal with sound. Specifically for using PSG sound system we have following "new" commands:
PSGCHORD - Starts or stops simultaneous notes on VERA PSG
PSGFREQ - Plays a frequency in Hz on VERA PSG
PSGINIT - Stops sound and reinitializes VERA PSG
PSGNOTE - Plays a musical note on VERA PSG
PSGPAN - Sets stereo panning on VERA PSG
PSGPLAY - Plays a series of notes on VERA PSG
PSGVOL - Sets voice volume on VERA PSG
PSGWAV - Sets waveform on VERA PSG
The document below was written before those commands existed. It might still be useful to read it for better understanding on how PSG works but it is probably easier to use new commands instead of VPOKE-ing directly into VERA PSG Registers. Of course teh below examples still work just fine.
PSG Capabilities
Let’s summarize capabilities of Programmable Sound Generator
- 16 independent channels or voices
- Each voice can have any of four available waveforms
- Each voice can be played in stereo with on/off control over left and right side
- Each voice has independent volume control from 0 - silent to 63 - Max volume
- 16 bit frequency value
We can also list some of the features that PSG can’t do, at least not directly in hardware.
- It does not support ADSR envelopes
- It doesn’t have timers to turn sound off, that has to be done programmatically
Registers in VRAM
VERA is responsible for PSG playback so we have to send data to VRAM where VERA can read it from. We already know that command to write to VRAM directly is VPOKE so where do we write?
Space reserved for PSG is from $1F9C0 - $1F9FF so total of 64 bytes. With 16 voices that means each voice has four bytes or registers to configure it. Or in other words following are memory locations per voice:
$1F9C4 - $1F9C7 Voice 1
$1F9C8 - $1F9CB Voice 2
...
Now let’s look at what those four registers look like:
Register | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
---|---|---|---|---|---|---|---|---|
0 | Frequency Bits 0-7 | |||||||
1 | Frequency Bits 8-15 | |||||||
2 | Right | Left | Volume | |||||
3 | Waveform | Pulse width |
I think it is becoming quite clear that programming sound using PSG is not very difficult. Let’s describe each of the registers in more detail. I think it makes most sense to go from back to front.
Register 3 - Waveform
We have two bits (Bit 6 and 7) to choose between four available Waveforms. The values are:
01 - Sawtooth
10 - Triangle
11 - Noise
Or to represent them graphically:
There are plenty of resources online that describe the characteristics of these waveforms and that is far beyond the scope of this post. However it is important to highlight a few things.
- PSG does not generate Sine waves, however Triangle is very similar and produces very warm sound and for most cases can be used instead of sine wave.
- Noise is not completely random and also changes with frequency so we can have different pitch noise which is very handy.
- Square wave is the sound we most commonly associate with the 8bit home computer era, however Pulse is not the same as square and we will discuss differences in the next paragraph.
Six bits of this register (Bits 0 to 5) are used to determine the width of the Pulse wave. Again let’s use graphics to illustrate how it works.
As we see all these three examples have the same frequency and therefore the same pitch but the width of hills and valleys is not which causes it to sound differently.
With 6 bits values can be between 0 and 63 and value 0 has a very narrow hill and therefore sounds very “thin”. Maximum valley of 63 will cause that hills and valleys are of the same width and the wave of that shape is what we call “square wave” and is much fuller and stronger.
Register 2 - Volume
Very simple and straightforward register. Bit 6 and 7 determine which side of stereo output the particular voice will sound. As expected 0 means off and 1 means on but left and right are switched if we assume that bit 7 is leftmost:
10 - sound on the right side only
01 - sound on the left side only
11 - sound on both sides
The remaining 6 bits are used to set volume. 6 bits have range 0-63 where 0 means sound does not generated and 63 is the loudest. Note that we can’t define volume per side so both left and right side are played at the same volume or not at all.
Registers 0 and 1 - Frequency
Two bytes are available to determine the frequency and there is a formula that can be used to calculate the values in order to hit the exact frequency. Here is copy from the official manual
output_frequency = sample_rate / (2^17) * frequency_word
To make your life a bit easier I calculated most of the music notes in hearable range:
Note ID | Note | Frequency | Hi | Lo | Note ID | Note | Frequency | Hi | Lo | Note ID | Note | Frequency | Hi | Lo | ||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | C0 | 16.35 | 0 | 44 | 37 | C3 | 130.81 | 1 | 95 | 73 | C6 | 1046.50 | 10 | 249 | ||
2 | C#0/Db0 | 17.32 | 0 | 46 | 38 | C#3/Db3 | 138.59 | 1 | 116 | 74 | C#6/Db6 | 1108.73 | 11 | 160 | ||
3 | D0 | 18.35 | 0 | 49 | 39 | D3 | 146.83 | 1 | 138 | 75 | D6 | 1174.66 | 12 | 81 | ||
4 | D#0/Eb0 | 19.45 | 0 | 52 | 40 | D#3/Eb3 | 155.56 | 1 | 162 | 76 | D#6/Eb6 | 1244.51 | 13 | 13 | ||
5 | E0 | 20.60 | 0 | 55 | 41 | E3 | 164.81 | 1 | 186 | 77 | E6 | 1318.51 | 13 | 211 | ||
6 | F0 | 21.83 | 0 | 59 | 42 | F3 | 174.61 | 1 | 213 | 78 | F6 | 1396.91 | 14 | 166 | ||
7 | F#0/Gb0 | 23.12 | 0 | 62 | 43 | F#3/Gb3 | 185.00 | 1 | 241 | 79 | F#6/Gb6 | 1479.98 | 15 | 133 | ||
8 | G0 | 24.50 | 0 | 66 | 44 | G3 | 196.00 | 2 | 14 | 80 | G6 | 1567.98 | 16 | 113 | ||
9 | G#0/Ab0 | 25.96 | 0 | 70 | 45 | G#3/Ab3 | 207.65 | 2 | 45 | 81 | G#6/Ab6 | 1661.22 | 17 | 107 | ||
10 | A0 | 27.50 | 0 | 74 | 46 | A3 | 220.00 | 2 | 79 | 82 | A6 | 1760.00 | 18 | 116 | ||
11 | A#0/Bb0 | 29.14 | 0 | 78 | 47 | A#3/Bb3 | 233.08 | 2 | 114 | 83 | A#6/Bb6 | 1864.66 | 19 | 141 | ||
12 | B0 | 30.87 | 0 | 83 | 48 | B3 | 246.94 | 2 | 151 | 84 | B6 | 1975.53 | 20 | 183 | ||
13 | C1 | 32.70 | 0 | 88 | 49 | C4 | 261.63 | 2 | 190 | 85 | C7 | 2093.00 | 21 | 242 | ||
14 | C#1/Db1 | 34.65 | 0 | 93 | 50 | C#4/Db4 | 277.18 | 2 | 232 | 86 | C#7/Db7 | 2217.46 | 23 | 64 | ||
15 | D1 | 36.71 | 0 | 99 | 51 | D4 | 293.66 | 3 | 20 | 87 | D7 | 2349.32 | 24 | 162 | ||
16 | D#1/Eb1 | 38.89 | 0 | 104 | 52 | D#4/Eb4 | 311.13 | 3 | 67 | 88 | D#7/Eb7 | 2489.02 | 26 | 25 | ||
17 | E1 | 41.20 | 0 | 111 | 53 | E4 | 329.63 | 3 | 117 | 89 | E7 | 2637.02 | 27 | 167 | ||
18 | F1 | 43.65 | 0 | 117 | 54 | F4 | 349.23 | 3 | 169 | 90 | F7 | 2793.83 | 29 | 76 | ||
19 | F#1/Gb1 | 46.25 | 0 | 124 | 55 | F#4/Gb4 | 369.99 | 3 | 225 | 91 | F#7/Gb7 | 2959.96 | 31 | 10 | ||
20 | G1 | 49.00 | 0 | 132 | 56 | G4 | 392.00 | 4 | 28 | 92 | G7 | 3135.96 | 32 | 226 | ||
21 | G#1/Ab1 | 51.91 | 0 | 139 | 57 | G#4/Ab4 | 415.30 | 4 | 91 | 93 | G#7/Ab7 | 3322.44 | 34 | 215 | ||
22 | A1 | 55.00 | 0 | 148 | 58 | A4 | 440.00 | 4 | 157 | 94 | A7 | 3520.00 | 36 | 233 | ||
23 | A#1/Bb1 | 58.27 | 0 | 156 | 59 | A#4/Bb4 | 466.16 | 4 | 227 | 95 | A#7/Bb7 | 3729.31 | 39 | 27 | ||
24 | B1 | 61.74 | 0 | 166 | 60 | B4 | 493.88 | 5 | 46 | 96 | B7 | 3951.07 | 41 | 110 | ||
25 | C2 | 65.41 | 0 | 176 | 61 | C5 | 523.25 | 5 | 125 | 97 | C8 | 4186.01 | 43 | 229 | ||
26 | C#2/Db2 | 69.30 | 0 | 186 | 62 | C#5/Db5 | 554.37 | 5 | 208 | 98 | C#8/Db8 | 4434.92 | 46 | 129 | ||
27 | D2 | 73.42 | 0 | 197 | 63 | D5 | 587.33 | 6 | 41 | 99 | D8 | 4698.63 | 49 | 69 | ||
28 | D#2/Eb2 | 77.78 | 0 | 209 | 64 | D#5/Eb5 | 622.25 | 6 | 134 | 100 | D#8/Eb8 | 4978.03 | 52 | 51 | ||
29 | E2 | 82.41 | 0 | 221 | 65 | E5 | 659.25 | 6 | 234 | 101 | E8 | 5274.04 | 55 | 77 | ||
30 | F2 | 87.31 | 0 | 234 | 66 | F5 | 698.46 | 7 | 83 | 102 | F8 | 5587.65 | 58 | 151 | ||
31 | F#2/Gb2 | 92.50 | 0 | 248 | 67 | F#5/Gb5 | 739.99 | 7 | 194 | 103 | F#8/Gb8 | 5919.91 | 62 | 19 | ||
32 | G2 | 98.00 | 1 | 7 | 68 | G5 | 783.99 | 8 | 57 | 104 | G8 | 6271.93 | 65 | 196 | ||
33 | G#2/Ab2 | 103.83 | 1 | 23 | 69 | G#5/Ab5 | 830.61 | 8 | 182 | 105 | G#8/Ab8 | 6644.88 | 69 | 173 | ||
34 | A2 | 110.00 | 1 | 39 | 70 | A5 | 880.00 | 9 | 58 | 106 | A8 | 7040.00 | 73 | 210 | ||
35 | A#2/Bb2 | 116.54 | 1 | 57 | 71 | A#5/Bb5 | 932.33 | 9 | 199 | 107 | A#8/Bb8 | 7458.62 | 78 | 54 | ||
36 | B2 | 123.47 | 1 | 75 | 72 | B5 | 987.77 | 10 | 92 | 108 | B8 | 7902.13 | 82 | 220 |
Armed with those values we can simply VPOKE them into registers and get the correct tone.
Example code
Ok enough theory let’s make some noise. Below are a few examples from simplest to more complicated.
Beep
Single beep in square wave by setting four registers of Voice 0, waiting in empty FOR loop and then setting volume to 0 to turn it off:
Falling
In the following demo we will be changing the frequency or pitch as we play the sound and make it sound like something is falling. We are using triangle waveform:
I used very common code to split value to high (Most significant) and low (Least significant) bytes:
LSB = VALUE AND 255
Engine throttling
This demo will be a little more interactive and we will use Sawtooth waveform. The rest is very similar to previous one except that we are reading Joystick (or Keyboard mapped to Joystick keys) to increase the frequency and with that simulating higher RPMs and when released the engine RPM is slowly dropping.
Explode
The last demo will use the final waveform, the noise. To achieve a good explosion sound this time we will not be changing frequency but instead the volume to sound explosion slowly fading.
What next
One of the problems of programming sound in BASIC is that in BASIC we don’t have very precise control over timing so music is not easy to program or at least to make it sound great is not easy.
For that reason I wrote couple of libraries in Assembly to make including sound effects and background music to BASIC programs easier, links below:
Comments
Post a Comment