
The ZX81-type computers don’t have “sound”, right? WRONG! If you ever listened to a tape of a computer program, you’ll realize that it is capable of screechy noises that could loosely be called “sound”. Well, ok, but it isn’t capable of music, right? WRONG AGAIN! If you’ve envied those other machines that can beep out a tune or a laser sound, there’s no longer any need to feel left out.
The machine-code program presented here gives you three octaves of sound. As listed, the lowest note is the “A” just below “Middle C”, but you can move your spectrum up or down as desired. But that’s not all; it is easy to write and play music using only BASIC commands, thanks to a built-in “music interpreter”. No, you can’t do multiple voices (though you can simulate two voices as shown in the demo), and you can’t vary the envelope or modify the waveform. Also, since it has to run in FAST mode, you can’t see your display screen while the music is playing (unless you have the Oliger video upgrade). Still, this relocatable machine code routine might be just what the conductor ordered to tune in your BASIC software.
You won’t need much in the way of hardware. If you wish, you could connect a mic-level amplifier/speaker to the MIC output. Alternately, simply use your cassette deck. Connect the MIC from the computer to the MIC jack on the recorder, and connect an earphone or a small speaker to the EAR jack of the recorder. Then take an old useless cassette, and cut or remove the tape, making a “dummy” cassette. This will allow you to place the recorder in RECORD mode without actually recording anything. Or you could, of course, simply save to tape, and listen to your computer sonata after it has been recorded. Finally, a cheap AM radio located near the computer might pick up the sound, but with reduced quality.
The machine-code routine takes up exactly 256 bytes. It is fully relocatable, so you can place it anywhere you want. A good place is in a 1 REM statement; this article will assume that this is where you’ll want to put it, but remember that you can move it elsewhere if you wish. Simply change the LET BEEP= statement to match the start of your code.
Also required is 144 bytes after the code, for the frequency/duration lookup table. So start by entering a 1 REM followed by 400 X’s or other character. Use a POKER program such as LISTING 2 of the “Kaleidoscope” article in TDM Vol.3 No.1 You only have to change line 3 to read: FOR A=16514 TO 16769. RUN your loader program, and enter the decimal values given in TABLE 1. When you’re done, delete the loader lines and enter line 2 and lines 9000-9991 of LISTING 1. (Incidentally, if your listing gets stuck at line 1, be sure you have a line 2, then LIST 2 followed by POKE 16419,2.) RUN 9000 to generate the data table. When it stops, enter CONT to fill the rest. Line 9170 may be removed to speed up the process; it is included to show the significance of the various data elements.
Each entry in the table corresponds to one of the 36 possible notes, and consists of four bytes. The first two give the “delay constant” that determines the frequency of the note. (We call this “BC” in the program since this is the register pair used for this purpose.) The second two bytes give the number of cycles required for each note, at the minimum possible duration.
The signal generated by the routine is perfectly symmetrical (50% duty cycle). The minimum ON and OFF time (BC=1) is 199 “T” states, and each increment of BC increases this time by 26 T states. Though the actual clock frequency is 3.5 mHz., the “effective” clock frequency of the machine is 3.192 mHz. (T-states per second) because of the keyboard-sensing routine in the non-maskable interrupt. So, the frequency outputted will be:
“` FREQ=3.192E6/(2*(199+26*(BC-1))) “`
By transposing this equation, the value of BC for a given frequency is:
“` BC=1 + ((1.596E6/FREQ)-199)/26 “`
Each note (half-step) will be a fixed ratio higher in frequency than the previous one. Since there are 12 half-steps per octave, and each octave represents a doubling in frequency, this ratio is 2^(1/12). The program calculates each frequency using this ratio, and prints is as the first entry in the screen table. From this is calculates BC, and then “back-calculates” the ACTUAL frequency which will result. (Since only integer values can be POKEd, there will always be some imprecision in the actual frequency.) Finally, it calculates how many cycles of each note are required for the minimum time interval (sixteenth note at the fastest tempo).
The “A” below Middle C is defined at 440 Hz. You can move your scales up or down by changing line 9040. For example, to move it down an octave LET FREQ=220, to move it up an octave, LET FREQ=880, etc. You can even transpose music to different scales by using other values. For instance, if you wrote a piece in the key of C and wish to transpose it to E (4 half-steps higher), simply define your lowest “A” to C# (LET A=554.4).
When experimenting with different ranges, you should be aware that the lower notes, the more accurate the PITCH becomes. On the other hand, the higher notes, the more accurate the DURATION becomes.
After generating your table, enter the rest of LISTING 1. Then RUN for a demo. Enter the desired tempo (more about that later). A good tempo for the first music demo (line 100) is 180. Press any key when done for the second part (line 200) which gives a “laser” effect. Again press any key for a “siren” demo (line 300). Finally, press a key to play the second music demo (line 400), which show how you can simulate two voices! A suggested tempo for this is 240. Pretty neat, eh?
Here’s how “MoTSart” is used. Your BEEP command must always be of the form: IF USR BEEP THEN… where BEEP has been initialized to the start of the program (16514 in this case). What follows the REM are your musical commands and data. COMMANDS include semicolons “;” and commas “,”.
A SEMICOLON is used to set the duration of the following notes. After the semicolon command must be a number or letter (1-G), defining duration as follows:
1 – Sixteenth note
2 – Eighth note
3 – Dotted eight note
4 – Quarter note
6 – Dotted quarter note
8 – Half note
C – Dotted half note
G – Whole note

