![]() |
Programming the AY-3-8910 Programmable Sound Generator (PSG) is a relative simple task. But since the Aquarius BASIC doesn't support any command's for INPUT / OUTPUT the task has to be solved by machinecode. The info on this page is therefor for the advanced programmer with some assembler knowledge.
Before we start programming the PSG, we have to know something about it's internal structure. It has 16 registers, which are used as followed:
| Register \ bit | B7 | B6 | B5 | B4 | B3 | B2 | B1 | B0 | |
| R0 | Channel A Tone Period | 8-Bit Fine Tune A | |||||||
| R1 | 4-Bit Coarse Tune A | ||||||||
| R2 | Channel B Tone Period | 8-Bit Fine Tune B | |||||||
| R3 | 4-Bit Coarse Tune B | ||||||||
| R4 | Channel B Tone Period | 8-Bit Fine Tune C | |||||||
| R5 | 4-Bit Coarse Tune C | ||||||||
| R6 | Noise period | 5-Bit Period control | |||||||
| R7 | Enable ( bit 0 = on, 1 = off ) | IN/OUT | Noise | Tone | |||||
| IOB | IOA | C | B | A | C | B | A | ||
| R8 | Channel A Envelope on/off, Volume | Env | volume | ||||||
| R9 | Channel B Envelope on/off, Volume | Env | volume | ||||||
| R10 | Channel C Envelope on/off, Volume | Env | volume | ||||||
| R11 | Envelope Period | 8-Bit Fine Tune Envelope | |||||||
| R12 | 4-Bit Coarse Tune Envelope | ||||||||
| R13 | Envelope Shape/Cycle | CONT | ATT | ALT | HOLD | ||||
| R14 | I/O Port A Data Store | 8-Bit Parallel I/O on Port A | |||||||
| R15 | I/O Port B Data Store | 8-Bit Parallel I/O on Port B | |||||||
The Aquarius has two I/O ports in order to programm the PSG. Port 0xF7 is used to select the register and port 0xF6 is used to read or write the selected register. In order to generate a tone on Channel A we do the following:
| data | label | opcode | operand | comment |
| 62, 8 | start: | LD | A,0x08 | ; Select register #8 |
| 211, 247 | OUT | (0xF7),A | ||
| 62, 15 | LD | A,0x0F | ; Volume channel A full | |
| 211, 246 | OUT | (0xF6),A | ||
| 62, 0 | LD | A,0x00 | ; Select register #0 | |
| 211, 247 | OUT | (0xF7),A | ||
| 62, 93 | LD | A,0x5D | ; Write #93 into register #0 | |
| 211, 246 | OUT | (0xF6),A | ||
| 62, 1 | LD | A,0x01 | ; Select register #1 | |
| 211, 247 | OUT | (0xF7),A | ||
| 62, 13 | LD | A,0x0D | ; Write #13 into register #1 | |
| 211, 246 | OUT | (0xF6),A | ||
| 62, 7 | LD | A,0x07 | ; Select register #7 | |
| 211, 247 | OUT | (0xF7),A | ||
| 62, 62 | LD | A,0x3E | ; Enable output Channel A (0011 1110) | 211, 246 | OUT | (0xF6),A |
| 201 | RET | ; Return to BASIC |
Translated to BASIC that would be:
10 DATA 62,8,211,247,62,15,211,246 20 DATA 62,0,211,247,62,93,211,246 30 DATA 62,1,211,247,62,13,211,246 40 DATA 62,7,211,247,62,62,211,246 50 DATA 201 60 FORX=32000TO32032:READA:POKEX,A:NEXT 70 POKE14340,0:POKE1341,125:X=USR(0)Which results in a nice everlasting tone. The tone you hear is the note C. In order to generate certain tones you can use these periods:
| Note | Period | Note | Period |
| C | 3421 | F# | 2419 |
| C# | 3228 | G | 2283 |
| D | 3047 | G# | 2155 |
| D# | 2876 | A | 2034 |
| E | 2715 | A# | 1920 |
| F | 2562 | B | 1892 |
The lower the period, the higher the note. In order to get a higher octave, just divide the period by two.
The registers 0 & 1 are used for the period of Channel A. To calculate the correct values for these registers you can use the following formula:
Now let's take it a little step further and try working with an effect. The following will generate a short decaying burst of noise, just like a gunshot:
| data | label | opcode | operand | comment |
| 62,11 | start: | LD | A,0x0B | ; Register 11 |
| 211,247 | OUT | (0xF7),A | ||
| 62,160 | LD | A,0xA0 | ||
| 211,246 | OUT | (0xF6),A | ||
| 62,12 | LD | A,0x0C | ; Register 12 | |
| 211,247 | OUT | (0xF7),A | ||
| 62,15 | LD | A,0x0F | ||
| 211,246 | OUT | (0xF6),A | ||
| 62,6 | LD | A,0x06 | ; Register 6 | |
| 211,247 | OUT | (0xF7),A | ||
| 62,15 | LD | A,0x0F | ||
| 211,246 | OUT | (0xF6),A | ||
| 62,7 | LD | A,0x07 | ; Register 7 | |
| 211,247 | OUT | (0xF7),A | ||
| 62,55 | LD | A,0x37 | ; 00110111 | |
| 211,246 | OUT | (0xF6),A | ||
| 62,13 | LD | A,0x0D | ; Register 13 | |
| 211,247 | OUT | (0xF7),A | ||
| 62,0 | LD | A,0x00 | ||
| 211,246 | OUT | (0xF6),A | ||
| 62,8 | LD | A,0x08 | ; Register 8 | |
| 211,247 | LD | (0xF7),A | ||
| 62,16 | LD | A,0x10 | ; Enable Enveloppe Channel A | |
| 211,246 | OUT | (0x246),A | ||
| 201 | RET | ; Return to BASIC |
Again, translated to BASIC:
10 DATA 62,11,211,247,62,160,211,246 20 DATA 62,12,211,247,62,15,211,246 30 DATA 62,6,211,247,62,15,211,246 40 DATA 62,7,211,247,62,55,211,246 50 DATA 62,13,211,247,62,0,211,246 60 DATA 62,8,211,247,62,16,211,246 70 DATA 201 80 FORX=32000TO32048:READA:POKEX,A:NEXT 90 POKE14340,0:POKE1341,125:X=USR(0)
The effect is generated by the Enveloppe register #13. It can make several effects which are graphical representated in the next table:
| R13 Bits | GRAPHICAL REPRESENTATION OF ENVELOPE GENERATOR OUTPUT (E3 E2 E1 E0) | ||||
|---|---|---|---|---|---|
| B3 | B2 | B1 | B0 | Decimal representation |
|
| Continue | Attack | Alternate | Hold | ||
| 0 | 0 | x | x | 0,1,2,3 | ![]() |
| 0 | 1 | x | x | 4,5,6,7 | ![]() |
| 1 | 0 | 0 | 0 | 8 | ![]() |
| 1 | 0 | 0 | 1 | 9 | ![]() |
| 1 | 0 | 1 | 0 | 10 | ![]() |
| 1 | 0 | 1 | 1 | 11 | ![]() |
| 1 | 1 | 0 | 0 | 12 | ![]() |
| 1 | 1 | 0 | 1 | 13 | ![]() |
| 1 | 1 | 1 | 0 | 14 | ![]() |
| 1 | 1 | 1 | 1 | 15 | ![]() |
EP is the Envelope Period, or the duration of 1 cycle. EP = R12*256 + R11 |
|||||
After running the last BASIC programm one can experiment with the enveloppe effects by poking the wanted effect on address 32037 and calling the USR(0) function. For example:
POKE32037,4:X=USR(0) POKE32037,8:X=USR(0)