Simple DIY Electronic Music Projects<p><strong>Z80 and AY-3-8910</strong></p><p>Finally starting to look at the <a href="https://diyelectromusic.com/2025/07/05/arduino-and-ay-3-8910/" rel="nofollow noopener" target="_blank">Arduino and AY-3-8910</a> was triggered by a couple of things recently. First getting an RC2014 and playing with AY-3-8910 based sound on that.</p><p>But also, having visiting RetroFest in Swindon this year, talking to Dean Belfield about the methods he used to develop for the ZX Spectrum and how he was donated a number of archive disks from the Follin brothers related to their producing music for 8-bit games.</p><p>The archive can be found here: <a href="https://github.com/breakintoprogram/archive-follin" rel="nofollow noopener" target="_blank">https://github.com/breakintoprogram/archive-follin</a> and it is a real treasure trove of information.</p><p>I wanted to try to understand some of it myself, so this is me poking about in the archives to learn a little about how some of this music was produced at the time.</p><p>After having a bit of an initial look and after posting a “this is interesting” post on Mastodon, Steven Tattersall told me about a similar thing they’d done with Follin’s ST driver too. This is for the YM2149 which is the Yamaha equivalent of the AY-3-8910. There is a great description of this one here: <a href="http://clarets.org/steve/projects/reverse_engineering_tim_follin.html" rel="nofollow noopener" target="_blank">http://clarets.org/steve/projects/reverse_engineering_tim_follin.html</a></p><p>Once I’m a bit more through my own discussion it will be really interesting to compare the two.</p><p>For now, I’m just posting this based on my initial thoughts, and will update it as I go.</p><p>To be continued…</p><p><em><strong>Warning!</strong> I strongly recommend using old or second hand equipment for your experiments. I am not responsible for any damage to expensive instruments!</em></p><p>If you are new to microcontrollers, see the <a href="https://diyelectromusic.wordpress.com/getting-started/" rel="nofollow noopener" target="_blank">Getting Started</a> pages.</p><p><strong>The Archives</strong></p><p>From Dean’s introduction:</p><p><em>“The music and sound effects were hand-coded by Tim and Geoff Follin in assembler as a series of DEFB’s representing note pitch and duration for each channel. This data can also contains commands, for example to loop a sequence, call a subroutine or switch on an effect.”</em></p><p><em>“Once completed, their music source was included in the intended game along with a small music driver, also written in assembler.”</em></p><p><em>“This could then be called from within the game for both in-game music and sound effects”</em></p><p>The sound driver effectively implemented a series of instructions and turned these into commands to write out to the chip as part of its main “scan”.</p><p>There is an example of the sound driver here: <a href="https://github.com/breakintoprogram/archive-follin/blob/main/Examples/AY/Ghouls%20n%20Ghosts/aydrive.z80" rel="nofollow noopener" target="_blank">https://github.com/breakintoprogram/archive-follin/blob/main/Examples/AY/Ghouls%20n%20Ghosts/aydrive.z80</a></p><p>And there is a set of instructions for the music here: <a href="https://github.com/breakintoprogram/archive-follin/blob/main/Examples/AY/Ghouls%20n%20Ghosts/g%2Bgmusic.z80" rel="nofollow noopener" target="_blank">https://github.com/breakintoprogram/archive-follin/blob/main/Examples/AY/Ghouls%20n%20Ghosts/g%2Bgmusic.z80</a></p><p>Combining these gives a block of assembly that can be built and run directly to play the sound.</p><p><strong>Ste Ruddy’s Sound Driver</strong></p><p>As far as I can make out, the aydrive.z80 sound driver is in two main parts:</p><ul><li>A manager interface to load the track, run some kind of user-interface to show what is going on, and then call the sound routines.</li><li>The sound driver itself.</li></ul><p>In the assembly, the sound driver starts with the following:</p><pre>;======================================<br>; AY MUSIC DRIVER V1.00 BY S.RUDDY<br> <br>CODE_TOP: </pre><p>There then follows a series of initialisation statements. These hold the internal state of the various aspects of the sound driver – things like current playing frequency, loop counts, volume levels, ADSR settings and so on. These are the items that implement the “language” that provides the instructions for what to play.</p><p>So what is that language? There is a list of commands (“OpCodes”) in the music file:</p><pre>FOR: EQU 080H<br>NEXT: EQU 081H<br>LENGTH: EQU 082H<br>STOP: EQU 083H<br>GOSUB: EQU 084H<br>RETURN: EQU 085H<br>TRANS: EQU 086H<br>DISTORT: EQU 087H<br>SEND: EQU 088H<br>ADSR: EQU 089H<br>ENVON: EQU 08AH<br>WOBBLE: EQU 08BH<br>PORT: EQU 08CH<br>VIB: EQU 08DH<br>IGNORE: EQU 08EH<br>EFFECT: EQU 08FH<br>GOTO: EQU 090H<br>GATE_CON: EQU 091H<br>ENDIT: EQU 092H</pre><p>The tune file contains sets of sound FX and tunes that consist of series of the above instructions to play notes and otherwise manipulate the AY-3-8910.</p><p>The basic code structure of the sound driver is as follows (using the assembler labels from aydrive.z80):</p><ul><li>Working values, variables, and parameters – “CODE_TOP”.</li><li>Main tune driver setup – “TUNE”.</li><li>Sound FX driver setup – “FX”.</li><li>Main driver scanning routine – “REFRESH”.<ul><li>This takes the current state of all the variables, parameters, and so on, performs any updates as per the provided commands, and outputs the latest status to the AY-3-8910.</li><li>This runs once “per frame”.</li><li>There are three copies of this routine, processing in turn each of channel A, B and C.</li></ul></li><li>Main driver command routine.<ul><li>A “jump off” table for each OpCode command in the sound driver.</li><li>Assembly routines for each command to update the variables, parameters, and process state of the sound driver.</li><li>Again there are three copies of the jump table and routines, one for each of channel A, B and C.</li></ul></li><li>AY-3-8910 driver routine.<ul><li>Architecture specific code to access the chip.</li></ul></li><li>Note frequency table for 102 notes.</li><li>Close-out code.</li></ul><p>The entry points, as far as I can see, are as follows:</p><ul><li>Main tune driver setup “TUNE”.</li><li>Sound FX setup “FX”.</li><li>Scanning “REFRESH” routine.</li></ul><p>The combined driver, tunes, and tester UI, when all combined, is structured as follows:</p><ul><li>UI / Tester.</li><li>Sound Driver.</li><li>Tune and FX instructions.</li></ul><p>This is expanded upon in pseudo-code below using the labels from the original assembly:</p><pre>Code_Start: EQU 40000<br>Data_Start: EQU 50000<br><br>;-----------------------------<br>ORG Code_Start<br><br>; The UI/tester code<br>TESTER:<br> LOOP: Calls the following for each scan:<br> HALT - Suspends until an interrupt comes in?<br> CALL UPDATE<br> CALL REFRESH<br> CALL CLOCK<br> CALL KEYSCAN<br> Repeat as necessary<br><br> KEYSCAN: UI scanning<br> CLOCK: Possibly maintain a 50Hz refresh rate clock?<br> UPDATE: Loads the internal state of all sound variables from<br> the driver and displays them in real time via the UI.<br><br>; The sound driver as described above<br>CODE_TOP:<br> TUNE: Select which tune to play.<br> TUNE_IN: Init all internal sound state variables for a new tune.<br> TUNEOFF: Stop a playing tune, eg to change tune or start an FX.<br><br> FX: Start playing an FX.<br> FLOOP: Keep processing FX instructions until complete.<br><br> REFRESH:<br> CHANNEL_A: IF STOP_A jump to CHANNEL_B<br> START_A: Process channel A state with CALLS to OUT_CA_AY or<br> OUT_FLESH as reqd and then RET back into this code.<br> Processing includes: ADSR, vibrato, distortion,<br> wobble, portamento, note frequency, etc.<br> DO_A: Entry point for getting next instruction.<br> COMMAND_A: Process a command for CH A using JUMP_A table.<br> GOTNOTE_A: Process a note for CH A with CALLS to OUT_CA_AY or<br> OUT_FLESH as reqd and RET back into this code.<br> Carry on to CH B<br><br> CHANNEL_B: IF STOP_B jump to CHANNEL_C<br> START_B: As above for Channel B<br> DO_B:<br> COMMAND_B:<br> GOTNOTE_B:<br> Carry on to CH C<br><br> CHANNEL_C: IF STOP_C RET to caller.<br> START_C: As above for Channel C<br> DO_C:<br> COMMAND_C:<br> GOTNOTE_C:<br> Finally JUMP to (not CALL) OUT_CA_AY so that the RET<br> is a RET to caller of the REFRESH code.<br><br> OUT_FLESH: Sets CH A,B,C and noise levels then jump to<br> OUT_CA_AY which includes the RET to caller.<br><br> JUMP_A: Jump table and associated instructions for CH A.<br> All instructions JUMP back to DO_A when complete.<br><br> JUMP_B: Jump table and associated instructions for CH B.<br> All instructions JUMP back to DO_B when complete.<br><br> JUMP_C: Jump table and associated instructions for CH C.<br> All instructions JUMP back to DO_c when complete.<br><br> OUT_CA_AY: Send contents of Z80 register C (0 to 255) to AY<br> register defined by Z80 register A (0 to 15).<br> RET to caller<br><br> NOTES: Frequency definitions for 102 notes.<br><br>CODE_BOT: End of Sound Driver<br><br>;-----------------------------<br>ORG Data_Start<br><br>; Tune and FX instructions<br>; 5 tunes and 21 effects<br>TUNES: EQU 5<br>EFFECTS: EQU 21<br><br>DATA_TOP:<br> TUNES_A: Jump table for 5 tunes info for CH A.<br> TUNES_B: Jump table for 5 tunes info for CH B.<br> TUNES_C: Jump table for 5 tunes info for CH C.<br> FX_TAB: Jump table for 21 effects info.<br><br> Instructions for 21 effects.<br> Instructions for 5 tunes.</pre><p>So there is a main logic block – the “REFRESH” code, which defines the current frequency and volume level according to the state of the playing note, ADSR and any added effects. Then there are logic blocks for each of the recognised sound commands to adjust that state as appropriate.</p><p><strong>The Sound OpCodes</strong></p><p>I was going to document each in turn, but actually Steven Tattersall has already done a really good job of that here: <a href="http://clarets.org/steve/projects/reverse_engineering_tim_follin.html" rel="nofollow noopener" target="_blank">http://clarets.org/steve/projects/reverse_engineering_tim_follin.html</a></p><p>I’ll expand on that as I go in terms of what it seems to mean for the Z80 driver, but for now that is a pretty good summary!</p><p><strong>The Main Logic Function</strong></p><p>I’ll work through the assembly for the main REFRESH function in time. Watch this space!</p><p><strong>Closing Thoughts</strong></p><p>I’ll keep coming back to this and posting an update as I get into more of it. When I think I’m done, I’ll post a proper conclusion.</p><p>For now, its just interesting to note that even though I’m sure many people have been through this before me already, it is a really interesting activity to try to figure it out myself.</p><p>As I say – to be continued…</p><p>Kevin</p><p><a rel="nofollow noopener" class="hashtag u-tag u-category" href="https://diyelectromusic.com/tag/ay38910/" target="_blank">#ay38910</a> <a rel="nofollow noopener" class="hashtag u-tag u-category" href="https://diyelectromusic.com/tag/chiptunes/" target="_blank">#chiptunes</a> <a rel="nofollow noopener" class="hashtag u-tag u-category" href="https://diyelectromusic.com/tag/tim-follin/" target="_blank">#TimFollin</a></p>