EUG PD


Music

 
Published in EUG #34

I have found a program which will play a tune while other programs are being run. It is included on this disk. I was trying to compose a different tune for the program, and decided I needed a music writing program.

It is really so easy for anyone with even a simple idea of BBC BASIC to understand how it works. But first how to use the program.

The program will produce two octaves of tones starting from middle C and include all naturals and sharps. It will draw a staff and place the notes correctly onto the staff.

The tune can be played back by pressing P.

The playback is based upon the BASIC command SOUND. It has four main parameters after it. The first is the sound channel. This is set to 1. The second is the volume. This is set to maximum, i.e -15 mainly because the Electron doesn't allow this parameter to be varied. The third is the actual tone or note and the fourth if the length or duration of each note.

Initially the duration of each note is set to 6 which is OK for three blind mice but not much use for anything else. The duration of each note can be altered by pressing '/'. You then tap SPACE for each note, holding it down for as long as you want the note to last.

Note that I have a bug here. When pressing SPACE for the first note press it quickly to keep the duration short otherwise the rest of this procedure doesn't work properly. Can anyone find out what can be done with this?

As each note is played, its pitch and duration values are printed onto the screen.

The edited tune can then be played back by pressing P. If it's fine then copy the numbers down onto a piece of paper and transfer them to the music playing program. Otherwise you can re-edit it by pressing '/' again or re-compose the whole tune by pressing '-'.

The biggest problem was to find a way to make a typewriter keyboard effectively produce musical notes in a logical manner, preferably chromatically, in the fashion of a piano.

This is the sequence. It is included in the program so don't worry too much about trying to remember it.

note played      A#  G#  F#      D#  C#  |      A#  G#  F#      D#  C#
                                         |
key to press     2   3   4       6   7   |      S   D   F       H   J
                                         |
key to press  Q   W   E   R   T   Y   U  |   Z   X   C   V   B   N   M

note played   B   A   G   F   E   D   C  |   B   A   G   F   E   D   C
Here the two octaves are split the higher one being on the top two rows the lower being on the bottom two.

Think of the notes as flowing from one end down to the other and remember that the top octave stops at U the bottom stops at M, the sharps can be remembered by avoiding keys 1 and 5 and A and G.

If you do press the wrong key, nothing will happen anyway.

When you have composed your symphony, press P to hear it, then either press '-' to re-do it or '/' to edit the duration of each note.

So much for instruction. Now the program itself.

I first wrote this simple program which will play the notes and print out the relevant numbers to transfer to the second program.

      10CLS
      20b%=0
      30*FX11,0
      40REPEAT:a%=INSTR(" AWSDRFTGHUJIKOL;"GET$)
      50IF a%=0 d%=0 ELSE d%=5
      60IF a%=1 d%=0 ELSE b%=(a%*4)+40
      70SOUND1,-15,b%,d%
      80PRINTb%
      90UNTIL a%=108
The SOUND command has 4 values after it: the first is the channel, the second is the volume, the third is the pitch, the fourth the duration.

The GET$ command in line 40 waits for a key to be pressed and if that key is one of those listed between the quotes it will assign a value to a% equal to the numerical position of the letter, for example, if S is pressed a%=4, ';' and a%=17, SPACE and a%=1.

Line 50 says if a%=0, that is any key other than one of those listed is pressed then d%=0 so the duration of the sound is 0 so no sound. Any other value for a% and d%=5 so the sound will last 5 x 0.05 seconds.

Line 60 says if a%=1, d%=0. The easiest way to explain the need for this is to write line 40 without the initial space and to leave out line 60 except what comes after ELSE. Provided a% does not equal 0 or 1, d% will accept what comes after ELSE. Provided a% does not equal 0 or 1, d% will equal 5 and b% will equal (a%*4)+40. As with arithmetic, sums in brackets are done first.

So if key A is pressed, "A" is the second item therefore a%=2. 2*4=8, 8+40=48 hence b%=48 and the SOUND command can be executed, playing the note B for a quarter of a second.

Line 80 prints b% onto the screen so that it can be copied down.

Line 90 tests for Z being pressed. Z will end the program.

Now that's fine as far as it goes. But wouldn't it be so much nicer if, as well as producing the correct sound and numbers for the accompaniment program, we could also produce a staff with the notes positioned correctly. Then, when someone comes in to find out what all the noise is about they can be suitably impressed and leave thinking that you might just be the next Noel Gallagher!

It would be even nicer if this could be done easily. Well it can, the program is simple to understand and even simpler to use.

First problem, draw the staff.

