Micromusic: Notate your Nascom
A series that focuses on using the popular microcomputers for making music.
Notate Your Nascom: How to play tunes without any extra hardware except an audio amplifier and speaker
Programming in machine code is difficult, so they say. But why do I find it so addictive? Once I start to work out a problem, it occupies my mind in every spare moment. It is like the challenge of a good puzzle, but much more satisfying because the solution is useful and leads on to other things. After building a Nascom 1, I spent a great deal of time in devising programs for playing tunes with controllable waveforms, using a little extra hardware. Even on Christmas Day, I was sitting down with a pencil and paper in a few odd moments!
This Nascom is being developed to control equipment in the Electronic Music Studio of The City University, where music students are given experience in recording and compositional techniques. It has 32K memory; a 16-channel analogue output board to 12-bit precision; 2K Tiny BASIC (surprisingly useful for controlling the analogue output board); ZEAP assembler; V&T Electronics cassette deck, about to be installed; and a PIO board on order. One of the things we hope to do soon is 'place' sounds accurately in a multi-speaker environment, using the analogue board to control a bank of VCA's. There are many other ways we could use the equipment, the main difficulty being to find enough time and manpower to build and develop hardware, and write software.
Although some personal computers have an optional tune-playing routine using a squarewave signal from just one line of an output port, I have never seen one for the Nascom. When the Scamp was launched, several years ago, I collected some information on a music program for a hand-held 'toy' which was demonstrated. It completely flummoxed me at the time, as there was far too much jargon for a beginner to take in at the meeting (what were these 'flags' they kept talking about?) and the music program wasn't explained. Yet it is the only simple tune program I have managed to acquire.
Probably this will cause a flood of letters from readers saying I haven't done a proper search, and these routines are available all over the place, for any system. If so, I'm sure E&MM will be pleased to publicise them!
Here, then, is my method of playing tunes on a Nascom 1, devised in some more spare moments (over Easter!) before assembly. It goes without saying that machine code is essential, for both speed and timing. There is plenty of room even in the 4K version, as the playing routine occupies only 54 (hex) bytes, and tunes can be fitted into the remaining RAM taking only 2 bytes per note.
The playing routine, as shown in the listing, starts at 0E00. It may be better to move it to 0F00, making room for a longer tune sequence from 0C50; I didn't use this because the ZEAP assembler uses 0F00 onwards, making the task a little easier than my earlier efforts, which were all hand-assembled. If this move is made, the two CALL instruction bytes at 0F1D and 0F30 must be changed from 0E to 0F. All other jumps are relative, so need no alteration. The £ sign in the listing indicates that the number which follows is hex, as required by ZEAP. Most assemblers use a following H, but one soon gets used to this alternative. I have omitted the line numbers which ZEAP incorporates, and typed the listing by hand as I wasn't able to get the use of a printer at the time of writing this article.
Anyone who wants just to use the routine can type in the second column of the listing at the addresses indicated in the first column. Then a 'tune table' needs entering at 0C50. A sample tune is shown in Table 1; this has slight relevance to an event due to take place in July. The tune table consists of pairs of bytes, the first in each pair being a duration code and the second one a note frequency code.
Also needed, of course, is connection to an audio amplifier, earpiece or headphones. The program produces identical squarewaves on all lines of Port 4, so any one of these may be connected through a volume control (a 5-volt squarewave is a very large signal) to the amplifier or high-impedance 'phones.
Finally, the program will run when executed from 0E00, and return to the Nascom monitor afterwards.
Usually people like to write their own tunes, so the codes for notes and their durations have to be looked up from Table 2. The duration is entered first. Values given are approximate, since the duration varies with the note for unavoidable reasons explained later. I have shown the values I used in the sample tune; those at the left form a basis for notes formed of minim, crotchet, quaver, semiquaver and demi-semiquaver, for instance, with their dotted versions at the right. The first note in the sample tune has 30, indicating a duration of about ⅜ second, and 59, indicating the note F above middle C.
For those who would like to know how the program was devised, here is my approach.
We start by assuming that a squarewave signal is required, i.e. the line is set high and low for equal times alternately. This uses the OUT instruction in a Nascom system. To get the required frequency, we must arrange to have a controlled time delay between the OUT's. In the absence of an external timer chip and interrupt system, this has to be done by software loops, requiring knowledge of how long each instruction takes. The OUT instruction itself takes 11 cycles of clock frequency, or 5½ microseconds.
At this stage, where we start to look at Z80 instructions, it is necessary to be aware of the range of instructions, what they will do, and the timing. I have found a Zilog booklet, giving the instructions in condensed form, immensely helpful here, but the manufacturers' programming manual gives more detail, as do several books on Z80 programming. One strange-look-ing instruction which is very useful is seen at address 0E3F of the listing: DJNZ L00P1. This decrements the B register and gives a jump if not zero. The mnemonic doesn't obviously relate to the B register - you just have to know this. The jump would take 13 cycles, but no jump, i.e. program continues, takes only 8.
To get a variable delay, we enter a number into a register and subtract 1 at regular intervals. When the result is zero, the delay is ended. So the B register is often used for this purpose. As it turns out in this case, it is better to use it as an inner timing loop with the main count-down in an outer loop, because if we increment the number initially loaded into B we get an extra 13 cycles of delay, whereas using any other register would take longer. The outer loop here counts down using the accumulator, which is loaded first with the frequency code.
Since the delay is proportional to this code, good resolution and accurate frequency generation require it to be as large a number as possible. Hence we try to use FF hex, or 255 decimal, or near to it, for the longest half-cycle period, which is at the lowest frequency we are to use. (256 is possible, but we reserve the maximum code of 00 to indicate a pause. Why does 00 give a maximum delay? I leave the reader to spot this.) My note table was chosen to span three octaves up from C at 130.8Hz, which has a corresponding periodic time of 7645 microseconds. If we now divide 7645 by 255 we get the length of time to be occupied by the counting loop. A greater length means the divisor is reduced, spoiling the resolution; but a smaller length means we can't get the full period at the lowest frequency. So 7645/255 is in fact a minimum, and it comes to almost exactly 30. We should aim, then, at getting a counting loop as near as possible to 30 microseconds per cycle; but since these will be used twice in every cycle in the countdown, we need 30 'T cycles' (Z80 clock cycles) in each half-period, with the Nascom 1's 2-MHz clock.
The loop shown between addresses 0E3D and 0E42 takes 31 cycles, as can be seen by adding the four cycle lengths for these instructions. The DJNZ line is a little luxury which enables a single change in the previous line to slow down the tune, although it also reduces the pitch. I found this very useful in checking my sample tune, which I loaded with one or two errors; changing 0E3E from 1 to 3 made it play more slowly, and I was able to spot the errors more easily. This change added 26 cycles to the delay loop.
Loading A at the beginning of DELAY, and returning from the subroutine, take an extra 7 and 10 cycles respectively, so the total delay is 17 + 31*n cycles, where n is the note frequency code.
Having got the note sounding, we need a method of deciding when to end it. One possible method is to decrement a counter by 1 after each cycle, but this would mean a different delay code for each note frequency, and would be very trying to enter. The Scamp system I mentioned earlier did this, in order to shorten and simplify a ROM program in a device not intended for flexibility.
There is a simple alternative. Each cycle lasts for 31*n microseconds, ignoring a small (we hope) extra time for loads and tests which we will check later. So let us subtract n from the duration code after every cycle; we have then subtracted effectively the length of the cycle from the code, measured in 31-microsecond units. The duration code must then be simply the number of 31-microsecond periods required. Since we need a large number, to get notes of about a second in length, the duration code is made the high-order byte 'd' in a 16-bit number with low-order byte zero. Then d*256*31 is the duration in microseconds. For example, 30 hex gives 380928, or 0.38 second. Duration is loaded into the Z80's register pair DE at programme steps 0E07 to 0E09.
The subtraction routine is done at TEST. To do a 16-bit subtraction, we must use the HL pair - but this is also used as a pointer to the tune table and we have to save its previous contents. What a beautiful instruction EX DE, HL turns out to be! It not only gets DE into HL but saves the pointer in DE until we are ready to swap back. It couldn't be more tailor-made for the job. All this takes 47 cycles, however, so we waste 47 cycles in the first half-cycle to preserve our squarewave, although we needn't really do so as square-waves aren't all that interesting.
The true half-cycle time can be calculated now, allowing for 35 cycles to output high or low and call delay; 47 for test or balance; and delay itself, 17 + 31*n. The total is 99 + 31*n, giving a full-period time of 99 + 31*n microseconds. The required values of n for the different frequencies are calculated from equating this formula to the period time. The result is about 3 less than the number of 31-microseconds loops in the period, and leads to the values in Table 2.
Pauses are as important in music as sounds. To simplify coding, it is best to use the same duration code for a pause as for a note. Since every subtraction in the note routine corresponds to n*31 microseconds, we need a 31-microsecond loop in PAUSE, i.e. 62 T-cycles, and count this down by ordinary decrements instead of subtractions. Note another quirk of the Z80: decrementing DE doesn't set any flags, so we have to test for zero by ORing D and E.
We can now check the inaccuracies caused by the load and test parts of the cycle. At the top of the musical scale, we have a large number of cycles in a given duration, so the duration is extended more than at the bottom by the fixed 99 microseconds per cycle. Taking extremes: top C, with duration 30 hex for example, will have INT (48*256/27) cycles, i.e. 455 cycles, each of which takes 99 + 27*31 = 936 microseconds, giving a total duration of 425880 microseconds. Bottom C, on the other hand, with the same duration, will have only 50 cycles of 7632 microseconds, giving a total of 381600 microseconds, which is about 10% less. The error is not noticeable for most purposes; if it proves to be, it is still easier to make a small adjustment to a duration code than to have a different one for every note.
For convenience in a first approach, I have classified code 30 as giving ⅜ second in the duration table. This is slightly low compared with the figures I have just shown, but is within about 2% at the bottom end of the range.
Finally, we may be stimulated into thinking how to alter and improve things. Can we alter speed without altering pitch? At step 0E32, the duration code is decremented. We could decrement faster by repeating this instruction, more than once if wanted, and this would speed the playing. I haven't tried it because it means reassembling the following program. And inserting a counting loop round this instruction slows the basic program. We could also write a program to multiply or divide all the duration codes in the tune table. We could experiment with pulse waveforms. We could have a sequence table, which plays several tunes or parts of tunes in the order specified. We could interact with BASIC to make loading even easier, generate random sequences, or compose. And so it goes on. Is anyone else hooked?
Feature by Don Finlay
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!