Emulator in the emulator for playing chiptune melodies on YM2149F
Who remembers Tetris 2 on the Spectrum? There were a lot of levels, the ability to play together and great music.
Recently, we made an 8-bit computer for simple games , but did not provide any sound capabilities in it. And I wanted to add some 8-bit music there. I remembered exactly the melody from Tetris 2 (spent a lot of hours behind it), so I began to poke around with it.
The ATmega328P processor in our computer is busy most of the time with rendering the image, so there is absolutely no time to synthesize normal music. So we need a sound processor YM2149F (aka AY-3-8910), the same as in ZX Spectrum and other computers.
We connect YM2149F to Arduino
I started by connecting a sound synthesizer to the Arduino board and outputting simple notes to it. YM is controlled by writing values to one of the 15 (we actually use 13) registers. Sound registers in each of the three channels, noise frequency, volume level, frequency and envelope shape are recorded in the registers. For addressing and data transmission 8 signals are used. A couple more are needed to control the bus mode - register selection or value loading. Connection diagram
found on the Internet
For clocking YM, a built-in processor timer with a frequency divider is used. As a result, 2 MHz comes to the input of the sound chip, which is wrong from the point of view of correspondence with the original, but for our tests it will do.
Code to initialize the clock generator
// Sets a 4MHz clock OC2A (PORTB3)
void set_ym_clock(void) {
// PB3 - output
DDRB |= 0x01 << PORTB3;
// Set Timer 2 CTC mode with no prescaling. OC2A toggles on compare match
//
// WGM22:0 = 010: CTC Mode, toggle OC
// WGM2 bits 1 and 0 are in TCCR2A,
// WGM2 bit 2 is in TCCR2B
// COM2A0 sets OC2A (arduino pin 11 on Uno or Duemilanove) to toggle on compare match
//
TCCR2A = ((1 << WGM21) | (1 << COM2A0));
// Set Timer 2 No prescaling (i.e. prescale division = 1)
//
// CS22:0 = 001: Use CPU clock with no prescaling
// CS2 bits 2:0 are all in TCCR2B
TCCR2B = (1 << CS20);
// Make sure Compare-match register A interrupt for timer2 is disabled
TIMSK2 = 0;
// Divide the 16MHz clock by 8 -> 2MHz
OCR2A = 3;
}
Test notes were lost successfully and it was necessary to move on.
I used to be not very interested in Spectrum music separately from the Spectrum itself, but still I heard about the AY format. In another article, I saw a mention of the PSG format. It is similar to any WAV in MP3 in the sense that it contains a linear sequence of actions with the registers of the musical coprocessor. Therefore, the files are large and do not fit into the ATmega memory.
AY files are much smaller. What is the secret? And the fact is that these are pieces of code from games or demos for playing tunes, as well as some data arrays for this code. Typically, players simply emulate the central processor of the Spectrum in order to run this program and so play the melody.
Why not simulate the Z80 on an AVR?
... I thought, and looked for some library simulator of the Z80 processor in C language. Such a simulator was found . He needed to slip only read / write functions of memory and input-output ports.
A little difficulty arose with memory - after all, the ZX Spectrum has 48 kilobytes of RAM, and ATmega328P has only 2 - it will not work to directly create an array of memory for read / write functions. I had to make an array of addresses and cells, and look for values in it when accessing from the processor.
It turned out (who would have thought!) That emulating one 8-bit computer on another 8-bit is not a good idea. Some sound is output, but everything happens so slowly that it is difficult to call it a melody. Then I decided to figure out the player code and rewrite it to C.
Manual decompilation or sunset
The code turned out to be a bit confusing. It was an interpreter of some bytecode that controls the music coprocessor. The bytecode even has support for loops and routines. Each YM channel is controlled by a separate program with a separate stack. It turned out that initially I emulated a computer that emulates another three-processor computer. And although the program turned out to be small (and ran only 50 times per second), still it was very slow.
After I almost saw through the format of this bytecode, I accidentally stumbled upon its description. It was Fuxoft AY Language. It was developed by Frantisek Fuka (Fuxoft), who wrote both Tetris himself and the music for him. This language is used in several dozen compositions. And their code is even extractedfrom games as FXM files. The code that I already analyzed had to be thrown out to start all over again (but it can still be seen in the history of changes in the repository ).
FXM player
A brief description of the format and its decoder came across to me in the source code of a Bulb player . Comparing it with disassembled code, I found only a couple of small differences, but the semantics of data structures have become much clearer.
Now the player is fast enough. Music is played, you can combine a computer and a programmer. Each song takes only a couple of kilobytes, so if you take the Arduino Mega (on the ATmega2560 processor), you can fit all the existing tunes in the FXM format into its memory.
What's next?
It remains to add the correct crystal oscillator, so that the frequency of the chip was as on the Spectrum. You can also write decoders for other tracker formats, add an SD card and you get a Spectrum music player. And when we connect it to our game box, we get a real gaming machine.
True, for some reason, it does not work out so that the speaker volume is normal, now everything is very quiet. Maybe someone knows how to make such a speaker from a toy phone sound? The Chinese phone is coping with this, and the outputs of the YM2149F are somehow not very good. Therefore, I do not post the video of the player.
UPD : Soldered the amplifier and recorded a video how it all plays: