Over the past few months, the Macromusic series has taken us from the humble beginnings of mainframe computers, squawking at each other in the confines of the Bell Telephone Labs to the high-tech wonder of the Lucasfilm Audio Signal Processor blasting Death Stars and the entire 'little people' population of the United States. Now, it's time to start off on a new tack with more immediate practical relevance, namely the synthesis of sound on microcomputers.
For the moment, though, how one actually gets sounds out of the computer is less important than some decision-making as to the nature of the fundamental parameters that constitute sound, which of these need to be varied in something approaching a musical context, and how all this can be best (or easily) achieved.
The starting point of sound synthesis is a series of operations that define two parameters — the frequency and the duration. Both of these involve the essential ingredient of the passage of time; with frequency, the time factor applies to the gap between one event and the next at the level of something like a waveform, ie. between the amplitude high states of square waves; with duration, we're concerned about a timing level one stage up, ie. between one sound (with a certain timing gap) and the next (with a different timing gap). So, the one crucial aspect of sound that needs working on from the word go is time.
One way to ensure that different frequencies and different durations have their say is by making use of the fact that micros can be very accurate time-keepers if they're given the right kind of instructions. In fact, the fundamental concept to ground level micro music is the 'timed loop', a series of instructions that will be entirely consistent in their speed of execution from one minute, hour, or day to the next. By choosing instructions that will both slow execution times down sufficiently to produce oscillations in the audio frequency range, and also effect, by fair means or foul, an analogue output to the waiting world, one should have succeeded in creating a simple digital oscillator capable of sound synthesis at a basic level.
So, what options are available for projecting a micro's timed loop oscillations to the outside world? Well, early micro music enthusiasts quickly discovered various short-cuts to digital-to-analogue conversion, including some that were ingenious, some that were a dead-end, and some that were just plain quirky. In fact, one particular method didn't even involve a direct connection to the micro! This was based on the chance observation that an AM radio tuned to a spare frequency, placed close to the computer, generated sounds as a result of fast-switching computer logic spewing out harmonics that extended into the radio frequency end of the spectrum. By altering instructions in the timed loop, it was actually possible to vary the timbre of sound, but, because of all the other logic switching going on in the average computer, the technique was incredibly noisy and erratic. In the States, RF interference from micros is considered detrimental to public health (like most things that are fun), and most new micros are now obliged to have some form of shielding against such electro-magnetic interference. However, if you're interested in following up this particular historical avenue, the Apple II and Video Genie micros are both pretty effective at making themselves out to be pirate radio stations in sheep's clothing.
Another early method of making the computer annunciate its timing loops centred around the use of a line printer. Hookups between some early computers and their printers gave the computer software control over the action of the printer hammer. By using a timing loop program again, it was fairly easy to make the hammer buzz away over a limited range of pitches. In fact, you could probably replicate the effect on a modern dot-matrix printer, but it's unlikely that anyone (let alone the printer itself) would really benefit from the exercise.
However, there is a modern alternative that's easily tried out which doesn't involve damage to either ear drums or printing heads, ie. the cassette relay on the BBC Micro. The Operating System command *MOTOR switches the relay on (*MOTOR 1) or off (*MOTOR 0), which, in turn, is designed to similarly switch the cassette motor on and off (though I've yet to hear of anyone bothering to do it). Writing a program that repeatedly toggles between these states will instruct the machine to manically flap the cassette relay, and, like the prior inflictions on the printer hammer, that action manifests itself as sound.
10 REM Hammer-klavier program
20 REM for the BBC Micro
50 DATA 12,11,10,9,8,7,6,5,4,3
60 FOR note%=0 TO 9
70 READ pitch%
80 FOR X%=0 TO 15
90 *MOTOR 0
100 FOR Y%=1 TO 3: NEXT
110 *MOTOR 1
120 FOR Z%=0 TO pitch%*5: NEXT
150 UNTIL 0
This program, simple as it is, actually demonstrates pretty well a couple of salient features about the use of timed loops in a sound synthesis context. Basically, Life, the Universe and Everything centres around three nested FOR... NEXT loops. One of these, namely the delay loop on line 100, in between the *MOTOR commands, is peculiar to this program and simply makes sure that the relay isn't toggled off and on at a rate that's beyond its innate capabilities. On some machines, a fast-responding relay may allow one to get away without it, but, on my machine, the loop has to be there to prevent the thing jamming up.
The loop starting on line 80 determines the number of passages around the relay toggling circuit before going on to the next value of pitch%. So, this delay loop effectively determines the duration of a 'note'. The third loop starting on line 120 is the other important practical realisation of our earlier discussion about basic sound parameters. Using values of pitch% read out of the DATA statement, this sets the gap between one off/on toggle of the relay and another, thereby determining the period between the individual relay clicks and, therefore, the frequency of the resultant buzz.
As it stands, the program merely churns out a repeating (sort of) scalic sequence and flashes the cassette motor LED in time — son et lumièrè a la BBC, one might say. Changing the values in the DATA statement should allow something like a tune to be played provided you can reach some sort of conclusions as to the scaling relationship between d% values and pitch. A potentially useful application of the relay-toggling effect is as an apoplexy-inducing sound effect in games and the like. For instance if you change line 120 to 120 FOR Z%=0 TO pitch%: NEXT and replace the REM statements with
10 ENVELOPE 1,1,127,-21,-1,255,121,231,127,-1,0,-127,126,126
you'll then have invader swoops plus an extremely dirty 'grunging' sound coming from within the innards of your BBC Micro. Not for the faint of heart, though.
Well, red herrings and fun aside, what are the other options for sending some sort of manifestation of timing loop machinations to the outside world? Firstly, there's the option of using the cassette port, which, on some machines, can actually allow a two-way exchange of sound info, in addition to the usual loading and saving of data. Thus, the cassette port on the Apple II has been used for such diverse applications as speech synthesis, speech sampling, two- and three-part music synthesis, sending and receiving tape syncs, producing metronomes and click tracks, and as an input for a software-based spectrum analyzer! Remember that all that's achieved with output and input circuitry which is only capable of sending or reading an on or off state (just like the cassette relay, in fact), ie. giving just a single bit of resolution. Note, however, that not all micros provide anything like a free passage to and from the cassette port (the BBC Micro being a case in point) and there are certain reasons why the Apple is uncannily effective as regards these sorts of tricks.
A second possibility is just to use one of the output port bits, with a speaker connected via some suitable hardware. By then toggling this port with POKEs, the speaker cone can be made to flap itself into square wave ecstasy. For instance, if we were playing around with the ZX81 that's now confined to use as an elegant door stop since we got a BBC Micro, and assuming that the LSB of the output port is our focus of attention (address = 11000), then the following few lines will give you a buzz!
10 REM 777777
30 FOR A=1 TO 200
40 POKE 11000,1
50 POKE 11000,0
60 NEXT A
Again, there's a FOR... NEXT loop (from line 30) that's responsible for establishing the duration of the emerging buzz. We could also insert a further loop, using a sequence of values read out of a DATA statement or two to do something about altering the frequency from time to time. The only drawback to this honourable intention is that, like the sounding-out program using the BBC Micro's cassette relay, the range of frequencies will be limited to something of the order of a few hundred Hertz. In this case, the main problem doesn't lie with a relatively immovable bit of mechanics like a relay but with the limited speed of the ZX81's BASIC interpreter. However, there is a solution to the limited frequency range, and that's to use a machine code subroutine that does all the necessary bit toggling.
One of the other problems with the ZX81 is the lack of input/output port facilities, so it's really a lot more straightforward to toggle the mic output of the infamous cassette port — unless, of course, you've got the necessary port circuitry conveniently to hand (the circuit shown on p.68 of the January 1982 issue of E&MM, for instance) — and most basic music programs for the ZX81 seem to follow this tact.
|NEXT: ||OUT ||(0FFH),A ||;Output contents of accumulator (A) to port |
| ||LD ||B,150 ||;Load register B with delay of 150 |
|LOOP: ||DJNZ ||LOOP ||;Decrement B by 1, if not zero remain in LOOP |
| ||INC ||A ||; increment A by 1 |
| ||JR ||NEXT ||;Return to 1st instruction and output bit value |
So, let's amalgamate the suggestion of toggling the cassette port (for convenience) with the idea of a machine code subroutine (for speed) on a further micro, the Tandy TRS-80: The above assembly language listing operates along the following lines:
1. The contents of the Z-80's accummulator (A) are sent to the cassette port (0FFH). The TRS-80 decodes this port address and sends the LSB of the accumulator to the cassette output circuit.
2. Register B is loaded with a delay value of 150 and then decremented until the result of the operation is zero (the DJNZ instruction). If the result of the operation isn't zero, there's a jump to the location LOOP, which happens to be the same place. With the value set in this instance, the program does this 150 times before moving on, thereby adding the magical ingredient of delay to the proceedings.
3. The accumulator is incremented by 1, with the result that the LSB is obliged to present a pattern of bit values that alternate between 0 and 1 each time round the program loop. So, if the contents of the accumulator were originally 0, the following bit values would be observed:
| ||MSB ||LSB |
| ||0000 ||0000 |
|1st INC ||0000 ||0001 |
|2nd INC ||0000 ||0010 |
|3rd INC ||0000 ||0101 |
|4th INC ||0000 ||1000 |
|5th INC ||0000 ||1001 |
|6th INC ||0000 ||1010 |
4. Finally, there's a return to the start of the routine, and the bit value arising from the current run-through of the program is sent to the cassette port.
With this routine, it's the constant switching of the state of the LSB that causes the cassette port to follow suit by producing a square wave. This time, the delay counter (register B) establishes the time between successive outputs to the cassette port and, therefore, the frequency of the resultant square wave (which is variable between 200 Hz and 20 kHz). Duration control can also be easily added, but this obviously requires a further bit of looping on top of the port toggling routine.
One of the few micros around that makes use of these toggling techniques for its standard repertoire of sound effects is the Apple II. Here, a speaker is connected via a simple amplifier to a flip-flop that can be persuaded to alter its state by performing a read or write operation to a particular address. As with the ZX81, toggling from BASIC is a slow business, and an extremely basic program like the following (from the Apple II User's Guide) doesn't exactly inspire great confidence!
10 REM Click
20 A=PEEK (-16336)
30 GOTO 20
In fact, the highest frequency possible with integer BASIC toggling is about 256 Hz, and, in Applesoft, only gets as far as 72 Hz. So, yet again, it's necessary to turn to a machine code subroutine for anything more than a low-pitched buzz. Bearing in mind that Apples are very fond of beeping at their owners, it's not surprising to find that this sonic shortcoming is met by a short routine in the monitor ROM (at location $FBDD) that produces a beep for .1 sec at 1 kHz.
So far, all we've been concerned with is the setting up of various loops that, a) switch something on and off rapidly and repeatedly, and b) do this for a certain amount of time before doing it again at a different rate. All we're doing here, then, is pulsing something — the cassette relay or speaker, for instance — on and off. What we haven't considered is the proportion that the toggle is in the on state to the off state — and therein lies the means of injecting some timbral variation into the basic scheme for sound synthesis.
The point is that a square wave is just one member of the pulse wave family, ie. where the mark/space ratio (the time spent high in each cycle of the pulse wave) is 50%. Now, by varying the mark/space ratio, the harmonic content of the waveform is also varied, which means that the timbre of sound is altered. So, one method for varying the timbre of toggling is to alter the proportion of time that a bit spends high to low.
This is easily investigated on the Apple II by plugging in a pair of game paddles and entering the following assembly listing from the monitor (CALL -167):
Assembly listing for Apple II.
|0300- ||A2 ||00 || ||LDX ||#$00 ||;Load register X |
|0302- ||20 ||1E ||FB ||JSR ||$FB1E ||;Jump to paddle-reading routine (value to X plus accumulator scrambled) |
|0305- ||8D ||30 ||C0 ||STA ||$C030 ||;Accumulator contents to location $C030 (speaker toggle) |
|0308- ||E8 || || ||INX || ||;Increment X |
|0309- ||20 ||1E ||FB ||JSR ||$FB1E ||;Jump to paddle-reading routine |
|030C- ||8D ||30 ||C0 ||STA ||$C030 ||;Accumulator contents to $C030 |
|030F- ||4C ||00 ||03 ||JMP ||$0300 ||;Jump back to beginning of routine |
Running the routine (300G (CR) from the monitor, or CALL 770 from BASIC), whilst twiddling the paddles, is a pretty effective demonstration of the range of timbres possible just by varying the pulse width. What's happening here is that the routine is using the paddles to vary the delay between the speaker toggles, with one paddle setting the proportion of time spent high, and the other the proportion spent low. Obviously, there's a good deal of scope for extending this routine to more fruitful use than variations on Apple bleeps, and one example that's worth investigating is the variable timbre music utility in the Programmer's Aid supplement to Integer BASIC.
Next month, we'll look at ways of escaping from the limitations of pulse wave toggling with that mainstay of quality digital synthesis, the digital to analogue converter.