Bottom     Previous     Contents

CHAPTER 10
Computer Compositions

Musical compositions produce the chaos and monotony we referred to in Chapter 2. Composing is very much an art, although there are several methods and ideas around which we produce compositions through scientific and mathematical means. This is one area which is relatively unexplored and there is great scope for us to discover new ideas - and compositions - with our BBC micro.
Even art must follow certain rules unless it is to be totally anarchistic. Music is no exception. Anarchy is easily expressed by this short program.

10 REPEAT
20 SOUND RND(3),RND(15)-16,RND(255),RND(10)
30 UNTIL FALSE

Interesting is perhaps the most apt description, and you can build upon this idea to produce even more interesting compositions.
Of course, this is not what we normally mean when we talk about composing music. Our main objection is likely to be that it is a completely random series of notes with no relation to each other at all. To this chaos we must bring some order: order is easily demonstrated by playing the same note over and over again, or by playing scales, up and down.
The point of all this is to show the two extremes, chaos v. monotony, and to illustrate the necessity of a compromise between the two.
The computer is unable to exercise any artistic judgement over the notes it produces and we must tell it, by careful programming, what will and what will not produce acceptable music. The more rules we lay down, the nearer we will get to a particular style and the more rigid will be the composition. Inspiration is provided by the RND function - and clever programming.

The human compositional process: algorithms and heuristics

In computing, algorithms are frequently used to solve problems. An algorithm is simply a method of solving a problem - providing a solution exists. If there is no solution, the algorithm should determine this. If it does not either solve the problem or determine that no solution exists, it is not an algorithm. For example, mathematical addition and subtraction can be solved by algorithms because we know and can describe exactly how to get to the solution.
A heuristic is frequently described as a rule of thumb. It is used in instances where there is no readily available algorithm or where such an algorithm would take too long to solve the problem. It involves a commonsense approach to point the way to, hopefully, the correct answer or best solution. Unlike an algorithm, a heuristic is not guaranteed to produce the best solution. It's not even guaranteed to produce any solution, but if it does it can often find it quicker than an algorithm.
Heuristic procedures are used to determine computer strategy in games such as draughts and chess. An algorithm for these games would involve working out every possible move and deducing the best play from the results. This is theoretically possible because the number of moves is not infinite but it is large enough to render such an algorithmic approach impossible.
A heuristic approach might work on the principle of controlling the centre of the board. This is likely to produce a good game but is not guaranteed to win. Algorithms and heuristics are often combined to produce doubly effective results.
The human compositional process is a mixture of algorithm and heuristic (and inspiration, but whether or not this would be classed as a part of the heuristic operation is debatable).
There are certain chord sequences and series of notes which a composer knows sound good. A common chord sequence such as A minor, G major, F major, E major creates a harmonic framework which has been used as the basis for many hit tunes. The composer knows this but still needs to apply rules of thumb to create a melody over the top of the sequence. These rules are the result of experience and inspiration - and this is where we resort to the RND function and to devising some heuristics and rules for the computer.

Aspects of a composition

There are three aspects of composition of direct relevance to us:

  1. Melody
  2. Rhythm
  3. Harmony.

The first two are very closely related and the third, harmony, is complex enough to have had many volumes written about it. In this chapter, we wm examine melody anti Its associated rhythm and delve into harmony in Chapter 11.
The first steps in computer compositions, once we've got past the totally random note stage, usually involve trying to create a pleasing melody or sequence of notes. We can devise a set of rules to do this, making them as simple or as complex as we like. This in itself is not so difficult, but the notes in a melody also form a rhythmic pattern, so we must take note lengths into consideration, too. This is certainly the more difficult task as note pitch and note length are generally so tightly interwoven that one can often determine the other - from an artistic viewpoint.
We will begin our experiments with the next program. It is an expanded version of Program 3.1, which played a series of completely unrelated notes. This program uses a set of rules to guide it towards a more musically meaningful output. If we restrict ourselves to the key of C, the rules which we set ourselves might look like this:

  1. The first note must be a member of a C major chord, ie C, E or G.
  2. The interval between any two consecutive notes must not be more than four notes.
  3. A B note must lead directly to the C above it.
  4. The last bar must end on a C and the note must last for the length of the bar.

These simple rules will give us better results than Program 3.1, as you will hear.

10 REM PROGRAM 10.1

20 REM Computer Composition

30 REM Based on Rules

40

50 PROCSetup

60

70 NoOfBars=4

80 Count=0

90

100 FOR B=1 TO NoOfBars

110 PRINT"Composing Bar ";B

120 PROCBar

130 NEXT B

140

150 PROCPlay

160

170 PRINT"Press SPACE For Another Tune

"

180 PRINT"Press ""R"" For A Replay"

190

200 REPEAT

210 *FX15,1

220 Key$=GET$

230 UNTIL (Key$=" " OR Key$="R")

240 IF Key$="R" GOTO 150 ELSE GOTO 80

250

260 END

270

280 DEF PROCSetup

290 Scale$=" C C# D D# E F F# G

G# A A# B"

300

310 ENVELOPE1,4,0,1,0,1,1,0,126,-8,0,-

8,126,80

320 Key=1

330 Tempo=2

340

350 DIM Tune(2,129), Tune$(129)

360

370 DIM NotesToChooseFrom$(15)

380 FOR S%=1 TO 15

390 READ Note$

400 NotesToChooseFrom$(S%)=Note$

410 NEXT S%

420 ENDPROC

430

440 DATAG1,A1,B1,C2,D2,E2,F2,G2,A2,B2,

C3,D3,E3,F3,G3

450

460 DEF PROCChooseNote

470 Note=RND(15)

480 Note$=NotesToChooseFrom$(Note)

490 ENDPROC

500

510 DEF PROCPlay

520 FOR P=1 TO Count

530 PRINTTune$(P),Tune(2,P)*Tempo

540 SOUND1,1,Tune(1,P),Tune(2,P)*Tempo

550 NEXT P

560 ENDPROC

570

580 DEF PROCAnalyseNote

590 IF LEN(Note$)<2 OR LEN(Note$)>3 TH

EN PRINT"ERROR IN DATA ";Note$:STOP

600 IF LEN(Note$)=2 THEN NoteName$=LEF

T$(Note$,1) ELSE NoteName$=LEFT$(Note$,2

)

610 PositionInScale=INSTR(Scale$,NoteN

ame$)/3

620 Octave=VAL(RIGHT$(Note$,1))

630 ENDPROC

640

650 DEF PROCCalculatePitch

660 Pitch=Key+PositionInScale*4+(Octav

e-1)*48

670 IF Pitch<0 OR Pitch>255 THEN PRINT

"ERROR IN PITCH DATA ";Note$;" Pitch = "

;Pitch:STOP

680 ENDPROC

690

700 DEF PROCBar

710 DurationSoFar=0

720 REPEAT

730 Count=Count+1

740 REPEAT

750 PROCChooseNote

760 PROCAnalyseNote

770 PROCRules

780 UNTIL NoteOK

790 LastNote=Note

800 PROCCalculatePitch

810 Tune(1,Count)=Pitch:Tune$(Count)=N

ote$

820 PROCDur

830 Tune(2,Count)=Dur

840 DurationSoFar=DurationSoFar+Dur

850 UNTIL DurationSoFar=16

860 ENDPROC

870

880 DEF PROCDur

890 REPEAT

900 Dur=2^RND(2)

910

920 REM Set Last Note To Semibreve

930 IF B=NoOfBars Dur=16

940

950 UNTIL DurationSoFar+Dur<=16

960 ENDPROC

970

980 DEF PROCRules

990 NoteOK=FALSE

1000

1010 REM Set First Note

1020 IF Count=1 AND NOT(NoteName$="C" O

R NoteName$="G" OR NoteName$="E") ENDPRO

C

1030

1040 REM Make a "B" Move up to a "C"

1050 IF Count>1 AND LEFT$(Tune$(Count-1

),1)="B" Note=LastNote+1:Note$=NotesToCh

ooseFrom$(Note):PROCAnalyseNote

1060

1070 REM Restrict Jumps To 4 Notes

1080 IF Count>1:IF ABS(LastNote-Note)>4

ENDPROC

1090

1100 REM Set Last Bar

1110 IF B=NoOfBars AND NoteName$<>"C" E

NDPROC

1120

1130 NoteOK=TRUE

1140 ENDPROC

Program notes

At line 350 the array Tune stores the pitch and duration values of the notes and Tune$ stores the note name and octave number. As you experiment with the program and add more rules, you will find it helpful sometimes to refer to the name of a note and sometimes to its pitch value or position in NotesToChooseFrom$.
NotesToChooseFrom$ stores the available notes. This has been altered slightly from Program 3.1 to base our experiments in the key of C.
In order to create some sort of order, the program composes in bars: the number of bars is determined by line 70. If you alter this to more than eight bars, you may have to redimension the Tune and Tune$ arrays.
After the bars have been composed, PROCPlay at line 510 plays them and prints out the notes. Lines 170 to 240 give you the opportunity to hear the tune again or to compose another one.
PROCAnalyseNote and PROCCa1cuIatePitch remain the same and have been kept separate so that you can refer to one aspect of the note without referring to the other. It also enables you to force a note into either of the procedures, as may be necessary, for example, in rule 3 above.
DurationSoFar in PROCBar keeps track of the cumulative length of the notes in each bar so that each bar contains the equivalent of 16 quavers. The program therefore produces music in 4/4 time.
The loop at line 720 repeats and calls PROCRules until NoteOK is TRUE, which means it's passed the rules. The pitch is then calculated and, along with the note name, assigned to the relevant array.
PROCDur gives the note a duration of 2 or 4 and ensures that the last note has a value of 16. This is assigned in line 830.
The outside loop in lines 720 to 850 repeat until the bar is full, when DurationSoFar is equal to 16.
PROCRules assumes that the note is not going to be OK in line 990 and it must run the gauntlet of the rules until it comes out OK at line 1130.
The first rule checks that the first note is C, E or G.
The second rule creates a C to follow a B. This principle could be used to ensure a particular note was always followed by another certain note but be careful how you arrange the note data. Note and LastNote refer to the position of the note in the data stream. If you want each G to move to an A, adding 1 to G3 (Note= 15) would take it off the scale.
The next rule checks the steps between the last note and this one, and rejects the new note if the distance between is too far.
Finally, the last rule ensures a C fills the last bar.

Experimenting with the program

Although this is a great improvement on Program 3.1, it's probably fair to say that it produces music only a programmer could love. We still need more melodic rules and the phrasing produced by PROCDur needs more attention. You can easily add and adapt the melodic rules described above. Here are some further suggestions:

  1. Not more than five notes rising or descending without complementary movement.
  2. A rising B moves to a C, a descending B moves to an A.
  3. A rising E moves to an F.
  4. A B will not move to an F or vice versa. (This is quite a harsh interval unless harmonically controlled.)
  5. Permit the inclusion of accidentals, possibly only F# and/or A#. Further rules would be needed to cope with these.

Music consists of a series of phrases, much like phrases in English grammar, which make sense but which are not complete. PROCDur makes no attempt to regulate the rhythmic content.
The phrasing can be controlled by passing the durations through a set of rules in a similar way to the melody notes. Such rules could include:

  1. If quavers occur, there must be at least two of them consecutively.
  2. A bar cannot start with a set of three quavers.
  3. Give the duration values of bar 1 to bar 2, bar 3 to bar 4, etc. Or bar 1 to bar 3 and bar 2 to bar 4.

Alter the RND parameter in fine 900 to produce other note durations.
Another alternative is to use a preset series of durations. This is the easiest option but, of course, results in a repetitive rhythm pattern. It can be helpful if you wish to concentrate on the melodic aspect and it is probably an improvement on the random method.

1 REM PROGRAM 10.2

2 REM Computer Compositions with

3 REM Fixed Rhythm Pattern

4 REM Insert in PROGRAM 10.1

85 RESTORE 920

890 READ Dur

900 ENDPROC

910

920 DATA 2,2,2,2,4,4

930 DATA 2,2,4,8

940 DATA 2,2,4,2,2,4

950 DATA 4,4,8

960

970

980 DEF PROCRules

990 NoteOK=FALSE

1110 IF Count=19 AND NoteName$<>"C" END

Program notes

