Being a part-time fan of the Forth language, I was delighted to find out a decent Forth compiler (named DX-Forth
) being under active development and targeting old machines working under the control of DOS
and CP/M
operating systems. I was particularly impressed by the existence of the CP/M
version. After I tried some of its graphics capabilities (provided by xplgraph
library for DOS
that I managed to port to CP/M
in somewhat cut-down version relying on CP/M
's GSX
interface), I started to look at the sound generating capabilities. I soon realized that in case of CP/M
, nothing more than just a beep can be achieved. Although the hardware on which CP/M
used to be run through the 80's (e.g. Amstrad CPC, Commodore C128, ZX Spectrum 128k +3) was often equipped with audio devices of one kind or another, no consisntent music programming interface (similar to GSX
for graphics) was developed for CP/M
. Even the only (AY
-based) sound generator developed by DK'Tronics for CP/M
-dedicated Amstrad PCW computers required direct talking to the hardware from the programs wanting to utilize it (and the only known example of software using it successfully was the Head Over Heels
game, see https://www.youtube.com/watch?v=Q7HsI460U2U&t=6s). I came to a conclusion that the easiest way to 'play a tune' or to 'signal an event with a sound effect' would be to send MIDI commands over one of available peripheral interfaces.
And here, again, although there were CP/M
running machines with MIDI interface (e.g. ZX Spectrum 128k +3), it was never exposed to the operating system in consistent form. Therefore I started to look for devices that could be connected through any of the interfaces widely available to the CP/M
computers. The RS232C serial and Centronics parallel ports were obvious candidates. Eventually, I've found the one off-the-shelf device utilizing Centronics parallel port (DreamBlaster S2P by Serdaco) and a few web pages describing how to build proper RS232C to MIDI converter (e.g. http://midi-and-more.de/midiconverter.htm). At the end it turned out that this S2P device is pretty much DOS
-oriented, not responding like a printer, hence it required direct (operating system bypassing) port-I/O communication (which I also succesfully attempted in my example C code for Turbo C
and a Forth code for the DOS
version of DX-Forth
). To try RS232C solution, I've used plain null-modem
serial cable (the proper one, with hardware flow control lines properly connected) in order to connect CP/M
with Linux
-running machine on which I've developed simple alsa
-based MIDI proxy. It required Teunis van Beelen's rs232-console
(see https://gitlab.com/Teuniz/rs232-console.git) to handle serial communication through a simple stdin
/stdout
pipe. On the receiving side of the proxy were the alsa
-compatible devices: real or emulated. Using alsa
-compatible USB MIDI adapter I could connect AY3 hardware synth by Twisted Electrons (which I initially planned to connect to the CP/M
machine through the RS232C to MIDI converter). For software-only solution I used fluidsynth
with one of its sound fonts with its output directed to pulseaudio
.
Having simple Forth routines for emitting MIDI events, I could add soume sound to my CP/M
programs.
My Forth routines are able to emit MIDI events to the standard printer device (usually, parallel port) of underlying operating system (DOS
or CP/M
). Hence the system controlled printer port must be redirected to a serial port. The whole of the pipeline looks as follows:
1. Set the serial communication parameters (adjust them to your needs) and redirect the printer port to a (selected) serial port:
CP/M
(the sio
device in the following example is a built-in RS232 serial port of the ZX Spectrum 128k +3 computer):> device lst:=sio[noxon, 19200]
DOS
:> mode COM1: 9600,N,8,1 > mode LPT1:=COM1:
Linux
-running machine or start fluidsynth
with one of its sound fonts, e.g.:$ fluidsynth -a pulseaudio -m alsa_seq /usr/share/sounds/sf2/FluidR3_GM.sf2
Linux
-running machine:$ aplaymidi -l
The example names of available MIDI port are 128:0
, 28:1
, etc.
passmidi
proxy coupled with rs232-console
(adjust the serial communication parameters accordingly!):$ ./passmidi 128:0 rs232-console -- -p ttyUSB0 -b 19200 -m 8N1 -f hardware
Alternatively, you can start FUSE
ZX Spectrum emulator (in the +3 mode) patched with my patch (see https://sourceforge.net/p/fuse-emulator/patches/426) redirecting +3's serial port to the stdin
/stdout
of the emulator. The emulator can be coupled with the passmidi
proxy as such:
$ ./passmidi 128:0 fuse -- -m plus3 ZXCPM3A.DSK
After I developed DX-Forth
routines for emitting MIDI events, I started to wonder if I can write some player capable to just play some MIDI tunes from a file. Unfortunately, I've found the most of widely used MIDI file formats too heavy to handle effectively on the most of theCP/M
-running machines. Thus I had to develop something light. I came up with the textual .mi
format and its binary counterpart, the .mib
format.
An human-readable .mi
file is a sequence of MIDI events where a single MIDI event is expressed as a set of numeric values, one value per line, as such:
Following example depicts seven consecutive MIDI events copied from an example .mi
file:
0 192 0 8 * 0 144 0 73 80 * 240 128 0 73 80 * 0 144 0 61 80 * 240 144 0 78 80 * 0 128 0 61 80 * 240 128 0 78 80 *
You can use mi2mib
conversion program to convert an human-readable .mi
file into a binary .mib
file.
The .mib
format encodes an MIDI event in a serie of bytes (each value is a single byte) as such:
or
'ed with MIDI channel number (values in a range 128-239),To construct a new textual .mi
file (with an usual text editor), one can examine the contents of some .mid
file. The .mid
is a popular binary format, it needs to be converted to something human-readable first. A good soultion is to convert .mid
binary file into a human-readable .csv
spreadsheet, e.g. using John Walker's midicsv
set of tools (see https://fourmilab.ch/webtools/midicsv). Note that the absolute tick values in so obtained .csv
spreadsheet file need to be re-calculated into tick intervals.