This is a simple but very useful music processing program that I wrote to assist me with compiling jingles for games and other programs. Before I go any further I should explain my method of storing note data, which is much more compact than conventional SOUND statements.
How to Store SOUND Data more Compactly
In BASIC we would write something like:
SOUND 1,-15,52,20
which sounds middle C for one second. In a BASIC program just this one SOUND statement takes up 12 bytes in memory (or 11 if you miss out the space between the SOUND keyword and the 1).
We could resort to machine code to save a few bytes because, as you may know, each parameter of the SOUND statement occupies two bytes (one word) each. Even so, this adds up eight bytes merely for one sound.
If we examine how sound works on the BBC and Electron, one of the first things we discover is that the pitch - the third number in the SOUND statement - is a multiple of four. Middle C has pitch value 52, C# is 56, D is 60 and so on. Aunty Acorn has blessed us with the ability to sound notes with a quarter of a semitone difference between them. Now, you may be interested in microtonal music, and I am in no way critizing you if you are, but for most applications this degree of pitch accuracy isn't required (how many games do you know with microtonal signature tunes?). So straightaway we can make a memory saving by storing pitches divided by four, i.e. in six bits instead of eight (or 16, bearing in mind that in machine code there are two bytes per SOUND parameter).
Now, it will also be observed that the channel number is always between zero and three inclusive, i.e. two bits. Therefore we can store channel and pitch in one byte. In fact, we don't need to bother about dividing the pitch by four because ANDing with 3 - i.e. masking off the bottom two bits - will give us the channel number, and ANDing with 252 will give us the pitch, already multiplied by four. For example, middle C on channel 1 can be stored as 53. 53 AND 3 = 1 (channel number) and 53 AND 252 = 52 (correct pitch for middle C).
I have found that for most BBC micro music, particularly game themes, you are unlikely to want notes of duration of more than 255 (about 12 seconds), Rarely will you have notes of duration one either (1/20th of a second, that's less than a demisemiquaver in a piece of music going a tempo of crotchet = 120). So we can store the duration in one byte and multiply up by a factor if necessary.
What about the volume or envelope parameter (second parameter in the SOUND statement)? Well, most of the time jingles tend to use only one ENVELOPE (instrument) or sound at the same volume per channel. So the second parameter in the SOUND statement can remain constant with pitch and duration varying for every note READ in from a DATA statement say. Thus, for every note - or rest - in the piece, we only need use two bytes. That's quite a saving over the original eight bytes.
Using MuPro
All very well in theory but let's put it to the test. This is where the music processing program comes in. It is assumed you that music is typed in, see DATA statements at the end of the progra, in a semi-intelligent language that I have written and called MuPro. It is based on a few simple rules:
- You specify the tempo of the piece as the number of shortest notes per minute, e.g. a piece at crotchet = 120 with semiquavers in it is specified as 480.
- Each note is specified by giving its name plus octave number i.e. C, C# Db etc. with R for rest, and relative value to the shortest note used i.e. 1=semiquaver, 2=quaver, 4=crotchet and so forth.
- The duration or octave number need not be specified if it's the same as the previous note (except for the first note of the channel). Octaves are numbered such that octave 2 is the octave starting with middle C.
- The end of the music for the current channel - or unused channel - is marked by two speech marks "".
For example, the first phrase of 'God Save the Queen' at crotchet = 80 would be:
160, | [shortest note in piece is quaver so tempo is 80x2 = 160] |
G2,2,G,A,F#,3,G,1,A,2, | [first phrase] |
B,B,C3,B2,3,A,1,G,4, | [second phrase] |
A,G,F#,G,"" | [third phrase then end-of-channel marker] |
Note that the program can handle mixtures of sharps and flats so the last phrase could be written (although not musically correct) as: A,G,Gb,G,"".
PROCprocess interprets the MuPro language given in the DATA statements at the end. These can be added in BASIC or, more conveniently, written with the help of a word processor like View and *EXECed.
PROCprocess stores the pitch and duration data in pit%(0..3) and dur%(0..3) for each channel. PROCsort 'interleaves' the notes from all channels by arranging them according to their vertical then horizontal position on the stave from channel 1 upwards. The data is stored in the compacted format discussed earlier. Let's say a piece at crotchet = 60 opens with a crotchet C major chord (root middle C). I have semiquavers in my piece so I specify the tempo at semiquaver = 240. The end result would be:
53,20,70,20,83,20
The procedure works out the correct sequence by successively by keeping a four cumulative duration records, one for each channel, q%(0) to q%(3), and comparing to a fifth, base total t%. Every time t% is incremented by one (i.e. semiquaver if a semiquaver is the shortest note in the piece) it is compared to q%(0..3). If t% is less then q%(0..3) a note is pulled from corresponding channel and its pointer ptr%(0..3) incremented.
When the program has finished compiling the music you can press 1 to play the piece or 2 to SPOOL out note data that can be READ in and played by a procedure similar to PROCplay in your own program. Simply delete line 830 and substitute line 850 for:
850 READ P%,D%
Limitations of MuPro
I wrote MuPro to help me compile simple jingles for games where one or two ENVELOPEs are used for instruments. MuPro doesn't allow you to change instruments partway through a piece although you could reserve special notes for this. 255 is the end-of-channel marker so maybe 254 could signify a change of ENVELOPE for that channel with the duration being the ENVELOPE number.
The noise channel, channel zero, has eight basic sounds. Because the pitch is stored as a multiple of four you can only ever get two of the sounds. You could add an extra condition to check for channel zero before playing a note and divide the pitch by four if the channel's zero. Or use an ENVELOPE with a pitch shift to get the required 'white noise' or 'tone'.
Demo
As it was jubliee time when I write this, program JUBILEE is a graphics and sound demonstration that draws a picture of Her Majesty then plays God Save the Queen. The note data was generated by MuPro. File GSTQ is a View file containing the note data as written in the MuPro language. Use this as a guide when typing in your own music. Enjoy - and happy Golden Jubilee!
Christopher Dewhurst, EUG #65