Values inbetween will give other (unorthodox) note durations. Once the duration has been set, it will remain in force until changed by another semicolon command.
COMMAS are used to tell MoTSart that a note or rest is to be played. If the comma is followed by a single space, a rest is played. Otherwise, you must follow the comma with a number (1-3) specifiying the octave, followed by a letter (A-G) specifying the note (pitch). The note letter may be followed by “accidentals”, represented as “+” for sharps and “-” for flats. Note that you can’t flat the lowest A (1A), or sharp the highest G (3G).
The only other command is to set the overall TEMPO. This is done using the RAND command. See the demo; simply RAND 1200/(desired tempo). The number you divide into 1200 represents the number of beats (quarter notes) per minute; 120 represents two quarter-notes per second, or one 4/4 bar every two seconds.
MoTSart generates its own error codes. Error R means that you are trying to use a non-valid way of calling the routine, or don’t have a REM after the IF USR BEEP THEN. Error (inverse semicolon) means that a SEMICOLON command (duration) is out of range. Finally, all other errors are trapped with error (inverse comma), which means that a COMMAN command is incorrectly formatted or out of range. If you get a semicolon or comma error report, the offending character in the line is flagged by turning to inverse video. BEWARE of errors right at the end of the line! If this happens, the end-of-line marker gets POKEd out, causing the next line to be “strung” together with your BEEP line. If you’re not careful and manage to fall into this trap, DO NOT try to edit the line! The best thing to do in this case is to delete the line and re-enter it from scratch.
Now you and your ZX/TS can make beautiful music together!

TABLE 1: MOTSART DECIMAL DATA
33 0 1 9 34 118 64 205 35 15 42 22 64 35 126 254 234 40 2 207 26 35 126 254 25 32 20 35 126 214 29 254 16 56 6 126 203 255 119 207 124 60 50 9 64 24 230 254 118 200 254 26 40 6 126 203 255 119 207 125 35 126 167 32 24 58 9 64 95 58 50 64 87 1 45 6 11 120 177 32 251 21 32 245 29 32 238 24 188 214 29 254 3 48 215 245 35 126 214 38 254 7 48 206 167 40 23 254 3 48 3 60 24 16 32 4 198 2 24 10 254 6 40 4 198 3 24 2 198 4 79 35 126 254 21 32 3 12 24 8 254 22 32 3 13 24 1 43 241 71 128 128 7 7 129 254 255 40 151 254 54 40 147 79 6 0 229 42 118 64 9 9 9 9 78 35 70 35 94 35 86 225 24 2 24 157 175 245 175 245 197 211 255 11 120 177 32 251 245 0 62 0 62 7 61 32 253 241 193 197 219 254 11 120 177 32 251 193 241 60 253 190 9 40 11 35 43 245 62 3 61 32 253 241 24 209 241 60 253 190 50 40 6 221 9 0 0 24 194 27 122 179 32 188 24 184
LISTING 1: BASIC
Note: line 350 is the same as line 240. Line 245 ends with a space.
“` 2 REM MOtsART 3 GOTO 100 10 REM SET TEMPO SUBROUTINE 20 PRINT “TEMPO?” 30 INPUT TEMPO 40 RAND 1200/TEMPO 50 LET BEEP=16514 60 RETURN 80 REM WAIT SUBROUTINE 90 IF INKEY$=”” THEN GOTO 90 95 RETURN 100 REM MUSIC DEMO 105 GOSUB 10 110 IF USR BEEP THEN REM ;8,2E; 5,2G;1, ;2,2G;C,2C;4,2D,2E,2F,2G ,3A;G,2D;8,2E;5,2F+;1, ;2,2F+;C, 2G;4,3A;3,3B;1,2B;4,3B;3,3A;1,2A ;4,3A;C,2G;2,2D,2E;6,2F;2,2E;4,2 D;2,2E,2F;6,2G;2,2F;4,2E;2,2F,2G ;4,3A,2G,2F,2E;B,2D;1,1D;2,2D,2E ;6,2F;2,2E;4,2D;2,2E,2F;6,2G;2,2 F;4,2E,2C,2D,2G;2,2G,2F+,2E,2F+; G,2G 120 IF USR BEEP THEN REM ;8,2E; 5,2G;1,1G;2,2G;G,2C;6,2F;2,2F;6, 3A;2,3A;G,2D;8,2G;6,2G+;2,2G+;4, 3A,2F,2E,2D;8,2C,2D;G,2E;8,2G;6, 3C;2,3C;4,3A,2F,2E,2D;8,2G,2B;G, 2C;2,2C,2E,2G,3C,3A,2F,2D,2E-,2E ,2G,3A,3B,3C;3,2C,1C 130 GOSUB 80 140 CLS 150 PRINT “RUN AGAIN?” 160 PAUSE 4E4 170 IF INKEY$=”Y” THEN GOTO 100 200 REM LASER 210 LET TEMPO=1200 220 GOSUB 40 230 FOR N=1 TO 10 240 IF USR BEEP THEN REM ;1,3G+ ,3G,3F+,3F,3E,3E-,3D,3C+,3C,3B,3 B-,3A,2G+,2G,2G-,2F,2E,2E-,2D,2C +,2C,2B,2B-,2A,1G+,1G,1G-,1F,1E, 1E-,1D,1C+,1C,1B,1B-,1A 245 IF USR BEEP THEN REM ;G, 250 NEXT N 260 GOSUB 80 300 REM SIREN 310 LET TEMPO=600 320 GOSUB 40 330 FOR N=1 TO 5 340 IF USR BEEP THEN REM ;1,1A, 1A+,1B,1C,1C+,1D,1D+,1E,1F,1F+,1 G,1G+,2A,2A+,2B,2C,2C+,2D,2D+,2E ,2F,2F+,2G,2G+,3A,3A+,3B,3C,3C+, 3D,3D+,3E,3F,3F+,3G,3G+ 350 IF USR BEEP THEN REM ,3G+,3 G,3F+,3F,3E,3E-,3D,3C+,3C,3B,3B- ,3A,2G+,2G,2G-,2F,2E,2E-,2D,2C+, 2C,2B,2B-,2A,1G+,1G,1G-,1F,1E,1E -,1D,1C+,1C,1B,1B-,1A 360 NEXT N 370 GOSUB 80 400 REM SIMULATING 2 VOICES 410 GOSUB 10 420 GOSUB 470 430 GOSUB 480 440 GOSUB 480 450 GOSUB 470 460 IF USR BEEP THEN REM ,2C,1E ,2C,1E,2C,1E,2C,1E;4,2C 465 GOSUB 80 466 STOP 470 IF USR BEEP THEN REM ;1,2C, 1E,2C, ,2C,1E,2C, ,2G,2E,2G, ,2G ,2E,2G, ,3A,2F,3A, ,3A,2F,3A, ,2 G,2E,2G,2E,2G,2E,2G, ,2F,2D,2F, ,2F,2B,2F, ,2E,2C,2E, ,2E,1G,2E, ,2D,2A,2D, ,2D,1F,2D, ,2C,1E,2C ,1E,2C,1G,2C,1G 475 RETURN 480 IF USR BEEP THEN REM ,2G,2C ,2G, ,2G,2E,2G, ,2F,2D,2F, ,2F,2 B,2F, ,2E,2C,2E, ,2E,1G,2E, ,2D, 2A,2D,2A,2D,2B,2D, ;1 485 RETURN 9000 REM SET UP DATA TABLE 9010 CLS 9020 PRINT “FREQ BC ACTUAL NO .CYCLES 9030 LET BYTE=16770 9040 LET FREQ=440 9050 LET RATIO=2**(1/12) 9060 FOR N=1 TO 36 9070 LET BC=INT (((1.596E6/FREQ) -199)/26+1.5) 9080 IF BC=0 THEN LET BC=1 9090 LET ACT=1.596E6/(199+26*(BC -1)) 9100 LET DE=INT (ACT*.013+.5) 9110 RAND BC 9120 POKE BYTE+0,PEEK 16434 9130 POKE BYTE+1,PEEK 16435 9140 RAND DE 9150 POKE BYTE+2,PEEK 16434 9160 POKE BYTE+3,PEEK 16435 9170 PRINT (INT (FREQ*10+.5))/10 ;TAB 7;BC;TAB 11;(INT (ACT*10+.5 ))/10;TAB 18;DE 9180 LET BYTE=BYTE+4 9190 LET FREQ=FREQ*RATIO 9200 NEXT N 9210 STOP 9990 SAVE “MOTSARt” 9991 GOTO 100 “`