The keyboard provides 2 characters which will draw horizontal lines; minus "-" and underline "_". Either of these might work but they you are left with the problem of what to use for the musical notes. Using minus o will be on the centre of the line, fine for E G B D and F, not much use for F A C and E.

We could use MOVE and DRAW.

      10MODE4
      20MOVE0,1010
      30DRAW1200,1010
      40MOVE0,945
      50DRAW1200,945
      60PRINTTAB(0,0)"O"
      70PRINTTAB(1,1)"O"
This will draw nice lines and the notes are correctly positioned. In practice the figures would be substituted with variables so that all 5 lines and the notes could be calculated and drawn arithmetically.

The lines are in fact graphics characters and the spacing is quite wide. Another problem is, if you want to print out a hard copy to show the rest of the band, you will need to use a graphic screen dump and the resolution with many printers is not that good.

The other option is to use the keyboard character underline. Here the notes would need to be defined using VDU23. VDU23 characters can be called using CHR$ and many printers will allow their character sets to be redefined so the next number one hit might be printed out using text printing, fast and sharp.

      10MODE4
      20FOR A=2 TO 6:PRINTTAB(1,A)STRING$(39,"_"):NEXT
That's one staff drawn. The notes need to be defined and for this we need to resort to some paper. Or you may have a character defining program, such as the one published in EUG #1.

Draw a grid of 8x8 boxes. Fill in the boxes that will make the shape you want. Now working horizontally, the first box has a value of 128, 2nd=64, 3rd =32, 4th =16, 5th =8, 6th =4, 7th=2 and 8th=1.

Start from the first horizontal row at the top. Check which boxes are filled in and add those values together:

         ________________________________
        |___|___|___|___|___|___|_ _|___| no boxes=0
        |___|___|___|_X_|_X_|___|___|___| 4th and 5th=16+8=24
        |___|___|_X_|___|___|_X_|___|___| 3rd and 6th=32+4=36
        |___|_X_|___|___|___|___|_X_|___| 2ed and 7th=64+2=66
        |___|_X_|___|___|___|___|_X_|___| 2ed and 7th=64+2=66
        |___|___|_X_|___|___|_X_|___|___| 3rd and 6th=32+4=36
        |___|___|___|_X_|_X_|___|___|___| 4th and 5th=16+8=36
        |___|___|___|___|___|___|___|___| no boxes=0
         128  64  32  16  8   4   2   1
Now type in:
      VDU23,224, 0 , 24, 36, 66, 66, 36, 36, 0
                      1st,2nd,3rd,4th,5th,6th,7th,8th
Check the character by typing PRINT CHR$226 (RETURN)

These numbers are different from those used for the character in this program. I used these numbers were clarity.

The number after 23, in this case 226, is the ASCII code for the character just defined. You can use any number between 224 and 255 so the next character you define might be VDU23,225,etc.

To call this character, I used a procedure:

      DEFPROCs(h,v):PRINTTAB(h,v)CHR$226:ENDPROC
DEF means DEFine a PROCedure or FuNction. Immediately following DEF comes the name of the procedure. In this case PROCs is followed by two variables within brackets. When this procedure is called, the call will include integers for these two variables. The procedure itself simply consists of a PRINTTAB statement; the parameters for the PRINTTAB co-ordinates are the variables sent by the procedure call. Remember that, in a PRINTTAB statement, two numbers must be included which will dictate the horizontal and vertical position on the screen of where the character to be printed is to be.

This character is fine for between the lines F, A, C and E but for the notes on the lines we need something else. If we define two semi-circles, one facing up, the other down, then program them to be placed into position together, the effect will be achieved.

This is a lot easier than you might think.

The characters I defined were CHR$224 for the lower half of the circle and CHR$225 for the upper half.

I'm not going to describe again how a character is defined but for the lower semicircle the cup side sits on the top row of the grid and the curve sits three rows down. The other semicircle of course is the exact opposite.

      VDU23,224,66,66,60,0,0,0,0,0
      VDU23,225,0,0,0,0,0,60,66,66
The procedure was defined as:
      DEFPROCl(h,v,v1):PRINTTAB(h,v)CHR$225;TAB(h,v1)CHR$224:ENDPROC
Here the procedure name had three variables: h, v and v1. In the PRINTTAB statements, both shared h which is the horizontal position but one used v for its vertical while the other used v1.

