Magazine Archive

Home -> Magazines -> Issues -> Articles in this issue -> View

Article Group:
Computer Musician

The Art of Going Soft (Part 2)

Jay Chapman takes a bold step for novice programmers, as he presents a Pascal program that converts musical note values into a language computers can understand. Don't worry if it leaves you standing.

Part Two of the series that takes the mystery out of writing music software for home computers.

We said last month that we don't intend The Art of Going Soft to be a programming course for beginners as such. For one thing, we haven't anything like enough space, and for another, the exercise would be fairly pointless anyway, as the individual micro user guides and magazines make a much better job of introducing newcomers to programming than we're ever likely to.

What we can do is provide basic algorithms (ie. descriptions of how to perform tasks) that relate specifically to musical programming. As your experience grows, you should be able to start putting some of these routines together with your own code to produce useful music-related utilities. Or at least, that's the idea.

Our first example routines deal with the common requirement to convert a note name (something like 'C#6'), which we might use outside a computer program, into an internal key number that can be more easily manipulated within the program. The key numbers you allocate to note names could be quite arbitrary, but we'll use the MIDI standard as our particular guide, mainly because it'll prove useful in later pieces of software.

Briefly, what the following two procedures do is enable ordinary musicians to input note information to a computer in an easily accessible and friendly way. All you do is key in the normal note name, and the procedure read-note (program lines 30 to 157) reads it and converts it into a key number. Similarly, if you want to view the results of your internal manipulations of musical notes, the last thing you want is the answer in the form of a key number; that would just confuse the issue by forcing you to convert 97, say, into its note name in your head. So, we have a second requirement, satisfied by procedure write_note, of converting key numbers back into note names. Simple, really.

MIDI Key Numbers

First, a limitation. Because MIDI data bytes must have the sign bit set to zero to distinguish them from MIDI status bytes, we only have seven bits available to represent the key number. This gives us the range of numbers from 0 (binary 0000 0000) to 127 (binary 0111 1111). Now, MIDI specifies Middle C - which we would write as 'C3' - as Key Number 60, so we can represent note names from 'C-2' (0) to 'G8' (127). The correlation between note name and key number is shown in lines 51 and 52 of the program listing.

How can we make use of these key numbers in our program? Well, key numbers are going to be useful if you want to create a chord based on a particular note, for example. It doesn't take too much thought to work out that a major triad based on the note with key number 'n' as its root would be the notes with key numbers 'n', 'n+4' and 'n+7'. So, if the root note was 'C0', the chord would be 'C0' (24), 'E0' (28) and 'G0' (31). Not of earth-shattering importance, perhaps, but suddenly the idea of your computer program dealing with transposition, intervals, keyboard splits and random note generation should all start to seem a little less mystical. It might even teeter on the edge of the realms of possibility...

Let's try to work out how to program a computer to transpose up a perfect fifth from 'Bbb-2'. Given just those four characters stored in a string or an array, how do you progress? Well, life gets much easier if you only have to deal with the key numbers in programs. Numerically, the interval of a perfect fifth is always seven semitones. Exactly how the upper note of the interval is displayed as a note name for human consumption depends on the key signature, but all you have to do internally is add 7 to the original key number. So, if you have the key number for 'Bbb-2', which is 9, you just add 7 to give 16 (which write_note could convert to 'E-1' if you so wished). Simple, huh?

The Listing

Before we look at the two procedures in detail, let's deal with some general points about the listing. The program is written in Pascal rather than BASIC, for reasons I went into in last month's Going Soft introduction. And whilst specific features of Pascal will need some explanation, the general run of the code is sufficiently English-like to make some sort of sense at first reading.

Effectively, the program is a description of the algorithms that solve the programming problems we've set ourselves. Given this description you should, with perseverance, be able to implement your own version of the routines in whatever programming language you have available. The more comprehensive and 'high-level' the language, the easier it will be, of course.

The first line - dcomp p.namekey o.namekey - is a command to the Acorn ISO-Pascal system to compile the source code it finds in the file p.namekey and store the compiled program in the file o.namekey. Unlike BASIC, which lets you execute the program you've just typed in directly, Pascal requires that the source code be translated before execution rather than during it. If you have a Pascal system on your computer, the user manual should tell you how to organise the compilation.

