How to make Arduino sing like ZX Spectrum. Part 2: Dizzy IV music on the Arduino Nano
In this part of the article, we will move on to the most interesting — we will disassemble the Dizzy IV music module, play the melody first on Windows and then on the Arduino Nano.
Link to the first part of the article.
Disclaimer:
I want to note that I implemented the project exclusively for the fan, understanding how many years had passed since the Spectrum was relevant. I tried to remember the past and touch the code written for the amazing Z80 processor. This processor was the first in my programmer experience.
Note: Lyndon Sharp wrote the theme song for Dizzy 4, and not David Whittaker, as indicated on many resources dedicated to the game.
From the code of the player, it is clear that the author has built a cross-platform app. It uses a buffer for the registers of the sound generator. Instead of directly accessing the I / O ports, the sound generator registers’ values are first written to a buffer in memory, which appears to be an overhead. Data from the buffer to registers is transferred in a simple cycle. The process of creating music on a different platform and the existence of a game with the same music for Amstrad CPC, explains the presence of such a buffer.
Disassembling the Music Module
I extracted the music module from the tap file and analyzed it in the Unreal Speccy emulator and IDA Pro disassembler. The complete listing of the module with my comments can be found here.
In short, this is a 0x4000 (or 16384, oh, what numbers) bytes module. It is loaded at 0xC000 and has two tunes with addresses 0xC000 and 0xD300. Both tunes come with an identical player, so the player code comes first, followed by position data, patterns, and instruments. I think this was done in most cases — one player for several tunes was rarely done, despite the possible memory savings.
The player itself allows you to customize its bank of instruments, so the same pattern can sound differently, depending on which instruments are used. This can be compared to some extent with the color palette in the old graphics modes.
The module consists of the following blocks (where possible, the address of the end of the block will also be indicated):
- 0xC000–0xC01B — (init) — initialization block, here the initial speed, addresses of positions, and tools are set.
- 0xC01C — 0xC032 — (play_loop) — main loop. In it, thanks to the ‘halt’ command, there is a delay of 20 ms, i.e., the loop’s body is executed 50 times per second. Here you can also control the melody and check if you have pressed the space bar at the moment to exit the player. This code was needed to listen to the melody without an additional code, but it was useless in games because there was its code that was interrupted every 20 ms and called the player’s code to change the state of the melody. Thus, melodies could be played in the background without blocking the execution of the game code.
- 0xC033 — (play_frame) — this place must be called 50 times per second for the melody to sound. This is where the number of calls is counted. If it exceeds the speed set by the author of the composition, the notes change. In these melodies, the speed is always constant, and the transition to the next note occurs every seventh interruption, i.e., the speed of all melodies in the module is 7. When somebody tries to write modern music for AY-3–8912, he uses a speed of 3, so he can fit in more details. The lower the speed number, the more often the notes change.
- 0xC043 — (init_next_note) — the code is called when the note changes.
- 0xC051 — (init_next_position) — if the notes in the pattern end, then you need to go to the next position or start the melody from the beginning.
- 0xC06C — (init_pattern) — initialization code for the next pattern.
- 0xC082 — (init_next_note_in_current_pattern) — a large piece of code to initialize the next note in the pattern. Here, the init_note_in_channel subroutine is called four times, which reads the control commands and the notes’ pitch to be played from the pattern data and stores this data in RAM. Note that the fourth channel is missing from the sound generator. This pseudo channel was conceived for special state control commands. It is not clear why these commands could not be placed on the remaining three channels. By the way, index registers “ix,” and “iy” are widely used here, which is an advantage of the Z80 processor compared to its progenitor Intel 8080. With proper use, they were a kind of execution context, a kind of “this” in C#, if you like. The use of “iy” also indicates that they did not try to make the player universal because this register was used in the built-in BASIC, and it was not recommended to change it in interrupts (the player could be called through interrupts).
- 0xC0B4 — (manage_current_note) — if you had to change notes less often than every 20 ms, in accordance with the specified speed, then the currently playing notes needed to be continuously maintained. This code calls the manage_current_note_in_channel routine three times to keep the instruments, once for each channel. The instruments can dynamically change the frequency of a note (sample) or a note’s pitch (ornament).
- 0xC0F5–0xC1E2 — (manage_current_note_in_channel) — maintenance of the instrument for this note in the channel. Along with the instrument, noise effects are also processed here. Based on the results of calculating the new state for the instruments in this procedure, the register buffer is filled, which is later transferred to the sound generator’s registers.
- 0xC1E3 — (ay_out) — transfer of data from the buffer to the sound generator’s registers.
Implementation on Windows
I started by writing most of the code first on Windows to make it easier to debug. I left the “fight” with Arduino when adopting the code to the board’s hardware capabilities. You can build my Windows implementation yourself (I have the win-implementation directory on GitHub; you need a compiler from Visual Studio 2017) or run ready-made assemblies: melody 1, melody 2.
A couple of comments on how everything is implemented.
To avoid porting problems later, I tried to write the code, focusing on the eight-bit nature of ATMega microcontrollers. Therefore, it is based on the ‘uint8_t’ and ‘uint16_t’ types.
To convert the original music file into the compilable code, I wrote a small python script (extractor directory I have it on GitHub), which copies the melody data into an array of bytes and extracts some addresses:
- address of the beginning of the melody data (excluding the player),
- address of the position table,
- address of the instrument banks table for the positions.
In the original Spectrum, the AY-3–8912 sound generator produced square-wave signals that I used to find gross. Initially, I wasn’t achieving authentic sound, so I tried to generate sinusoidal signals instead of a meander in the first version. It turned out that all the harmonics were gone along with the rectangular waveform, and the sound became dull. So the sinusoidal waveform had to be abandoned. Also, a volume mapping table has been added to the implementation. Let me remind you that the output signal amplitude of the sound generator has 15 levels. Enthusiasts that develop AY-3–8912 emulators have found that the relationship between the value in the volume register and the output voltage is not at all linear. This is how tables for the corresponding transformation appeared. In “large” emulators, such tables are 16-bit. In our emulator, 4 bits are used for the speed of calculations. Therefore, if you look into the source, you can see that the volume levels from 0 to 3 are not reproduced at all, and the levels from 4 to 9 have an insignificant output volume. Indeed, subjectively, I felt it even during my experiments in writing music. I think that the engineers developing the sound generator did this on purpose, in accordance with the Weber-Fechner law (the intensity of sensation is proportional to the logarithm of the intensity of the stimulus).
The music module from Dizzy IV uses simple noise effects. They managed to be implemented using a simple random number generator — xorshift.
Arduino Implementation
Since the project was purely for fun, I chose the Arduino Nano board. It has a widely used microcontroller, but it is the least suitable for this task. One could have chosen, for example, STM32. Even a DAC is there, so it would be completely uninteresting, especially since it was possible on STM32 to run ZX Spectrum emulation, including video signal generation. What can we say about the Raspberry Pi and its analogs — “They can emulate the spectrum without writing a single line of code.”
TIMER2 provides PWM signal generation, pin OC2B or PD3 (or pin D3 in Arduino terminology). The PWM signal frequency was chosen high enough — 31373 Hz, so the Arduino output was connected directly to a portable speaker without any filtering circuits; there were no extraneous sounds.
On the Arduino Nano board, only two LEDs can be controlled. I made it so that the intensity of one of them depends on the volume of the melody. For ease of implementation, the PWM signal for this LED is generated from the second timer interrupt. Until there is a goal to save processor time, you can leave it that way. Another LED is controlled via the UART -> USB converter. Here it is possible to display the noise channel indication on it.
The choice of melody is still hard-coded in software, but it can be easily changed by calling the ‘get_music_data_1’ or ‘get_music_data_2’ function. For the first composition, the main voice is on channel A. For the second — in channel B, this must be taken into account when visualizing the LEDs’ melody.
The project can be assembled from the Arduino-sketch catalog. You can listen to melodies on your Arduino or watch the resulting result on the video.
As I already wrote, all source codes of the project can be found on Github.
Author of the article: Anton Dmitrievsky, Maxilect
PS Once my friend and I made a clone of the famous game “Color Lines” (it was fashionable then to make clones of this particular game, there are thousands of them) and there, in addition to beautiful graphics, also beautiful music. I present to you the intro for this game. If you want to listen to in-game music, you will have to launch the emulator’s game — the TR-DOS disk image can be downloaded here. After starting, you must click on the menu item “Select melody”.
PPS I would like to express my gratitude to all the authors of the Unreal Speccy emulator, participants in the speccy.info project.
PPPS Subscribe to our social networks: Twitter, Telegram, FB to learn about our publications and Maxilect news.