═══ 1. Introduction ═══ MPUDEV Driver 1.0 Please read this document in entirety. I took the time to make it explicit, clear, and very useful. It took me longer to write it than it will ever take you to read it. Therefore, you owe it to both of us to read it. OK? The MPUDEV.SYS driver is an OS/2 device driver for OS/2 2.X+ designed to work with any audio/MIDI card containing an MPU-401 compatible MIDI interface. (Only MPU-401 UART mode is required of that card). It allows a program to do MIDI input and output through that card. Such cards include Roland's MPU-401 and SuperMPU, and MusicQuest's MQX-16 and MQX-32, as well as many other cards. Many sound cards, including the Roland RAP-10 and SCC-1 also have an MPU-401 compatible interface, and also internally attach a built-in General MIDI sound module to that interface. So this driver can be used to send MIDI data to play that card's built-in GM module, or adjust its parameters via System Exclusive MIDI messages. MPUDEV.SYS is not an MMPM (ie, MultiMedia Presentation Manager) driver. MPUDEV.SYS will not work with MMPM programs. It requires a program written to specifically follow my own protocol for dealing with MIDI input and output. I myself have written a number of such free programs including a System Exclusive dump utility, a MIDI piano controller program, a utility to view incoming MIDI data, etc. Programming information is available in this manual for others to write programs to use this driver, in languages such as C or even REXX. There is also information for making drivers and Dynamic Link Libraries for other hardware, maintaining compatibility with MPUDEV.SYS and its DLLs, so that programs which use MPUDEV can be used with other hardware as well. Some of the words in this manual are highlighted in bold text, such as Simple Approach. These are words that refer to MPUDEV definitions. Other words are in colored text such as Channel Pressure. These refer to MIDI messages (ie, data). Underlined words, such as Pitch Wheel, refer to hardware, such as if I was referring to the Pitch Wheel on your MIDI unit. Words that are in colored text such as Read This are meant to be emphasized. Words in italics refer to aspects of OS/2. ═══ 2. Copyright ═══ This OS/2 Online Book and the related file MPUDEV.SYS are all copyright 1995 by Jeff Glatt. These files are freely redistributable, and may be used by and distributed along with any software, be it commercial or otherwise, provided that these files are not internally modified, nor specifically sold as a complete product by themselves. The only price that you have to pay is the one that you're already paying by spending all of your time in front of a computer instead of developing healthier outlets. NOT SO STANDARD DISCLAIMER: These programs are provided "as is" without warranty of any kind either expressed or implied or tatooed in a place that only a few people have ever seen, including but not limited to the implied warranties of merchantability, fitness for a particular purpose, and the dubious assumption that the software has been created by a sane individual who would never do anything that may hurt you. The entire risk as to the results and performance of the programs is assumed by you or someone who looks exactly like you. Jeff Glatt does not guarantee that the functions in these programs will meet your requirements, especially if your requirements involve lots of latex and some docile, domesticated animal. Nor does Jeff Glatt warranty the programs to be uninterruptable or error-free, although mercifully free of "General Protection Faults". If you use said programs, you can not say anything nasty about the author, even if the programs inadvertently cause the erasure of your collection of X-rated GIFs of a conservative, overweight and overrated TV "personality" plooking himself vigorously with his royalty checks from some rancid paperback. Jeff Glatt is not responsible for any damages as a result of anything that he has done, or hasn't done, or was supposed to do but never got around to it, and furthermore, he doesn't even care so leave him alone, ratface. You may have more or less protections in certain states of the union, depending upon how far your local politician is willing to bend over for some bribe from a business lobbyist. Just remember that Jeff Glatt has no money, so don't bother suing him as a result of any damages caused by this OS/2 program. Tell your greasy lawyer to go after IBM, and make sure that you pick 12 really stupid pinheads for the jury. If swallowed, induce vomiting immediately by contemplating the asthetics of Microsoft Windows. OS/2 is a trademark (and mismarketed product) of International Business Machines Corporation. Windows is a trademark of Microsoft Incorporated, and furthermore, Bill Gates is to blame for it. If you have unreasonably presumptuous suggestions (ie, an enduser who expects outrageous amounts of free support), snide comments, criticisms, and anything else other than dollar bills, then send them to someone else because you got it for free, and you know what you get for nothing? On the other hand, any type of positive contribution from other programmers is very much welcome and encouraged as these are the only folks who can made things happen for OS/2. IBM ain't gonna do it. If you do need to contact the author, then either phone some of the more prominent psychiatrict clinics in central New York state, or try this: Jeff Glatt 6 Sycamore Drive East New Hartford, NY 13413 (315) 735-5350 Sure, this copyright notice has attitude. Get used to it, or kill yourself. ═══ 3. Setup ═══ First of all, in order to use MPUDEV.SYS, you need a card that offers MPU-401 hardware compatibility. The card can't simply offer MPU-401 emulation through a software driver or TSR. It must have the 2 MPU-401 ports; STATUS and DATA. It must implement MPU-401 UART mode (ie, Intelligent Mode isn't needed). Bit #6 (DRR) of the STATUS port must be clear when the card is ready to accept another byte for output. Bit #7 (DSR) of the STATUS port must be clear when the card has an incoming MIDI byte waiting to be read. The bi-directional DATA port is used to output and input the MIDI data bytes. The card may also have the MPU-401's COMMAND port if it supports Intelligent mode, although MPUDEV.SYS doesn't require this since Intelligent mode isn't used. (Actually, the COMMAND port is really half of a bi-directional STATUS port). Check with your card's manufacturer or literature to verify that it supports MPU-401 UART mode in hardware. If necessary, try to get ahold of a technician who knows something about his own company's products (good luck), read this here paragraph to him, and ask him if it's applicable to his company's audio/MIDI card. Next, you need to copy the driver to a convenient place where OS/2 can access it when booting up. You also have to add a line to your CONFIG.SYS file to have OS/2 automatically setup the driver each time OS/2 boots. The line begins with the DEVICE= statement, followed by the driver's filename, and the driver's arguments. The driver's filename is MPUDEV.SYS. If you place the driver in some directory where OS/2 normally looks for drivers, then you only need specify this as the filename. Otherwise, you need to tell OS/2 the complete path where to locate the driver. For example, if you place the driver in a directory called blort on your D: drive, then your statement would begin as so: DEVICE=D:\BLORT\MPUDEV.SYS After this, upon the same line, you list arguments for the driver. MPUDEV.SYS can control up to 4 MPU-401 compatible devices. These 4 units have the internal names of MPUDEV1$, MPUDEV2$, MPUDEV3$, and MPUDEV4$. You specify the arguments for each unit within a set of brackets, for example: DEVICE=MPUDEV.SYS [the args for unit 1 go here] [the args for unit 2 go here] You don't need to specify args for all 4 units if you don't actually have 4 such cards. The above example only specifies args for the first two units; MPUDEV1$ and MPUDEV2$. Args are optional. If you don't specify them, then defaults are used. Even you want want all default values, you still need to have at least the opening and closing bracket for the unit's specification. Args can usually be placed in any order, as long as they remain within the brackets for their intended unit. The arguments are: Pxxx The base (ie, I/O) address where the card is installed. xxx is that address in hexadecimal. If you don't specify this arg, then MPUDEV assumes that this unit is at a base address of 330. Most cards have jumpers that allow them to be set to various base addresses. You must make sure that your card is not set to the same base address as any other card in your system (regardless of what type of card it is). Ixx The Interrupt (ie, IRQ) number that the card uses. xxx is that Interrupt number in decimal. If you don't specify this arg, then MPUDEV assumes that this unit uses Interrupt 9. Most cards have jumpers that allow them to be set to various IRQ numbers. You must make sure that your card is not set to the same IRQ as any other card in your system (regardless of what type of card it is). /Q This suppresses informational messages about this unit that the driver prints to the screen during OS/2's boot process. If you don't specify this arg, then the driver prints out the Version and Revision numbers of the unit. /U The driver skips setting the card into MPU-401 UART mode. If the card doesn't have the MPU-401's COMMAND port (ie, Roland cards such as the MPU-401, RAP-10, and SCC-1 do have this port), then you should specify this option. Undoubtably, the card will powerup in MPU-401 UART mode. If it doesn't, you'll need some other software to force it (and keep it) in this mode before any program can utilize MPUDEV.SYS to do MIDI I/O. Skipping this initialization means that MPUDEV's Revision and Version commands do not return the actual information supplied from the card itself, and default to 0. Furthermore, MPUDEV skips checking that the card is indeed an MPU-401 UART compatible. In fact, if you see the message Unit X failed to respond. Bad? where X is the unit number, then this means that either you've specified the wrong base address, or the card doesn't have a COMMAND port. If you specify /U and reboot, and the card appears to be working with MPUDEV, then you've got a card without a COMMAND port. If you don't specify this arg, then the driver switches the card into MPU-401 UART mode, checks that it has the COMMAND port (and therefore is definitely an MPU-401 compatible), and retrieves the version and revision numbers of the card. /R The card is a Roland RAP-10. This causes the driver to handle the card in a manner that is more flexible and efficient for this card. If you don't specify this arg, then the driver assumes that the card is a plain MPU-401 compatible. So, if you have one card installed at address 220 using interrupt 5, it's a RAP-10, and you want it to be the first unit (ie, MPUDEV1$), then your line would be: DEVICE=MPUDEV.SYS [P220 I5 /R] It's possible to skip some of the units. For example, if you wanted to skip the first two units (ie, MPUDEV1$ and MPUDEV2$) and set the above device to be the third unit (ie, MPUDEV3$), then you'd include the brackets for the first 2 units, but set their base address to 0 which means to skip the unit. DEVICE=MPUDEV.SYS [P000] [P000] [P220 I5 /R] ═══ 4. Use ═══ Check the documentation with your software to see if can be made to work with MPUDEV. If the docs don't say outright that the program works with this driver, it still may work if the program outputs MIDI data using DosWrite() and inputs MIDI data using DosRead() and can be made to DosOpen() one of the MPUDEV units (ie, MPUDEV1$, MPUDEV2$, MPUDEV3$, or MPUDEV4$). The MIDI data must be in raw binary format (ie, without any Time-stamp bytes like what MMPM adds to the MIDI data). ═══ 5. Driver Sharing ═══ Some programs forbid other simultaneously running programs from reading and/or writing MIDI data to the same driver. This is referred to as not sharing the driver. Usually, such a program will open the driver, do its MIDI input or output, and then close the driver so that other programs can subsequently use the driver. While the program has the driver open and is using it, other programs are prevented from opening and using that driver. In this case, you'll have to wait for the program to complete its "MIDI task" (and close the driver) before trying to use other programs which use the same driver, and vice versa. Sometimes, a program that doesn't share the driver will open up the driver and leave it open for the life of the program, thus preventing any other programs from ever reading and/or writing to that driver while this program is running. In this case, you'll have to terminate such a program when you want to use another program to access the same driver. Obviously, a program's inability to share a driver could cause undue hassles under a multi-tasking system, particularly with a program that never closes an open driver. Some programs allow shared access to a driver. Such a program allows other programs to read and/or write MIDI data to that driver while this program is running, and maybe even while this program is reading or writing MIDI data itself. But, there are caveats with such driver sharing. Programs that share access to a driver should either cooperate with each other to manage that shared access, or carry out a complete MIDI operation with each call to the driver, for example, not output only part of one MIDI message with one call to DosWrite(), and then subsequently output the remainder of that one MIDI message with another call to DosWrite(). You should avoid simultaneously running two programs that allow shared MIDI input from the same driver unless at least one of the programs looks for MIDI input ONLY when performing a specific operation, and otherwise doesn't do any DosRead() to the driver while "sitting idle". So, that one program may be left "sitting idle" without interfering with the operation of the other program that reads from the same driver. On the other hand, if the other program doesn't also stop reading MIDI data while it is "sitting idle", it could interfere with the first program's input. In that case, you'll be forced to run the second program only when needed, let it do its thing, and then terminate it before going back to use the first program for MIDI input. In other words, you won't be able to let that second program continue running, even though it allows shared driver access, while operating the first program. Shared MIDI output is usually fine, since when programs are "sitting idle", they usually aren't writing to the driver. So, it's generally OK to simultaneously run two programs that do MIDI output to the same driver, as long as both programs aren't performing such operations simultaneously (ie, one program is "sitting idle" while the other is performing an operation that causes MIDI data to be output). On the other hand, it may be possible for both programs to simultaneously output MIDI data to the same driver. But both programs need to DosWrite() full MIDI messages to the driver (ie, not break up one MIDI message into several calls to DosWrite()) and resolve Running Status at the start of each write. If a program doesn't conform to these restriction, then its MIDI output may be destroyed by another program attempting to output MIDI data simultaneously. In that case, stick to preceding precaution (ie, make sure that only one program at a time is outputting MIDI data, and other simultaneously running programs are "sitting idle" during that time). Of course, even if the two programs conform to this restriction, and therefore, their MIDI data can be successfully "merged", this merge process will slow down the operations of each program, and may cause each program to experience significantly longer delays in MIDI output than if both programs weren't simultaneously outputting MIDI data. This may or may not make any difference to you, the program, and/or any external MIDI devices connected to the computer. Some drivers, such as MPUDEV, support several independent "ports" or units (ie, each unit has its own MIDI hardware interface card), and each unit has its own shared status. Therefore, it's perfectly acceptable to have two programs running which both use MPUDEV simultaneously, and you won't have to worry about any of the above sharing conditions as long as each program uses a different unit, for example, one program uses MPUDEV1$ and the other program uses MPUDEV2$. ═══ 6. Error Messages ═══ Here are the possible error messages that you may see when MPUDEV.SYS is being booted. Following each message is a description of likely causes for that error and possible remedies. ═══ 6.1. Unit X failed to respond. Bad? ═══ Synopsis The card didn't respond to an MPU-401 command, within a reasonable amount of time, that the driver issued to the card. Therefore, MPUDEV aborted the card's installation. Cause The base address that you specified for this unit is not the same as the base address that the card is really set to. Cure Check the base address jumpers on your card, and either set them to what you specified, or change your CONFIG.SYS DEVICE statement for MPUDEV to reflect the true base address. Cause The card doesn't have an MPU-401 COMMAND port. (It may have the STATUS and DATA ports, which is all that is really needed by MPUDEV). Cure Specify the /U option in your CONFIG.SYS file as described in Setup so that MPUDEV assumes that the card is already in MPU-401 UART mode and doesn't understand MPU-401 commands, and then reboot. Cause The card isn't MPU-401 compatible, meaning that it doesn't have the MPU-401's STATUS and DATA ports. Cure Throw out that junk, and buy an MPU-401 compatible. Result The unit is not installed, and any program that tries to open this unit will get an error return. ═══ 6.2. Timer install failed! ═══ Synopsis OS/2 didn't fulfill the driver's request to install a timer handler. Cause There's some problem with OS/2's internal driver handling. Cure Close down and try rebooting again. Maybe comment out other drivers to see if there's some conflict (doubtful). Result The driver fails to install, and any program that tries to open any of its units will get an error. ═══ 7. Programming ═══ The following sections deal with writing programs that use MPUDEV (or compatible drivers), as well as developing MPUDEV compatible drivers for other hardware (ie, besides MPU-401 UART mode cards). MIDI programs may be written in compiled languages or even REXX. Endusers do not need to read the next sections, and will probably be bored and confused by such. On the other hand, an enduser is encouraged to suggest to OS/2 programmers that they check out such, especially if the enduser wants more OS/2 MIDI software, or wants to use some MPUDEV compatible programs with his hardware. Please read the preceding sections, particularly Driver Sharing first. ═══ 7.1. Programs ═══ There are two different approaches that a program can use to access MPUDEV, depending upon whether you prefer simplicity or speed. I'll arbitrarily call these the Simple and Speed Approaches. Don't worry. This isn't nearly as bad as MMPM, and I promise not to dish out the sort of poorly expressed, excessively jargon-infested garble that you get from IBM's MMPM documentation. When I refer to sequencing, I'm referring to the process of recorded each MIDI message along with the time that it is received, and then later playing back all of those recorded MIDI messages at their relative times. In other words, the computer records the entire musical performance including not only the actual notes and effects played (ie, MIDI data), but also the rhythms of that performance (ie, the time interval inbetween each note or effect). Then, the computer later "plays back" all of those notes and effects, recreating the original rhythm of the performance. The Speed Approach is faster than MMPM, while allowing for a LOT more flexibility and better performance for the purposes of sequencing. (Actually, MMPM doesn't even support recording time-stamped MIDI data, and is therefore useless for creating MIDI music). The Simple Approach is very easy and yet still can be faster than MMPM. Both approaches consume a whole lot less RAM overhead than installing and using MMPM. ═══ 7.1.1. Simple Approach ═══ The Simple Approach consists of using the OS/2 API DosOpen() to open the driver (ie, allow access to the card), DosWrite() to output MIDI data, DosRead() to read incoming MIDI data, and DosClose() to close the driver. In this case, the driver serves simply as a means to read/write MIDI data to/from the card. All other considerations, if any, are left up to the program, for example, if the program is sequencing playback of MIDI data, the program will have to manage its own timing functions to determine when to write each MIDI message to the driver (ie, when to output a MIDI message via DosWrite()). If you're working in REXX, then my File Rexx DLL can allow you to open/read/write to the driver, or a combination of this DLL and Rexx Dialog DLL can allow you to make such a REXX program with a Presentation Manager user interface. Alternately, you can use STREAM to open or close the driver, CHAROUT to output MIDI data, and CHARIN to read incoming MIDI data. Since many MIDI utilities, such as patch editors and librarians, diagnostic tools, and MIDI "software controllers" which simply output MIDI messages in realtime as the user operates software "knobs and sliders", do not need to do sequencing, the Simple Approach is very useful. It's straightforward, and offers as much of a realtime response as you're ever going to get under OS/2 without resorting to MUCH more complexity. For patch editors and librarians, you're mostly dealing with outputting and inputting System Exclusive messages. You don't really care about optimum speed in inputting and outputting those messages (ie, most MIDI devices require a delay or "handshake" inbetween each message anyway). You just want reliable transmission. So too, with a software controller that the user operates to generate MIDI messages in realtime, the user's actions typically don't generate more MIDI messages than can be handled in realtime via the Simple Approach. In order to cooperate with other simultaneously running programs using the same driver, you'll probably want to follow a few guidelines covered in the next sections. This will ensure that your program can share the driver with other simultaneously running programs and perhaps even do simultaneous MIDI output. If you've already used DosOpen(), DosWrite(), and DosRead() (and who hasn't?), you know most of what you really need to know in order to use MPUDEV for MIDI input and output. Now, isn't that a LOT easier than MMPM? ═══ 7.1.1.1. Opening the driver ═══ Before reading and/or writing MIDI data, your program must first open the driver for reading and/or writing. You use the OS/2 API DosOpen() to do so. In REXX, you can use STREAM, or the FileOpen() function of my File Rexx DLL (if you want to also use other File Rexx functions such as FileWriteValue() or FileReadValue() or use my Rexx Dialog DLL). For DosOpen()'s FileName arg, you specify the internal name of the driver unit that you wish to open. This name is not necessarily the same name as the driver's actual filename. If a driver supports several units (ie, cards), then each will likely have its own internal name. For example, MPUDEV.SYS supports up to 4 units, named MPUDEV1$, MPUDEV2$, MPUDEV3$, and MPUDEV4$. So, if you wanted to open the first unit for reading and/or writing, then you would specify the string MPUDEV1$. A program should always give the enduser the option of specifying the unit name to be opened. This is so that not only can he choose which one of MPUDEV's 4 units he wishes, but if he obtains an MPUDEV compatible driver for other hardware, he can use the program with that driver too. Preferably, the program ought to allow the enduser to pass the driver name as an argument when invoking the program (ie, OS/2 passes the arg to the program's main() routine in the argv[] array). In this way, the enduser can set that driver name in the Desktop object's Parameters field to be automatically passed to the program when launched. It's also useful to allow the enduser to change the driver while the program is running so that he can redirect the program's MIDI input and output to different "ports". For DosOpen()'s OpenFlags arg, you'll specify OPEN_ACTION_OPEN_IF_EXISTS. The Attributes, FileSize, and Extended Attribute Buffer args are 0. For File Rexx's FileOpen(), the Flags arg is 'e', and the Attributes arg is 0. Since those are defaults, you can simply omit those two args. DosOpen()'s OpenMode arg requires you to specify whether you want to read incoming MIDI data (ie, call DosRead()), output MIDI data (ie, call DosWrite()), or do both. To only read MIDI data (not output it), you specify OPEN_ACCESS_READONLY. To only output MIDI data (not read MIDI data), you specify OPEN_ACCESS_WRITEONLY. To be able to read and write MIDI data, you specify OPEN_ACCESS_READWRITE. For File Rexx's FileOpen(), you specify 'r' for reading, 'w' for writing, and both ('rw') for reading and writing. DosOpen()'s OpenMode arg also requires you to specify whether you want to allow other programs to open the same driver for reading and/or writing while your program has the driver open. If you don't want any other program to be able to read from the driver (ie, call DosRead()) while you have it open, but writing to the driver is OK, then specify OPEN_SHARE_DENYREAD. If you don't want any other program to be able to write to the driver (ie, call DosWrite()) while you have it open, but reading from the driver is OK, then specify OPEN_SHARE_DENYWRITE. If you don't want any other program to be able to read or write to the driver while you have it open, then specify OPEN_SHARE_DENYREADWRITE. If you don't mind letting other programs read and write to the driver while you have it open, specify OPEN_SHARE_DENYNONE. Depending upon which 1 of the 4 sharing options you choose, there are certain restrictions that you should follow in order to avoid interfering with the operation of other programs that share the driver. These restrictions are detailed in following sections. Examples Here's a C example of opening MPUDEV's first unit, for reading and writing, and allowing both shared read and write access, and storing the resulting handle (to be used with DosRead(), DosWrite(), and DosClose()) in the variable myDriver: #define INCL_DOSFILEMGR #include #include HFILE myDriver; ULONG action; APIRET rc; rc = DosOpen("MPUDEV1$", &myDriver, &action, 0, 0, OPEN_ACTION_OPEN_IF_EXISTS, OPEN_ACCESS_READWRITE | OPEN_SHARE_DENYNONE, 0}; if (rc) printf("MPUDEV1$ didn't open. Error #: 0x%08X", rc); Here's the exact same thing in REXX using File Rexx's FileOpen. CALL FileLoadFuncs myDriver = FileOpen("MPUDEV1$", 'rws') IF myDriver = 0 THEN SAY "MPUDEV1$ didn't open." ═══ 7.1.1.2. Outputting MIDI data ═══ After opening the driver unit for writing, you can output data to that unit by calling DosWrite(). For REXX, if you used STREAM to open the driver unit, then you can use CHAROUT to output MIDI values. But remember that REXX stores decimal values as ascii strings, so you'll have to express each value in hexadecimal (ie, to express a MIDI Clock status byte, it would be 'F8'X). LINEOUT can't be used to output MIDI values, because LINEOUT also appends line feed and carriage return characters which the driver will misinterpret as 2 MIDI data bytes. If you're using File Rexx's FileOpen(), you'll use FileWrite() or FileWriteValue() to output one or more MIDI bytes. With FileWrite(), you need to express values as hexadecimal (ie, in binary form) like with CHAROUT. But if you have a string that is already in binary form, then you can use FileWrite() to send it to the driver. Even better, FileWriteValue() allows you to send one or more values to the driver without having to worry about expressing or converting each value into its binary equivalent. You just pass values to FileWriteValue() as you would to most any other standard Rexx function, and File Rexx does the proper data conversion for the driver. Note: When I say 'binary' above, I don't mean REXX values such as '1011'. That isn't real binary. That's an ascii representation of binary. Here's a real limitation of REXX's typeless data. Sometimes, you really need data in a certain form, and REXX doesn't handle much beyond decimal values (expressed as ascii strings) very well. File Rexx compensates for these limitations by offering ascii to binary conversion built into certain functions. If you're going to allow shared write access (ie, OPEN_SHARE_DENYWRITE is not specified), then your program should follow 2 restrictions in order to ensure that it will work well with other programs, including being able to do simultaneous MIDI output with other programs. The first restriction is that you should always output full MIDI messages with every DosWrite(). For example, let's say that you wish to output a MIDI Note On. This MIDI message has 3 bytes; the Status, the Note Number, and the Velocity. Don't output the Status byte with a call to DosWrite(), and then output the remaining bytes with one or two further calls to DosWrite(). Instead, output all 3 bytes with one call to DosWrite(). This is a lot more efficient anyway. In fact, you could output more than one MIDI message with a single call to DosWrite. For example, maybe you have a buffer filled with 6, complete MIDI messages, and you want them all to be output at the same time. Output the whole buffer with one call to DosWrite(). In conclusion, what you want to avoid doing is to split up any one MIDI message into multiple calls to DosWrite(). This may only be a problem with a large System Exclusive message, but most MIDI devices split up their "data dumps" into a series of smaller System Exclusive messages (ie, packets) anyway, and besides, OS/2's virtual memory system is suited for handling a potentially large, contiguous buffer of data containing one System Exclusive message for output. The second restriction is that you should always resolve MIDI running status, at least upon the first MIDI message contained within each buffer passed to DosWrite(). In other words, the first MIDI message in that buffer must have a Status byte. If you have more than one MIDI message contained in a buffer that you're passing in a single call to DosWrite(), it's OK for the subsequent MIDI messages to implement running status; but the first message should have a Status byte. In fact, it's OK to not utilize MIDI running status at all (ie, every MIDI message that you write has a Status byte). MPUDEV implements running status upon output anyway, so there is no loss of efficiency. It's possible to ignore the above restrictions (although ignoring the second restriction could be particularly troublesome with shared access), but if you do so, then you should either not allow shared write access, or else warn endusers that they should not cause other running programs to do MIDI output at the same time that your program is outputting MIDI data (ie, other programs should be "sitting idle" while your program is outputting MIDI data). Of course, by not allowing shared write access at all, then OS/2 itself prevents other programs from opening and writing to the driver. There are 2 potential problems with not allowing shared write access. First, if another program already has the driver open, even with shared write access allowed, then your attempt to open the driver will fail (ie, because you won't allow shared write access with that other program). Secondly, while you have the driver open, no other program can output MIDI data, even if that program is designed to properly share access. If you absolutely must prevent shared write access, then you should open the driver immediately before you need to output MIDI data, output that data, and then immediately close the driver. Don't keep the driver open longer than necessary. Ideally, you should attempt to follow the above 2 restrictions, and then you can always allow shared write access. If it's not feasible to follow these restrictions, then my own preference is to allow shared write access, but tell the enduser to avoid causing other running programs to do simultaneous MIDI output to the same driver to which your program is outputting data. It's true that this leaves the possibility of an enduser causing 2 programs to mess up each other's output. But, it also gives an enduser the option of simultaneously running two programs that use the same driver, and switching between them at will. Besides, garbled MIDI output usually isn't associated with disasterous consequences such as the loss of irreplaceable data. On the other hand, preventing shared write access will prevent the enduser from even starting a program that opens that driver upon startup. It's better to let the user make the choices of when those programs are given the opportunity to do MIDI output. Note: MPUDEV.SYS doesn't buffer MIDI data. Therefore, a call to DosWrite() won't return until all MIDI bytes are output. For hardware like a RAP-10 or SuperMPU, the hardware buffered output of the card make DosWrite() much quicker. Examples Note: The following examples assume that the driver has already been opened for writing, and the file handle was stored in the variable myDriver. Here's a C example of outputting a MIDI Note On. It's on the first MIDI channel, it's a middle C note, and the velocity is 64. Therefore, the 3 data bytes are (in hexadecimal) 0x90, 0x3C, and 0x40. #define INCL_DOSFILEMGR #include #include #define MIDI_DATA_LEN 3 ULONG bytesWritten; APIRET rc; UCHAR buffer[MIDI_DATA_LEN] = {0x90, 0x3C, 0x40}; rc = DosWrite(myDriver, &buffer[0], MIDI_DATA_LEN, &bytesWritten); if (rc) printf("Error outputting MIDI data. Error #: 0x%08X", rc); else if (bytesWritten != MIDI_DATA_LEN) printf("Only %ld data bytes were output.", bytesWritten); Here's a variation on the above. We now have 2 MIDI messages in our buffer, and are going to output them with one call to DosWrite(). The second MIDI message is also a Note On upon the same MIDI channel. Therefore, we can implement running status. #define MIDI_DATA_LEN 5 UCHAR buffer[MIDI_DATA_LEN] = {0x90, 0x3C, 0x40, 0x39, 0x40}; rc = DosWrite(myDriver, &buffer[0], MIDI_DATA_LEN, &bytesWritten); Here are the above two examples in REXX using File Rexx's FileWriteValue(). For the second example, I'm passing a variable to describe the MIDI values, just for the sake of illustration. Note that the values are expressed in decimal (ie, the Status byte of 90 hexadecimal is 144 decimal). actual = FileWriteValue(myDriver, 144 60 64) IF actual <> 3 THEN SAY "An error in outputting the MIDI data." myData = "144 60 64 57 64" actual = FileWriteValue(myDriver, myData) IF actual <> 5 THEN SAY "An error in outputting the MIDI data." ═══ 7.1.1.3. Inputting MIDI data ═══ After opening the driver unit for reading, you can read data from that unit by calling DosRead(). For REXX, if you used STREAM to open the driver unit, then you can use CHARIN to read MIDI values. If you're using File Rexx, then the FileRead() function does the same. But remember that MIDI data is binary data, and yet REXX expresses numeric values as ascii strings, so when using CHARIN or FileRead(), you'll have to convert each MIDI value (ie, each character) in the returned string to its equivalent ascii numeric value before you can perform mathematical operations upon it. For this reason, you're much better off using File Rexx's FileReadValue() or FileReadBlock() to input one or more MIDI bytes. These functions automatically convert each MIDI data byte to its equivalent ascii string for REXX. If you're using Rexx Dialog, then you won't be able to use CHARIN, FileRead(), nor FileReadValue(). You'll need to use Rexx Dialog's RXDEV command, which not only has a feature for converting each binary value to ascii form, but can also read, convert, and return numerous MIDI data bytes all in one string, or even separated into individual stem variables. Note: When I say 'binary' above, I don't mean REXX values such as '1011'. That isn't real binary. That's an ascii representation of binary. Here's a real limitation of REXX's typeless data. Sometimes, you really need data in a certain form, and REXX doesn't handle much beyond decimal values (expressed as ascii strings) very well. File Rexx compensates for these limitations by offering binary to ascii conversion built into certain functions. MPUDEV always resolves MIDI running status on all incoming messages (ie, all incoming MIDI messages are given a Status byte). Realtime messages are filtered from the input stream. It just doesn't make any sense to try to do any kind of sync via OS/2's DosRead() what with all of the OS overhead associated with such. If you need to do MIDI sequencing, that's what the Speed Approach is for. Being that MIDI transmits data within "messages", MPUDEV's DosRead() processing has been enhanced to allow a program to easily read one complete MIDI message with a single call to DosRead(). The way to do this is to supply a buffer that is as large as the largest MIDI message that you intend to be able to receive intact, and then pass the size of this buffer to DosRead(). If MPUDEV can fit the next incoming MIDI message into this buffer intact, then it will do so, and return only as many bytes as are in that one MIDI message. Note that you'll have to check the returned BytesRead from DosRead() in order to determine how many bytes have been returned in your buffer. If the next MIDI message is too large for the buffer, then as many bytes as possible as returned in the buffer. The next DosRead() call will return as many more bytes of that same message as will fit into the buffer again. This will continue until all of the bytes of that message are read. Note that since MPUDEV resolves running status, you can always tell when you're receiving back a new MIDI message (as opposed to a continuation of a message that is larger than your supplied buffer) by checking if the first byte in the buffer is a MIDI Status byte (ie, greater than 0x7F) but not 0xF7 (ie, the ending mark of a System Exclusive message). If so, then you've got the start of the next MIDI message, and in fact, all of it if the returned BytesRead is less than the size of your supplied buffer. On the other hand, if you only read one byte at a time with DosRead(), then obviously it can only return one byte at a time anyway, so it works as one would normally expect a DosRead() to any driver to work. Note: The Delta Music Systems MPU401 driver, while mostly compatible with MPUDEV (in terms of the Simple Approach), doesn't resolve MIDI running status. It also doesn't implement MPUDEV's enhanced feature of reading one complete MIDI message with each DosRead() call. But if you're going to be using an MPU-401 in UART mode anyway, it makes sense to use MPUDEV instead of the DMS driver. On the other hand, if your program only ever reads 1 byte with each DosRead(), then it will work with the DMS driver too. If using RXDEV, you can specify a large buffer, and therefore take advantage of MPUDEV's enhanced reading feature. For File Rexx, you could use FileRead() to read a MIDI message containing upto 256 bytes, but then you need to translate each character of the returned string from its binary format into an ascii numeric string in order to use it in REXX mathematical expressions. FileReadBlock() can allow you to read already converted values, without limitations upon the number of values returned. If you're going to allow shared read access (ie, OPEN_SHARE_DENYREAD is not specified), then your program should follow 1 restriction in order to ensure that it will work well with other programs. This restriction is that you should never call DosRead() while the program is "sitting idle" (ie, when it's not necessary to be looking for incoming MIDI data). If your program always calls DosRead(), then it could potentially steal part or all of a MIDI message that another program needs to see. For example, if two programs are simultaneously calling DosRead() to read one data byte, it's possible that one program may grab the Note Number byte of a particular MIDI Note On message, and while that's being returned to this one program, the other program grabs the subsequent Velocity byte of that same Note On message. Obviously, that's not good. So, the solution is to only call DosRead() when the program needs to read MIDI data or is made active by the enduser, and stop calling DosRead() when the program no longer needs to read MIDI data or is made inactive by the enduser. Furthermore, the enduser should be warned not to cause 2 programs to simultaneously read from the same driver. Doing otherwise could potentially result in garbled input for both programs, but the alternative of no shared read access could result in eliminating the enduser's options of simultaneously running those 2 programs, and switching between them at will. If a program needs to prevent shared read access, that program should open the driver immediately before getting input, read all the desired input via DosRead(), and then immediately close the driver. But there is a drawback here. If another program already has the driver open, even with read sharing allowed, your attempt to open the driver will fail (ie, because you won't allow shared read access with that other program). When a program calls DosRead() and there's no MIDI data waiting to be read, the driver will "put the program to sleep" until such time as some data arrives. This is perfectly OK for a Rexx script launched from the OS/2 command line, or a non-PM program (although while the program is asleep, it has no way of interacting with the enduser, which could be deemed bad). But a Presentation Manager program can never be put to sleep, or it will likely lockup the OS/2 single message queue (ie, a much-deservedly criticized design limitation of OS/2). So, a PM program (or a non-PM program that wishes to leave its main thread free to manage user interaction) must create a second thread which does all of the calls to DosRead() to input MIDI bytes. Rexx Dialog's RXDEV already implements this, so there's no problem with a script which uses Rexx Dialog for its PM user interface. Sometimes, when a thread is put to sleep within a call to DosRead(), it may be necessary for the main thread in that program to cause the other thread to wake up and return from its call to DosRead(). There are 2 ways to do this. First, you can set some flag variable that your read thread can test to verify that the main thread does not want any more calls to DosRead(), and then DosClose() the driver. DosClose() automatically causes a flush all of the unit's read requests including your own thread's call to DosRead to abort (as well as DosRead() calls made by other programs), and may return a BytesRead count of 0 (ie, no bytes returned). If you don't want to actually close the driver, you can substitute the DosClose() for a DosDevIOCtl() to issue the command to flush all read requests. Because another program can always abort one of your program's calls to DosRead(), you should always be prepared to ignore a DosRead() that returns 0 BytesRead. Examples Note: The following examples assume that the driver has already been opened for reading, and the file handle was stored in the variable myDriver. If this were a PM program, this code would not be in the main thread. Here's a C example of inputting 1 MIDI data byte. #define INCL_DOSFILEMGR #include #include ULONG bytesRead; APIRET rc; UCHAR buffer[1]; rc = DosRead(myDriver, &buffer[0], 1, &bytesRead); if (rc) printf("Error inputting MIDI data. Error #: 0x%08X", rc); else if (!bytesRead) printf("No MIDI input. Possibly an input flush occurred."); Here's a C example that inputs an entire MIDI message upto 256 bytes with each call to DosRead(). It keeps looping around to pick up each message, and identify whether it is the next MIDI message, or a continuation of one MIDI message. #define MIDI_DATA_LEN 256 UCHAR buffer[MIDI_DATA_LEN]; for (;;) { rc = DosRead(myDriver, &buffer[0], MIDI_DATA_LEN, &bytesRead); if (rc) printf("Error inputting MIDI data. Error #: 0x%08X", rc); else if (!bytesRead) printf("No MIDI input. Possibly an input flush occurred."); else { if (buffer[0] > 0x7F && buffer[0] != 0xF7) printf("Received the next MIDI message. Its size is %ld.", bytesRead); else printf("Received a continuation of a MIDI message for %ld more bytes.", bytesRead); } } Here's an example of reading one byte in REXX using File Rexx's FileReadValue(). bytes = FileReadValue(myDriver) IF bytes == "" THEN SAY "An error in inputting the MIDI data or a flush." ═══ 7.1.1.4. Closing the driver ═══ After you're done reading/writing to the driver, you need to close it by calling DosClose(). In REXX, if you used STREAM to open the driver, use STREAM to close it. If you used FileOpen() to open the driver, use FileClose() to close it (unless you also used Rexx Dialog's RXDEV to read from the device, in which case, use RXDEV to close the device). This will cause all current calls to DosRead() to be aborted, and perhaps return BytesRead = 0. Furthermore, when the last program with a given unit still open, closes that unit, any queued write requests are flushed (as a failsafe mechanism). ═══ 7.1.1.5. Issuing commands ═══ Commands are sent to the driver via OS/2's API DosDevIOCtl() using a catagory of 128 or 11 and where the function argument is the command number. For REXX, you can use File Rexx's FileDevIOCtl(). Here are the available command numbers that my MPUDEV driver understands for a catagory of 128, and what each command does. 0 Resets the card (and driver) so that doing a DosRead() will return incoming MIDI bytes, and doing a DosWrite() will output MIDI bytes. (Note that this doesn't flush input and output buffers. There are separate commands below to do that). This command exists because certain hardware sometimes needs to be placed into a certain mode in order to pass incoming MIDI data through to the computer. For example, MPUDEV.SYS needs an MPU-401 to be in Uart mode (ie, as opposed to Intelligent mode). If an MPU-401 is not in intelligent mode, then it will not supply incoming MIDI bytes to MPUDEV.SYS. A program should normally issue this command once upon startup (perhaps giving the enduser the option to not issue this command) to make sure that the driver and hardware are prepared to handle DosRead() and DosWrite() calls to process incoming and outgoing MIDI data respectively. Unless you specify the /U option for a unit, MPUDEV automatically does a reset, placing the MPU-401 into UART mode, upon bootup. 1 Returns the version and revision numbers of the unit. These are two bytes that are returned within the Data Packet that you pass to DosDevIOCtl(). If you specify the /U option for a unit, the version and revision numbers default to 0 and may not reflect the true version of the unit. The MPU-401 returns its version number as a BCD (Binary Coded Digit). The only real purpose of this command is if your program wishes to check certain capabilities of specific hardware. For example, later versions of the MPU-401 (for example, the RAP-10 and SuperMPU) have hardware buffered output, whereas earlier units don't. Here are the available command numbers that my MPUDEV driver understands for a catagory of 11, and what each command does. 1 Flushes (ie, aborts) all queued read requests (ie, calls to DosRead()) for this unit. All current DosRead()'s are aborted, returning a BytesRead of however many bytes were already read (ie, could be 0 if no bytes were read before the read was aborted). It does not return an error condition for a given read request unless no bytes were returned. This is so that if one program is doing an Enhanced Read, and another program causes the abort of this Enhanced Read, the first program will still successfully receive however many bytes were able to be read before the abort. In other words, this flush tries to be as minimally disruptive as possible to all programs other than the one which caused the flush. A flush of all read requests for a given unit is automatically done (as a failsafe operation) whenever any program calls DosClose() to close that unit. 2 Flushes (ie, aborts) all queued write requests (ie, calls to DosWrite()) for this unit. All current DosWrite()'s are aborted, returning an error condition indicating that the write was aborted. Normally, you should not do this as all calls to MPUDEV's DosWrite() always return within a reasonable amount of time unless the hardware is malfunctioning. Examples Note: The following examples assume that the driver has already been opened, and the file handle was stored in the variable myDriver. Here's how to flush all read requests in C. #define INCL_DOSFILEMGR #define INCL_DOSDEVICES #include #include APIRET rc; if( (rc = DosDevIOCtl(myDriver, 11, 1, 0, 0, 0, 0, 0, 0)) ) print("Error flushing read requests: %ld", rc); Here's how to get the version number in C. UCHAR version[2]; ULONG len; len = 0; if( (rc = DosDevIOCtl(myDriver, 128, 1, 0, 0, 0, &version, 2, &len)) ) print("Error getting version and revision: %ld", rc); else printf("Version is %d. Revision is %d.", version[0], version[1]); Here's how to flush all read requests in REXX. error = FileDevIOCtl(myDriver, 11, 1) IF error <> 0 THEN SAY "An error flushing read requests." Here's a REXX snippet on how to get the version and revision, and display it. /* Get MPU-401 version */ DATA.0 = 2 DATA.1 = '1.1' DATA.2 = '1.1' err = FileDevIOCtl(handle, 128, 1, , 'DATA', 'd') IF err <> 0 THEN SAY "ERROR getting MPU Version and Revision:" err ELSE DO /* MPU-401 version is encoded in BCD. We need to convert the decimal return to hex, and then get the first digit */ DATA.1 = LEFT(D2X(DATA.1), 1) SAY 'MPU Version =' DATA.1 SAY 'MPU Revision =' DATA.2 END ═══ 7.1.2. Speed Approach ═══ This is not yet implemented for MPUDEV. Look for an update to offer such, with details of what this entails. ═══ 7.2. Driver ═══ If you want to use software that utilizes MPUDEV with other hardware, then you need to write an MPUDEV.SYS compatible driver for that hardware. At a minimum, you should support the Simple Approach outlined in the previous sections. This at least allows the driver to be used with a variety of existing OS/2 MIDI programs and REXX scripts. Make sure that you read the preceding sections on writing programs which use MPUDEV. ═══ 7.2.1. Simple Approach ═══ For Simple Approach, your driver's Strategy routine will need to process Open (13), Close (14), Read (4), Write (8), and IOCtl (16) commands. Of course, you can process additional commands if you wish (for example, you'll likely have an Init handler). The following sections detail what each command's handler must do. ═══ 7.2.1.1. Open ═══ The Open handler can do any kind of initialization that it needs to do for the unit that has been requested to be opened. The driver doesn't need to manage shared read and write access. That's already done by the OS/2 kernal before any program can ever call your Open, Read, Write and IoCtl handlers (assuming that you set the Shared bit of your Device Header's Attributes). Typically, if the unit is not already currently open, the Open handler may set up the IRQ and interrupt handler for the hardware (if it doesn't do so once only during its Init handler). Since the driver will offer a program commands to flush the unit's input and output queues, and reset the unit's card into the proper mode to input and output MIDI data, it's not necessary for the Open handler to do these things. ═══ 7.2.1.2. Write ═══ The Write handler must output all of the bytes sent in each request packet (in the order that the request packets are received from OS/2). It's up to the Write handler whether it wants to return immediately before all of the bytes are actually output, or whether it wants to return only after all bytes are output. It's usually safer to do the latter so that if any errors are encountered during output, they can be reported back to the calling program (ie, when the Write handler sets the Error Code field of the request packet before returning it to OS/2). In this case, since the hardware may accept data bytes much slower than the computer CPU can output them, it may be necessary to put a request to sleep (ie, via the Block DevHlp service) whenever the hardware says that it's not yet ready to accept the next byte to be output. If the Write handler Blocks a request, then another request wanting to write some data could then call the Write handler. (ie, Remember that OS/2 is a multi-tasking system. Multi-tasking is disabled while a Write handler is executing unless/until the handler calls Block. Whenever the Write handler Blocks, that means that OS/2 can now call it again with another request. Therefore, your Write handler should regard itself as a potentially reentrant routine if it calls Block). The Write handler should be prepared to queue this second request packet into a list, and then block this second request too. (Obviously, the Write handler will either call Block with a time-out value equal to how long it expects the hardware to be ready for another byte -- it takes 320 microseconds to send one byte over MIDI -- or will wait for some interrupt handler triggered by the card to call the Run DevHlp service). After the first request is finally output, and its request packet is ready to be returned, the Write handler can then remove that queued, second request packet and use the DevHlp service Run to set that second request to start up as soon as the first request packet has been returned to OS/2. In this way, the Write handler manages requests in the order that they're received, and yet allows for putting requests to sleep while waiting for the hardware to be ready to accept output bytes. Alternately, the Write handler could quickly copy all of the bytes of a given request to some internal buffer, to be output by some interrupt handler, and then immediately return that request packet before those bytes are actually output. The handler still needs to make sure that bytes are output in the order that they're received from OS/2. Of course, a situation may arise where programs will be sending bytes faster than they can be output, resulting in the internal buffer eventually being filled. In that case, the handler will probably have to eventually block and queue requests as described above. But, such a buffering scheme may allow programs to do other useful work that they couldn't do if they were otherwise Blocked as in the preceding approach. For hardware that doesn't have a hardware buffered MIDI output, this software buffering could be a good approach. Of course, when the Write handler returns the request packet, it should have indicated that all of the requested bytes have been written, even though they may not actually have been output yet. The Write handler should be prepared to implement running status for output since many programs may follow the restriction of always providing a Status byte with each DosWrite() regardless of whether that Status is needed. ═══ 7.2.1.3. Read ═══ The Read handler must resolve running status (ie, all received MIDI messages must be given a Status byte). If an interrupt handler is actually grabbing the incoming MIDI bytes from the hardware, and placing them into some "input buffer" from which the Read handler extracts data to fill requests, it's probably better to have the interrupt handler resolve running status so that any potential buffer overruns won't result in running status being messed up (even if some data is lost). Realtime messages should be filtered out. It just doesn't make any sense to feed an application Realtime messages through the driver's Read handler given all of the OS overhead associated with such. If an application needs to do MIDI sequencing, it will have to use the Speed Approach. Filtering Realtime, especially MIDI Clock and Active Sense, helps stream-line the passing of data between the driver and program, and can help reduce possible buffer overruns (since Clock and Sense can fill up a buffer very quickly). The Read handler must do 1 of 2 things depending upon how many bytes a given read request desires, and whether or not the driver has bytes still unread of some MIDI message. If the request is for only 1 byte, the Read handler should return the next MIDI input byte waiting to be read. This could be the Status of the next MIDI message, or another data byte of that message. If the request is for more than 1 byte, then the Read handler should assume that the calling program wants one complete MIDI message placed into its buffer, if possible. In other words, the calling program doesn't necessarily expect that it will get back as many bytes as it has requested, but rather, only as many bytes as are in the complete MIDI message which the Read handler will be placing into the request packet's buffer. The Read handler should then check the next MIDI byte waiting to be read. If it's not a Status byte (ie, running status has already been resolved at this point) or it's an 0xF7 marking the end of a System Exclusive message, then the Read handler obviously still has unread bytes from a MIDI message waiting to be read (ie, the previous request grabbed only part of a MIDI message, leaving behind some data bytes which this next request has encountered). If that's the case, then the Read handler should place as many of those remaining data bytes as possible into the request packet's buffer (as much as all of the remaining data bytes for that MIDI message), and return this "remainder of a MIDI message". In this case, the Read handler will NOT be returning a complete MIDI message, but rather, part of a MIDI message. (The program will notice this when it checks the first byte of the returned data and sees that it is not a Status byte or it's the 0xF7 ending mark of a System Exclusive message). On the other hand, if the next byte waiting to be read is indeed a Status other than 0xF7, then this means that the Read handler is prepared to place a complete MIDI message into the request packet's buffer. It should attempt to do so, copying all data bytes up to the next MIDI message's Status byte, or until the request packet's buffer is full. If the request packet's buffer is too small to fit the entire message, the Read request should fill that buffer and return this portion of the MIDI message. Obviously, what that means is that the next read request(s) will pick up the remaining data bytes of that MIDI message. Hopefully, this will be the same program following up with more calls to DosRead(). In this way, the Read handler implements an Enhanced Read which returns complete MIDI messages if possible, but which also allows for the situation where, if the program passes a buffer that is too small to contain a particular MIDI message, the program can still get the remaining bytes of that message with subsequent calls to DosRead(). This scheme also allows the driver to be able to deal with a program that requests being fed 1 byte at a time as well as a program that wants to grab an entire MIDI message at a time, without needing the driver to be switched between 2 "modes". Needless to say, the enhanced read feature makes it a lot easier and even more importantly, faster, for a program to read MIDI messages, or at least large pieces of MIDI messages, in particular, typically large messages such as System Exclusive. And that increased speed which the program grabs MIDI bytes from the driver lessens the risk of a buffer overrun which could happen if the MIDI card receives MIDI bytes faster than the driver and program can process those bytes. The Read handler must satisfy each request packet in the order that the request packets are received from OS/2. Of course, a request packet must not be returned until the Read handler has satisfied its request for bytes (ie, either by placing a complete MIDI message in the request packet's buffer, or filling that buffer). Because incoming MIDI bytes may not be received as fast as a program requests them, it may be necessary to put a request to sleep until those incoming bytes are available (ie, Block the request). See the comments about Block and Run in Write. ═══ 7.2.1.4. IoCtl ═══ The IoCtl handler must recognize those catagory 11 commands (ie, 1 and 2) described in Issuing commands. It's important to allow a program to be able to flush any queued Read and Write requests. The IoCtl handler may also implement whichever of those catagory 128 commands that it deems applicable. For example, if it wishes to return version and revision numbers, it should implement command 1. It's probably a good idea to implement the Reset command (0) if the hardware has to be setup in any way before it will input and output MIDI data. A driver should probably recognize the Reset command, even if the hardware does not need to be setup in order to input/output MIDI data, if only to ignore this command (ie, as opposed to returning some error). After all, a program will likely issue a Reset command upon startup. ═══ 7.2.1.5. Close ═══ The Close handler normally should undo everything done in the Open handler. For example, if an interrupt handler for the unit was installed in the Open handler, it should be uninstalled in the Close handler. It's best for the Open handler to increment a count of the number of times that the driver is open, and the Close handler will decrement this count. If the Close handler detects that this count is 0 (ie, nobody has the driver open anymore), then the Close handler should make sure that all queued Read and Write requests are flushed (ie, in the event that some program's main thread has called DosClose() while a second thread is "sleeping", (ie, its request packet is queued and Blocked, in a call to DosWrite() or DosRead()). Even if the count is not 0, the driver should do a flush of all read requests. ═══ 7.2.2. Speed Approach ═══ The docs for this are not yet available. I initially designed a "Speed Approach" that was closely tied to the architecture of the Roland RAP-10. This yielded high resolution MIDI playback/recording with synced digital audio using OS/2 native software. But of course, it was tied to the RAP-10's hardware. Other programmers have expressed an interest in a more general approach, so I'm currently redefining my protocol with such in mind. Programmers interested in joint efforts are welcome to contact me about such. I'm also aware of IBM's "Realtime MIDI" subsystem and would like to take a look at that, but if it doesn't meet my specific performance requirements, I'll be going ahead with my own protocol anyway. What really makes me shy away from "IBM solutions" is that I've seen firsthand that IBM is a very short-term, bottom-line company whose only interest is in its multi-million-dollar corporate accounts. IBM simply is not very successful, nor interested, in the consumer market, particularly a vertical market like the music market which is of no interest to IBM's big buck corporate accounts and therefore can't offer IBM those short-term, big buck returns. As such, I feel that any MIDI subsystem owned by IBM will be treated to same lack of development and promotion that we saw with MMPM, and will be handled in IBM's usual manner which means that it will be overpriced and poorly distributed in the consumer market, and fail to somehow offer something over Windows' MIDI competition. I feel that a cooperative, non-commercial development venture, such as how Linux or GNU operate, will offer at least as good a result as IBM will ever be willing to fund, and will be more affordable, better distributed, and less pervious to suffering development and promotion problems due a single, controlling entity being obsessed with short-term, big buck profits. That's no way to get OS/2 MIDI off of the ground. I'd prefer to see OS/2's MIDI subsystem owned and controlled by absolutely every OS/2 programmer who feels like contributing to such, even those for whom profit motive is not the only factor that determines its fate like with IBM.