BeeBMIDI Monitor (Part 2)
The second, and final part of Jay Chapman's Monitor program for E&MM's own BBC-MIDI interface. This month's instalment gives the program listing and explains how it was written.
The second and concluding feature on our MIDI data display program for the BBC Micro. This month: the listing, a description of how it works, and some pictures to give some idea of what it can do.
So you missed last month's E&MM because the newsagents had sold out which, in turn, was because you went there too late, and now you're wondering just what all this BeeBMIDI Monitor business is about.
Well, the only way you're going to know exactly what it's about is to buy last month's episode from E&MM's Back Issues department. But just in case you haven't had time to do that yet (I know a lot of you are very busy people), I'll just say that Monitor is a new software package that intercepts MIDI data from suitably-equipped musical instruments, and displays it on the nearest connected TV or monitor — hence the name.
The version of the program described here (see the listing) is the straightforward one that records a copy of the bytes flowing down the MIDI link that's being observed, and then displays a colour-coded sequence of MIDI messages. These messages represent sets of status and data bytes which group together to form such things as, for example, a Key On event or an Active Sensing message.
The program listed is slightly optimised for my own synth setup, though, so feel free to make some alterations of your own in the same vein. The optimisation is concerned with two points. First, neither of the synths I'm observing use MIDI running status, so my version of the Monitor program is slightly simplified because it doesn't have to keep track of what the last status byte means in terms of the number of data bytes required (1 or 2) to form a message. Second, where particular MIDI controller numbers have a specific meaning on the particular synthesisers being observed, I can make the Monitor program display more specific in terms of naming these controllers, eg. DX7 DATA SLIDER in preference to the impersonal CONTROLLER 6.
Before the main program can run, there's some setting-up to be done. In particular, the assembler level code must be translated and connected to the NMI interrupt. Don't forget to connect the NMI link (and not the IRQ link) on the BeeBMIDI board, because if you don't you're not going to see an awful lot of action.
If you have an early BeeBMIDI board, you'll need to cut the track on the circuit board leading to the IRQ pin on the 1MHz bus connector, and connect the track to the NMI pin on the 1 MHz bus connector. This was all described and discussed by yours truly in the BeeBMIDI 4 article in E&MM November '84.
PROCsetup starts at line 2090. Line 2100 sets up a string of the hexadecimal digits which are used in a general method of displaying hexadecimal numbers (in line 2020). The BBC Micro can, in fact, display values in hexadecimal (by use of the ~ prefix in the PRINT statement), but some other micros can't, so I thought you might be interested to see how easily it can be done... Line 2110 sets up the characters that need to be printed to change the colour of text on the BBC Micro's Mode 7 screen. R$ is red, G$ green, and so on.
In line 2130, the size of the buffer into which the MIDI bytes will be saved is defined as 10,240 bytes, which should allow enough space for most events to be recorded. Locations &70 and &71 (the '&'prefix means hexadecimal), named as bufpointer%, are used to hold the address of the next free position in the buffer. The DIM statement in line 2140 defines an area of 1000 bytes to hold the translated assembler code, followed by the actual space for the buffer whose address is assigned to the variable buffer%.
The variables start_rec% and stop_rec% hold the addresses of two single byte locations which are used as flags - I'll describe what they're used for later. Lines 2050 and 2060 set up variables holding the addresses of the BeeBMIDI 6850 ACIA's registers.
The rest of the procedure organises the translation of the assembly code. Let's look at each of the routines in turn. Subroutine setup (lines 2220 to 2320) is called once to connect the interrupt routine called nmicode to the NMI (non-maskable interrupt), and to reset the 6850 ACIA. The reset just mentioned is necessary to ensure that BeeBMIDI has no possibility of causing NMIs before you're ready to deal with them. The BBC Micro's NMI is used only by very high-priority devices such as disk and Econet interfaces, both of which should not be active when the Monitor software is running. If NMIs are allowed to occur (because BeeBMIDI has been left switched on and not reset and is interrupting, say) when you're trying to use the disks, all sorts of strange things can happen, such as the directory seeming to be empty. You have been warned. Ideally, as soon as you've finished running Monitor you should disconnect BeeBMIDI and reset your Beeb with a control_break.
Whenever an NMI occurs, the code starting at &D00 is entered and, of course, you have to ensure that your code is run to see if the NMI was from BeeBMIDI. Unlike the maskable IRQ interrupt, there's no vector location for you to intercept, so you have to do some dirty work to grab the NMI. What you do is replace the first three bytes of whatever routine already exists at &D00 with a jmp nmicode (which is three bytes long). So, whenever an NMI occurs, the code at &D00 is run which immediately jumps to your code located at the label nmicode (line 2430).
On my system, there are only two possibilities for what might already exist at &D00: (a) the single byte rti, or(b) phatyapha. Case (a) is when the disk system is not yet active (OK, to be more accurate, it means that nobody is currently claiming NMI), and is just a safety default. In other words, no NMIs should occur, but if they do, just ignore them and return cleanly from NMI. In case (b), we have the first three bytes of the NMI routine for handling interrupts from the disk drives. You can see that the Accumulator and the Y register are saved on the stack, so that the disk routine can use them without corrupting the mainline program (which is usually the BASIC interpreter 'running' your BASIC program).
When an NMI occurs, the first job your routine will have to do is check whether the NMI is for you or for somebody else, ie. the disk interface on my system. Yes, I know I said the disks shouldn't be active when Monitor is running, but if the NMI intercept code does handle this check correctly, I won't need to worry about de-intercepting it when I exit Monitor.
If the NMI is for the disks, you must ensure that the three instructions you have replaced at &D00 are executed; you then continue with the original code at &D03. In case (a) above, there isn't any further code at &D03. Will the system go bang, fall over, die, or worse at this point? The answer is No, since you'll have loaded the Monitor program from disk, so the code at &D03 will have been set up to deal with that. Additionally, if the disks aren't in use (or you're still living in constant pain — sorry, I mean using tapes), there won't be any disk NMIs, so you'd always run your code and not dive off to &D03.
Figure 4 (the first three were printed last month, dummy) shows the changes performed by subroutine setup to the locations starting at &D00, assuming that the disk interface NMI code was already in place. First, no matter what was in &D00, you replace it with an rti. This is really just me being overcautious, but at least if an NMI comes in before you have the full jmp nmicode set up, the machine won't have a fit. The two address bytes for the jmp being formed are set, low byte then high byte, into locations &D01 and &D02 in lines 2240/2250 and 2260/2270 respectively. Finally, the jmp opcode is set into &D00 in lines 2220/2230.
It shouldn't come as too much of a surprise that the saving of the Accumulator and Y register suits our purposes admirably, since you need some registers to work with anyway.
So, the first thing you do in NMICODE is execute (lines 2430 to 2450) the phatyapha sequence of instructions effectively 'transferred' from locations &D00 to &D02. You then check the 6850 ACIA's status register for the arrival of a byte at MIDI In and jmp to &D03 if no byte has arrived (lines 2460 to 2490).
So, at the label nmicode1, you know that the NMI was for you, and that a byte has arrived. You get the byte from the ACIA's Receive register into the accumulator (line 2500) and immediately put a copy straight into the ACIA's Transmit register (line 2510), so that the original MIDI connection apparently works without interference, as discussed earlier.
This is where we come to the use of the start_rec% flag. Even with a large buffer, it's a good idea not to start recording until you're ready, in order to save space and (especially) to avoid the first 300 lines of the display, which are all Active Sensing. When you are ready to store the MIDI bytes, you press the space bar, which sets the start_rec% flag (see lines 1120 to 1180). You test the start_rec% flag in line 2520 to see if you should bother storing the byte just received. If you should, you head for the label record rather than exiting via nmiret. If you do exit (lines 2550 to 2580), you should remember to restore the Y register and Accumulator — in the correct order — from the stack.
If you are recording the byte, you should employ a form of indirect addressing (line 2610) that uses bufpointer% (locations &70 and &71) to point to the next free location in buffer%. The complexities of addressing on the 6502 mean you have to zero the Y register, which is normally used as an index register in this context because you're going to increment the two-byte pointer bufpointer% directly. You must be able to handle more than the 256-byte space that the Y register can index, since buffer% is larger than 256 bytes. Personally, I reckon that effectively ignoring the Y register makes life a lot easier in this case. If all this sounds complex (oh no, not a bit of it - Ed.), just take my word for it that lines 2600 and 2610 cause the byte just received to be stored in the buffer% location pointed to by bufpointer%.
Lines 2620 to 2640 increment the two-byte pointer, carefully accounting for any carry between the low byte (bufpointer%, location &70) and the high byte (bufpointer% +1, location &71). Note that a carry occurs when the low byte is incremented from its largest value (255) to zero. This is just like 9+1 = 10 in decimal: when the units column becomes a '0' there's a carry, and exactly the same principle is what's at work here. You just happen to be working modulo 256, rather than modulo 10!
Having incremented bufpointer%, you check whether the byte now pointed to (into which the next MIDI byte will be placed) is, in fact, the first byte beyond buffer% (lines 2650 to 2700). If it is, you'd best not record anymore bytes, as you've run out of space and might start overwriting something important. The stop_rec% flag is set (line 2720) to tell the BASIC program (which is patiently waiting to be told by the user to stop recording), that buffer space is exhausted. This will result in the message BUFFER FULL being displayed (line 1260). The start_rec% flag is turned off so that recording is immediately prevented.
So whilst recording is taking place, the main BASIC program is held in the Repeat Until loop in line 1230, waiting for you to press the space bar for the second time - which will then stop recording unless buffer% becomes full first.
The only other assembly routine is setupbuf (lines 2340 to 2410), which makes bufpointer% point to the very start of buffer% and resets both the start_rec% and stop_rec% flags. This routine is called at the start of each attempt at recording from line 1100.
And now (cue sounding of fanfare, drumming of drums, firing of 31-gun salute) we come to the main program.
After setting the BBC Micro's screen into Mode 7 to give coloured text, PROCsetup/s called, and the link into the NMI is then made by calling the machine code subroutine setup. The program then loops forever (lines 1020 to 2060), recording and displaying.
A title screen appears, the buffer pointer and flags are set up by a call setupbuf, and you, the long-suffering end user, are asked to start and stop recording by pressing the space bar once in each case. Note that only when recording starts is the ACIA completely configured by setting &95 into the control register (line 1170). This is to avoid any spurious ACIA interrupts before you are ready for them. As soon as recording stops, the ACIA is reset for the same reason by setting &03 into the control register (line 1250).
Line 1300 looks vicious, but all it's really doing is working out how many bytes you've actually recorded by subtracting the start address of buffer% from the final address pointed to by bufpointer%. If there are some bytes to display (checked in line 1320), you set up variables in line 1340 to say that you haven't yet had any MIDI status bytes. Later in the display part of the program, you can avoid reprinting the name of a status (eg. Key On) when you keep the same status over many messages.
So, having carefully stowed away a load of MIDI bytes in buffer%, you display them, in colour-coded messages, on your monitor screen. The FOR loop from lines 1380 to 2030 deals with each stored byte in turn. After the next byte has been peeked from buffer% into the variable byte% in line 1390, you can choose to ignore it if it's an active sensing byte (line 1400).
This is purely for convenience to avoid streams of active sensing messages on the screen corresponding to moments when you aren't playing your synthesiser during recording.
Line 1420 checks whether byte% contains a MIDI status byte by seeing if its sign bit is on which would make the value of the byte greater than or equal to 128.
I'll quickly deal with the MIDI data bytes first, but note that if you have a MIDI status byte, the variable offsets is set to zero (line 1560) so that the relative position of following data bytes can be calculated to keep printing tidy. The net result of the code dealing with data bytes (lines 1440 to 1520) is that either the byte is printed in white immediately following the last displayed byte if it forms part of the same message, or on the next line if it's the start of the data part of the next message. Have a took at the screen photographs - a picture's worth a thousand words.
In the case of a MIDI status byte, lines 1610 to 1650 check whether you have the same status as the last message to avoid repeatedly printing the same status name - this makes the display easier to read because repeated message types, like four Key Ons one after the other, appear as a distinct block of messages on the screen.
Lines 1670 to 1730 deal with the special case where a Key On actually means a Key Off, ie. when a Key On message has its second data byte (the velocity byte) set to zero. You can tell whether there has been a real Key Off, rather than a special Key On meaning off, because the status byte's value is still displayed on the screen.
Lines 1770 to 2000 deal with most of the MIDI status codes. If you're interested in any that I've missed out, you'll need to add some lines of your own before line 1980, to set the printing colour for the messages associated with the current status byte into the variable S$, and to print the relevant title.
Taking line 1860 as an example, you can see that a status byte whose left nibble (a nibble is four bits, by the way) is &D has the title AFTERTOUCH and will print in yellow (Y$). Notice that where more detail is available, as in the case of the controllers, more detail is displayed. In lines 1770 to 1820, the first data byte of the controller message tells you exactly which controller you're dealing with.
Finally, the value of the byte (status or data) that you've dealt with this time round the FOR loop is printed out as two hexadecimal digits, in the correct colour if status or white if data, by line 2020. Once the complete contents of buffer% have been displayed, you have the choice of viewing them again or trying for another recording (line 2050).
The Monitor program is the sort of software tool that anyone seriously working with MIDI tends to have around in a more or less sophisticated (and finished!) form. In my own case, it's tended to grow in a rather unstructured manner as I required different facilities to help debug specific problems.
There's plenty of scope for you to add code to optimise Monitor so that you can observe the various machines in your own MIDI system - in much the same way that my version displays the names of some of the DX7's MIDI controllers. If your synth responds to particular System Exclusive messages, it shouldn't be too difficult to arrange for the Monitor program to send the request message from BeeBMIDI's MIDI Out when told to start recording, and observe the response arriving via MIDI In.
Well, I must get on now, as I've got a little job for the Monitor software. It's a prototype combined Jew's harp, G-string and euphonium MIDI retrofit 17-track recording package that the Editor has asked me to give top priority to. And it isn't quite working yet.
Part 1 | Part 2 (Viewing)
Gear in this article:
Feature by Jay Chapman
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!