The Ultimate CPC MIDI Sound & Interface Card

Building a state-of-the art CPC MIDI Synthesizer with the Blue Pill, Serdashop's WaveBlaster S2, and Adafruit's MIDI Feather Modules

Similar projects worth following
I am building a MIDI synthesizer / MIDI interface for the Amstrad CPC line of computers. Features:
- Blue Pill-based (72 MHz)
- Send and receive MIDI data from (resp. with) the CPC
- Standalone MIDI Instrument
- MIDI Soft Through
- Flexible MIDI routing
- GM MIDI Sound Card for the CPC
- MIDI Interface for the CPC
- Works with Serdashop's DreamBlaster S2, X2 (GS), and E-Wave
- Only one additional chip required for address decoding (GAL22V10)
- Uses only software ISR's for IOREQ READ and WRITE handling (no additional \WAIT circuitry required)
- Python-based MID -> BIN converter for unlimited content creation for the card
- ASM MID BIN player utilizing the 512 KB DK'tronics RAM expansion for large MIDI file playback from the CPC memory

Review Videos

"MeinElektronikHobby" (in German): 


Development History:

Part 14: 

By now, the BIN MID file player is also able to use the 512 KB memory expansion (DK'tronics "standard"). The Python-based converter has been improved a bit; timing is much better by now. Here is a line out-recording of the card with he X2GS. Enjoy!

Part 13:

Previously, to generate song files for the card, I had to record the MIDI events generated by playing back a MIDI song in realtime into the CPC. MID playback was done with the PC and MIDIBAR, and the CPC recorded the messages arriving with a USB->MIDI cable in the card's MIDI IN DIN port. Now, depending on the complexity of the MIDI data stream this can be very close to the limit of what the card / CPC can process (some .MID's require CRAZY bandwidth...) Whereas this works mostly fine for MIDI songs of medium complexity (whatever that means - e.g., all the song fragments demonstrated in Part 12 were created in that way), this "realtime MIDI message recording into the CPC"-approach is not feasible for very complex MIDI songs.

Hence, I created a Python-based .MID to .BIN converter which allows me to generate song content for the card much more readily and conveniently by now (with the push of a button). Also, since these BIN song files get very large, we need to use the CPC's extended RAM. The CPC 6128 already offers 128 KBs, and I simply set aside the second 64 KB bank exclusively for MIDI data. Here is a video of the first songs created by the Python-converter, utilizing the extended memory - AxelF and MammaGamma unabridged!

The Python converter still needs a bit of work, but I am going to release it on the Github repo soon.

Next, I am also going to support the standard DK'tronics 512 KB memory extension for the CPC, which is a modern and wel-supported "CPC standard" (e.g., ToTO's XMem and Revaldinho's RAM extensions are all DK'tronics compatible). 

Part 12:
I have a new favorite sound module for the Ultimate MIDI Card - the X2 GS! It really sounds as good as my Roland Sound Canvas. The GS certified Roland MIDI sound bank makes all the difference in terms of sound quality. Whereas I was a bit disappointed by the X2, the X2 GS exceeded my expectations! Really an amazing sound module. Hence, I recommend the S2 for the price sensitive customer, and the X2 GS as the audiophile no-compromise-MIDI solution. Listen to the X2 GS here; this is a line-out recording. Sound starts at 2'10:

Part 11:
Thanks to PCBWay for sponsoring this project by giving me a free batch of PCBs!

They are thick and sturdy and I can recommend them. I am making this endorsement as an individual, and it should not be construed as coming from or being related to my employer in any ways. Interestingly, the PCBWay PCBs appear to be identical to the Seeed Fusion PCBs to me (maybe they are even using the same factory?) This is of course speculation.

Anyhow, I have assembled one with the new PCBWay PCB, and you can see and hear it in the following video, where I am comparing 4 different MIDI options: the Roland Sound Canvas, the S2, the McFly, and the EWave, all but the Sound Canvas are from Serdashop. The S2 is still my favorite. The line out audio of the EWave is at a lower level by default, and the McFly gave up on the MIDI data. It doesn't like the song it seems, there is some MIDI data in the song which makes it give up. Maybe the complexity of the song data is too high. Anyhow, there might be other songs which play fine with the McFly, I have to investigate that a bit further. However, it seem the S2 has highest line out levels and best clarity; the filtering (?) on both of the McFly and the EWave is too strong IMHO, they lack a bit of clarity. I will try another MIDI song on the McFly at some point to get a better understanding of what's going on.

Part 10:
Complex MIDI playback from the CPC, standalone. I took some time to implement the MIDI data stream...

Read more »

View all 4 project logs

Enjoy this project?



Fred wrote 08/15/2022 at 06:59 point

