═══ 1. Introduction ═══ GENMIDI Programmer's Manual General MIDI is a specification that standardizes the setup of sound modules such that a MIDI file (containing Program Change, Controller, Pitch Wheel, and Note events) designed to be played upon one GM module should sound relatively similiar upon another GM module, even an entirely different model made by another manufacturer. For a lengthy description of General MIDI, please read my "MIDI Book" (a collection of OS/2 INF files to be read by VIEW, detailing the MIDI spec). This manual assumes that you're familiar with General MIDI. Since many MIDI applications need to deal with General MIDI, often displaying those 128 GM Patch names, defined Controller names, and the GM Drum Key names, it made sense to put all of those ascii strings into one shared dynamic link library. But more than that, I found that I was using several related routines in all of my MIDI apps. For example, frequently I needed to look up the program number that was associated with a given GM Patch name. For example, if the user typed "Grand Piano" into some entry control, I needed to look up what program number that was (ie, 0), in order to construct a MIDI Program Change message to select the Grand Piano patch. So, I decided that, in addition to putting the full set of GM Patch names, defined MIDI Controller names, and GM Drum Key names in the DLL, I also would put some related routines in the DLL. There's a routine to look up a program number based upon a GM Patch name. There's a complementary routine to look up a GM Patch name based upon a program number. There's a routine to look up a controller number based upon a defined Controller name. There's a complementary routine to look up a Controller name based upon a controller number. There's a routine to look up a note number based upon a GM Drum Key name. There's a complementary routine to look up a GM Drum Key name based upon a note number. In addition, there are two complementary routines; one takes a MIDI note number and returns a note name (as a musician might specify it, with a note letter and octave, for example "C3"), and the other takes a note name and returns its respective MIDI note number. GENMIDI.DLL is an OS/2 2.X Dynamic Link Library (DLL) that a programmer can use to simplify writing OS/2 applications that deal with General MIDI and MIDI Controllers. Many programs can be using that one copy of the DLL simultaneously, thus reducing redundant code that eats up RAM needlessly. Furthermore, the DLL helps to eliminate discrepancies and incompatibilities in the way that two programs name GM Patches, GM Drum Keys, and MIDI Controllers, as well as assigning note names to MIDI note number, if both programs use this DLL, since both will be using the same ascii strings and look-up routines. To avoid source code name conflicts with DLL functions and data, avoid naming any of your functions and data starting with the letters Midi. ═══ 2. Copyright Notice ═══ This OS/2 Online Book and the related files, GENMIDI.DLL, GENMIDI.H, GENMIDI.LIB, and examples for using GENMIDI.DLL 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 of International Business Machines Corporation. Windows is a trademark of Microsoft Incorporated, and furthermore, Bill Gates is to blame for it. If you have suggestions, 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? But, 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 ═══ 3. Archive ═══ The GENMIDI archive consists of the DLL and several example applications with source code. The DLL itself is named GENMIDI.DLL, and must be copied to one of the directories specified by the LIBPATH statement in your config.sys file. Usually, one of the specified directories is .;, which means that the DLL can be placed in the same directory as the applications. The applications will not run if the DLL isn't placed somewhere in your LIBPATH. These applications are designed to be run from an OS/2 command line. For compiling apps, you'll need GENMIDI.H and GENMIDI.LIB copied to where you keep your C include files and C link libraries respectively, although these files aren't needed by the executables you produce. Of course, GENMIDI.DLL should be included with your executable if you distribute that executable, and the user should be instructed to copy GENMIDI.DLL to his LIBPATH. ═══ 4. Functions for GM Patches ═══ There are two functions dealing with GM Patches. MidiGetPgmNum() looks up a program number based upon a GM Patch name passed to it. MidiGetPgmStr() looks up a GM Patch name based upon a program number passed to it. ═══ 4.1. MidiGetPgmNum() ═══ Template UCHAR MidiGetPgmNum(UCHAR * PgmName); Description This is passed a null-terminated string containing a Patch name, for example "Grand Piano". It tries to find a match to one of the defined GM Patch names, and returns the respective Program number (ie, to be used in a MIDI Program Change message) for that Patch. Leading and trailing blanks upon the string are ignored. Also, upper or lower case is irrelevant. Furthermore, the string can be a substring of a GM Patch name. For example, if the string is simply "Clav", then the DLL considers this to match the first GM Patch with "Clav" somewhere in its name, which would be "Clavinet". Or, if the string is "Bell", then the DLL would match this with "Tubular Bells". Note that the GM Patch name containing the substring closest to the beginning of the GM name is matched. For example, "Harp" matches "Harpsichord" rather than "Orch. Harp". If the passed string does not match any GM Patch name (or isn't a substring of one), then a Program number of 0xFF is returned. Being that allowable MIDI Program numbers are 0 to 127, 0xFF can only be construed as "no matching GM Patch found". Example printf("Grand Piano is Program %d\n", MidiGetPgmNum("Grand")); The example PGMNUM.C demonstrates using MidiGetPgmNum(). ═══ 4.2. MidiGetPgmStr() ═══ Template UCHAR * MidiGetPgmStr(UCHAR PgmNumber); Description This is passed a program number (ie, 0 to 127 inclusive), and it returns a null-terminated string containing the respective GM Patch name, for example a program number of 0 returns "Grand Piano". Program numbers > 127 return a null string (ie, pointer to a null byte). Example printf("Program 1 is %s\n", MidiGetPgmStr(1)); The example PGMNAME.C demonstrates using MidiGetPgmStr(). It also can be used to display all 128 GM Patch names (ie, the ascii strings) within GENMIDI.DLL. ═══ 5. Functions for GM Drum Keys ═══ There are two functions dealing with GM Drum Keys. MidiGetDrumNum() looks up a note number based upon a GM Drum Key name passed to it. MidiGetDrumStr() looks up a GM Drum Key name based upon a note number passed to it. ═══ 5.1. MidiGetDrumNum() ═══ Template UCHAR MidiGetDrumNum(UCHAR * DrumArray, UCHAR * DrumKeyName); Description This is passed a null-terminated string containing a Drum Key name, for example "Kick 1". It tries to find a match to one of the defined GM Drum Key names, and returns the respective note number (ie, to be used in MIDI note messages to play that drum sound). Leading and trailing blanks upon the string are ignored. Also, upper or lower case is irrelevant. Furthermore, the string can be a substring of a Drum Key name. For example, if the string is simply "Snare", then the DLL considers this to match the first Drum Key with "Snare" somewhere in its name, which would be "Snare 1". Or, if the string is "Tom", then the DLL would match this with "Low 2 Tom". Note that the GM Drum Key name containing the substring closest to the beginning of the GM name is matched. For example, "HiHat" matches "Open HiHat" rather than "Closed HiHat" or "Pedal HiHat". Not all 128 possible MIDI note numbers have a defined drum sound assigned to them. For example, note numbers below 35 and above 81 have no drum sounds assigned to them by the GM specification. On the other hand, many manufacturers make modules that have a larger drum kit than the GM spec, and they use these unassigned note numbers to play the extra drum sounds. MidiGetDrumNum() therefore allows a second parameter to be passed to it, pointing to an array that defines the names of any extra Drum Keys (ie, sounds) in a kit, plus their note numbers. This is useful if your software is used for a specific module that extends the GM Drum Kit. For each drum sound, there is a length byte of its name, (counting the terminating null), then a byte representing the MIDI Note number that the sound is mapped to, and then the null-terminated name. After the last drum sound, an extra null byte ends the array. For example, assume that you have a module with 2 extra drum sounds which you wish to name "Big Boom" and "Little Boom". These are assigned to MIDI note numbers 126 and 127 respectively. You would make an array as so: UCHAR my_array[] = { 9,126,'B','i','g',' ','B','o','o','m',0, 12,127,'L','i','t','t','l','e',' ','B','o','o','m',0, 0 }; Now you can pass this to MidiGetDrumNum(), which will search this array first when matching any passed string. For example, the following call returns a note number of 127. note_number = MidiGetDrumNum(&my_array[0], "Little"); Note that substring matching and case-insensitivity applies to matching the contents of your array. Because the contents of the array are searched before the standard GM Drum Key names, you can even replace any of the standard names. For example, in the standard Kit, note number 43 is "Low 1 Tom". Perhaps you have a module that has various drum kits. Switching to an "Orchestral Kit" causes the Low 1 Tom sound to be replaced by a Timpani, but the rest of the Kit's Drum Keys remain the same as the standard kit. Therefore, you can replace just the Low 1 Tom by passing an array that sets note number 43 to "Timpani", as so: UCHAR my_array[] = { 8,43,'T','i','m','p','a','n','i',0, 0 }; note_number = MidiGetDrumNum(&my_array[0], "TIMPANI"); /* returns note number 43 */ If you have no extensions or replacements to the GM Kit, then simply pass a 0 instead of a pointer to an array. If the passed string does not match any GM Drum Key name (or isn't a substring of one), then a note number of 0xFF is returned. Being that allowable MIDI note numbers are 0 to 127, 0xFF can only be construed as "no matching GM Drum Key found". Example The example DRUMNUM.C demonstrates using MidiGetDrumNum(). ═══ 5.2. MidiGetDrumStr() ═══ Template UCHAR * MidiGetDrumStr(UCHAR * DrumArray, UCHAR NoteNumber); Description This is passed a note number (ie, 0 to 127 inclusive), and it returns a null-terminated string containing the respective Drum Key name, for example a note number of 36 returns "Kick 1". Note numbers > 127, or a note number for which there is no defined Drum Key in the GM Kit, return a null string (ie, pointer to a null byte). A pointer to an array that defines the names of any extra Drum Keys (ie, sounds) in a kit, plus their note numbers, may be passed as well. See the discussion in MidiGetDrumNum() for the purpose and setup of the array. If passed a 0, then only the standard GM Drum Kit is considered. Example printf("Drum Key 36 is %s\n", MidiGetDrumStr(0, 36)); The example DRUMKEY.C demonstrates using MidiGetDrumStr(). It also can be used to display all defined GM Drum Key names (ie, the ascii strings) within GENMIDI.DLL. ═══ 6. Functions for MIDI Controllers ═══ There are four functions dealing with MIDI Controllers. MidiGetCtlNum() or MidiGetCtlNum2() looks up a controller number based upon a Controller name passed to it. MidiGetCtlStr() or MidiGetCtlStr2() looks up a Controller name based upon a controller number passed to it. ═══ 6.1. MidiGetCtlNum() ═══ Template UCHAR MidiGetCtlNum(UCHAR * CtlName); Description This is passed a null-terminated string containing a Controller name, for example "Mod H". It tries to find a match to one of the defined MIDI Controller names, and returns the respective controller number (ie, to be used in a MIDI Controller message). Leading and trailing blanks upon the string are ignored. Also, upper or lower case is irrelevant. Furthermore, the string can be a substring of the Controller name. For example, if the string is simply "Mod", then the DLL considers this to match the first Controller whose name begins with "Mod", which would be "Mod H". The substring match must be at the head of the string. For example, "+" does not match "Data +", but "Dat" does. If the passed string does not match any MIDI Controller name (or isn't a substring of one), then a controller number of 0xFF is returned. Being that allowable MIDI controller numbers are 0 to 127, 0xFF can only be construed as "no matching Controller found". Example printf("The Mod H Controller is #%d\n", MidiGetCtlNum("Mod")); The example CTLNUM.C demonstrates using MidiGetCtlNum(). ═══ 6.2. MidiGetCtlStr() ═══ Template UCHAR * MidiGetCtlStr(UCHAR CtlNumber); Description This is passed a controller number (ie, 0 to 127 inclusive), and it returns a null-terminated string containing the respective MIDI Controller name, for example a controller number of 1 returns "Mod H". Controller numbers > 127, or a controller number for which there is no defined Controller assigned to it, return a null string (ie, pointer to a null byte). Example printf("Controller #1 is %s\n", MidiGetCtlStr(1)); The example CTLNAME.C demonstrates using MidiGetCtlStr(). It also can be used to display all defined Controller names (ie, the ascii strings) within GENMIDI.DLL. ═══ 6.3. MidiGetCtlNum2() ═══ Template UCHAR MidiGetCtlNum2(UCHAR * CtlArray, UCHAR * CtlName); Description Not all 128 possible MIDI controller numbers have a defined parameter assigned to them. For example, controller number 105 has no defined parameter assigned to it by the MIDI specification. On the other hand, many manufacturers make modules that allow these unassigned controller numbers to be used to control some parameter. MidiGetCtlNum2() therefore allows a second parameter to be passed to it, pointing to an array that defines the names of any extra controllers, plus their controller numbers. This is useful if your software is used for a specific module that uses these extra controller numbers. For each controller, there is a length byte of its name, (counting the terminating null), then a byte representing its MIDI controller number, and then the null-terminated name. After the last controller, an extra null byte ends the array. For example, assume that you have a module with 2 extra controllers which you wish to name "Filt Cutoff" and "Filt Res". You want these assigned to MIDI controller numbers 105 and 106 respectively. You would make an array as so: UCHAR my_array[] = { 12,105,'F','i','l','t',' ','C','u','t','o','f','f',0, 9,106,'F','i','l','t',' ','R','e','s',0, 0 }; Now you can pass this to MidiGetCtlNum2(), which will search this array first when matching any passed string. For example, the following call returns a controller number of 105. note_number = MidiGetDrumNum(&my_array[0], "Cutoff"); Note that substring matching and case-insensitivity applies to matching the contents of your array. Furthermore, substring matching is not limited to the head of the Controller name (for your array names only). Because the contents of the array are searched before the defined Controller names, you can even replace any of the defined names. For example, controller number 16 is "Genr 1", a general purpose slider. Perhaps you have a module that allows that to control LFO Rate. Therefore, you can replace just the Genr 1 name by passing an array that sets controller number 16 to "LFO Rate", as so: UCHAR my_array[] = { 9,16,'L','F','O',' ','R','a','t','e',0, 0 }; ctl_number = MidiGetCtlNum2(&my_array[0], "LFO"); /* returns controller number 16 */ If you have no extensions or replacements to the defined Controllers, then simply pass a 0 instead of a pointer to an array. ═══ 6.4. MidiGetCtlStr2() ═══ Template UCHAR * MidiGetCtlStr2(UCHAR * CtlArray, UCHAR CtlNumber); Description MidiGetCtlStr2() allows a second parameter to be passed to it, pointing to an array that defines the names of any extra controllers, plus their controller numbers. See the discussion in MidiGetCtlNum2() for the purpose and setup of the array. ═══ 7. Functions for MIDI Note names ═══ There are two functions dealing with MIDI note names and numbers. MidiGetNoteStr() returns a note name based upon a MIDI note number passed to it. MidiGetNoteNum() returns a MIDI note number for a given note name. ═══ 7.1. MidiGetNoteStr() ═══ Template ULONG MidiGetNoteStr(UCHAR * Buffer, UCHAR NoteNumber, CHAR Key); Description This is passed a MIDI note number (ie, 0 to 127 inclusive), as well as a pointer to a buffer into which MidiGetNoteStr() places a null-terminated string representing the respective note name. For example, a note number of 60 returns "C 3" (meaning the C in the third octave of a piano). The buffer should be at least 5 characters long to hold the largest possible returned string. MidiGetNoteStr() returns the length the note name (not counting the null byte). The Key arg is a negative number if flats are desired, or a positive number if sharps are desired, whenever an accidental is used. A program should display MIDI notes with their respective note names (rather than numbers), as this is how a musician names notes. Example ULONG Len; UCHAR Buffer[5]; Len = MidiGetNoteStr(&Buffer[0], 60, 0); The example NOTENAME.C demonstrates using MidiGetNoteStr(). ═══ 7.2. MidiGetNoteNum() ═══ Template UCHAR MidiGetNoteNum(UCHAR * NoteName, UCHAR * DefaultOct, UCHAR ** EndPtr); Description This is passed a string (does not have to be null-terminated, but there should definitely be a non-numeric character after the note name, whether it be a space, null, or some other non-numeric value) containing a note name, for example "C 3". It returns the respective MIDI note number (ie, 0 to 127). If the passed note name is an illegal one (ie, H4), then the value 0xFE is returned. If the note name is out of range (ie, the lowest note is C-2 and the highest note is G8, so "C9" would be out of range), then the value 0xFF is returned. If the passed handle EndPtr is not null, then MidiGetNoteNum() returns a pointer to where the note name ends in the buffer. This is handy if you have one long string containing numerous note names, and you wish to repeatedly call MidiGetNoteNum() to extract each respective note number from that string. You can use the updated handle to pass back to MidiGetNoteNum(), to extract the next note name in the string. When there are no more note names, MidiGetNoteNum() will end up returning 0xFE. MidiGetNoteNum() requires a pointer to a UCHAR containing the default octave to be used if the note name omits the octave. For example, the user can simply specify "E". If the default octave is 60 (ie, middle C octave), then the note number returned will be for the E above middle C (ie, 64). MidiGetNoteNum() updates the passed default octave whenever the octave is specified upon the passed note name. Example Here we pull the four note names out of MyNames[] and stuff the 4 respective MIDI note numbers in MyNumbers[]. (Note that # is used to denote "sharp" and b, a small B, is used to denote flat. Double sharps or flats are also supported. Also, any amount of blank space may be used in between each note name. Lower or upper case letters may be used. Spaces may appear inbetween the note letter, any sharp or flat, and the octave. Octaves can be omitted, in which case the default octave is used). UCHAR MyNames[] = "G#2 A -1 d5 E b"; UCHAR MyNumbers[5]; UCHAR Cnt; UCHAR DefOct; UCHAR * EndPtr; Cnt = DefOct = 0; EndPtr = &MyNames[0]; while ( ( MyNumbers[Cnt] = MidiGetNoteNum(EndPtr, &DefOct, &EndPtr) ) < 128 ) Cnt++; The example NOTENAME.C demonstrates using MidiGetNoteNum().