home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Shareware BBS: 4 Drivers
/
04-Drivers.zip
/
mpudev10.zip
/
mpudev.INF
(
.txt
)
< prev
next >
Wrap
OS/2 Help File
|
1995-09-22
|
52KB
|
1,238 lines
ΓòÉΓòÉΓòÉ 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 <os2.h>
#include <stdio.h>
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 <os2.h>
#include <stdio.h>
#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 <os2.h>
#include <stdio.h>
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 <os2.h>
#include <stdio.h>
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.