After experimenting with the programs in Chapter 8, you will probably have come to the conclusion that the QWERTY keyboard is not the easiest musical instrument to play. If you have tried to play a three-part or even a two-part tune with any degree of accuracy, you will be aware of the restrictions the keyboard places upon us. It's fine for improvising and playing along with a rhythm or bass pattern, but playing true multi-part tunes is extremely difficult.
We can overcome these problems by providing the computer with all necessary information regarding pitch, envelopes, durations, etc, and letting it get on with the hard work of putting them together. It can play sequences and harmonies we would never be able to imagine and it will play them right every time.
The simplest and most obvious method is to read the information into the program through DATA statements: this method is used in most of the programs in this book. For short musical examples this is fine, but it soon becomes quite complicated when you have more than 20 or 30 items of data. This is where Program 3.1 can help by converting note names and octave numbers into pitch values. This permits us to enter data in a way more comprehensible to us and it makes subsequent editing considerably easier. If you include error routines it will also report instances of incorrect data.
Another problem arises if we want more than one channel to sound at once, namely that of synchronization. If we just want to play a single melody line there are no problems. This short program will do exactly that:
10 FOR Note=1 TO NumberOfNotes
20 READ Env,Pitch,Dur
30 SOUND1,Env,Pitch,Dur
40 NEXT Note
50 DATA E1,P1,D2,E2,P2,D2,E3,P3 etc
If we want to play more than one channel at a time we need to use a slightly different method. We mentioned this in earlier chapters and now we will examine it in detail.
Because of the sound queues used by the BBC micro, the sound channels seem to race ahead of the rest of the program. We found this in Program 6.5 where we had to hold up the program with a TIME loop before redefining the envelope.
The optional synchronization parameter of the SOUND command can be used to delay a sound channel until another channel appears with the same synchronization value. However, the organisation of information to each channel must be carefully controlled if the program is not to seize up.
Problems arise when two or three voices are to be played together and each voice contains notes of different durations. If we try to read data for two channels from one DATA statement, each time the data is read it must supply information for both channels. This means we would have to organise the data so that the two sets of notes end up with the same number of data items. This is a time-consuming and inconvenient process. With three-part tunes the problem is even worse as one channel will probably only be playing one note to the others' four or more.
The most obvious answer is to fill arrays, one for each channel, with the relevant data, and only read from a particular array either when a note is required or when the sound channel is capable of taking it without holding up the program.
Figure 9.1
As an example, Figure 9.1 shows the first eight bars of Mozart's Rondo Alla Turca. You can see that, by the time we reach the end of bar 3, 24 notes will have passed through channel 1 and only eight through channel 3. If these were queued together, the program would be held up waiting for the notes on channel 3 to execute. We can solve this in two ways:
(1) By keeping track of the relative durations of each channel and releasing data accordingly.
Or
(2) By only releasing data when the channels can take it at a time determined by the negative ADVAL function.
Before beginning our experiments, let us see how we can best organise the data.
The programs presented here read note information from DATA statements. This gives us immediate visual and physical access to the information which is saved along with the program.
When programming a musical score, the first thing we must look at is the range of notes it uses. This example ranges from B an octave below middle C to C two octaves above middle C. This is fortunate in that we can use the correct notes and octaves produced by the sound chip and illustrated on the keyboard in Figure 2.4. It does mean that we need to transpose or mentally shift the keyboard up an octave to make middle C correspond to the middle C on the stave. This is not as complicated as it might seem. If you enter the notes as names and octave numbers, read down from the stave to the note name and octave number and, if you want it lower, subtract 1 from the octave number.
An alternative, as we mentioned in Chapter 3, is to enter the notes exactly as they read, eg middle C as 101, and use the variable, Key, to take the piece down an octave. Using this method, of course, you can play the tune in any key at all. I decided on the first method.
The second eight bars of Rondo Alla Turca (not illustrated) uses A an octave below middle C. In this case you would use the keyboard and octave numbers exactly as set out in Figure 2.4, an octave higher than written.
Another solution is to 'cheat' by substituting a note within your range for the A. If only one or two notes are outside the range this will usually produce acceptable results and I prefer to do this rather than take the whole tune up an octave. Musically (and, perhaps, sardonically) this is known as doing an arrangement.
I have stated before my preference for the lower octaves. I tend to program as low as possible. These notes are richer and more full than the high ones, but use whichever range suits you and the music best. Not all pieces will benefit by being so low.
I normally arrange the channels as follows:
Channel 1: | Melody line. |
Channel 2: | Bass line. |
Channel 3: | Anything in between, often reinforcing the bass/accompaniment |
The melody and bass fines are the most important and set the character of the piece. I use channel 3 to fill in the harmony where required, and it can switch from bass to melody as necessary. If the piece has a prominent bass fine, I may sometimes program that first into channel 1.
The next program uses only one channel to play a melody. You may prefer not to type it in and use the listing only as a reference as we will be moving on to a more sophisticated program. However, as we will be using the data and PROCAnalyseNote in the other program, if you type it in, it will not be wasted.
This raises another point worthy of consideration, which is - the programs need only be as complicated and sophisticated as you require. If you only want a monophonic tune, this program will do fine.
10 REM PROGRAM 9.1
20 REM 1 Channel Version of Mozart's
30 REM Rondo Alla Turca
40
50 Scale$=" C C# D D# E F F# G
G# A A# B"
60 Key=1
70
80 ENVELOPE1,1,0,0,0,0,0,0,126,-2,0,-
10,126,100
90 CurrentEnv=1
100
110 FOR N=1 TO 46
120 READ Note$,Dur
130 PROCPlayNote
140 NEXT N
150
160 END
170
180 DATA B2,2,A2,2,G#2,2,A2,2
190 DATA C3,4,R,4,D3,2,C3,2,B2,2,C3,2
200 DATA E3,4,R,4,F3,2,E3,2,D#3,2,E3,2
210 DATA B3,2,A3,2,G#3,2,A3,2,B3,2,A3,
2,G#3,2,A3,2
220 DATA C4,8,A3,4,C4,2,B3,1,A3,1
230 DATA B3,4,A3,4,G3,4,A3,2,G3,1,A3,1
240 DATA B3,4,A3,4,G3,4,A3,2,G3,1,A3,1
250 DATA B3,4,A3,4,G3,4,F#3,4
260 DATA E3,8
270
280 DEF PROCPlayNote
290 PROCAnalyseNote
300 PRINT Note$,Pitch,Octave
310 SOUND1,Env,Pitch,Dur
320 ENDPROC
330
340 DEF PROCAnalyseNote
350 IF Note$="R" Env=0:ENDPROC ELSE En
v=CurrentEnv
360 IF LEN(Note$)<2 OR LEN(Note$)>3 TH
EN PRINT"ERROR IN DATA ";Note$:PRINT"Not
e Number ";N:STOP
370 IF LEN(Note$)=2 THEN NoteName$=LEF
T$(Note$,1) ELSE NoteName$=LEFT$(Note$,2
)
380 Octave=VAL(RIGHT$(Note$,1))
390 Pitch=Key+INSTR(Scale$,NoteName$)/
3*4+(Octave-1)*48
400 IF Pitch<0 OR Pitch>255 THEN PRINT
"ERROR IN PITCH DATA ";Note$;" Pitch = "
;Pitch:PRINT"Note Number ";N:STOP
410 ENDPROC
This is very similar to Program 3.1. PROCCa1culatePitch has been incorporated into PROCAnalyseNote and the tune is under envelope control. Line 350 sets the volume to 0 if a rest is required. Extra information has been included in lines 360 and 400 to print the note number in case of an error. The data has been organised into a bar per fine to aid editing and debugging.
The main points of the program were discussed in Chapter 3 and, from here, we will move straight on to a three channel version and discuss some options open to us.
This entails keeping track of the elapsed durations of the three channels. When a SOUND command is executed the duration value is added to its 'track'. The program is arranged to execute the SOUND commands as follows :
You may like to work out a program which follows the above guidelines. In operation, you will find that the buffers are usually full and consequently the program will be unable to do anything other than play the tune. Why should that matter? Well, if that's all you want to do, fine, but the BBC micro is capable of (apparently) doing more than one thing at a time. If the programming is not too long or complex it is no hardship to program for that possibility. By using, once more, the negative ADVAL function we can do just that.
ADVAL with a negative argument is described quite clearly in the User Guide on page 203. If we fill two or three arrays with the required notes and use ADVAL to send SOUND commands only when a channel has space to take them, this will be enough to synchronize the music.
To control every aspect of a note we need to specify not only its pitch and duration but also which envelope it is to use and whether or not the sound channel is given a flush, synchronization or hold command. It takes no computer to calculate that each note would require four items of data. This in itself presents no problem providing you do not object to entering it.
The next program gives control over the four elements of each note. In practice, it is possible to devise some time-saving procedures, which I have done in certain cases - and you will find that you rarely have to enter four data items for each note.
10 REM PROGRAM 9.2
20 REM 3 Channel Version of Mozart's
30 REM Rondo Alla Turca
40 REM Using a Single Array for
50 REM Each Channel
60
70 REM C1=Number of Notes for
80 REM Channel 1 etc
90 C1=46:C2=30:C3=29
100
110 REM 1st Subscript Refers to:
120 REM 1 - Channel Number/Attributes
130 REM 2 - Envelope Number
140 REM 3 - Pitch Value
150 REM 4 - Duration
160
170 DIM Chan1(4,C1)
180 DIM Chan2(4,C2)
190 DIM Chan3(4,C3)
200
210 Scale$=" C C# D D# E F F# G
G# A A# B"
220 Key=1
230 Tempo=1
240
250 ENVELOPE1,1,0,0,0,0,0,0,126,-2,0,-
5,126,100
260 ENVELOPE2,4,0,0,1,1,0,1,63,-1,0,-1
0,126,100
270 ENVELOPE3,1,0,0,0,0,0,0,126,-4,-1,
-4,126,100
280
290 REM Channel 1
300 FOR N=1 TO C1
310 PROCChan
320 Chan1(1,N)=Chan
330 IF Note$="R" Env=0 ELSE IF N=5 OR
N=11 OR N=25 OR N=30 OR N=36 OR N=42 Env
=2 ELSE Env=1
340 Chan1(2,N)=Env
350 PROCAnalyseNote
360 Chan1(3,N)=Pitch
370 Chan1(4,N)=Duration
380 NEXT N
390 PRINT"Channel 1 Complete"
400
410 REM Channel 2
420 FOR N=1 TO C2
430 PROCChan
440 Chan2(1,N)=Chan
450 IF Note$="R" Env=0 ELSE Env=3
460 Chan2(2,N)=Env
470 PROCAnalyseNote
480 Chan2(3,N)=Pitch
490 Chan2(4,N)=Duration
500 NEXT N
510 PRINT"Channel 2 Complete"
520
530 REM Channel 3
540 FOR N=1 TO C3
550 PROCChan
560 Chan3(1,N)=Chan
570 IF Note$="R" Env=0 ELSE Env=1
580 Chan3(2,N)=Env
590 PROCAnalyseNote
600 Chan3(3,N)=Pitch
610 Chan3(4,N)=Duration
620 NEXT N
630 PRINT"Channel 3 Complete"
640
650 Ch1=0:Ch2=0:Ch3=0
660
670 REPEAT
680 IF ADVAL(-6)>0 AND Ch1
(3,Ch1),Chan1(4,Ch1)*Tempo
690 IF ADVAL(-7)>0 AND Ch2
(3,Ch2),Chan2(4,Ch2)*Tempo
700 IF ADVAL(-8)>0 AND Ch3
(3,Ch3),Chan3(4,Ch3)*Tempo
710 UNTIL Ch1=C1 AND Ch2=C2 AND Ch3=C3
720
730 END
740
750 DEF PROCChan
760 READ Note$:IF LEFT$(Note$,1)="&" C
han=EVAL(Note$):READ Note$,Duration ELSE
Chan=0:READ Duration
770 ENDPROC
780
790 DEF PROCAnalyseNote
800 IF Note$="R" Pitch=255:ENDPROC
810 IF LEN(Note$)<2 OR LEN(Note$)>3 TH
EN PRINT"ERROR IN DATA ";Note$:PRINT"Not
e Number ";N:STOP
820 IF LEN(Note$)=2 THEN NoteName$=LEF
T$(Note$,1) ELSE NoteName$=LEFT$(Note$,2
)
830 Octave=VAL(RIGHT$(Note$,1))
840 Pitch=Key+INSTR(Scale$,NoteName$)/
3*4+(Octave-1)*48
850 IF Pitch<0 OR Pitch>255 THEN PRINT
"ERROR IN PITCH DATA ";Note$;" Pitch = "
;Pitch:PRINT"Note Number ";N:STOP
860 ENDPROC
870
880 REM Channel 1
890 DATA &200,B2,2,A2,2,G#2,2,A2,2
900 DATA &200,C3,4,R,4,D3,2,C3,2,B2,2,
C3,2
910 DATA &200,E3,4,R,4,F3,2,E3,2,D#3,2
,E3,2
920 DATA &200,B3,2,A3,2,G#3,2,A3,2,B3,
2,A3,2,G#3,2,A3,2
930 DATA &200,C4,8,A3,4,C4,2,G3,1,A3,1
940 DATA &200,B3,4,A3,4,G3,4,A3,2,G3,1
,A3,1
950 DATA &200,B3,4,A3,4,G3,4,A3,2,G3,1
,A3,1
960 DATA &200,B3,4,A3,4,G3,4,F#3,4
970 DATA &200,E3,8
980
990 REM Channel 2
1000 DATA &200,R,8
1010 DATA &200,A1,4,C2,4,C2,4,C2,4
1020 DATA &200,A1,4,C2,4,C2,4,C2,4
1030 DATA &200,A1,4,C2,4,C2,4,C2,4
1040 DATA &200,A1,4,C2,4,C2,4,C2,4
1050 DATA &200,E1,4,B1,4,B1,4,B1,4
1060 DATA &200,E1,4,B1,4,B1,4,B1,4
1070 DATA &200,E1,4,B1,4,B1,4,B1,4
1080 DATA &200,E1,8
1090
1100 REM Channel 3
1110 DATA &200,R,8
1120 DATA &200,R,4,E2,4,E2,4,E2,4
1130 DATA &200,R,4,E2,4,E2,4,E2,4
1140 DATA &200,R,4,E2,4,R,4,E2,4
1150 DATA &200,R,4,E2,4,E2,4,E2,4
1160 DATA &200,R,4,E2,4,E2,4,E2,4
1170 DATA &200,R,4,E2,4,E2,4,E2,4
1180 DATA &200,R,4,E2,4,R,8
1190 DATA &200,R,8
Like Program 9.1, the data is arranged in lines of one bar: you can use the data from Program 9.1 and add the extra data items to it. PROCAnalyseNote has one change in it at line 800 as you will see.
The numbers of notes for each part are assigned to variables, C1, C2, and C3 in fine 90. These numbers need to be accessed several times during the program so that, if you want or need to alter data, you only need change these values once. They refer to the number of notes allocated to each channel, not to the number of data items.
Subscript-l: Channel attributes, ie sync, flush, etc.
The next section sets the data into its relevant arrays and the process is repeated once for each channel. I have kept it this way to aid understanding, although you might like to substitute a single procedure to avoid the repetition. As the process is exactly the same for each channel we will only look at channel 1 in detail.
Once you have this program, you will be able to play any three-part tune by inserting new data and altering the variables C1, C2 and C3 in line 90.
635 Chan1(1,1)=&200:Chan2(1,1)=&200:Chan 3(1,1)=&200
The other elements in this array will be set to 0 upon dimensioning.
685 PRINT;Chan1(1,Ch1)+1TAB(4)Chan1(2,Ch1)TAB(9)Chan1
If the channels are not synchronized, the delay between channel 1 and channel 2 (caused by BASIC taking time out to process the command) will throw the program out of sync.
100 FOR E=1 TO 100
You may find this more convenient than entering 100 or more separate figures. Assuming you do want 100 or so envelopes, you could enter them in a separate DATA statement.
It will be a rare occurrence if you enter a set of data statements for a tune and get no errors. PROCAnalyseNote will detect errors in pitch data entry and tell you which data item it does not understand and which note number this is associated with. You will know which channel the item belongs to because a message is printed when a channel has been successfully filled with data.
If you want to play the music in a separate program, for example as background to a graphics display, you can use the program to check out the data values and then *SPOOL the figures for the sound commands to tape or disk. The next program shows the additions necessary to do this.
1 REM PROGRAM 9.3
2 REM *SPOOL Routine To Put Sound
3 REM Data Onto TAPE or DISC
4 REM Include These Lines In
5 REM PROGRAM 9.2
6 REM Tempo=2 For DISC, 5 For TAPE
7
230 Tempo=2
664 Line=5000
665 PRINT"INSERT DISC OR TAPE then RET
URN"
666 REPEAT:A=GET:UNTIL A=13
667 *SPOOL"TUNE"
680 IF ADVAL(-6)>0 AND Ch1
(3,Ch1),Chan1(4,Ch1)*Tempo:PROCSpool1
690 IF ADVAL(-7)>0 AND Ch2
(3,Ch2),Chan2(4,Ch2)*Tempo:PROCSpool2
700 IF ADVAL(-8)>0 AND Ch3
(3,Ch3),Chan3(4,Ch3)*Tempo:PROCSpool3
715 *SPOOL
861
862 DEF PROCSpool1
863 PRINT;Line;" DATA ";Chan1(1,Ch1)+1
;",";Chan1(2,Ch1);",";Chan1(3,Ch1);",";C
han1(4,Ch1)*Tempo
864 Line=Line+10
865 ENDPROC
866
867 DEF PROCSpool2
868 PRINT;Line;" DATA ";Chan2(1,Ch2)+1
;",";Chan2(2,Ch2);",";Chan2(3,Ch2);",";C
han2(4,Ch2)*Tempo
869 Line=Line+10
870 ENDPROC
871
872 DEF PROCSpool3
873 PRINT;Line;" DATA ";Chan3(1,Ch3)+1
;",";Chan3(2,Ch3);",";Chan3(3,Ch3);",";C
han3(4,Ch3)*Tempo
874 Line=Line+10
875 ENDPROC
876
10000 DATA -1,-1,-1,-1
10010
10020 REM These Lines Play the DATA
10030 RESTORE5000
10040 REPEAT
10050 READ Chan,Env,Pitch,Dur
10060 REM Set Divisor Equal to Tempo
10070 SOUNDChan,Env,Pitch,Dur/2
10080 UNTIL Chan=-1
The three Spool procedures are identical for each channel and the data items are printed one note per line. You may like to modify the program to print more data per line and reduce the Spool procedures into one. Be sure to REM out these lines or insert a GOTO to jump over them until your data has been checked and verified.
*EXEC"TUNE"
See page 402 of the User Guide for more information about *SPOOL and *EXEC.
Program 9.2 is only the beginning. You could add an extra array and include channel 0 for drum effects. You could also use channel 0 instead of channel 1 to produce the lower notes we mentioned in Chapter 7.
These three programs include the data and information required to play other tunes. If the programs are entered as written and saved as files, using *SPOOL, you can load Program 9.2 and *EXEC these programs into the computer: they will be ready to run. See Appendix 2 and the User Guide page 402 for futher information.
1 REM PROGRAM 9.4
2 REM Anothr 24 bars of Mozart's
3 REM Rondo Alla Turca
4 REM Insert These Lines into
5 REM PROGRAM 9.2
6
90 C1=168:C2=114:C3=114
275 ENVELOPE4,4,0,8,-8,0,1,1,126,-1,0,
-2,126,100
305 IF N=47 OR N=129 RESTORE 890
306 IF N=153 RESTORE 979
330 IF Note$="R" Env=0 ELSE IF N=52 OR
N=58 OR N=72 OR N=77 OR N=83 OR N=89 En
v=2 ELSE Env=1
335 IF N=153 OR N=165 Env=4
345 IF N=153 OR N=165 Env=4
425 IF N=31 OR N=85 RESTORE 1000
426 IF N=98 RESTORE 1089
545 IF N=30 OR N=85 RESTORE 1110
546 IF N=98 RESTORE 1199
880 REM Channel 1
970 DATA &200,E3,8,E3,4,F3,4
971 DATA &200,G3,4,G3,4,A3,2,G3,2,F3,2
,E3,2
972 DATA &200,D3,8,E3,4,F3,4
973 DATA &200,G3,4,G3,4,A3,2,G3,2,F3,2
,E3,2
974 DATA &200,D3,8,C3,4,D3,4
975 DATA &200,E3,4,E3,4,F3,2,E3,2,D3,2
,C3,2
976 DATA &200,B2,8,C3,4,D3,4
977 DATA &200,E3,4,E3,4,F3,2,E3,2,D3,2
,C3,2
978 DATA &200,B2,8
979 DATA &200,C4,8,A3,4,B3,4
980 DATA &200,C4,4,B3,4,A3,4,G#3,4
981 DATA &200,A3,4,E3,4,F3,4,D3,4
982 DATA &200,C3,8,B2,6,A2,1,B2,1
983 DATA &200,A2,8
990 REM Channel 2
1080 DATA &200,E1,8,R,8
1081 DATA &200,C1,4,C2,4,E1,4,E2,4
1082 DATA &200,G1,8,R,8
1083 DATA &200,C1,4,C2,4,E1,4,E2,4
1084 DATA &200,G1,8,R,8
1085 DATA &200,C1,4,A1,4,C1,4,C2,4
1086 DATA &200,E1,8,R,8
1087 DATA &200,C1,4,A1,4,C1,4,C2,4
1088 DATA &200,E1,8,R,8
1089 DATA &200,F1,4,A1,4,A1,4,A1,4
1090 DATA &200,E1,4,A1,4,D1,4,F1,4
1091 DATA &200,C1,4,E1,4,D1,4,F1,4
1092 DATA &200,E1,4,E1,4,E1,4,E1,4
1093 DATA &200,A1,8
1100 REM Channel 3
1190 DATA &200,R,8,C3,4,D3,4
1191 DATA &200,E3,4,E3,4,R,8
1192 DATA &200,B2,4,G2,4,C3,4,D3,4
1193 DATA &200,E3,4,E3,4,R,8
1194 DATA &200,B2,8,A2,4,B2,4
1195 DATA &200,C3,4,C3,4,R,8
1196 DATA &200,G#2,4,E2,4,A2,4,B2,4
1197 DATA &200,C3,4,C3,4,R,8
1198 DATA &200,R,8
1199 DATA &200,R,4,D#2,4,D#2,4,D#2,4
1200 DATA &200,R,4,E2,4,R,4,B1,4
1201 DATA &200,R,4,A1,4,R,4,B1,4
1202 DATA &200,A1,4,A1,4,G#1,4,G#1,4
1203 DATA &200,A1,8
Notice how repeats have easily been programmed by the RESTORE function at lines 305, 306, 425, 426, 545 and 546. The only other programming has been an additional envelope.
10 REM PROGRAM 9.5
20 REM Tschaikowsky's
30 REM Dance of the Sugar-plum Fairy
40 REM Insert These Lines Into
50 REM PROGRAM 9.2
60
90 C1=104:C2=95:C3=86
230 Tempo=2
250 ENVELOPE1,6,0,0,0,0,0,0,126,-8,0,-
8,126,94
260 ENVELOPE2,4,0,0,1,1,0,1,100,-10,-1
0,-10,100,70
270 ENVELOPE3,3,0,0,0,0,0,0,116,-8,0,-
8,116,84
275 ENVELOPE4,6,0,0,1,1,0,1,100,-5,-5,
-5,100,80
276 ENVELOPE5,1,0,0,1,1,0,1,100,-5,-1,
-10,100,50
330 IF Note$="R" Env=0 ELSE IF N=C1 En
v=5 ELSE IF N<17 Env=2 ELSE Env=1
450 IFNote$="R" Env=0 ELSE IF N=C2 Env
=5 ELSE IF N>73 Env=4 ELSE Env=2
570 IF Note$="R" Env=0 ELSE IF N=C3 En
v=5 ELSE IF N<68 Env=2 ELSE Env=3
870
880 REM Channel 1
890 DATA &200,R,4,E3,4,R,4,F#3,4
900 DATA &200,R,4,G3,4,R,4,D#3,4
910 DATA &200,R,4,E3,4,R,4,F#3,4
920 DATA &200,R,4,G5,2,E5,2,G5,4,F#5,4
930 DATA &200,D#5,4,E5,4,D5,2,D5,2,D5,
4
940 DATA &200,B4,2,E5,2,C5,2,E5,2,B4,4
,R,4
950 DATA &200,R,4,G4,2,E4,2,G4,4,F#4,4
960 DATA &200,B4,2,E5,2,C5,2,E5,2,B4,4
,R,4
970 DATA &200,R,4,G4,2,E4,2,G4,4,F#4,4
980 DATA &200,C5,4,B4,4,G5,2,G5,2,G5,4
990 DATA &200,F#5,2,F#5,2,F#5,4,E5,2,E
5,2,E5,4
1000 DATA &200,D#5,2,F#5,2,E5,2,F#5,2,D
#5,4,R,4
1010 DATA &200,R,4,G5,2,E5,2,G5,4,F#5,4
1020 DATA &200,D#5,4,E5,4,D5,2,D5,2,D5,
4
1030 DATA &200,C#5,2,C#5,2,C#5,4,C5,2,C
5,2,C5,4
1040 DATA &200,B4,2,E5,2,C5,2,E5,2,B4,4
,R,4
1050 DATA &200,R,4,E4,2,C#4,2,E4,4,D#4,
4
1060 DATA &200,R,4,D4,2,B3,2,D4,4,C#4,4
1070 DATA &200,R,4,C4,2,A3,2,C4,4,B3,4
1080 DATA &200,R,4,B3,1,D#4,1,F#4,1,B4,
1,E4,4,B2,4
1090
1100 REM Channel 2
1110 DATA &200,E2,4,G2,4,E2,4,A2,4
1120 DATA &200,E2,4,A#2,4,E2,4,A2,4
1130 DATA &200,E2,4,G2,4,E2,4,A2,4
1140 DATA &200,E2,4,A#2,4,E2,4,A2,4
1150 DATA &200,E2,4,B2,4,E2,4,C3,4
1160 DATA &200,E2,4,C#3,4,E2,4,D3,4
1170 DATA &200,E2,4,E3,4,E2,4,F#3,4
1180 DATA &200,E3,4,E3,4,E3,4,E2,1,D2,1
,C2,1,B1,1
1190 DATA &200,A#1,4,C3,4,A1,4,C3,4
1200 DATA &200,G1,4,B2,4,F#1,4,A#2,4
1210 DATA &200,F#2,4,B2,4,F#2,4,C#3,4
1220 DATA &200,B1,4,C2,4,B1,4,B1,1,A1,1
,G1,1,F#1,1
1230 DATA &200,E1,4,B2,4,E2,4,C3,4
1240 DATA &200,E2,4,C#3,4,E2,4,D3,4
1250 DATA &200,E2,4,E3,4,E2,4,F#3,4
1260 DATA &200,E3,4,E3,4,E3,4,G3,1,F#3,
1,E3,1,D3,1
1270 DATA &200,C#3,4,F#2,8,F#3,1,E3,1,D
#3,1,C#3,1
1280 DATA &200,B2,4,E2,8,E3,1,D3,1,C#3,
1,B2,1
1290 DATA &200,A2,4,D2,8,D3,1,C3,1,B2,1
,A2,1
1300 DATA &200,G2,4,F#2,4,E2,4,B0,4
1310
1320 REM Channel 3
1330 DATA &200,R,4,B2,4,R,4,C3,4
1340 DATA &200,R,4,C#4,4,R,4,C4,4
1350 DATA &200,R,4,B2,4,R,4,C3,4
1360 DATA &200,R,4,C#4,4,R,4,C4,4
1370 DATA &200,R,4,G2,4,R,4,A2,4
1380 DATA &200,R,4,C#4,4,R,4,C4,4
1390 DATA &200,R,4,G2,4,R,4,A2,4
1400 DATA &200,G3,4,A3,4,G3,4,R,4
1410 DATA &200,R,4,E2,4,R,4,D#2,4
1420 DATA &200,F#4,4,E4,4,A#4,2,A#4,2,A
#4,2
1430 DATA &200,G#4,2,G#4,2,G#4,4,F#4,2,
F#4,2,F#4,4
1440 DATA &200,B2,4,A#2,4,F#2,4,R,4
1450 DATA &200,R,4,G2,4,R,4,A2,4
1460 DATA &200,R,4,A#2,4,R,4,B2,4
1470 DATA &200,R,4,C#3,4,R,4,D#4,4
1480 DATA &200,G3,4,A3,4,G3,4,R,4
1490 DATA &200,R,4,A#3,2,F#3,2,A#3,4,A3
,4
1500 DATA &200,R,4,G#3,2,E3,2,G#3,4,G3,
4
1510 DATA &200,R,4,F#3,2,D3,2,F#3,4,G3,
4
1520 DATA &200,R,4,A2,4,E2,4,B1,4
The envelopes have been redefined and re-allocated, but otherwise this just supplies new data to Program 9.2
10 REM PROGRAM 9.6
20 REM John Philip Sousa's
30 REM The Liberty Bell March
40 REM Insert These Lines Into
50 REM PROGRAM 9.2
90 C1=174:C2=207:C3=173
250 ENVELOPE1,1,0,0,0,0,0,0,50,50,-2,-
2,50,126
260 ENVELOPE2,1,0,0,0,0,0,0,32,-8,-2,-
4,126,90
270 ENVELOPE3,1,0,0,0,0,0,0,16,-8,-2,-
4,126,90
275 ENVELOPE4,4,0,8,-8,0,1,1,110,0,0,-
10,110,110
276 ENVELOPE5,2,1,0,1,0,1,1,16,-8,-2,-
4,126,90
277 ENVELOPE6,1,1,0,1,0,1,1,32,-8,-2,-
4,126,90
305 IF N=65 RESTORE 930
330 IF Note$="R" Env=0 ELSE IF (N=1 OR
N=78 OR N=93 OR N=108 OR N=162) Env=4 E
LSE Env=1
425 IF N=79 RESTORE 2560
450 IF Note$="R" Env=0 ELSE IF N=C2-1
Env=1 ELSE IF N>78 Env=5 ELSE Env=3
545 IF N=71 RESTORE 3050
570 IF Note$="R" Env=0 ELSE IF N=130 E
nv=4 ELSE IF N=C3-1 Env=1 ELSE Env=2
880 REM Channel 1
890 DATA &200,F4,60,&1000,R,9
900
910
920 DATA C3,3
930 DATA &200,A2,6,A2,3,A2,3,G#2,3,A2,
3
940 DATA &200,F3,6,C3,3,C3,6,A2,3
950 DATA &200,A#2,6,A#2,3,A#2,6,C3,3
960 DATA &200,D3,15,A#2,3
970 DATA &200,G2,6,G2,3,G2,3,F#2,3,G2,
3
980 DATA &200,E3,6,D3,3,D3,6,A#2,3
990 DATA &200,A2,6,A2,3,A2,6,A#2,3
1000 DATA &200,C3,15,C3,3
1010 DATA &200,A2,6,A2,3,A2,3,G#2,3,A2,
3
1020 DATA &200,A3,6,F3,3,F3,6,C3,3
1030 DATA &200,B2,6,G3,3,G3,6,G3,3
1040 DATA &200,G3,15,F3,3
1050 DATA &200,E3,6,G3,3,G3,3,F#3,3,G3,
3
1060 DATA &200,D3,6,G3,3,G3,3,F#3,3,G3,
3
1070 DATA &200,C3,6,B2,3,C3,6,B2,3
1080 DATA &200,C3,9,C3,9
1090 REM 2nd Part
1100 DATA &200,A2,3,G#2,3,A2,3,D3,6,C3,
3
1110 DATA &200,A2,9,F2,9
1120 DATA &100,D2,9,G2,9
1130 DATA &100,F2,15,F2,3
1140 DATA &200,G2,3,A2,3,A#2,3,E3,6,D3,
3
1150 DATA &200,C3,9,F3,9
1160 DATA &200,E3,9,D3,9
1170 DATA &200,C3,15,C3,3
1180 DATA &200,D3,6,D3,2,E3,1,D3,3,C#2,
3,D3,3
1190 DATA &200,E3,9,E3,9
1200 DATA &200,F3,6,F3,2,A3,1,G3,3,F3,3
,G3,3
1210 DATA &200,A3,15,A3,2,A3,1
1220 DATA &200,G3,6,F3,3,D3,6,A#2,3
1230 DATA &200,A2,9,F2,9
1240 DATA &200,G2,9,E2,9
1250 DATA &200,F2,12,R,6
2500
2510 REM Channel 2
2520 DATA &200,F2,6,E2,3,D#2,6,D2,3
2530 DATA &100,C2,6,B1,3,A#1,6,A1,3
2540 DATA &100,G1,3,A1,3,A#1,3,A1,6,G1,
3
2550 DATA &100,C1,6,R,12
2560 DATA &200,F1,6,F2,3,F2,6,F2,3
2570 DATA &200,F1,6,F2,3,F2,6,F2,3
2580 DATA &200,E1,6,F1,3,G1,6,F1,3
2590 DATA &200,E1,6,D1,3,C1,6
2600 DATA &200,C1,6,A#1,3,A#1,6,A#1,3
2610 DATA &200,C1,6,A#1,3,A#1,6,A#1,3
2620 DATA &200,F1,6,G1,3,A1,6,G1,3
2630 DATA &200,F1,6,E1,3,D1,6,C1,3
2640 DATA &200,F1,6,F2,3,F2,6,F2,3
2650 DATA &200,F1,6,F2,3,F2,6,F2,3
2660 DATA &200,D1,6,E1,3,F1,6,E1,3
2670 DATA &200,D1,6,C1,3,B0,6,A1,3
2680 DATA &200,G1,6,E2,3,E2,6,E2,3
2690 DATA &200,G1,6,F2,3,F2,6,F2,3
2700 DATA &200,C2,6,T1,3,C2,6,G1,3
2710 DATA &200,C1,6,R,3,C2,6,R,3
2720 REM 2nd Part
2730 DATA &200,F1,6,A1,3,C1,6,A1,3
2740 DATA &200,F1,6,A1,3,C1,6,A1,3
2750 DATA &100,A#1,6,F1,3,C1,6,E1,3
2760 DATA &100,F1,6,A1,3,A1,6,A1,3
2770 DATA &200,E1,6,A#1,3,C1,6,A#1,3
2780 DATA &200,F1,6,A1,3,D1,6,G#1,3
2790 DATA &200,C1,4,F1,2,G1,3,D1,4,F1,2
,G1,3
2800 DATA &200,C1,6,G1,3,G1,6,81,3
2810 DATA &200,B0,6,B1,3,B1,6,B1,3
2820 DATA &200,C#1,6,A1,3,A1,6,A1,3
2830 DATA &200,D1,6,A1,3,D1,6,A#1,3
2840 DATA &200,C1,6,A1,3,A1,6,A1,3
2850 DATA &200,G1,6,A#1,3,A#1,6,A#1,3
2860 DATA &200,C1,6,A1,3,A1,6,A1,3
2870 DATA &200,E1,6,A#1,3,C1,6,A#1,3
2880 DATA &200,F1,6,R,3,F1,6,R,3
2890
3000 REM Channel 3
3010 DATA &200,F3,6,E3,3,D#3,6,D3,3
3020 DATA &100,C3,6,B2,3,A#2,6,A2,3
3030 DATA &100,G2,3,A2,3,A#2,3,A2,6,G2,
3
3040 DATA &100,C2,6,R,12
3050 DATA &200,F2,6,C2,3,C2,6,C2,3
3060 DATA &200,F2,6,C2,3,C2,6,C2,3
3070 DATA &200,G2,6,G2,3,G2,6,G2,3
3080 DATA &200,G2,15,R,3
3090 DATA &200,G1,6,C2,3,C2,6,C2,3
3100 DATA &200,G1,6,C2,3,C2,6,C2,3
3110 DATA &200,F2,6,F2,3,F2,6,G2,3
3120 DATA &200,A2,15,A2,3
3130 DATA &200,F2,6,C2,3,C2,6,C2,3
3140 DATA &200,F2,6,C2,3,C2,6,C2,3
3150 DATA &200,D2,9,B1,9
3160 DATA &200,B1,6,C2,3,D2,6,R,3
3170 DATA &200,G2,6,C2,3,C2,6,C2,3
3180 DATA &200,G2,6,B1,3,B1,6,B1,3
3190 DATA &200,E2,6,R,3,G1,6,R,3
3200 DATA &200,E2,6,R,3,E2,6,R,3
3210 REM 2nd Part
3220 DATA &200,R,18
3230 DATA &200,F4,54
3240
3250
3260 DATA &200,R,6,C2,3,R,6,C2,3
3270 DATA &200,R,6,A1,3,R,6,G#1,3
3280 DATA &200,G2,9,F2,9
3290 DATA &200,R,6,C2,3,C2,6,C2,3
3300 DATA &200,F2,6,D2,3,D2,6,D2,3
3310 DATA &200,R,6,C#2,3,C#2,6,C#2,3
3320 DATA &200,R,6,D2,3,R,6,D2,3
3330 DATA &200,R,6,E2,3,E2,6,E2,3
3340 DATA &200,R,6,G2,3,G2,6,G2,3
3350 DATA &200,R,6,C2,3,C2,6,C2,3
3360 DATA &200,R,6,C2,3,C2,6,C2,3
3370 DATA &200,F2,6,R,3,F2,6,R,3
This makes uses of the RESTORE function for repeats and several envelopes for tone variation. The trill envelope is also used to good effect.Program notes
Three arrays are dimensioned at lines 170 and 190 to hold information about each note. The first subscript refers to the following aspects of the note:
Subscript=2: Envelope number.
Subscript=3: Pitch value.
Subscript=4: Duration value.
A FOR . . . NEXT loop runs through the data, once for each note. The first step is a call to PROCChan at line 750. This 'cautiously' examines the first item of data. If the data begins with an ampersand (&) it knows it is a channel instruction, and evaluates the string with EV AL to produce the channel attribute, Chan. It then proceeds to read Note$ and Duration. I have used hexadecimal notation to represent the sound channel parameters because it is easy to use and the ampersand tells us at a glance that this data item is a special channel command. (See Chapter 4 for further details.)
If the string does not begin with an ampersand it is taken to be a note and a second item of data, Duration, is read. As the procedure has no specific channel information, Chan is set to 0. This method of assigning the channel attributes saves us having to enter a channel parameter for every note. We only enter a parameter if we require a sync, flush or hold. The channel attribute is assigned to Chan1(1,N) in line 320.
Next, Note$ is examined to see if it is an 'R', which I have used to represent a rest. If it is an 'R', Env is set to 0 to produce a sound at zero volume: otherwise, it is set to the required envelope number. You can see this more clearly in lines 450 and 570, where only one other envelope is used.
Line 330 is arranged to give certain notes different envelopes. This saves us including a whole set of new data for the envelope number, which can be time-consuming if the envelope only changes once or twice throughout the piece. Env is assigned to Chan1(2,N) in line 340.
The program then calls PROCAnalyseNote. Unlike Program 9.1, which sent each note through PROCAnalyseNote before playing it, this program sends it through to calculate the correct pitch value of the note, and to check for any incorrect data entries. The first thing it does is check if Note$ equals 'R', ie a rest. If it does, Pitch is given an arbitrary value of 255 and the procedure ends. This is the only way this procedure differs from that in Program 9.1. Having turned the note name into a pitch value, assuming no errors, Chan(3,N) is set equal to Pitch at line 360.
Line 370 copies Duration directly into Chan1(4,N).
At the end of these three sections, the arrays are filled with figures which the SOUND command can work on directly; although they can be modified if required as we shall see.
Line 650 sets three variables, Chl, Ch2 and Ch3, to 0 and these are used to check the number of data items sent to each channel.
The REPEAT loop at lines 670 and 710 does all the work. The principle is the same for each sound channel, so only channel 1 at fine 680 will be described.
The sound buffer is checked for free spaces and the checker variable. Ch1, is compared with C1 to see if channel 1 has had all its notes. If there is free space in the buffer and there are more notes to come, Ch1 is incremented and used to provide information for the sound channel from the Chan1 array. It is at this point that the channel number is assigned, and this; is added to the channel attribute derived from PROCChan. The duration is adjusted according to the variable, Tempo.
Line 710 ensures that the loop repeats enough times to play all the notes, and then terminates it.
Modifications, alterations and suggestions
If you arrange the data in fines of one bar it will aid editing and debugging.
You will notice that the channel attributes are arranged to synchronize at the beginning of each bar. This may not always be possible, especially if the music is heavily syncopated (where notes are tied or held over from one bar to the next) but you should be able to fit some sync commands in somewhere.
Earlier I said that simply using ADVAL statements would provide synchronization. Basically it will, but we must be aware of one or two points. If none of the notes are synchronized, the channels will not be exactly in sync because of the time taken by BASIC to move from the first sound command to the second, etc, especially if it has to look up values in arrays and do some calculating as in this program. If you synchronize even only the first notes, the program should keep good time provided that it is not interrupted. This can be done by altering the READ command to read only Note$ and Duration and by inserting a line such as:
If you were to go to the trouble of removing the sync commands, you would find that the program stayed in time reasonably well, although it might drift if programmed with a longer piece. Another reason for synchronizing the channels is to allow the computer to perform other functions as the music is playing. We will cover this later in the book, but for the time being assume that we want a print-out of the notes as the music is playing. We could insert a line similar to fine 300 in Program 9 .1 such as:
(3,Ch1)TAB(14)Chan1(4,Ch1)*Tempo
If you feel that you do not require four parameters per note, it is easy to alter the arrays.
The envelope changes are often tedious {a enter. If your program requires a lot of envelopes you could arrange a separate routine to set the envelope numbers such as:
1010 IF E>0 Chan1(2,E)=1
1020 IF E>12 Chan1(2,E)=2
1030 IF E>28 Chan1(2,E)=3
1040 IF E>40 Chan1(2,E)=1
. . . .
. . . .
1100 NEXT E
The method of allocating envelopes in Program 9.2 by a series of IF . . . THEN . . . ELSE statements (the THEN commands are implied) will prove satisfactory for all but the most demanding of programs,
When programming other tunes, if all channels {lo not start together, ie if each does not sound a note at the start of the piece, be sure to include rests in the channels which are not used to keep them in sync. This was necessary in Program 9.2.
When calculating the duration of each note, I base the values on those in the chart in Figure 2.6. The important thing is to get the relationship between the notes correct. Adjustments can be made with the variable Tempo in fine 230. This is most effective when given integer values. Insert values less than 1 and observe the results.
Debugging the data
An easy mistake to make is to insert wrong C1, C2 or C3 values, causing a channel to be filled with another channel's notes. You could provide further checks by inserting a termination character on the end of each channel's data: so, for example, if Note$ read a 'F and N did not equal C1 you would know that you had set C1 to the wrong value. You could use this principle to read through the data before assigning any information and use the value to dimension the arrays. You could also incorporate checks on the other data.
Once the data is correct, you could remove the error routines.
Saving the tune
Program notes
The data is retrieved by executing:
As they stand, these procedures may require some modification. The principle is to let the ADVAL function calculate the correct spacing of the data items. However, because the filing systems themselves require attention from the operating system, the ADVAL function may sometimes be waiting for notes when the operating system is not ready to give them. This will happen if a channel is supplied with a lot of short notes.
One way around this is to increase the variable Tempo in line 230, so that the sound channels do not empty while waiting for the filing system to finish with the OS. Tape will require longer values than disk, but experiments have shown that setting Tempo to 5 for Tape and to 2 for disk will work for Rondo Alla Turca. On playback, divide the duration by the increase in tempo value or, in the PROCSpool procedures, divide Tempo by whatever you needed to multiply it by in line 230. Other tunes may require different values.
The routine beginning at line 10000 will play the data. If you substitute 'SOUND' for the string 'DATA' in the procedures at fines 863, 868 and 873, the *SPOOLed program will play the tune when run.
The program depends so much upon the buffers not emptying before another SOUND command comes along, that the whole program may be better served by a routine which produces SOUND commands according to the tracking method described earlier. This would remove it from the mercy of the *SPOOLing filing system: although if you are using disks this will not be a serious problem.
Experimenting with the program
If 16 envelopes are not enough, you can redefine envelopes in mid-program. You can do this by calling a procedure from a fine which is inserted in a similar way to the PRINT statement in fine 685 described earlier.
If you include the variable Key inside the REPEAT loop instead of in PROCAna1yseNote, you can produce a key change by altering the value of Key as explained in Chapter 3.
A separate array can be used to trigger calls to any number of procedures, to create any of the effects we've covered in the previous chapters; you can produce your own arrangements of your favourite tunes as well as programming your own compositions - which leads us straight into the next chapter. But first . . .
More tunes to play
Program 9.4 plays the next 16 bars of Ronda Aha Turca and includes a repeat of the first eight.
Notice the empty data lines, where a note lasts for more than one bar, and the necessary change in sync parameters in the other channels. The empty fines are there purely to aid readability.
Notice also the use of the dummy hold parameter to allow the initial trill to fade rather than cease abruptly.