The ISO-Pascal compiler announces itself on the second line of the listing, and what follows is the listing produced by the compiler as it translates the source code. The program is error-free and actually works(!) so there are no errors reported in the listing. The left-hand column of numbers is simply a list of line numbers that the compiler has added to the listing so that error messages can refer to lines by this number. Unlike their BASIC brethren, Pascal source programs don't have line numbers when the program is edited into its source file.

The next column of numbers tells us whether the line of source we're looking at is at the main program level (0) or in a procedure (1). If a procedure had its own local procedures declared within it, they would be at Level 2, and so on. The third column, consisting of the characters '-' or 'C', tells us if the line is the second or subsequent line of a comment (C).

Program line 1 specifies that the name of the program is read_n_write_notes and that we'll be using the standard input and output files - in my case, that means the BBC Micro's keyboard and screen. Note the use of the underscore character ('_') to make identifiers (such as the program name and variable names) more readable where they are made up of two or more words. This facility is a very welcome extension added to ISO-Pascal by Acorn. Program lines 3 to 5 define the constant identifier space as a synonym for the space character; we'll see this used later in the program.

Lines 7 to 9 specify a type called MIDI_key_number_T. You should already be used to the idea that all variables and expressions in BASIC have a type (real and string are two examples), which defines what sort of data item the variables can hold or the expressions evaluate to. In fact, BBC BASIC has three different types, and variables are shown to be of a particular type by the addition of an extra character to the variable's identifier. So, the variables 'A', 'A%' and 'A$' are three different variables of type, namely real, integer and string respectively.

In Pascal, you can also define your own types, which you can use later in specifying the type of some variable you wish to declare. When a variable is declared to be of MIDI_key_number_T, as is the variable internal_note in line 13, it's only able to hold one of the values between 0 and 127 inclusive. Since there are no legal MIDI key numbers other than those in the range 0 to 127, the program stops with an error message if you try to assign a silly value to internal_note. Usually, it'll indicate where in the program the illegal assignment was attempted, too. You can then check the program out and find out why it's going wrong.

In BASIC it's often the case that the program will blunder on even if you do make some silly/illegal assignment, and end up producing an (incorrect) answer of some description. If the answer looks ridiculous ("your age is -3562564798 years"), you'll have good cause to check the program out for errors, but if the answer looks reasonable, you may well try to make use of it - with, ahem, disastrous results.

Anyway, back to the real world. Lines 11 to 14 declare two variables: internal_note, which we've just talked about, and error, which is of boolean type. The boolean type has only two values (true and false) which are often represented in BASIC by the numeric values -1 and 0 respectively. You'll see the variable used later in the program.

The Main Program

Having defined the global constants, types and variables that are to be visible - and therefore usable - from any point in the program code, we can go on to look at the main program body (lines 219 to 245). The main program is a fairly simple example of how the two procedures in question can be used, once they've made their way into your library of useful software routines. Lines 221 and 243 are the enclosing start and end of a repeat—until loop that never stops, so all the Pascal statements between these two lines are executed in a never-ending loop (sounds like Dynasty - Ed).

Line 223 asks you to Enter a note name (such as 'Cbb3') at the keyboard, after which the procedure read—note is called in line 224 to do all the hard work. Once this procedure has read the note name and performed the conversion, the resulting key number (58) is returned to the caller in the variable internal_note supplied as the first parameter. If the reading and conversion process can't be successfully completed, the procedure sets the boolean value true into the boolean variable error supplied as the second parameter.

Now, most BASICS can't actually handle the return of values via parameters. If that's the case with your own system, you might have to return values using global variables which the procedure code and the caller's code can agree to access.

The if statement commencing in line 227 tells you if there was an error in line 229, but if no error occurred, the code following else (line 232) is executed.

The key number is displayed by the writeln statement in line 234, after which the second of the procedures under scrutiny, write_note, is called in lines 236, 237 and 238. Its job? To convert the key number stored in internal_note back to a note name and then display it. Three calls are made to show the effect of different second parameter values ('#', 'b' and ' ') on the call - more on this in a moment.

Procedure 'Read_Note'

The definition of this procedure starts at line 30 and finishes at line 159. Line 30 defines the name of the procedure and the details of the parameters that can be passed into and out of the procedure when it's called. Suffice it to say that the var in front of both key_number and error means that you can pass values in or out.

