Micromusic (Part 2)
Make music with the ZX81
Last month we covered programming the ZX81 in machine code; this month is more hardware orientated, starting with the input/output part shown in Figure 1. No construction details are given, since this is a straightforward circuit; the prototype was built on Vero DIP board, wired up with ordinary single core wire. One aspect that will be covered, however, is the actual connection to the ZX81.
The appropriate 23 way double sided edge socket is now fairly easy to obtain through various advertisers in the computing magazines, but we still need to fabricate a plug to enable the RAM pack to be connected at the same time as the port. Figure 2 shows how one may be made from 1.6mm double sided PCB material. No etching is necessary - simply divide the copper into 23 0.1" strips with light hacksaw cuts. This plug may now be soldered onto the pins on the back of the socket. Since the contacts are not gold plated, they will need cleaning from time to time; tarnished con tacts make the RAM pack very sensitive to vibration, and it is very easy to lose several hours' work through one careless knock.
The port circuitry itself consists of an output latch IC3, an input gate IC4 and an address decoder IC1. IC2 buffers the data lines so that several latches may be driven for polyphonic use; if only one output is required the data bus may be connected straight to IC3. Several alternatives are given for IC3; in this application it does not matter whether a D type flipflop or a transparent latch is used. The author's prototype in fact used 74LS373 latches.
The links on IC1's enable inputs allow the port addressing to be in I/O space when, links 'B' are made - in which case machine code must be used for input and output routines - or memory mapped when links 'A' are made. Memory mapping allows input and output to be performed solely in BASIC using PEEK and POKE - the port acts like a memory location with wires connected to it - and as such is useful for logic experiments, light flashing and so on as well as simple sequencer programs.
Figure 3 shows the ZX81's memory space divided up into 8K byte blocks. Due to the incomplete address decoding used in the machine, the ROM repeats itself between 2000 and 3FFF (hex), whilst the operating system only ever uses the bottom copy. D1 deselects the ROM whenever A13 is high, freeing this address space for use by the port. The actual addresses selected by IC1 are given below:
|IC1 pin no.||Memory mapped address||I/O map. address|
|15||3F1F (hex)||16159 (dec)||1F|
|14||3F3F (hex)||16191 (dec)||3F|
|13||3F5F (hex)||16223 (dec)||5F|
|12||3F7F (hex)||16255 (dec)||7F|
|11||3F9F (hex)||16287 (dec)||9F|
|10||3FBF (hex)||16319 (dec)||BF|
|9||3FDF (hex)||16351 (dec)||DF|
|7||3FFF (hex)||16383 (dec)||*do not use|
Since the port's address decoding is also incomplete, these are not the only addresses' that could be used, but it will avoid confusion if one figure is used throughout. Note that IC1 pin 7 should not be used to select a port when in the I/O mapped mode, since this will be addressed by the ZX81's internal workings.
The easiest synthesisers to connect to will be those that use a digital encoding system already; instruments with a control voltage input will require an additional digital to analogue convertor, and this will be covered later.
Synthesisers with their own internal D/A convertors generally use 6 bits to specify the note, giving a range of 5 octaves plus 3 semitones (64 notes) and the Maplin 3800 and 5600S work this way. For reasons that will become clear later on, the bottom 6 bits of the 8 on the output port will be used to select the note required, giving a range from B (code 0) to D (code 63). The top bit will be used to operate the gate, so that a note which is sounding will have 128 added to its code. Note that rests must have a code too, since the bottom 6 bits need to stay constant while the note decays; in this case the code itself would be output, a number between 0 and 63. Remember that a 0v connection will also be required between the computer and synthesiser.
The Wasp and Gnat also have digital in/out sockets, but the system used is rather different. Four bits are used to select one of twelve notes in an octave, and two bits specify the octave. Although the Wasp's keyboard is only two octaves, three are available when driving the 'link' socket. The author's Wasp is connected this way:
|DIN plug pin no.||output bit no.|
0v goes to the plug casing. Remember that DIN plug pin numbers do not go in numerical order: refer to the numbers moulded next to the pins if in doubt.
Using the above wiring, the basic codes for the notes (without the gate high) are:
Octave 1 C# to C (a.s.*) : 43 to 32 (n.d.**)
Octave 2 C# to C (a.s.*) : 27 to 16 (n.d.**)
Octave 3 C# to C (a.s.*) : 11 to 0 (n.d.**)
*(ascending scale) **(numbers descending)
Note that firstly, bottom C is not available; secondly, the codes descend whilst the notes ascend, and finally, some codes are not used. To sound a note, 128 should be added to the code as before; but note that when driving the Wasp via the link socket the notes do not sustain, and so long notes are difficult to program. Information on this point would be welcome if anyone has the answer.
One easy way to store a sequence of notes is to split up the passage to be stored into segments of equal duration, each one shorter than the shortest note value. Each segment may then be represented by a single 8 bit byte, and the sequence may be stored as a series of characters in a string array.
Here is a simple program, written entirely in BASIC, which allows entry of notes into an array, and subsequent replay.
10 LET A = 1
20 LET A$ = ""
40 INPUT N$
50 PRINT A;
60 PRINT TAB 6; N$;
70 IF N$ ="R" THEN GOTO 100
80 LET A$ = A$ + CHR$ (VAL N$+128)
90 GOTO 130
100 LET L$ = A$ (A-2)
110 IF CODE L$ > 127 THEN LET L$ = CHR$ (CODE L$-128)
120 LET A$ = A$+L$
130 INPUT L
140 PRINT TAB 12;L
150 LET A$ = A$ + CHR$ L
160 LET A = A+2
170 GOTO 30
200 REM REPLAY
210 FOR I = 1 TO LEN A$ STEP 2
220 FOR J = 1 TO CODE A$ (I+1)
230 POKE 16351, CODE A$ (I)
240 NEXT J
250 POKE 16351, 0
260 NEXT I
270 GOTO 200
Instead of using the 'equal time segment' approach, this program stores the note value and its length as 2 consecutive elements of A$. Lines 10 to 170 allow entry of notes into A$, which expands automatically with each entry, and hence does not need dimensioning. The note is entered first, either as a number between 0 and 63 or the letter R for a rest; note that a rest cannot be the first entry without some fiddling. Lines 100 to 120 also allow for the possibility of entering two rests consecutively.
Next, the note length is entered as a number between 1 and 255; normally, the shortest note in a sequence would be given the value of 1. The program will then step on to the next note; to finish entering, input RUBOUT followed by STOP.
To replay, use the command GOTO 200. The sequence will repeat until interrupted by the BREAK key unless line 270 is deleted. Line 250 is included to retrigger Wasps, and should be omitted if other synthesisers are used.
This program is fairly simple, and so it has no editing facilities; if you make a mistake entering a note, you will have to alter it in a roundabout way afterwards. More seriously, there is no speed control; a PAUSE statement can be inserted as line 235 to slow things down, but in practice the use of BASIC will make things slow enough already.
This is where machine code comes in, as covered in part 1. Next month we will put parts 1 and 2 together to make a fast sequencer program, deal with D to A converters for those of you without digital synthesisers, and hopefully get a bit polyphonic.
Side A Tracklisting:
08:28 Poly ZX81 music.
E&MM Cassette #5 provided by Pete Shales, digitised by Mike Gorman.
Feature by Peter Maydew
Previous article in this issue:
Next article in this issue:
mu:zines is the result of thousands of hours of effort, and will require many thousands more going forward to reach our goals of getting all this content online.
If you value this resource, you can support this project - it really helps!