Page  459 ï~~The Design of a C+ + Class Library for MIDI Applications Greg Sepesi cMIDI, P.O. Box 11586, Lynchburg, VA 24506; (804) 385-0477; fax (804) 385-0434 Bill Punch Computer Science Dept, A714 Wells Hall, Michigan State University, East Lansing, MI 48824 Using the manipulation of ASCII text as an analogy, this report describes the design of a set of C+ + classes that manipulate MIDI data. 1. Overview 1.1 C++ C+ + [3,4] is a general purpose programming language which is a superset of the C programming language. C supports procedural programming, a design method which results in a collection of potentially reusable procedures. C+ + supports procedural programming and also object oriented programming, a design method which results in a collection of potentially reusable abstract data types, called classes. A data type is "abstract" when written so users can focus on the operations allowed on the data, instead of the structure of the data itself. For instance, the push and pop operations abstractly define a stack regardless of the underlying data structure, such as an array or a linked list. Although a C+ + program is no more capable than a C program, C+ + provides mechanisms which make it easier to reuse code. This suggests that once these mechanisms are learned, more C+ + code will be reused and C+ + programming will take less time. 1.2 MIDI MIDI [1] is a hardware and protocol standard for the transmission and reception of music information. The data is sent serially and asynchronously at 31.25K bits per second over an optically isolated 5 mA current loop. MIDI messages can be grouped into four categories. A channel message is a 2 or 3 byte message containing a 4-bit field defining the message's destination address, called the channel number. An example is a message to instruments receiving channel 5 to play middle C. A system common message is a 1, 2, or 3 byte message whose destination is the entire MIDI system. An example is a message to request tuning. A system real time message is a 1 byte message whose destination is the entire MIDI system and whose purpose is synchronization. An example is a MIDI clock message. A system exclusive message is a variable length message whose destination is a specific MIDI device. An example is a message to change the voice (patch) of a synthesizer. 1.3 Classes for MIDI Data In C+ +, a stream is an abstraction for the flow of data. In the design of classes for manipulating MIDI data, the MIDI stream is called mstream and is similar in usage to the C+ + file stream called fstream and the C+ + standard I/O stream called iostream. In addition to mstream, there are the following supporting classes: ICMC 459

Page  460 ï~~m chan: A channel MIDI message. m syscom: A system common MIDI message. m sysrt: A system real time MIDI message. m sysex: A system exclusive MIDI message. m Track: A sequence, with timing information, of MIDI messages. m Score: A group of Tracks. m Player: A controllable reader and transmitter of Tracks. m Ensemble: A group of Players. m TrackIterator: A pointer which can access individual MIDI messages in a Track. m Scorelterator: A pointer which can access individual Tracks in a Score. EnsembleIterator: A pointer which can access individual Players in an Ensemble. 2. Low Level Control In several ways, MIDI is analogous to ASCII. For example, it is sometimes convenient to process a single ASCII character at a time. Similarly, in MIDI applications it is sometimes convenient to process a single MIDI message at a time. To provide this low level control, message queues are configured as shown in Figure 1. 2.1 Low Level Input The paths to the queues are enabled by the ' operator and disabled by the'! =' operator. For example, the following enables the path from mstream midi's input to its queue for channel messages: midi = = midi.chanQueue; Once enabled, the '*' and '!=' operators are used to determine if the queue has received any messages. For example, mstream midi's channel queue is not empty if (*midi.chanQueue!= 0) is true. In order to remove a message from a queue, the '> >' input operator must be used. For example, the following receives chan msg from mstream midi's channel queue: midi > > msg; 2.2 Low Level Output Unlike the input, the output has no message queuing. Therefore, no queue enabling operations are necessary. The operator for mstream output is '< <'. For example, the following transmits chan msg to mstream midi: midi < < msg; 2.3 Low Level By-pass Quite often, MIDI applications are required to echo all MIDI input back to MIDI output. Therefore, mstreams have a by-pass path. As with the paths to the queues, the by-pass path is enabled with the '= =' operator and disabled with the '!-' operator. For example, midi = = midi; enables mstream midi's by-pass path. mstream input by-pass Figure 1. mstream queue structure ICMC 460

Page  461 ï~~3. Mid Level Control Continuing with the MIDI/ASCII analogy, it is often more convenient to process variable length arrays of ASCII characters, called strings, than processing a single character at a time. Similarly, it is often more convenient to process variable length sequences of MIDI messages, called Tracks, than processing a single MIDI message at a time. A Track is just a storage object. In order to produce sound, a Track must be read and played, which is done by an object called a Player. A Score is an object for storing a group of Tracks. Finally, an Ensemble is a group of Players which read and play a Score. 3.1 Mid Level Input The operator for the input of Tracks and Scores is '> >', which can be used to input MIDI data from a MIDI stream (mstream) or a file stream (fstream). In C+ +, a stream's function that does formatting is called a manipulator. The MIDI library provides the following manipulators for MIDI data: m smf, for formatting MIDI data in Standard MIDI File format [2] text, for an ASCII representation of the MIDI data m data, for a binary representation of the MIDI data For example, the following inputs Score s, from fsream fin, which contains a Standard MIDI File: fin > >smf >> s; 3.2 Mid Level Output The operator for the output of Tracks and Scores is 'Â~ ', which can be used to output MIDI data to an fstream or an mstream. Of course, the three manipulators used with the ' ' input operator may also be used with the 'Â~ ' output operator. As previously mentioned, MIDI data from a Track or Score must be read and played by a Player or Ensemble before it can be output to an mstream. Therefore, it is necessary to assign a Track or Score to a Player or Ensemble. This assignment is done by the '>' operator. For example, the following assigns Score s to Ensemble e, which then plays the Score to mstream midi: s > e; midi < < e; Before or during playing, the following operations control Players: U U m m U U m n, changes output to channel n ^ n, transposes n half steps > n, increases note-on velocity by n < n, increases note-off velocity by n > > n, delays by n time units << n, scales time by n % n, quantizes time to n time units 3.3 Mid Level Example The following example of the use of mid level control records a Track and creates and plays an Ensemble which contains the originally recorded Track along with its echoes. The example allows for the user specification of the echo time delay, volume reduction, ansd transposition. Note that many Players are using the same Track. 1* * recplay.cpp * Record a Track, then play it with echoes. * COMPILE and LINK (Borland C+ +): * bcc recplay.cpp midis.lib * ARGUMENTS: * (int) # echoes * (int) # ms delay between Track pointers * (int) echo volume reduction * (int) echo transposition * PROCESSING: * Read command line. ICMC 461

Page  462 ï~~* Record until any key is entered. * Prepare Player(s). * Play. *1 #include < mstream.h > #include < iostream.h > #include < stdlib.h > #include <conio.h > mstream midi; void main( int argc, char *argv[]) { int i, copies, delay, volume, trans; Player *p; Ensemble e; Track t; /1 Read command line. if (argc= =5) { copies = atoi(argv[l]); delay = atoi(argv[2]); volume = atoi(argv[3]); trans = atoi(argv[4]); } else { cerr < < "usage: count delay" < < "volume tranposition\n"; exit(1); } /1 Record until any key is entered. midi > > t; while (!kbhitO); getch0; midi.stop0; /1 Prepare Player(s). for (i=0; i< =copies; i++) {!/ Assign Track to new Player. p = new Player; t> *p; /1 Add to Ensemble a Player delayed, /1 volume controlled, and transposed. e &= *p> >i*delay>i*volume ^ i*trans; } IIPlay. midi Â~ e; while ( midi.output0O); } 4. Conclusion C+ + is a good fit for MIDI data manipulation because it is efficient enough for real time applications object oriented tools are easy to learn when they are designed to mirror real world objects already familiar to the user operator overloading makes source code more symbolic and easier to read and write. The described C+ + class design for low and mid level MIDI data manipulation has been implemented in version 2 of the cMIDI C+ + class library, which currently requires an IBM PC/AT/PS2, a Roland MPU-401 interface, DOS, a Microsoft or Borland C+ + compiler, and a Microsoft or Borland assembler to rebuild library. The library (written by Sepesi) is available from cMIDI. Although the MIDI/ASCII analogy is difficult to stretch farther, work is being done on a higher level of MIDI data manipulation. This higher level has the ability to dynamically alter, based upon Track content and user input, the ordering of a list of Tracks being played. Work is also being done on porting the cMIDI class library to the NeXT computer. 5. References [1] "MIDI 1.0 Specification", The International MIDI Association, 5316 W. 57th Street, LA, CA 90056, (213)649-6434. [2] "Standard MIDI Files 1.0 Specification", The International MIDI Association, 5316 W. 57th Street, LA, CA 90056, (213)649-6434. [3] Stephen Dewhurst and Kathy Stark, "Programming in C+ +", Prentice Hall, New Jersey, 1989. [4] "Borland C+ + Programmer's Guide, version 2.0", Borland, Scotts Valley, CA. ICMC 462