Lines 33 to 56 form a comment that describes what the procedure actually does. The symbols '{' and '}' bracket the comment, and the compiler simply ignores any enclosed text that exists purely for the benefit of human readers. The format definition in line 38 will probably become a little more readable if I explain that the '[' and ']' characters bracket optional items. So, we must have at least a letter and a single digit for the octave number (eg. 'C0') because the 'l' and 'n' aren't bracketed and are therefore not optional. The '|' character separates alternatives, so you can opt to have a '#', an 'x' , a 'b' or a 'bb'. Note that the second 'b' in a 'bb' must follow a 'b' (oh yes, obviously - Ed). It cannot follow a '#', which forms a different alternative choice. You should be able to work out that '[+|-]' means that you can have either a '+' or a '-' before the octave digit 'n'. This sort of shorthand definition becomes useful with experience, but that doesn't mean to say you shouldn't apply a bit of commonsense as well: without further complicating the format, it unfortunately allows 'C-8', which MIDI does not!

The procedure's local variables are defined in lines 58 to 64. Note that octave_number can only hold legal octave numbers, so even if you program something silly in the way of calculations, you'll soon know if you've inputted an octave value of, say, -325. The range of values allowed for the contents of the variable check_key_number requires a little more explanation - this will come later when we see it in use.

Now, at last, we've reached the code of the procedure itself. In line 68, we effectively take the view that whoever's typing in the note name is innocent until proven guilty, ie. that he or she is typing in a legal note name until we discover otherwise. In line 72, we read in the first character of the note name using the procedure read_iws to ensure that we ignore leading spaces and new lines. The if statement (comprising lines 74 to 88) tests that the character read in is a legal note letter by checking whether it's in the set of upper and lower case letters 'A' through 'G'. This range is defined by the Pascal syntax ['A'..'G','a'..'g']. If the test fails, this can't be a legal note name and the else part of the if statement is executed (line 88); this sets the variable error to true. German readers who use 'H' as a legal note letter would no doubt want to modify the set definition...

Assuming the letter is OK, the case statement (lines 76 to 84) assigns a key number offset corresponding to the letter's position in relation to the white notes starting from 'C'. If our character is an upper or lower case 'D', for example, only line 78 of those within the case statement is executed, and the variable key_number is assigned the value 2. Thus, the white note D is two semitones, and therefore two key numbers, above C. In line 85, a further offset of 24 is added so that our note is assumed to be in the octave commencing with 'C0', key numbers 24 ('C0') to 35 ('B0') 11 semitones higher. The octave number tells us how many octaves (or 12 key numbers) to add or subtract. For instance, if the full note name turned out to be 'D3', the key number would start off as an offset of 2 (within the octave), change to 26 thanks to line 85 while we assume it is 'D0', and then have 2 (its actual octave number) multiplied by 12 added, to give its actual key number of 50.

If we've already had an error somewhere along the line, we avoid executing the rest of the code thanks to the if statement commencing in line 90. If a good letter name has been dealt with, the next non-space, non-new-line character is obtained courtesy of procedure read_iws. The idea in code lines 98 to 128 is to deal with the character just read if it is a '#', an 'x', a 'b', or the first character of a 'bb'. Since it may actually be none of these things but part of the octave number instead, we may not actually use the character, in which case we must remember to use this rather than reading in another character. The variable read_next is used to record whether or not (true or false) we're forced to perform the next read.

If you apply some concentration to the careful reading of the nested if statements in lines 98 to 121, you'll see that the various possibilities for sharps, flats and naturals are checked for and appropriate modifications made to the key_number variables contents.

For example, if the note name has one sharp applied to it, the key number is incremented by 1. If we had 'Dbb3' rather than 'D3', the key number would be decremented by 2 to become 48. The comment in lines 130 to 133 sums up our situation at this point - we have only the octave number to deal with.

If the character we have is a sign for the octave number, we remember if it was negative in the variable octave_negative in line 135, and read in the octave digit in line 138. If there is no sign, the character we have is the octave digit value, held as an ASCII character code.

Lines 142 and 143 check that the octave number will, when eventually calculated as a numeric value, be in the range -2 to +8. Line 147 performs the conversion from the ASCII character code version of the digit to a numeric value stored in the variable octave_number, and lines 148 and 149 are responsible for negating this value if we'd remembered a '-' sign. We can now complete our calculation by shifting the key number up or down into the correct octave (line 151).

