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.
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.
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%
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'.
Christopher Dewhurst, EUG #65