The value that Count is checked against in line 1110 refers to the number of notes in the DATA statements.
You can add variation by allowing the option of switching between sets of preset durations. This will give you the best of both worlds.
As you add more rules, you affect the style of the composition and if you add enough you will create a style unique to yourself (and the computer). There are other ways of programming style into a composition program and we will look at one such method next.

Note analysis in composition

If we analyse a musical composition note by note and construct a table of how often each note occurs, we will have a 'first order' note analysis of that tune. If we then arrange a program to play the notes according to the frequency of their appearance in the table, we will have a composition which tends towards the style of the music we analysed.
This idea is not new and experiments along these lines were carried out over 20 years ago on Stephen Foster compositions. (He wrote such songs as 'Camptown Races', 'Oh Susannah' and 'Old Folks at Home' J)
The success of such experiments depends upon the composition(s) under analysis. If the notes of the scale appear in roughly equal proportions, the result is not going to sound unlike random music. In fact, using only first order note analysis, the result will tend to sound a little like random notes anyway. There is a need, too, to take into account the note durations and perform a similar analysis upon them.
We can increase the accuracy of our analysis by recording how often any note follows every other one. This is known as second order analysis and we can take it even further and do a third and fourth order analysis. This produces considerably better results but, as we perform higher and higher order analysis on the music, we end up with a composition which sounds increasingly more like the original.
The next program performs a first, second and third order analysis upon a tune entered in DATA statements. It will then compose a tune based upon one of these levels of analysis: the level can be changed as the tune is playing.

10 REM PROGRAM 10.3

20 REM Computer Compositions

30 REM Based Upon Note Analysis

40

50 MODE 7

60 REM Page Mode Off

70 VDU15

80 ENVELOPE1,4,0,1,0,1,1,0,126,-8,0,-

8,120,90

90

100 DIM Tune$(238),Dur%(238),F1%(36)

110 Key%=37

120 Scale$=" A1 A#1B1 C1 C#1D1 D#1E1

F1 F#1G1 G#1A2 A#2B2 C2 C#2D2 D#2E2 F2 F

#2G2 G#2A3 A#3B3 C3 D#3D3 D#3E3 F3 F#3G3

G#3"

130

140 PROCGetTune

150 PROCNewScale

160 PROCAnalyseTune

170 PROCCalcPercentages

180 PROCPrint

190

200 INPUT"Enter 'LAST BUT ONE' and 'LA

ST NOTE' in terms of note number in N

ew Scale",Penult%,LastNote%

210 PRINT'"Press 'S' to STOP"'"Enter s

earch depth (1/2/3) - This may bealtered

as the program is running?":Play%=GET

220

230 D%=0

240 REPEAT

250 PROCGetNote

260 PROCPlay

270 PL%=INKEY(0):IF PL%>48 AND PL%<52

THEN Play%=PL%

280 UNTIL PL%=83

290

300 END

310

320 DEFPROCGetTune

330 PRINT"Reading in Tune For Analysis

"'

340 REM RESTORE To Required Tune

350 RESTORE 1810

360

370 Count%=0

380 REPEAT

390 Count%=Count%+1

400 READ Note$,Dur:IF Note$="X" GOTO 4

30

410 Tune$(Count%)=Note$

420 Dur%(Count%)=Dur

430 UNTIL Note$="X"

440

450 TuneLength%=Count%

460 ENDPROC

470

480 DEFPROCNewScale

490 PRINT"Calculating New Scale"'

500

510 FOR Note%=1 TO TuneLength%

520 Pos%=INSTR(Scale$,Tune$(Note%))/3

530 F1%(Pos%)=F1%(Pos%)+1

540 NEXT Note%

550

560 Scale2$=" "

570

580 FOR Note%=1 TO 36

590 IF F1%(Note%)>0 THEN Scale2$=Scale

2$+MID$(Scale$,Note%*3,3)

600 NEXT Note%

610

620 PRINT"New Scale = ";Scale2$'

630 ScaleLength%=(LEN(Scale2$)-2)/3

640 PRINT"Scale length = ";ScaleLength

%'

650

660 DIM F2%(ScaleLength%,ScaleLength%)

,F3%(ScaleLength%,ScaleLength%,ScaleLeng

th%)

670

680 REM Reset F1% Array

690 FOR C%=1 TO 36