As for the key number, this is temporarily stored in the variable check_key_number in case it lies outside the MIDI key number range of 0 to 127. Yet although we've already checked we have a legal note letter and octave number there's still the possibility of error. Consider the note name 'Cbb-2', for example. 'C' is a legal letter and -2 is a legal octave number, but the 'bb', whilst seeming to be a legal modifier, actually reduces the key number from 0 (for 'C-2') to -2, which is illegal. Similarly, notes which would be above G8 (up to Bx8, in fact) look legal in terms of note letter and octave, but all give a key number greater than 127. Anyway, the range of values the check_key_number variable can hold (defined in its declaration in line 64) should now make more sense.

Finally, if there's been no error of any kind, the checked key number is returned to the caller thanks to the key_number parameter (line 154), and the procedure call ends.

Procedure 'Write-Note'

This procedure's parameters are defined in lines 161 and 162. You can see that both parameters allow data to be passed in but not out (since var was not specified). In fact, the use of these parameters is explained in the comment that begins in line 164: the local variables have been declared in lines 174 to 178.

As soon as it's called, the procedure starts off by calculating the key number's position within the octave in line 182, and its octave number in lines 183 and 184. If the caller has shown no preference for printing the names of black notes as '#'s or 'b's by the time things get to lines 186 and 187, the default of '#' replaces the space character that the caller specified (see line 238). Line 191 checks if the key number within the octave we have is one of the black notes (ie. in the set [1,3,6,8,10]). If it is a black note, the nested if statement (lines 194 to 198) changes the key number to be the white note above or below, depending on whether we wish to print the black note's name as the white note above flattened or the one below sharpened. This is where the logic of high technology stands to one side and personal taste comes to the fore.

We now know everything about the note name, so we can print out each portion in turn. The correct note letter is printed out by selecting the relevant arm of the case statement (lines 202 to 210) and is followed by a '#' or 'b' as required (lines 212 to 213). Finally, the octave number is written out in line 215; the :1 indicates the field width that the value should be printed in, and in this case, is an instruction to print with no leading spaces, so that there's no separation between the octave number and the rest of the note name.

What Next?

If you've managed to digest the content of this month's instalment without going completely insane (congratulations!), you will at worst have a couple of routines to slot into your catalogue of useful software bits and pieces. At best, you should be itching to put them to some incredibly exciting and wonderful purpose. You may have some of your own already-written software that you can now make a little more user-friendly by taking advantage of the two procedures.

In fact, examples of the sort of software that might benefit from the use of such routines come to mind fairly easily: a sequencer package's step-time editing section, for instance, or a music theory 'intervals test' program for the less musically-literate.

You might even consider these routines as the first step down the road towards our own Music Composition Language. Well, there's no harm in being ambitious, after all.

(Click image for higher resolution version)

(Click image for higher resolution version)

(Click image for higher resolution version)

Series - "The Art of Going Soft"

Read the next part in this series:

All parts in this series:

Part 1 | Part 2 (Viewing) | Part 3

More with this topic

Browse by Topic:


Previous Article in this issue

Fairlight Goes MIDI

Electronics & Music Maker - Copyright: Music Maker Publications (UK), Future Publishing.


Electronics & Music Maker - Jun 1985

Scanned by: Stewart Lawler

Computer Musician




The Art of Going Soft

Part 1 | Part 2 (Viewing) | Part 3

Feature by Jay Chapman

Previous article in this issue:

> Fairlight Goes MIDI

Help Support The Things You Love

mu:zines is the result of thousands of hours of effort, and will require many thousands more going forward to reach our goals of getting all this content online.

If you value this resource, you can support this project - it really helps!

Donations for January 2022
Issues donated this month: 2

New issues that have been donated or scanned for us this month.

Funds donated this month: £135.00

All donations and support are gratefully appreciated - thank you.

Magazines Needed - Can You Help?

Do you have any of these magazine issues?

> See all issues we need

If so, and you can donate, lend or scan them to help complete our archive, please get in touch via the Contribute page - thanks!

If you're enjoying the site, please consider supporting me to help build this archive...

...with a one time Donation, or a recurring Donation of just £2 a month. It really helps - thank you!

Small Print

Terms of usePrivacy