The a%=INSTR( line used above is used in this program. This line has a REPEAT UNTIL loop; the REPEAT is before a%=INSTR( and UNTIL is at the end. The loop will cycle indefinitely so that, when a key is pressed, the relevant action is taken. Then the cycle will continue testing for another keypress and so on.

The procedures are called by:

      IF a%=1 PROCl(h%,v%,(v%+1)):c%=88
c% is the value for the pitch in the SOUND command.

Now I said that when the procedure is called, it will include values for the variables defined in the procedure name. What I've done here is to use other variables to define those variables. The reason why is when this key is pressed, it will place a character (2 in fact, this note sits on a line) onto the staff in a particular place. But the next note will need to be further along so the TAB positions will need to be altered each time. This is done by using some surprisingly simple arithmetic to produce the values h% and v%. The third value is simply v% with 1 added.

h% is the number of spaces away from the left hand side of the screen. It must change by 2 each time and also if the right hand side of the screen is reached, it will need to be brought back to the start. h% will take the printing down to the next staff.

I defined another variable to take care of h% and v%. It's called b%.

Initially b% was given the value 0 from outside the REPEAT UNTIL loop. It will keep this value until the end of the loop a line:

      b%=b%+2
which adds 2 to it. It won't get to the end of the loop until a key has been pressed. So, for the first keypress b%=0, for the second b%=2, and so on.

Back at the start of the loop were 3 lines:

      IF b%<38v%=a%:ELSE v%=a%+8
      IF b%>75v%=a%+16
      IF b%>112END
b% will be less than 38 while printing on the first staff. So here, v% will be equal to a%. So in the example line printed above, the second two v% variables (h%,v%,(v%+1)) will equal 1 and 2.

If b% was more than 38, v% would be equal to a%+8, so in our example: a%=1, a%+8=9, v%=9, v%+1=10. This is to print onto the second staff.

If b% was more than 75, v% would be equal to a%+16.

Now there is an important point here. Pressing the first key, a%=1 so the vertical printing positions will be 1 for the top half and 2 for the bottom.

Pressing the second key, a%=2 so the vertical printing positions will be 2 for the top half and 3 for the bottom. So, as you press each successive key, the proper note is printed in the proper vertical position on the staff.

h% is calculated with the line:

      IF h%<38h%=h%+2:ELSE IF h%=38h%=0:h%=h%+2
h% starts as being equal to 2. With each note printed, 2 is added and, since h% governs the horizontal position of where the next note is printed, this means that each note will be two spaces apart. Until the end of the line is reached. Then h% will once again be reduced to 2 and the sequence will start again except of course that printing will be on the next staff down but moving down is done by the v% variable.

As each note is composed its pitch value is placed into an array, d%.

I should say something about arrays.

An array can be thought of as a row of boxes. It is defined with the command DIM eg. DIM A(5). Here a whole row is called A. There are 6 boxes in the array, 0, 1, 2...5. The first is called A(0), the second A(1) and so on.

The program below explains how an array works:

       10X=0:Y=0 
       20DIM A(5) 
       30REPEAT 
       40INPUT B
       50A(X)= B 
       60X=X+1   
       70UNTIL X=6 
       80REPEAT
       90PRINTA(Y),Y
      100Y=Y+1 
      110UNTIL Y=6
Line 10 declares 2 variables which are going to be used later. The computer needs to be told that variables are going to be used and what value they will, initially at least, have. In this case both are set to 0. The reason will become clear.

Line 20 sets up an array, ie DIMensions an array. This can be thought of as a sequence of boxes each of which is named. The first will be called A(0), the next A(1) and so on until A(5). Because the first is A(0), DIM A(5) will set up an array of 6.

Line 30 is the start of a REPEAT UNTIL loop. The instructions within the loop will be executed by the computer continuously until the condition set after UNTIL is met. In this case until X=6.

Line 40 waits for information to be typed in and RETURN is pressed. INPUT is used so that several key presses can be made for each entry. Numbers need to be entered. If RETURN is pressed without an entry being made or after letters have been pressed the value will be 0. The entry value is put temporarily into a variable called B.

Line 50 says that A(X)=B. In other words on the first loop the array box called A(0) will have the values in the variable B placed into it.

Line 60 adds 1 to the value of X so that during the next loop X will =1.

Line 70 checks if X=6. If it doesn't then the loop cycle is repeated. The value of X is increased by 1 each time the loop passes line 60 so that the whole loop will cycle six times. When X=6 the loop won't cycle.

The second loop will of course start at the REPEAT statement at line 30. During the second loop the value of X will be 1 so that the keypresses that are called for by the INPUT command will be put into array box A(1).

After the first loop has cycled five times it will end and the computer will go to the next line to see what the next instruction is Bit like me after I've finished washing the dishes.

Line 80 is the start of another REPEAT UNTIL loop.

Line 90 says PRINT the contents of array box A(Y) and also, beside t, print Y. The purpose of the comma is to separate the two figures by a few spaces. Y was of course set at 0 in line 10 at the same time as X used in the last loop. So this line will print the contents of array box A(0) and the number 0.

Line 100 adds 1 to Y so that the next time round the loop the next array box will have its contents printed.

Line 110 tests for if Y=6 . If it does the loop will end.

The array d% has 58 boxes. For each note that is pressed, the value c% is used as the pitch level for the SOUND command and c% is also placed into the d% array boxes. This is done with the command d%(e%)=c%. e% was initially set at 0 but is increased by 1 with each loop, ie each time a key is pressed.

There is another array, t%. This array also has 58 boxes and is used to contain the duration value for the SOUND command. In the first part of the program, this has been set to 6 by the command t%(e%)=6. Again e% is used to count through the boxes of this array.

One further feature in the compose part of this program is a means to change the octaves up and down. Each octave is separated by 48 so C is octave 2=52 while c in octave 3=100, octave 4=148 and so on. Yet another variable was created, w%. As I said previously, the pitch value for the SOUND command is set with c%. The value of w% is changed using the < and > keys with the lines, IF a%=27 w%=w%-1 and IF a%=28 w%=w%+1. There is another line just before the SOUND command which says c%=c%+(48*w%). So, if w%=1, c%=the given value for c%+(48*w%) or, to put it another way, 48*1=48+c% means that the octave played will be raised by one. w% can in fact be equal to 0 so that octave 1 can be played and w% can also be equal to 4 so that the highest octave can be played.

Now for the playback.

Remember that e% represents the number of notes played and is used to count through the arrays d% for the pitch values and t% the duration values.

The call PROCplayback(e%) takes the current value of e% to the procedure. A variable, another one, f% is set to 0. A REPEAT UNTIL loop begins by adding one to f% with f%=f%+1. Then the command SOUND1,-15,d%(f%),t%(f%) plays the tune. On the first loop, f%=1 so the pitch played will be stored in the box d%(1) which happens to be the first note composed. The duration will be 6 since every value for duration was set to 6.

So much for playback.

Now to set the length of each note.

This is done with PROC edit(e%).

Again e% takes the number of notes composed to the procedure.

The screen is cleared. If no notes have been played e% will=0 and the program will go back to compose. f% is set to 0. x and y which are part of a TAB statement are set to 0 and 2 respectively.

A REPEAT loop is started. 1 is added to f% and y and u% are made equal to 0.

The next line is a means of formatting a printout so that it will be printed in columns so many items long. Then a second column will start from the top again but shifted right.

Then comes a line:

      IF INKEY(-99) REPEAT:u%=u%+1:SOUND&11,-4,d%(f%),(u%/4):UNTIL NOT I
      NKEY(-99):PRINTTAB(x,y);(u%DIV4);" ";d%(f%)
INKEY(-99) will wait for the SPACE BAR to be pressed. So long as it is pressed, a REPEAT UNTIL loop will cycle. This loop is nested within the first REPEAT UNTIL loop.

1 will be added to u% and the SOUND command will play the note defined by the pitch stored in the array box d%(f%). Since this is the first cycle the pitch value will be stored in box d%(1). The duration of this note will be 1/4 as defined by (u%/4). If SPACE is still pressed, u% will =2 and the note will continue. For as long as SPACE is pressed the note will continue playing, each time 1 will be added to u%. When SPACE is released, the note will stop. The accumulated value of u% will be divided by 4, without a remainder, and printed beside the value of d%(1), ie the note which was played.

This accumulated value of u% divided by 4 will also be put into the array t% which stores the values of the duration of each note and which originally had all of its values set to 6.

The next line will check if e%=f%, if it does not the loop will cycle back to the first REPEAT. 1 will be added to f% making the next note to be played d%(2), u% will be reset back to 0 and the program will wait for SPACE to be pressed again.

When e%=f% ie when all of the notes composed have been edited, the procedure will finish and jump to PROCmenu. Here you have the option to repeat the editing procedure, playback the tune with the new values for duration or to re-enter the compose section and re-do the tune from scratch.

And that is all there is to it.

If anyone can improve on any aspects of this program then please do and send it in.

I originally wrote this program and text for inclusion in EUG #33, but held it back so that I could sort out bugs. All except the one in PROCedit(e%) have been sorted. Some minor alterations have been made to the program but the text is still relevant. If I tried to rewrite the text now, I might be here forever.

Gus Donnachaidh, EUG #34

Gus Donnachaidh