700 F1%(C%)=0

710 NEXT C%

720 ENDPROC

730

740 DEFPROCAnalyseTune

750 PRINT"Analysing Tune..."'

760 FOR Note%=1 TO TuneLength%

770 Pos1%=INSTR(Scale2$,Tune$(Note%))/

3

780 F1%(Pos1%)=F1%(Pos1%)+1

790 IF Note%>TuneLength%-1 THEN GOTO 8

20

800 Pos2%=INSTR(Scale2$,Tune$(Note%+1)

)/3

810 F2%(Pos1%,Pos2%)=F2%(Pos1%,Pos2%)+

1

820 IF Note%>TuneLength%-2 THEN GOTO 8

50

830 Pos3%=INSTR(Scale2$,Tune$(Note%+2)

)/3

840 F3%(Pos1%,Pos2%,Pos3%)=F3%(Pos1%,P

os2%,Pos3%)+1

850 NEXT Note%

860 ENDPROC

870

880 DEFPROCCalcPercentages

890

900 PRINT"Calculating First Order Freq

uency..."

910 Sum1%=0

920 FOR n1%=1 TO ScaleLength%

930 Sum1%=Sum1%+F1%(n1%)

940 NEXT n1%

950 FOR n1%=1 TO ScaleLength%

960 F1%(n1%)=F1%(n1%)*100/Sum1%

970 NEXT n1%

980

990 PRINT"Calculating Second Order Fre

quency..."

1000 FOR n1%=1 TO ScaleLength%

1010 Sum2%=0

1020 FOR n2%=1 TO ScaleLength%

1030 Sum2%=Sum2%+F2%(n1%,n2%)

1040 NEXT n2%

1050 IF Sum2%>0 THEN FOR n2=1 TO ScaleL

ength%:F2%(n1%,n2)=F2%(n1%,n2)*100/Sum2%

:NEXT n2

1060 NEXT n1%

1070

1080 PRINT"Calculating Third Order Freq

uency..."

1090 FOR n1%=1 TO ScaleLength%

1100 FOR n2%=1 TO ScaleLength%

1110 Sum3%=0

1120 FOR n3%=1 TO ScaleLength%

1130 Sum3%=Sum3%+F3%(n1%,n2%,n3%)

1140 NEXT n3%

1150 IF Sum3%>0 THEN FOR n3=1 TO ScaleL

ength%:F3%(n1%,n2%,n3)=F3%(n1%,n2%,n3)*1

00/Sum3%:NEXT n3

1160 NEXT n2%

1170 NEXT n1%

1180 ENDPROC

1190

1200 DEF PROCPrint

1210 PRINT'"Do You Want a Printout (Y/N

)?"

1220 Ans$=GET$:IF Ans$="N" THEN PRINT:E

NDPROC ELSE IF Ans$<>"Y" THEN 1220

1230

1240 FOR n1%=1 TO ScaleLength%

1250 IF F1%(n1%)>0 THEN PRINTMID$(Scale

2$,n1%*3,2);"...";F1%(n1%)

1260 NEXT n1%

1270

1280 FOR n1%=1 TO ScaleLength%

1290 FOR n2%=1 TO ScaleLength%

1300 IF F2%(n1%,n2%)>0 THEN PRINTMID$(S

cale2$,n1%*3,2);"-";MID$(Scale2$,n2%*3,2

);"...";F2%(n1%,n2%)

1310 NEXT n2%

1320 NEXT n1%

1330

1340 FOR n1%=1 TO ScaleLength%

1350 FOR n2%=1 TO ScaleLength%

1360 FOR n3%=1 TO ScaleLength%

1370 IF F3%(n1%,n2%,n3%)>0 THEN PRINTMI

D$(Scale2$,n1%*3,2);"-";MID$(Scale2$,n2%

*3,2);"-";MID$(Scale2$,n3%*3,2);"...";F3

%(n1%,n2%,n3%)

1380 NEXT n3%

1390 NEXT n2%

1400 NEXT n1%

1410 PRINT

1420

1430 ENDPROC

1440

1450 DEFPROCGetNote

1460 Dice%=RND(100)

1470 Note%=0:Sum%=0

1480

1490 REPEAT

1500 Note%=Note%+1

1510

1520 REM In Case a Note Has Never

1530 REM Followed a Particular

1540 REM Sequence of Notes

1550 IF Note%>ScaleLength% THEN Note%=R

ND(ScaleLength%):Sum%=Dice%

1560

1570 REM Third Order

1580 IF Play%=51 THEN Sum%=Sum%+F3%(Pen

ult%,LastNote%,Note%)

1590

1600 REM Second Order

1610 IF Play%=50 THEN Sum%=Sum%+F2%(Las

tNote%,Note%)

1620

1630 REM First Order

1640 IF Play%=49 THEN Sum%=Sum%+F1%(Not

e%)

1650 UNTIL Sum%>=Dice%

1660

1670 Note$=MID$(Scale2$,Note%*3,3)

1680 Penult%=LastNote%:LastNote%=Note%

1690 PRINTNote$;TAB(12)"Depth = ";CHR$P

lay%

1700 ENDPROC

1710

1720 DEFPROCPlay

1730 Position%=(INSTR(Scale$,Note$))/3

1740 Note%=Key%+(Position%*4)

1750 SOUND1,1,Note%,Dur%(D%)

1760 D%=D%+1

1770 IF D%>TuneLength% D%=0

1780 ENDPROC

1790

1800 REM Ode To Joy - Beethoven

1810 DATA E2,8,E2,8,F2,8,G2,8,G2,8,F2,8

,E2,8,D2,8,C2,8,C2,8,D2,8,E2,8,E2,12

1820 DATA D2,4,D2,16,E2,8,E2,8,F2,8,G2,

8,G2,8,F2,8,E2,8,D2,8,C2,8,C2,8,D2,8

1830 DATA E2,8,D2,12,C2,4,C2,16,D2,8,D2

,8,E2,8,C2,8,D2,8,E2,4,F2,4,E2,8,C2,8

1840 DATA D2,8,E2,4,F2,4,E2,8,D2,8,C2,8

,D2,8,G1,16,E2,8,E2,8,F2,8,G2,8,G2,8

1850 DATA F2,8,E2,8,D2,8,C2,8,C2,8,D2,8

,E2,8,D2,12,C2,4,C2,8,X,0

1860

1870 REM Jesu, Joy - Bach

1880 DATA G1,6,A2,6,B2,6,D2,6,C2,6,C2,6

,E2,6,D2,6,D2,6,G2,6,F#2,6,G2,6,D2,6

1890 DATA B2,6,G1,6,A2,6,B2,6,C2,6,D2,6

,E2,6,D2,6,C2,6,B2,6,A2,6,B2,G1,6

1900 DATA F#1,6,G1,6,A2,6,D1,6,F#1,6,A2

,6,C2,6,B2,6,A2,6,B2,6,G1,6,A2,6,B2,6

1910 DATA D2,6,C2,6,C2,6,E2,6,D2,6,D2,6

,82,6,F#2,6,G2,6,D2,6,B2,6,G1,6,A2,6

1920 DATA B2,6,E1,6,D2,6,C2,6,B2,6,A2,6

,G1,6,D1,6,G1,6,F#1,6,G1,18,X,0

1930

1940 REM Irish Jig

1950 DATA D2,3,B2,3,G1,3,G1,3,D1,3,G1,3

,G1,3,B2,3,G1,3,B2,3,D2,3,C2,3,B2,3

1960 DATA C2,3,A2,3,A2,3,E1,3,A2,3,A2,3

,C2,3,A2,3,C2,3,E2,3,D2,3,C2,3,B2,3

1970 DATA G1,3,G1,3,D1,3,G1,3,G1,3,B2,3

,G1,3,B2,3,D2,3,C2,3,B1,3,C2,3,B1,3

1980 DATA C2,3,A2,3,D2,3,C2,3,B2,3,G1,3

,D2,3,G2,3,B3,3,A3,3,G2,3,F#2,3,D2,3

1990 DATA F#2,3,F#2,3,D2,3,F#2,3,F#2,3,

D2,3,F#2,3,A3,3,G2,3,F#2,3,E2,3,G2,3

2000 DATA G2,3,D2,3,G2,3,G2,3,C2,3,G2,3

,G2,3,B2,3,G2,3,G2,3,C2,3,B2,3,C2,3

2010 DATA A2,3,D2,3,C2,3,B2,3,G1,3,G1,3

,G1,6,X,0

The program prompts you for input where required and will merrily trundle out a continuous composition.

Program notes

Note analysis is the type of operation ideally suited to a computer. First and second order analyses do not take very long and consume little memory, but when we move up to third order analysis we need to keep track of a fist of three consecutive elements. If we work with arrays this could mean, working with a three-octave range, dimensioning an array such as:

DIM F3%(36,36,36)

or larger, which uses up more memory than we can afford. One solution is to use byte arrays, as described in the User Guide page 237, along with indirection operators, as described in Chapter 39, which will save some memory.
With the aim of maintaining readability, I have developed another method which can still be used with byte arrays. It consists of calculating a new scale based upon the notes used in the tune and dimensioning the arrays often; we see how large they need to be, ie calculate their minimum size.
The range of available notes is put into Scale$ in line 120. If you enter other tunes, check that they do not contain notes outside this range or else alter Scale$ to suit.
Tune$ is dimensioned to hold the notes and Dur95 is dimensioned to hold the durations. F1% and, later, F2% and E3% hold the first, second and third order sequences.
PROCGetTune at line 320 reads in the tune from DATA statements. If you have more than one tune in the program, set RESTORE to point to the required line. Tunetength% is set to the number of notes read.
PROCNewScale at line 480 runs through the notes of the tune, comparing them with the ones in Scale$, and forms a new scale, Scale2$, consisting of the notes used in the tune. A new scale will typically contain about 12 notes which shows how much memory we are saving. The arrays for storing the second and third order sequences are then dimensioned in fine 660. The F1% array is cleared for further use.
PROCAnalyseTune at line 740 runs through the tune note by note and counts the number of times each sequence of notes occurs. The results are stored in the arrays F1%, F2% and F3%. F1% just counts the notes, F2% counts each sequence of two notes and F3% counts each sequence of three notes. The F1% array is used for a different purpose here, after being used to count the notes in PROCNewScale.
PROCCalcPercentges at line 880 calculates each order analysis one at a time. First, the number of times each sequence occurs in the tune, now resident in the F1%, F2% and F3'% arrays after PROCAnalyseTune, are added to find how many there are in total. The loop runs through them again and assigns new values to the arrays on a percentage basis. This is most easily seen in the first example between lines 910 and 970. The second and third order sequences use a series of nested loops to check every combination, but a percentage is only calculated if there is something there to calculate as in lines 1050 and 1150. This method reuses all the arrays, saving memory.
As we are using integer variables and arrays, you may wonder at the values which are going into the arrays to represent the percentages. Clearly such fines as 960 will not always produce an integer. The result is a set of figures which are not always 100% accurate. The integer variables and arrays reduce any figure to the next lowest integer. The overall inaccuracy will be very small and the sums of the contents of the arrays may not always add up to 100. This has a negligible effect on the composition and results in a faster program and a saving in memory.
You can alter these lines to produce true percentages or correctly rounded integers if you so wish. In the following formula:

B=INT (A 10^D)+0.5)/10^D

B will have the value of A to D decimal places.
You can use the same principle to calculate higher order analysis. The results should be very interesting but you will see how close we are to the original tune even with third order analysis.
PROCPrint at line 1200 is similar to PROCCalcPercentges except that it prints out the first, second and third order sequences along with their occurrence expressed as a percentage. If you see three notes followed by the figure 100, you know that, after the first two notes, the third note always occurs. If the figure was only 50, it would indicate that the third note occurs after the other two 50% of the time. The following figures will show how the remaining 50% is split up.
Before it can start composing, the program needs two seed notes on which to base its first calculation. These are asked for by line 200, and line 210 asks if you want a first, second or third order composition.
PROCGetNote at line 1450 decides which note should be played according to their percentage chance of occurrence. Dice% represents a random percentage. Sum% is incremented in line 1580, 1610 or 1640, depending upon the depth of analysis required.
This procedure may require further explanation. Note%, LastNote%and Penult% have values which are used to access a certain note or sequence of notes in the arrays F1%, F2% and F3%. These arrays contain percentages ranging from 0 to 100, which represent the frequency occurrence of the notes represented by Note%, LastNote% and Penult%. As an example, assume that the F2?£ array held the following:

F2%4LastNote%,1) = 20
F2%(LastNote%,2) = 50
F2%(LastNote%,3) = 10
F2%(LastNote%,4) = 8

F2%(LastNote%,5 = 12

The only figure we are concerned with is Note% which is increased by one each time round the loop. Also each time round, Sum% has added to it the figure (percentage) held in the array.
If Dice% equals 72 the procedure would work as follows: Sum% begins with a value of 0 and Note% with a value of 1. We add F2%(LastNote%,Note%) to Sum%. If it equals or is more than Dice'%, we leave the loop. In this case, with Note% equal to 1, it will equal 20, which is not enough so Note% is incremented by one and we try again. This time, 50 is added to Sum'%, which is still not enough so we repeat the process until Note% equals 3 which will give Sum% a value of 80, greater than Dice%, and so the loop is exited.
Note$ is calculated from the value of Note%. Penult% and LastNote% are adjusted, the note name is printed and we move to PROCPlay.
PROCPlay at line 1720 works out the correct pitch in a similar manner to PROCAnalyseNote and PROCCalculatePitch in Program 10.1. You can include these procedures as data checks if you wish; none has been included in this program, although the conversion principles are the same.
D% in line 230 is used to keep track of the duration values held in Dur% array: once the program runs through the values it is set to 0 at line 1770 and starts again.

Experimenting with the program

The tunes supplied in the DATA statements were selected because the durations of each note are roughly equal. This makes it easier for us to tell how close the compositions are to the original.
The duration values are used to play back the compositions with exactly the same note lengths. This does not encourage originality, but as the durations are stored in a separate array it would be easy to program your own note lengths into the compositions. You could also run the durations through a routine similar to the one used on the melody notes. Combining the two should prove interesting.
The data included only analyses one tune and the result is, predictably, a variation on the tune. If you use several tunes by the same composer, you should get a composition in that composer's style but a new tune.
You could also try several rock 'n' roll tunes. These use the same basic chords and harmonic structure but have different melodies.
The program will produce the most interesting results when used in this way. When you are entering large amounts of data you will realise how important it is to save memory. If the tunes and data are very long you may have to increase the size of the arrays at fine 100.
As you already have the data for some tunes from Chapter 9, Rondo Alla Turca, etc, you could extract the data, remove the ampersand commands, renumber them, save as an ASCII file by *SPOOL and then *EXEC them into the program for analysis. You may have to alter Scale$ in some cases.

A total tune analysis program

From the principles we have discussed and demonstrated so far, it is possible to envisage a program which would analyse every part of a tune.
There are at least two ways to approach this. One has been suggested and involves running the durations through the same sort of procedures as the melody notes. This would result in a rhythm pattern based on the tune's rhythm pattern but not related to the melody notes.
A second method would be to analyse the notes and their respective durations togeth&, so that C1 with a duration of 2 would be treated as one case and C1 with a duration of 4 would be treated as another, etc. You can see how this would consume memory very quickly ag it is quite possible that each note may have four or more different durations in a piece. This, however, would fie the melody and rhythm together in a much more realistic way.
Taking the idea a step further, we could also include an analysis of the harmony indicated by the chord structure. Most modern songs change chords perhaps every bar or every four bars and this could easily be analysed.
If you decided to analyse rock 'n' roll tunes there would be no need to analyse the chords as all songs (for pedants - most of them) use the same pattern. In the key of C the bars would contain the following chords:

C/C/C/C/F/F/C/C/G/F/C/C

This is the famous 12 Bar Blues, known in the music business simply as a 12 bar. All the chords are major chords although they are often played as sevenths (ie dominant seventh - C7 is formed from the notes C, E, G and A# and many of the melody notes will play around the seventh (ie in the case of C7, A#. If another chorus is to be played, the last bar of C is often replaced with a bar of G (or G7).

In an ideal program, while we are analysing the notes and their durations, I suppose that we could also see what chord was playing underneath and analyse these three combinations as one item. That would be very interesting indeed, not terribly difficult to program but a little greedy of memory. I leave it to the virtuoso.


Next     Top