Is the github repo up-to-date? There are some mismtach between CPC code, using addresses &FEAC/&FEAD and GAL code, decoding &FBEE/&FBFE...

I also see some mismatch in STM32 code and schematics: it looks like you inverted PA11 and PC13 (S2 reset and LED) in the main() function!

  Are you sure? yes | no

Michael Wessel wrote 08/15/2022 at 15:17 point

Please use the Github versions, I think I stopped maintaining the Hackaday files.

  Are you sure? yes | no

Fred wrote 08/15/2022 at 15:32 point

I am using the github!

  Are you sure? yes | no

Fred wrote 08/14/2022 at 12:53 point


I and a few other people are about to build your nice MIDI synth card for CPC.

But we are concerned by the HALT usage on the Z80. As we plan to use this card in precise timings, mainly during a frame on demos/games, we would like to know if the time while the Z80 is halted is constant in all cases (READ1, READ2 and WRITE)?

If it is the case, it should be OK, but if not, it will introduce jitter in the frame, which is not good :o/


  Are you sure? yes | no

Michael Wessel wrote 08/14/2022 at 15:48 point

Hi Fred 

Yes that's an issue and there is no easy solution without pulling waitstates. The databus handling is done in software by the BluePill via ISRs so timing is extremely delicate.  It doesn't work on a Plus either.

  Are you sure? yes | no

Fred wrote 08/14/2022 at 16:17 point

As long as the waitstates are constant, this is not an issue; IN/OUT will appear a little bit longer, which can be handled. But is it the case?

On the other hand, I think the waitstate while sending serial data to the synth can be removed, and replaced by an additional wait on the Z80 side...

  Are you sure? yes | no

Michael Wessel wrote 08/14/2022 at 16:23 point

So timing relies delicately on NOPs as well. If you change the number of NOPs in one ISR, it affects the timings of the other  ISRs, so it's a nightmare.  You will need a logic analyzer to tune it.

  Are you sure? yes | no

Fred wrote 08/15/2022 at 05:35 point

Whait if, in exti9_5_isr() interrupt routine, I change from:



    counter = gpio_port_read(GPIOB);
    uint16_t ch = (counter >> 8);

    if (cpc_to_midi_out) usart_send(USART1, ch);
    if (cpc_to_s2) usart_send(USART2, ch);




    counter = gpio_port_read(GPIOB);


    uint16_t ch = (counter >> 8);

    if (cpc_to_midi_out) usart_send(USART1, ch);
    if (cpc_to_s2) usart_send(USART2, ch);

Will it work? The goal is to avoid freezing the Z80 for 640µs, the time needed to write on the serial bus... Of course, on the Z80 side, we need to ensure not to write again before this delay, but if we send only one value per frame, it is ok.

  Are you sure? yes | no

adam.klotblixt wrote 05/29/2021 at 16:28 point

Congrats on your fast success! Nice to see/hear it working.

  Are you sure? yes | no

Michael Wessel wrote 05/29/2021 at 17:19 point

Thanks Adam! And when I am done, I am going to make the sources public, as usual. Cheers Michael

  Are you sure? yes | no

adam.klotblixt wrote 05/26/2021 at 14:01 point

I've read that the Z80 refresh is disabled during WAIT, so memory might corrupt if you hold it too long.

  Are you sure? yes | no

Michael Wessel wrote 05/26/2021 at 15:16 point

That might be the case for Z80-based computers that rely on the Z80 for DRAM Refresh. The CPC does not though, so you can hold it as long as you want. The
Z80 is not refreshing the DRAM in the CPC, and there are actually very few Z80-based old computers that use the build-in Z80 hardware DRAM refresh feature. Most of them implement their own refresh logic.

  Are you sure? yes | no

Michael Wessel wrote 05/20/2021 at 19:42 point

Thanks Adam, yes - the Blue Pill has awesome performance for its price tag. Tensy 4.0 might also be tempting... if it only was 5V-compatible! So I rather go with the Blue Pill for now and the extra GAL for address decoding, that's still a cheaper and easier setup than all the additional level shifters I would need for a more powerfull MCU like the Tensy 4.0.

I will post it at some point when it does a little bit more.  At this point, it is nothing more than the GPIO port declarations, and an ISR that handles both IOREAD and IOWRITE requests coming from the GAL. To be sure I am not missing the databus value, I also halt the Z80 CPU long enough for IOWRITE requests to get a stable databus readout, and also while switching the databus GPIO from input to output  while serving the IOREAD request (and while switching it back to input mode again after the IOREAD request was served).

  Are you sure? yes | no

adam.klotblixt wrote 05/20/2021 at 19:06 point

Would love to have a look at the Blue Pill code. This is a great way to build cheap expansions to many retro computer systems.

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates