37
MIDI Programing with XOJO Stanley Humphries, Ph.D. Copyright 2014 KBD-Infinity PO Box 13595, Albuquerque, NM 87192 U.S.A. Telephone: +1-505-220-3975 Fax: +1-617-752-9077 E mail: info@kbd-infinity.com Internet: http://www.kbd-infinity.com 1

MIDI Programing with Xojo

Embed Size (px)

DESCRIPTION

Xojo is a coding environment for creating compiled, cross-platform programs. This report describes how to create music software with Xojo using the MIDI (Musical Instrument Digital Interface) convention. Xojo programs can address a broad range of applications: recording and playing performances, arranging and mixing tracks, home or professional studio control, composing and arranging songs, creating and playing accompaniments, computer-generated music and MIDI file processing. A common feature of the applications is interactivity. They involve complex connections between the performer/composer, the computer and one or more output devices. For this reason, the event-driven Xojo environment is an ideal programing platform.

Citation preview

Page 1: MIDI Programing with Xojo

MIDI Programing with XOJO

Stanley Humphries, Ph.D.

Copyright 2014

KBD-InfinityPO Box 13595, Albuquerque, NM 87192 U.S.A.

Telephone: +1-505-220-3975Fax: +1-617-752-9077

E mail: [email protected]: http://www.kbd-infinity.com

1

Page 2: MIDI Programing with Xojo

Contents

1 Introduction 3

2 MIDI basics 52.1 Serial port properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52.2 MIDI messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52.3 Status byte types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62.4 System common messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72.5 MIDI channels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

3 MIDI files 10

4 Opening a MIDI port 14

5 Receiving MIDI data 18

6 Sending real-time MIDI data 20

7 Playing MIDI files 23

8 MIDI programing – challenges and resources 28

9 Appendix 319.1 Variable length quantities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319.2 Receiving System Exclusive data . . . . . . . . . . . . . . . . . . . . . . . . . . . . 329.3 General MIDI voices (program numbers) . . . . . . . . . . . . . . . . . . . . . . . 349.4 Standard drum set . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 359.5 Reading a MIDI file . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36

2

Page 3: MIDI Programing with Xojo

Figure 1: Augmenting digital keyboard functions with Xojo programs.

1 Introduction

Xojo1 is a coding environment for creating compiled, cross-platform programs. This reportdescribes how to create music software with Xojo using the MIDI (Musical Instrument DigitalInterface) convention. Xojo programs can address a wide range of applications:

• Recording and playing performances

• Arranging and mixing tracks

• Home or professional studio control

• Composing and arranging songs

• Creating and playing accompaniments

• Computer-generated music

• MIDI file processing

The common feature of the applications is interactivity. They involve complex connectionsbetween the performer/composer, the computer and one or more output devices. For thisreason, the event-driven Xojo environment is an ideal programing platform.

1Information at http://www.xojo.com

3

Page 4: MIDI Programing with Xojo

The content of this report divides into two areas:

• How MIDI works.

• How to use Xojo to control MIDI events.

Note that theXojo distribution does not support MIDI communication. The applications I willdiscuss require the Audio plugin from Monkeybread Software2. The plugin handles the fullrange of music interfacing, both MIDI and audio. This is a good point to emphasize the differ-ence between the two. A MIDI message from a computer to an output device (like a synthesizer)is typically a set of three 8-bit numbers telling the operation (e.g., NoteOn), the note value (e.g.F5 ) and the volume. It is up to the output device to create a complex waveform to representan instrument playing the note and then to send it to an amplifier/speaker. For audio output,the computer creates the waveform and then sends it directly to the amplifier/speaker. Clearly,MIDI involves much less work for the computer than audio and considerably less data transfer.Therefore, MIDI applications are a good match to Xojo, which has extensive functionality butrather slow speed.

The following section covers MIDI basics including the types of MIDI messages. Section 3reviews the format of MIDI binary files. Section 4 shows how to open a line of communicationwith connected MIDI devices using theMonkeybread routines. Section 5 illustrates howXojoprograms can read data from devices like digital keyboards, while Sect. 6 shows how programscan control output devices. Section 7 covers the advanced topic of reading and playing MIDIfiles. Finally, Sect. 8 reviews some problems of MIDI programing and resources that I havemade available. The Appendix (Sect. 9) contains extended code examples and MIDI referencematerial.

I assume that the reader is familiar with Xojo and with binary and hexadecimal numbers.The code excerpts in the report illustrate how a FORTRAN programer does Xojo. To avoidoffending anyone, I did not use goto statements. As with most code examples, there is latitudeto make them more sophisticated. The code extracts have been tested on Yamaha keyboards.

2Information at http://www.monkeybreadsoftware.de

4

Page 5: MIDI Programing with Xojo

2 MIDI basics

2.1 Serial port properties

The acronym MIDI stands for Musical Instrument Digital Interface – a standard for sharingmusical information3 between digital instruments via a serial interface. It was developed in the1980s by a consortium of manufacturers and has remained largely unchanged since the days ofthe Commodore 64. The inviolability of MIDI is both a blessing and curse – Section 8 coverssome challenges for creating MIDI programs.

A MIDI serial port operates at a baud rate of 31,250 bits/second, roughly 15,000 timesslower than a USB 2.0 port. Information is sent as a series of bytes (8 bit) with start and stopbits, so that the byte rate is 3,125 bytes/s. The glacial standard has remained intact largelybecause digital music is a relatively undemanding application. A performance of Flight of theBumblebee typically involves less than 20 note-change messages per second. The one advantageof the low speed is that there are no problems with long cable runs. In principle, it would bepossible to wire a stage several kilometers wide.

2.2 MIDI messages

Most MIDI messages consist of three bytes: Status, Data1, Data2. The standard specifiesthat status bytes have bit 8 set, so they can represent numbers in the range &h80-&hFF. Bit8 is unset for the data bytes, so their values are limited to the range &h00-&h7F. As shown inFig. 2, the top four bits of the status byte represent the message type, and the lower four bitsrepresent the MIDI channel. Therefore, there are a maximum of 8 MIDI message types and 16MIDI channels. As an example, the MIDI message

&h90+&h0A &h64 &h68

has the following meaning. The &h90 part of the status byte designates a NoteOn message andthe &h0A part means that the operation should be applied to MIDI channel 10. The valuesof Data1 and Data2 imply that note 100 should be turned on with a volume of 104. Themaximum number of notes (127) is not too restrictive when you consider that a high-rangeinstrument like a piano can generate only 88 notes. Although three-byte messages constitutethe bulk of MIDI communications, keep in mind that there are longer types like tempo changesand system exclusive messages.

3To be more specific, MIDI is limited to information based on the western twelve-tone musical scale.

5

Page 6: MIDI Programing with Xojo

Figure 2: Byte usage in MIDI.

Table 1: Types of MIDI operations

StatusByte Function&h80 Note off&h90 Note on&hA0 Polyphonic aftertouch&hB0 Control change&hC0 Program change&hD0 Channel aftertouch&hE0 Pitch wheel&hF0 System common

2.3 Status byte types

The status byte determines the operation type and the channel to which it applies. Thisexpression extracts the operation part:

StatusBase = Status and &hF0

The expression

ChanNo = Status and &h0F

gives the channel number. Table 1 shows the eight possible operations. A review of thefunctions demonstrates the ad hoc nature of MIDI, a challenge to the programer. You mightassume that if only eight numbers were available for all present and future operations, theywould be parceled out carefully. Instead, the distribution of commands has grown haphazardlyfrom the needs of manufacturers.

To begin, assigning individual types to NoteOff and NoteOn seems sensible, except forthe fact that NoteOff is redundant. The same result can be achieved by sending a NoteOnmessage with a volume of zero, usually the case in practice (Sect.7). In a program, you mustalways test for both possibilities. Two of the operation bytes (&hA0 and &hD0) are devoted

6

Page 7: MIDI Programing with Xojo

to an effect called aftertouch. The idea is that after pressing a key, you can press it harderto indicate that you want something else to happen. What something else is depends on thespecific hardware. It is hard to imagine that aftertouch support was added in response to thedemands of performers – the only analogy on a real keyboard instrument is the technique ofbebung on a clavichord. A cynic might posit that the primary purpose of aftertouch is to addmore transducers to increase the value of high-end keyboards. In any case, aftertouch functionscould have been merged into a single operation type.

Control change messages (&hB0) generally define voice parameters for the synthesizers. TheData1 value identifies the parameter and Data2 gives the value. There are 128 possible pa-rameter types, many of them unused. Some of the parameters are well standardized, such assetting the reverberation or pan level of a channel voice. Others are more esoteric, and manyapply only to specific devices. A complete review is beyond the scope of this report – for adetailed description, see the references listed in Sect. 8. There are two control commands thatare particularly useful for programers:

• LocalOn/LocalOff. In the LocalOn state, a digital keyboard sends messages generatedby key presses directly to its output synthesizer. Turn off this option when your programis echoing the device (Sect. 6). [&hB0+ChanNo &h7A &h7F/&h00]

• All notes off. This is a useful safety command to extinguish hanging notes (Sect. 8).[&hB0+ChanNo &h7B &h00]

The program change type (&hC0) has the sole function of specifying the GM (general MIDI)program number, which gives the musical instrument represented by a synthesizer or virtualinstrument. Section 9.3 lists the options. In contrast to other MIDI messages, a program changeincludes only two bytes: [&hC0+ChanNo GMNumber]. The irregular number of bytes requiressome vigilance interpreting MIDI files. Devoting an operation type to this command was anunfortunate choice – the function could have been accomplished with a standard three-bytecontrol command.

The operation type &hE0 represents pitch wheel control, a time-dependent shift of note pitch.Pitch bend is used to enhance the character of music with twangy or bluesy notes. Commandshave the form [&hE0+ChanNo LSB MSB]. The pitch shift Pb is determined from the 7-bit datavalues as

Pb = (&h80)(MSB) + LSB - 8192.

The range is therefore -8192 ≤ Pb ≤ 8191. Unfortunately, there is no rigid rule that relates thenumber Pb to the actual pitch shift. The MIDI standard specifies ±2 half steps, but in almostall devices the range is ±1 octave (24 semitones). Pitch control is far more data intensive thatsimple NoteOn/NoteOff messages, so it is well-deserving of its own operation type.

2.4 System common messages

Messages with status byte type &hF0 are called system common messages and differ from theprevious types in two aspects: 1) they do not apply to a specific MIDI channel and 2) theirlengths generally exceed three bytes. Because the channel bits of the status byte are not used,they may be used to define up to 16 subtypes of system common messages. Table 2 lists theoptions. A complete description of all the options is beyond the scope of this report. Again,

7

Page 8: MIDI Programing with Xojo

Table 2: Subtypes of system common

StatusByte Function&hF0 System exclusive&hF1 MIDI time code quarter frame&hF2 Song position pointer&hF3 Song select&hF6 Tune request&hF7 End of system exclusive&hF8 Timing clock&hFA Start&hFB Continue&hFC Stop&hFE Active sensing&hFF Reset

the references of Sect. 8 give detailed information. Briefly, types &hF1, &hF6, &hF8 and &hFE

would be useful if your program were intended to synchronize an array of output devices for aperformance or add a soundtrack to a video. The types &hF2, &hF3, &hFA, &hFB and &hFC areaimed at the control of MIDI sequencers, software programs or hardware devices to organizemultiple-song programs.

The bytes &hF0 and &hF7 mark the beginning and end of a system exclusive message. Thesemessages have two distinguishing characteristics: 1) they may have any length and 2) they areusually intended only for products of a specific manufacturer. The system exclusive messageallows manufacturers to set up internal communications between the components of a deviceusing the MIDI convention or to create MIDI files that function fully only on their products.In a standard system exclusive message, a one- or three-byte manufacturer code follows thestatus byte (e.g., Yamaha is &h43). Generally, you would not use system exclusive messages inprograms unless you were working for manufacturers and had access to their proprietary data.

The exception is a universal system exclusive message, a data sequence that follows a stan-dard format. A useful example is the master volume control which can be used to implementsmooth fades for all channels by lowering the volume of output devices:

&hF0 &h7F &h7F &h04 &h01 &h00 VLevel &hF7

The second byte (&h7F) indicates that the message is of type real-time and should be appliedimmediately, the third byte (&h7F) that the operation should be applied to all channels, thebytes &h04 and &h01 specify that the message is a master volume control, and the bytes &h00and VLevel gives the LSB and MSB of the volume (most devices ignore the LSB value). Thisis a complicated way to set the volume, but it works.

Finally, a message with status byte &hFF resets the output device. Note that this byte codehas an entirely different function in a MIDI file (Sect. 3).

8

Page 9: MIDI Programing with Xojo

2.5 MIDI channels

The sixteen available MIDI channels are typically used to carry information for different musicalinstruments. The information may be directed to multiple hardware synthesizers, or to a singlepolyphonic synthesizer. Modern synthesizers (even on low-end keyboards) are able to createseveral simultaneous instrument voices. The information could also be used inside the computerfor virtual instruments (software programs or VST plugins). Again, some virtual instrumentsare polyphonic and others are intended to create audio only for a single channel.

MIDI commands may be divided into two functional classes: setup and real-time. NoteOff(&h80), NoteOn (&h90), aftertouch (&hA0 and &hD0) and pitch bend (&hE0) messages are clearlyin the real-time group. Control change (&hB0), program change (&hC0) and system exclusivemessages are usually sent before the start of a song. Their primary function is to set the voicesof channel synthesizers. For example, after sending the command

&hCA &h49

subsequent notes to Channel 10 would sound like a flute. Sophisticated synthesizers can repre-sent far more than the 128 general-MIDI voices (Sect. 9.3). The additional sounds are invokedby sending the number of an XG (extended general MIDI) bank with the control commands:

&hB0+ChanNo &h00 MSB

&hB0+ChanNo &h20 LSB

The two numbers represent the most-significant and least-significant byte of the XG banknumber. Note that XG voices are not standardized between different manufacturers and mayeven vary between devices from the same manufacturer. It is best to avoid XG voices if youwant to generate output that can be played on all MIDI devices.

Up to this point, the discussion has concerned tonal instruments. In other words, theinstruments listed in Sect 9.3 create sounds with different pitches – the fundamental frequencyis given by the note value of a NoteOn message. We now turn to percussion instruments, whichplay a large role in modern music. A percussion sound is generally localized in time and hassuch a broad spectrum that pitch is indistinguishable. There is a wide variety of percussionsounds, depending on the instrument or where is it struck.

The signal to a synthesizer that a MIDI channel should represent percussion is an XG MSBcontrol message with a value of &h7F or &h7E:

&hB0+ChanNo &h00 &h7F

A following program message sets the drum set:

&hC0+ChanNo DrumSetNo

In subsequent NoteOn messages for that channel, the note number (Data1 ) specifies a per-cussion sound type rather than a pitch. Synthesizers and drum machines may support severaldrum sets (e.g., symphonic, middle eastern,...), but there is only one standardized set thatgives about the same sound on all MIDI devices, DrumSetNo = 0. Section 9.4 shows thecorrespondence between note number and the percussion sound for the standard set. Finally,a common practice is to use channel &h09 for percussion, although theoretically any channelcould be used.

9

Page 10: MIDI Programing with Xojo

3 MIDI files

The standard MIDI binary file (Fig 3) is a collection of MIDI messages. Each message musthave an associated timestamp so that programs know when to send it. MIDI files offer morechallenges to programers than dealing with real-time messages. Part of the problem is that theformat was designed for optimal performance on an Apple II with a 360 kB floppy disk. Severalclever but annoying strategies are applied to minimize the file length.

A MIDI file has binary format and consists primarily of unsigned bytes (uint8). A fileconsists of a single header section4 and one or more track sections that contain MIDI messagesand other data. The simplest MIDI file (Type 0 ) contains a single track section with MIDImessages arranged in chronological order. To play the file, a program proceeds through themessage list, much like a real-time performance. We shall consider this type of file first, andthen address Type 1 files which may include several track sections. Files of Type 2 may containmultiple songs and are generally intended for specialized software or hardware sequencers.

Table 3 shows the structure of the header section which always appears at the beginningof the file. Section 7 covers how to input header information to a Xojo program. The sectionlength gives the number of following data bytes. The header data always consists of three 16-bitintegers (for a total byte length of 6): the MIDI file type, the number of tracks and the numberof pulses per quarter note. The final quantity raises the question: what is a pulse? This is agood point to discuss timing in MIDI files.

Pulses are a convenient division of a quarter note – the pulse length is short enough to beimperceptible to the listener but long enough so that intervals may be represented by shortinteger numbers. A typical value is Pq = 120 pulses per quarter note. A tempo command thatgives the number of microseconds per quarter note (Usq) occurs in the track section before anynotes are played. This command is discussed later in the section. If there are N pulses betweentwo messages, then the time interval in seconds is given by

∆t = NUsq

(106) Pq.

The track section has the structure shown in Table 4. The byte length is the sum of bytesfor intervals, messages and the end-of-track marker. Note that when writing a MIDI file, thebyte length must be known in advance. This feature makes it difficult to record MIDI sequenceson the fly or to edit existing MIDI files.

To minimize the length of the file, time quantities are given as the elapsed number of pulsesfrom the preceding message rather than as the absolute time. For even more reduction, intervalsare written as variable-length quantities. This method uses only a single byte to represent asmall interval (<&h80 pulses), but may use up to four bytes to represent pulse numbers to&hFFFFFFF. Section 9.1 reviews the mathematics. The main feature to note is that the programknows when it has reached the end of a variable-length quantity.

4File sections are often called chunks

10

Page 11: MIDI Programing with Xojo

Figure 3: Type 0 MIDI file – content display of header and first part of the track chunk.

Table 3: Header section of a MIDI file

Data type Function4 × uint8 Section marker (string MThd or &h4D &h54 &h68 &h64)uint32 Section data byte length (always 6)uint16 MIDI file type (0,1 or 2)uint16 Number of tracks (1 for Type 0 )uint16 Pulses per quarter note, Pq

Table 4: Track section of a MIDI file

Data type Function4 × uint8 Section marker (string MTrk or &h4D &h54 &h72 &h6B)uint32 Section data byte length1-4 bytes Interval to Message 12 or more bytes Message 11-4 bytes Interval to Message 22 or more bytes Message 21-4 bytes Interval to Message 32 or more bytes Message 3... ...End of track &hFF &h2F &h00

11

Page 12: MIDI Programing with Xojo

Table 5: System exclusive message in a MIDI file

Data type FunctionVariable quantity Pulse interval from previous message&hF0 Status byteVariable quantity Nb, byte length of message including termination(Nb-1) bytes The message&hF7 Termination

A MIDI message follows the interval. At the start of the message, a program would expectto encounter a status byte with a value ≥&h80 followed by data bytes. The number of databytes depends on the value of the StatusBase. A &hC0 message is followed by one data byte,and messages of type &hA0, &hB0, &hD0 and &hE0 have two data bytes. Messages with status&hF0-&hFF have a variety of lengths and must be treated as special cases. After reading thedata bytes, a program expects to start reading the next variable-quantity interval.

Lest programers become complacent, there is an exception: the running status. BecauseMIDI files consist mainly of NoteOn signals, continually rewriting &h90+ChanNo would be re-dundant. The following rule applies. If a program encounters a byte with value <&h80 afterreading the time interval, it should interpret it as the first data byte of a message and usethe status of the previous message. We can now understand why a NoteOn message with avolume of zero is used in preference to NoteOff – a string of identical status bytes reduces thefile length. And lest progamers become reasonably comfortable, there is an exception to theexception. System exclusive message cannot invoke running status.

System common messages (&hF0-&hFF) represent about 90% of the programer’s work forreading and interpreting MIDI files. The functions are shown in Table 2. Again, it is impossibleto cover all options in a short report, so I refer you to the references of Chap. 8. We shallconcentrate instead on two important cases: system-exclusive messages (&hF0) and non-MIDImessages (&hFF).

System-exclusive messages in a file have a modified format from that discussed in Sect. 2.4.Table 5 shows the format. As with all file messages, a variable-length quantity occurs before thestatus byte to give the pulse-interval from the preceding message. A variable-length quantityafter the status byte gives the number of bytes that will follow (including the terminating &hF7).

Because there is no instance where a file would call for a synthesizer to reset itself, thestatus byte &hFF is used for a different purpose: non-MIDI messages. The term implies thatthe messages contain non-musical information, such as copyright data or the lyrics to a song.The first data byte following the status byte gives the type of message. Table 2 lists theoptions. Each type has a unique format that must be handled by a program as a special case.To illustrate, we shall consider two instances.

A lyric message has the form

&FF &05 Length Text

where Length represents a variable-length quantity equal to the text bytes length. The quantityText is a series of bytes, the ASCII values of characters. Special characters may be included todesignate carriage returns or new paragraphs. Lyric messages are timed events, synchronized

12

Page 13: MIDI Programing with Xojo

Table 6: Types of non-MIDI messages

MarkerByte Function&h00 Sequence number&h01 Text&h02 Copyright&h03 Sequence/track name&h04 Instrument&h05 Lyric&h06 Marker&h07 Cue point&h20 MIDI channel&h21 MIDI port&h2F End of track&h51 Tempo change&h54 SMPTE offset&h58 Time signature&h59 Key signature&h7F Proprietary event

with the musical notes. It is straightforward to create a program that displays the lyrics at thecorrect time – this is how karaoke programs work. A MIDI file with embedded lyrics usually hasthe suffix kar. Messages of the type text, copyright, sequence/track name, instrument, markerand cue point contain text for various purposes and follow the same format.

The most important non-MIDI message is the tempo change. Every MIDI file must containat least one such message to define how fast to play the content. The message always occupiessix bytes and has the form

&FF &51 &h03 T1 T2 T3

The value &h03 redundantly states that there are three following data bytes. In contrast toother data bytes, the values T1, T2 and T3 are 8-bit numbers. The number of microsecondsper quarter note is given by

MuQ = (&h100)(&h100) T1 + (&h100) T2 + T3.

For example, the byte values [&h07 &hA1 &h20] translate to 500,000 µs per quarter note.

13

Page 14: MIDI Programing with Xojo

4 Opening a MIDI port

This section begins our consideration of how to create MIDI programs with Xojo The MIDIfunctions of the operating system can control a complex array of software programs and hard-ware devices. For example, a computer may have both input and output communication witha keyboard through a USB cable and also send information to devices like drum machines viaan I/O box and standard MIDI cables. Xojo can communicate with the MIDI functions of theoperating system through routines in the Monkeybread Software Audio Plugin. The pluginmay seem intimidating at first – it includes the following ten classes, the description of whichoccupies 46 pages in the instruction manual:

4.1. class PortMidiStreamMBS

4.2. class PortMidiDeviceInfoMBS

4.3. class PortMidiMBS

4.4. class PortMidiEventMBS

4.5. class WindowsMidiOutputMBS

4.6. class WindowsMidiStreamMBS

4.7. class WindowsMidiInputMBS

4.8. class WindowsMidiInputInfoMBS

4.9. class WindowsMidiOutputInfoMBS

4.10. class WindowsMidiMBS

The situation is actually much simpler. Classes 4.5-4.10 are legacy routines that function onlyin Windows and have no advantage in speed. The capabilities of the PortMidi routines aresufficient to create full-functioned interfaces. Most important, it is possible to build executablesfor Apple, Linux and Windows computers with the classes.

Of the four PortMidi classes, two perform functions and two are structures to hold data forthe functional classes:

• PortMidiMBS sets up communication with the operating system. This class is used onlyat the beginning of the program or when there is a change in the connected hardware.The associated data class is PortMidiDeviceInfoMBS.

• PortMidiStreamMBS exchanges data through connected MIDI ports (devices). Theassociated data class is PortMidiEventMBS.

This section discusses the first function, establishing communications. For the examples,assume that the following global variables are available:

dim MIDIPort as new PortMidiMBS

dim MIDIIn as new PortMidiDeviceInfoMBS

dim MIDIOut as new PortMidiDeviceInfoMBS

dim LastInPortOpened,LastOutPortOpened as string

The string variables record the name of the last device opened. Suppose that port setup isperformed in the dialog of Fig. 4. Two listboxes show the available input and output devices.

14

Page 15: MIDI Programing with Xojo

Figure 4: Dialog to choose the MIDI input and output ports.

Table 7 shows the code to fill the list boxes. The first group of commands checks that theoperating system has MIDI support. If so, the variable NCount is set equal to the total numberof MIDI input/output devices connected to the computer.

In the second group, the program loops through all devices. For each one, the DeviceInfoproperty fills the PortMidiDeviceInfoMBS data structure MIDIIn. If the device has input, itsname is included in the list. A similar procedure is used for the output devices. The deviceentries in Figure 4 illustrate a typical setup for a Windows computer with a digital keyboard at-tached. The entry Microsoft GS Wavetable Synth is a rudimentary virtual instrument includedwith Windows. Here, the term virtual instrument denotes a software utility that converts MIDInumbers into an audio waveform resembling GM instruments. The output is usually availableon the sound card. The entry Coolsoft VirtualMIDISoft is a better utility with lower latency.

The action event of the dialog OK button checks which entries in the input and output tablesare highlighted to set the string variables LastInPortOpened and LastOutPortOpened. Theprogram then calls the subroutine CheckMIDIPorts(LastInPortOpened,LastOutPortOpened)listed in Table 8 (for brevity, only the input section is shown). The program loops throughall MIDI devices and singles out those that have input. The integer variable MidiPortIn is setequal to the number of the device that has the name LastInPortOpened or to 0 if the device isnot present. This number is used to open an input data stream, the subject of the next section.

To conclude, the examples in this report apply to a keyboard application with one inputand one output. MIDI messages resulting from key presses on the input device come in andmodified information is sent back to a synthesizer to generate appropriate audio signals. It ispossible to open multiple streams to control several MIDI devices. Here, the program decideswhere to direct the data. For this case, the dialog routines could be modified so that the usercan make multiple selection in the listboxes of Fig. 4.

15

Page 16: MIDI Programing with Xojo

Table 7: Open event code for the window of Fig. 4. The default selections are LastInPortOpenedand LastOutPortOpened

dim NCount as integer

dim n as integer

n = MIDIPort.ReInitialize

if (n <> 0) then

MsgBox "MIDI port error"

self.close

end if

NCount=MIDIPort.CountDevices - 1

// Fill the input list box

ChooseInPortListBox.SelectionType = ListBox.SelectionSingle

if (NCount > -1) then

for n = 0 to NCount

MIDIIn = MIDIPort.DeviceInfo(n)

if MIDIIn <> Nil then

if MIDIIn.HasInput then

ChooseInPortListbox.AddRow MIDIIn.Name

if (MIDIIn.Name = LastInPortOpened) then

ChooseInPortListBox.Selected(n) = True

end if

end if

end if

next

end if

// Fill the output list box

ChooseOutPortListBox.SelectionType = 0

if (NCount > -1) then

for n = 0 to NCount

MIDIOut = MIDIPort.DeviceInfo(n)

if MIDIOut <> Nil then

if MIDIOut.HasOutput then

ChooseOutPortListbox.AddRow MIDIOut.Name

if (MIDIOut.Name = LastOutPortOpened) then

ChooseOutPortListBox.Selected(n) = True

end if

end if

end if

next

end if

16

Page 17: MIDI Programing with Xojo

Table 8: Subroutine CheckMIDIPorts. Opens either the first available ports or those with thenames LastInPortOpened and LastOutPortOpened.

dim NCount as integer

dim n as integer

dim FoundPort as Boolean

NCount = MIDIPort.CountDevices-1

if (NCount < 0) then

exit

end if

// Input

FoundPort = False

MidiPortIn = 0

for n = 0 to NCount

MIDIIn = MIDIPort.DeviceInfo(n)

if (MIDIIn <> Nil) then

if MIDIIn.HasInput then

FoundPort = True

if (MIDIIn.Name = CheckInPort) then

MidiPortIn = n

end if

end if

end if

next

if (FoundPort) then

MIDIIn = MIDIPort.DeviceInfo(MidiPortIn)

LastInPortOpened = MIDIIn.Name

InPortPresent = True

else

InPortPresent = False

end if

...

17

Page 18: MIDI Programing with Xojo

5 Receiving MIDI data

A computer can read and analyze a MIDI data stream from an attached keyboard, drum pador other transducer if an input port has been opened (as discussed in the previous section).Applications include recording a performance as a MIDI stream or computing an accompa-niment based on incoming information. The PortMidiStreamMBS class is used for real-timecommunication. In the following discussion, assume that we have defined global variables:

dim MIDIInput as new PortMidiStreamMBS

dim MIDIEvent as new PortMidiEventMBS

The first step is to open the input stream:

dim NCheck as integer

NCheck = MIDIInput.OpenInput(MIDIPortIn,1000)

if (NCheck <> 0) then

MsgBox "Error opening MIDI input device"

return

end if

NCheck = MIDIInput.Setfilter(&h1D00 + &h4000)

The integer parameter MIDIPortIn is the input device identification number returned by thesetup routines of the previous section. The number 1000 is the byte size of the input buffer.When activated, the MIDIInput stream accumulates MIDI messages arriving from the device inthe buffer. This data is generally not available to your program until it has been fetched fromthe buffer. We’ll discuss this operation below. Note the statement with the SetFilter method.The parameters specify 1) make filters active (&h4000) and 2) do not store clock messages(&h1D00). A device like a keyboard sends out a continuous stream of synchronization signals(status bytes &hF8, &hFA, &hFB and &hFC) in addition to musical information. Unless you arewriting a high level application to synchronize slave devices, it is unlikely you will need thisinformation. With the filter set, messages are not recorded in the buffer.

In order to use incoming information, it must be transferred from the buffer to your programvariables. This is accomplished with a timer operating in ModeMultiple that performs thefollowing action:

dim Status,Data1,Data2 as integer

while MIDIInput.Poll<>0

NCheck = MidiInput.Read(MIDIEvent)

Status = MIDIEvent.Status

Data1 = MIDIEvent.Data1

Data2 = MIDIEvent.Data2

if NCheck = 1 then

// Perform actions

end if

wend

18

Page 19: MIDI Programing with Xojo

On each cycle, the program transfers the accumulated buffer messages to the program variablesand takes appropriate actions. A typical action is to echo the messages back to the synthesizerof the keyboard after processing or recording the MIDI stream. In the latter case, the programmust also store the reception time of the messages in order to create a MIDI file or to playthe song. For real-time applications, the timer period should be short. A period of a fewmilliseconds is usually sufficient for musical applications because the threshold for distinguishingdifferent notes is about 25 ms.

The Read method transfers data in chunks of three bytes, sufficient for most MIDI mes-sages. In the case of the two-byte program message (&hC0), the method returns Data1 =GMProgNum and Data2 = &h00. If the input device sends a system exclusive message ofarbitrary length, the Read method delivers it in chunks of three bytes starting with Status =&hF0. The process continues until the end of the message, with padding bytes (if necessary) ofvalue &h00 after the termination &hF7. Section 9.2 illustrates how to read regular and systemexclusive messages in a stream. If your program does not use system exclusive information, themessages may be ignored. Simply take no action if Status = &hF0 or &hF7 or if Status <&h80.

19

Page 20: MIDI Programing with Xojo

6 Sending real-time MIDI data

Next, we turn to MIDI output from a computer to a keyboard, synthesizer, virtual instrumentor other MIDI device. There are two types of data output operations:

• Sending real-time MIDI messages

• Playing MIDI files

In the first case, information is simply sent at the time it becomes available. Examples includean electronic piano demo, echoing input from a performer or output from your own electronicmusic program. In contrast, the second application involves analysis of the timing informationassociated with the messages. The next section covers this topic. Here, the assumption is thatMIDI messages become available at the time they are needed.

Examples in this section use the following global variables:

MIDIOutput = new PortMidiStreamMBS

MIDIEvent = new PortMidiEventMBS

MBlock = new MemoryBlock(1000)

To send output, open one or more streams with the command

dim NCheck as integer

NCheck = MIDIOutput.OpenOutput(MidiPortOut,1000,0)

if (NCheck <> 0) then

MsgBox "MIDI output error"

exit

end if

The first integer parameter (1000) is the byte size of the output buffer, while the second (0) isthe latency is milliseconds. The zero value indicates that the data should be sent immediately.The latency setting may or may not be useful for synchronizing MIDI with audio and may ormay not work in Windows, so it is probably best not to use any value but zero. If necessary,you can handle latency in your software.

Once the stream has been opened, a simple three-byte MIDI message is sent with code likethe following:

dim Status,Data1,Data2,StatusBase as integer

double precision VTemp

constant StatusBaseMask = &hF0

StatusBase = Status and StatusBaseMask

if (StatusBase = &h90) then

VTemp = VolumeLevel*Data2

Data2 = VVInt

end

MIDIEvent.set Status, Data1, Data2

NCheck = MIDIOutput.write(MIDIEvent)

20

Page 21: MIDI Programing with Xojo

The last two lines appear in any output transfer. The initial lines check whether the outputmessage is of type NoteOut. If so, the volume level is adjusted by a parameter set by theuser, 0.0 ≤ V olumeLevel ≤ 1.0. Note that in the Monkeybread routines, all messages havethree-byte length. For a two byte program command, set Status = &hC0+ChanNo, Data1 =GMProgNo and Data2 = &h00. As an example, here is a code template for setting the voice(instrument sound) of a channel, complete with XG parameters:

SendMIDIMessage(&hB0+ChanOut, &h79, &h00)

SendMIDIMessage(&hB0+ChanOut, &h00, XGMSB)

SendMIDIMessage(&hB0+ChanOut, &h20, XGLSB)

SendMIDIMessage(&hC0+ChanOut, ProgNo, &h00)

The subroutine has the content

sub SendMIDIMessage(Status as integer,Data1 as integer,Data2 as integer)

dim NCheck as integer

MIDIEvent.set status,data1,data2

NCheck = MIDIOutput.write(MIDIEvent)

end sub

The first call to the subroutine sends a channel reset message. Although this is not absolutelynecessary, it is always a good idea to include such safety valves. Remember that your programmay be interacting with a keyboard, an intelligent device that remembers things. The keyboardmay accumulate data that puts it in an unanticipated state, causing peculiar behavior that isdifficult to track down. Note that the order of calls in the example is important – always setthe XG parameters before sending the program message.

A different procedure is necessary for system exclusive message of variable length. Toillustrate, suppose the message (complete with initial &hF0 and final &hF7) is stored in thestring SysExData in the form of ASCII characters. The following code fills a memory blockand then uses the method WriteSysEx of the class PortMIDIStreamMBS :

dim m as integer

dim NLength as integer

dim NCheck as integer

NLength = len(SysExData)

for m=1 to NLength

MBlock.Byte(m-1)= asc(mid(SysExData,m,1))

next

MBlock.Byte(NLength) = &h00 // 0 termination

NCheck = MIDIOutput.WriteSysEx(0,MBlock)

The additional byte &h00 to terminate the memory block is required. This is an undocumentedquirk of the Monkeybread routine. The first parameter for the WriteSysEx method is thelatency – again, use a value of zero to send the data immediately.

Echoing is a process that combines input and output operations. Here, a program mayreceive input MIDI messages from a keyboard and return them to the same device after pro-cessing. In this case, the program would send an initial LocalOff message to the keyboardso that only the computer messages reach the output synthesizer. To illustrate, consider howto implement Irving Berlin’s piano. The famous composer was entirely self-taught and never

21

Page 22: MIDI Programing with Xojo

Table 9: Irving Berlin’s piano routine

constant StatusBaseMask = &hF0

dim Status,Data1,Data2 as integer

dim NCheck as integer

StatusBase = Status and StatusBaseMask

while MIDIInput.Poll<>0

NCheck = MidiInput.Read(MIDIEvent)

Status = MIDIEvent.Status

Data1 = MIDIEvent.Data1

Data2 = MIDIEvent.Data2

if NCheck = 1 then

StatusBase = Status and StatusBaseMask

if (StatusBase = &h90) or (StatusBase = &h80) then

Data1 = Data1 + KeyOffset

end if

MIDIEvent.set Status, Data1, Data2

NCheck = MIDIOutput.write(MIDIEvent)

end if

wend

learned to play the piano in more than one key. To play in different keys, he had a custom pianobuilt with a sliding keyboard. It is easier to accomplish this with MIDI. Playing a CMajor piecein the key of E♯Major is simply a matter of setting up an echo and adding 3 to the note valueof outgoing NoteOn and NoteOff messages. Table 9 shows the core of a transposition program.

22

Page 23: MIDI Programing with Xojo

7 Playing MIDI files

A Xojo program to play MIDI files must address three tasks:

• Read the file, converting the data to a form usable in a program.

• If the file contains multiple tracks, combine the information into a single timeline of events.

• Output the events with the correct timing.

We shall start by discussing how to read the file. The following code opens a binary MIDIfile identified as folder item InputFItem:

dim bstream as BinaryStream

bstream = BinaryStream.open(InputFItem,False) // Read only

The file of bytes can be read sequentially with statements of the type

dim binput as uint8

binput = bstream.readuint8

As an example, the following function returns the first four characters of a chunk as a string.The calling program then determines whether it corresponds to MThd or MTrk :

function ReadChunkName as string

dim CName as string

dim binput as uint8

dim NCount as integer

CName = ""

for NCount = 0 to 3

binput = bstream.readuint8

CName = CName + chr(binput)

next

return CName

By good fortune, the byte storage order for longer integers in MIDI is the same as that in Xojo.Therefore, there are options for reading such numbers. As an example, consider input of a tracksection length, a 32-bit unsigned integer that starts after the MTrk designator (Table 4). Youcould use either

dim NSLength as uint32

dim n as integer

dim binput as uint8

NSLength = 0

for n = 0 to 3

binput = bstream.readuint8

NSLength = &h100*NSLength + binput

next

23

Page 24: MIDI Programing with Xojo

Figure 5: Utility to play MIDI files.

or

dim NSLength as uint32

NSLength = bstream.readuint32

The primary task in reading a a MIDI file is to transform the data to a form useful for theprogram function. There are many options – we shall consider one possible approach. The codeexcerpt of Sect. 9.5 employs the following data arrays:

dim T0Type() as integer

dim T0Time() as integer

dim T0Message() as string

The first array records the type of message (e.g., standard MIDI messages, system exclusivemessages, tempo changes,...). The second array records the absolute time, the total number ofpulses that elapsed since the file start. The quantity equals the sum of all previous intervalsin the track. The third array holds the message itself, stored as a string of characters toaccommodate messages of different lengths. The code of Section 9.5 illustrates how to transferthe data of track section to the arrays.

When the arrays have been filled, we can play the file data. Here, the term play impliesthat the MIDI messages are sent to an output device in the proper sequence and with theproper timing to sound like music. The following routines form the core of the MIDI player ofFig. 5. The quantities MuQ and Pq (defined in Sect. 3) give the number of microseconds per

24

Page 25: MIDI Programing with Xojo

Table 10: Run event of the thread PlayThread to output a stored MIDI sequence.

dim NCheck,MaxIndex,NPTimeMax,NPTimeMin,NextEventIndex as integer

dim TimeNow,TimePrev,DTime,NPTime as double

MaxIndex = UBound(T0Time)

NPTimeMin = T0Time(0)

NPTimeMax = T0Time(MaxIndex)

NextEventPTime = 0

NextEventPTimeD = 0.0

NextEventIndex = 0

NPTime = NPTimeMin

DTime = 1.0/(NPTimeMax-NPTimeMin)

TimePrev = Microseconds

while (NPTime <= NPTimeMax)

TimeNow = Microseconds

NPTime = NPTime + (TimeNow-TimePrev)/UsPerPulse

if (NPTime >= NextEventPTimeD) then

if (NextEventPTime < NPTimeMax) then

VolumeLevel = PassVolumeLevel

SendMIDIEvents

end

ProgressValue = 500.0*(NPTime-NPTimeMin)*DTime

end

TimePrev = TimeNow

wend

ProgressValue = 0

DisplayTimer.Mode = Timer.ModeSingle

PlayThread.kill

pulse, UsPerPulse = MuQ/Pq. Sequencing is performed by the thread PlayThread with therun event listed in Table 10. In keeping with the new Xojo convention, the thread receives andtransfers information to the main window by updating the global variables PassVolumeLeveland ProgressValue. A timer (DisplayTimer) operating in ModeMultiple updates the main win-dow progress bar from ProgressValue and determines the current relative volume level from theposition of the main window slider.

The Start button activates PlayThread. After initialization, the thread enters a loop whereit calculates the elapsed number of pulses, NPTime. The thread exits the loop when NPTimereaches NPTimeMax, the maximum absolute time in the file. On each cycle, the thread checkswhether NPTime equals or exceeds the absolute time of the next entry in the message list. Ifso, it calls the subroutine SendMIDIEvents (Table 11). This routine outputs all pending MIDImessages with absolute time less than or equal to NPTime, and then updates the list orderparameters. Note that the output volume depends on the current value of VolumeLevel, so thatthe user actively controls the output volume. With regard to other controls, the Pause buttonaction calls the methods PlayThread.Suspend and PlayThread.Resume, while the Stop buttonaction calls PlayThread.Kill.

25

Page 26: MIDI Programing with Xojo

Table 11: Subroutine SendMIDIEvents called by PlayThread (Table 10).

sub SendMIDIEvents

dim n as integer

dim NType as uint8

dim StatusByte as uint8

dim DataByte1 as uint8

dim DataByte2 as uint8

dim StatusBase as uint8

dim VVInt as integer

dim NCheck as integer

n = NextEventIndex

while(T0Time(n) <= NextEventPTime)

NType = T0Type(n)

select case NType

case MidiMessage

StatusByte = asc(mid(T0Data(n),1,1))

DataByte1 = asc(mid(T0Data(n),2,1))

StatusBase = StatusByte and StatusBaseMask

DataByte2 = asc(mid(T0Data(n),3,1))

if (StatusBase = &h90) then

VVInt = DataByte2

VVInt = (VVInt*VolumeLevel)/99

DataByte2 = VVInt

end

MIDIEvent.set StatusByte, DataByte1, DataByte2

NCheck = MIDIOutput.write(MIDIEvent)

case TempChange

UsPerQNote = val(T0Data(n))

UsPerPulse = UsPerQNote/PPQuat

end select

n = n + 1

NextEventIndex = n

wend

NextEventPTime = T0Time(NextEventIndex)

NextEventPTimeD = NextEventPTime

end sub

26

Page 27: MIDI Programing with Xojo

MIDI files of Type 1 may contain any number of tracks. Multiple tracks may be usedto organize data. For example, voice information might be collected in one track, tempochange information in another, and the notes for each MIDI channel in individual tracks. Thedifference between Type 0 and Type 1 files is solely one of organization, not content. A Type1 file can always be converted to a Type 0 file that produces the same sounds. A potentialproblem interpreting multiple tracks is that the stored time intervals relate only the events ina particular track. The issue is easily resolved by storing the absolute pulse time of events inthe T0Time array. The content of multiple tracks is accumulated in the data arrays, and thenthe Xojo sort method is used to order the data according to T0Time:

redim T0Time(-1)

redim T0Type(-1)

redim T0Data(-1)

for nt = 1 to NTracks

ChunkName = ReadChunkName()

if (ChunkName = TrackChunk) then

if (not AddTrackChunk()) then

MsgBox "Invalid track in MIDI file"

bstream.close

exit

end

end

next

T0Time.sortwith(T0Type,T0Data)

The sorted arrays are played the same way as those of a Type 0 file.As final topic, consider how to write a MIDI file from the filled data arrays T0Type, T0Time

and T0Data. In principle, the task should be easy. Simply reverse the read process, convertingvalues of T0Time to intervals expressed as variable quantities (Sect. 9.1). The one complicatingfactor is that the total byte length of the track must be known in advance (Sect. 4). One solutionis given by the following steps: 1) append the stored data to an array of unsigned bytes, 2) usethe resulting array size to write the data byte length and 3) transfer the data bytes to the file:

dim RawByte() as uint8

bstream.write("MTrk")

AssembleTrackBytes

MaxIndex = UBound(RawByte)

bstream.writeuint32(MaxIndex+1) // Length

for n=0 to MaxIndex

bstream.writeuint8(RawByte(n))

next

Here, the subroutine AssembleTrackBytes initializes RawByte and then appends the intervalsand messages stored in the data arrays.

27

Page 28: MIDI Programing with Xojo

8 MIDI programing – challenges and resources

When MIDI was created in the 1980s, its sole purpose was real-time communication betweenelectronic instruments. In this application, the interaction between the performer and thehardware ensured the logic of the signal stream. If a performer pressed a key, then (barringa heart attack) he/she would eventually release the key. In other words, a NoteOn signalwas always followed by a corresponding NoteOff signal. The relationship between signals wasdetermined solely by their position in time. The nature of the hardware guaranteed that anaftertouch signal would always occur between the NoteOn and NoteOff signals of the note towhich it should be applied.

With the development of personal computers, MIDI became the de facto standard for any-thing having to do with electronic instruments, expanding far beyond its intended role. (Infact, many current references refer to a MIDI file as a digital score, an electronic version ofmusical notation.) We can divide computer programs that interact with MIDI devices into twocategories:

• Creative applications

• Performance assistance

To start, consider creative applications. Program examples include composition software,MIDI sequencers, digital-audio-workstations and musical notation converters. The distinguish-ing feature of these applications is that the duration and other attributes of notes are knownin advance. This may seem like an obvious point, but it is important enough to repeat: in acreative application, the note duration is a known quantity. Contrast this with the situation ina real-time application where the program does not know when the performer wants the noteto end. In the real-time world, the NoteOn/NoteOff paradigm is unavoidable. On the otherhand, it is unsuitable for creative applications. For example, suppose we stretch or move anote in a composition program. If we follow the MIDI convention where independent eventsare connected only by their position it time, there is no guarantee that a pitch bend will occurwithin its intended note. Even worse, a NoteOff signal may precede its NoteOn counterpart,

The implication is that no creative program can actually work in MIDI. The MIDI messagestream must be converted to an internal note-based representation. Here, the notes carryattributes such as duration, pitch, volume, aftertouch, and pitch-bend profiles. Any changeof the note makes a corresponding change of the attributes. After an operation, the internalrepresentation must be transformed back to a consistent MIDI format for display or playback.The unfortunate circumstance is that there are hundreds of electronic music programs, andeach one has resolved the MIDI paradox in its own unique way. In an ideal world, there wouldtwo MIDIs: the standard real-time version and a note-based creative version. Furthermore, thebrave new world would feature standardized, documented algorithms to move between the tworepresentations. Until that day arrives, you will have to create yet another unique system ifyou hope to write a MIDI editor.

28

Page 29: MIDI Programing with Xojo

Figure 6: MIDI metronome project set to make a drum sound.

The challenges to real-time performance programs are even greater because of two potentialpitfalls:

• Hanging notes (a NoteOn signal not balanced by a NoteOff signal).

• Overlapping identical notes on the same MIDI channel.

Hanging notes are so fearsome that the MIDI standard includes a dead-man switch, ActiveSensing. When it is turned on, a MIDI device extinguishes all notes if the sender does notcheck in every 0.3 seconds.

An example will illustrate how hanging notes may occur in a program. Suppose you wantto develop accompaniment software to play MIDI loops where the key and chord type aredetermined by real-time signals from the performer. In an accompaniment, several notes ondifferent channels may be active at any time. The question is what to with those notes when theperformer signals a chord change. If the program takes no action, subsequent NoteOff signalsfor the active notes may have different note values, leaving hanging notes. If the programsimply turns off all active notes, there will be a gap in the accompaniment. A solution requiressome effort. The program must independently keep track of active notes. At a chord shift, theprogram turns off present notes and immediately restarts replacements to reflect the new chord.As a result, any following NoteOff signals will be are matched to the active note values. Thereis a complicating problem for instrument sounds with sharp attack like guitars and pianos. Inthis case, restarting held notes to reflect a chord change may have a jarring effect.

Although some MIDI output devices handle overlapping notes on the same channel grace-fully, others crash. To illustrate sources of problems, consider generating an automatic harmonicaccompaniment to a melody line based on chord signals from the performer. The harmony noterepresents a best choice based on the chord and melody note. For example, in CMaj this choicemight be G3 if the performer plays C4-D4-E4. There would be no problem if the performerplays the notes marcato, but there would be overlapping notes if the phrase were performedlegato. Solutions are surprisingly complex – in addition to a list of active notes, the programmust record how many times the performer attempted to start the note. The bottom line forcoping with MIDI in real-time programs is vigilance and testing.

29

Page 30: MIDI Programing with Xojo

To learn more about MIDI, there are two essential references for programers:

• David Miles Huber, The MIDI Manual, Third Edition (Focal Press, New York, 2007)

• Robert Guerin, MIDI Power, Second Edition (Course Technology, Boston, 2008).

There is an extensive array of reports on MIDI and downloadable MIDI files in all genresavailable on the Internet. I have set up a resource site containing the code examples and tablesdiscussed in this report. The URL is

http://www.kbd-infinity.com/xojo_resource.html

On the site, you can download a complete Xojo project for the MIDI metronome shown inFig. 6. The metronome program is useful, even for non-musicians. Any clock sound may becreated by setting the GM or XG parameters, so you can check out the voice settings discussed inSect. 2.5. Alternatively, the metronome is a good way to irritate the person in the next cubicle.If you have questions or comments, please contact me at [email protected].

30

Page 31: MIDI Programing with Xojo

9 Appendix

9.1 Variable length quantities

Time offsets and the lengths of system exclusive messages in MIDI files are stored as a series ofbytes in a form called a variable-length quantity. The idea is to represent a assortment of smalland large integer numbers with the minimum number of bytes. A variable-length quantity maycontain from one to four bytes. The eighth bit of each byte is used as a marker, so each bytecan represent the range 0-127 (&h00-&h7F). If the eighth bit is set, additional bytes follow. Theeighth bit is unset in the final byte to mark the end. As an example, the three bytes [&h81 &h80

&h00] represent the number &h4000. The largest number that can be represented by four bytesis [&hFF &hFF &hFF &h7F] → &FFFFFFF. It is probably easier to understand the rules from acode example rather than a verbal description. Here is a Xojo function that reads as manybytes as necessary from a MIDI file to fill out a variable quantity:

function GetVariableQuantity as integer

const NMask = &h7F

const NMult = &h80

dim NSum as integer

dim NIn as uint8

NSum = 0

NIn = bstream.readuint8

if (NIn < &h80) then

return NIn

exit

else

NSum = (NIn and NMask)

do

NSum = NSum*NMult

NIn = bstream.readuint8

NSum = NSum + (NIn and NMask)

loop until (NIn < &h80)

end

return NSum

end function

31

Page 32: MIDI Programing with Xojo

The next example shows how to go the other way, converting an integer number in the range&h0000000-&hFFFFFFF to a set of 1-4 bytes stored as characters in a string:

function CreateVarQuant(NIn as integer) as string

dim RString as string

dim b1,b2,b3,b4,BShift as uint32

if (NIn < &h80) then

// One byte

RString = chr(NIn)

elseif (NIn < &h4000) then

// Two byte

b2 = NIn and &h7F

b1 = (NIn - b2)/&h80 + &h80

RString = chr(b1) + chr(b2)

elseif (NIn < &h200000) then

// Three byte

b3 = NIn and &h7F

BShift = (NIn - b3)/&h80

b2 = BShift and &h7F

b1 = (BShift - b2)/&h80

RString = chr(b1+&h80) + chr(b2+&h80) + chr(b3)

else

// Four byte

b4 = NIn and &h7F

BShift = (NIn - b4)/&h80

b3 = BShift and &h7F

BShift = (BShift - b3)/&h80

b2 = BShift and &h7F

b1 = (BShift - b2)/&h80

RString = chr(b1+&h80) + chr(b2+&h80) + chr(b3+&h80) + chr(b4)

end function

end if

9.2 Receiving System Exclusive data

The Read method for the PortMidiStreamMBS class transfers data from the input buffer tothe program variables in three-byte chunks. A system-exclusive message begins with the statusbyte &hF0 and ends with &hF7 after an arbitrary number of data bytes. The following exampleillustrates how to record both standard and system exclusive messages from a MIDI device. Thearray T0Data contains the message as a string of characters and the T0Type array designatesthe type of data.

32

Page 33: MIDI Programing with Xojo

dim NCheck as integer

dim Status,Data1,Data2 as integer

dim NNote as integer

dim CurrentTime as double

while MIDIInput.Poll<>0

NCheck = MidiInput.Read(MIDIEvent)

if NCheck = 1 then

Status = MIDIEvent.Status

Data1 = MIDIEvent.Data1

Data2 = MIDIEvent.Data2

if (ReadingSysEx) then

if (Status = &hF7) then

SysExString = SysExString + chr(Status)

T0Type.Append SysEx

T0Data.Append SysExString

ReadingSysEx = False

elseif (Data1 = &hF7) then

SysExString = SysExString + chr(Status) + chr(Data1)

T0Type.Append SysEx

T0Data.Append SysExString

ReadingSysEx = False

elseif (Data2 = &hF7) then

SysExString = SysExString + chr(Status) + chr(Data1) + chr(Data2)

T0Type.Append SysEx

T0Data.Append SysExString

ReadingSysEx = False

else

SysExString = SysExString + chr(Status) + chr(Data1) + chr(Data2)

end if

else

if (Status = &hC0) or (Status = &hB0) then

CurrentTime = 0.0

T0Type.Append MidiMessage

T0Data.Append chr(Status) + chr(Data1) + chr(Data2)

elseif (Status = &hF0) then // SysEx start

SysExString = chr(Status)

if (Data1 = &hF7) then

SysExString = SysExString + chr(Data1)

T0Type.Append SysEx

T0Data.Append SysExString

elseif (Data2 = &hF7) then

SysExString = SysExString + chr(Data1) + chr(Data2)

T0Type.Append SysEx

T0Data.Append SysExString

else

SysExString = SysExString + chr(Data1) + chr(Data2)

ReadingSysEx = True

end if

end if

end if

end if

wend

33

Page 34: MIDI Programing with Xojo

9.3 General MIDI voices (program numbers)

The numbers (which appear as the Data1 value in a program change command &hC0) designatethe instrument listed. A GM compliant device will produce a sound like the instrument, butno two synthesizers sound exactly the same. The final entries are interesting. If you had 128opportunities to represent every musical instrument on earth, would you include telephone ring,helicopter and gunshot?

000 Acoustic Grand Piano 043 Contrabass 086 Lead 7 (fifths)

001 Bright Acoustic Piano 044 Tremolo Strings 087 Lead 8 (bass + lead)

002 Electric Grand Piano 045 Pizzicato Strings 088 Pad 1 (new age)

003 Honky-tonk Piano 046 Orchestral Harp 089 Pad 2 (warm)

004 Electric Piano 1 047 Timpani 090 Pad 3 (polysynth)

005 Electric Piano 2 048 String Ensemble 1 091 Pad 4 (choir)

006 Harpsichord 049 String Ensemble 2 092 Pad 5 (bowed)

007 Clavinet 050 Synth Strings 1 093 Pad 6 (metallic)

008 Celesta 051 Synth Strings 2 094 Pad 7 (halo)

009 Glockenspiel 052 Choir Aahs 095 Pad 8 (sweep)

010 Music Box 053 Voice Oohs 096 FX 1 (rain)

011 Vibraphone 054 Synth Choir 097 FX 2 (soundtrack)

012 Marimba 055 Orchestra Hit 098 FX 3 (crystal)

013 Xylophone 056 Trumpet 099 FX 4 (atmosphere)

014 Tubular Bells 057 Trombone 100 FX 5 (brightness)

015 Dulcimer 058 Tuba 101 FX 6 (goblins)

016 Drawbar Organ 059 Muted Trumpet 102 FX 7 (echoes)

017 Percussive Organ 060 French Horn 103 FX 8 (sci-fi)

018 Rock Organ 061 Brass Section 104 Sitar

019 Church Organ 062 Synth Brass 1 105 Banjo

020 Reed Organ 063 Synth Brass 2 106 Shamisen

021 Accordion 064 Soprano Sax 107 Koto

022 Harmonica 065 Alto Sax 108 Kalimba

023 Bandoneon 066 Tenor Sax 109 Bagpipe

024 Acoustic Guitar (nylon) 067 Baritone Sax 110 Fiddle

025 Acoustic Guitar (steel) 068 Oboe 111 Shanai

026 Electric Guitar (jazz) 069 English Horn 112 Tinkle Bell

027 Electric Guitar (clean) 070 Bassoon 113 Agogo

028 Electric Guitar (muted) 071 Clarinet 114 Steel Drums

029 Overdriven Guitar 072 Piccolo 115 Woodblock

030 Distortion Guitar 073 Flute 116 Taiko Drum

031 Guitar Harmonics 074 Recorder 117 Melodic Tom

032 Acoustic Bass 075 Pan Flute 118 Synth Drum

033 Electric Bass (finger) 076 Blown Bottle 119 Reverse Cymbal

034 Electric Bass (pick) 077 Shakuhachi 120 Guitar Fret Noise

035 Fretless Bass 078 Whistle 121 Breath Noise

036 Slap Bass 1 079 Ocarina 122 Seashore

037 Slap Bass 2 080 Lead 1 (square) 123 Bird Tweet

038 Synth Bass 1 081 Lead 2 (sawtooth) 124 Telephone Ring

039 Synth Bass 2 082 Lead 3 (calliope) 125 Helicopter

040 Violin 083 Lead 4 (chiff) 126 Applause

041 Viola 084 Lead 5 (charang) 127 Gunshot

042 Cello 085 Lead 6 (voice)

34

Page 35: MIDI Programing with Xojo

9.4 Standard drum set

Specify a standard percussion channel by sending the messages

&hB0+ChanNo &h00 &h7F

&hC0+ChanNo &h00

Subsequent NoteOn signals of the form

&h90+ChanNo InstNo Volume

generate the sounds listed below.

InstNo Instrument InstNo Instrument

---------------------------------------------------------------

035 Bass Drum 2 059 Ride Cymbal 2

036 Bass Drum 1 060 High Bongo

037 Side Stick/Rimshot 061 Low Bongo

038 Snare Drum 1 062 Mute High Conga

039 Hand Clap 063 Open High Conga

040 Snare Drum 2 064 Low Conga

041 Low Tom 2 065 High Timbale

042 Closed Hi-hat 066 Low Timbale

043 Low Tom 1 067 High Agogo

044 Pedal Hi-hat 068 Low Agogo

045 Mid Tom 2 069 Cabasa

046 Open Hi-hat 070 Maracas

047 Mid Tom 1 071 Short Whistle

048 High Tom 2 072 Long Whistle

049 Crash Cymbal 1 073 Short Guiro

050 High Tom 1 074 Long Guiro

051 Ride Cymbal 1 075 Claves

052 Chinese Cymbal 076 High Wood Block

053 Ride Bell 077 Low Wood Block

054 Tambourine 078 Mute Cuica

055 Splash Cymbal 079 Open Cuica

056 Cowbell 080 Mute Triangle

057 Crash Cymbal 2 081 Open Triangle

058 Vibra Slap

35

Page 36: MIDI Programing with Xojo

9.5 Reading a MIDI file

The code extracts illustrate how to transfer MIDI file messages to theXojo data arrays T0Type,T0Time and T0Data discussed in the report. Note that the routines can handle the runningstatus condition.

function AddTrackChunk as Boolean

dim ChunkLength as uint32

dim EventStatus as uint8

dim PreviousStatus as uint8

// Initialize

NbRead = 0

PreviousStatus = 0

TAbsolute = 0

// Program has read chunk name

// Length of chunk

ChunkLength = bstream.readuint32

// Process the track chunk

do

// Bail out if end of file reached

if (bstream.EOF) then

return False

exit

end

EventTime = GetVariableQuantity()

TAbsolute = TAbsolute + EventTime

EventStatus = bstream.readuint8

NbRead = NbRead + 1

NbTotal = NbTotal + 1

// Running status

if (EventStatus < &h80) then

if (PreviousStatus = &hF0) then // SysEx cannot use running status

return False

exit

end

FirstByte = EventStatus

RunningStatus = True

ProcessStatus(PreviousStatus)

// New status

else

RunningStatus = False

ProcessStatus(EventStatus)

PreviousStatus = EventStatus

end

loop until (NbRead = ChunkLength)

return True

end function

36

Page 37: MIDI Programing with Xojo

subroutine ProcessStatus(Status as uint8)

dim StatusBase as uint8

dim ChanNo as uint8

StatusBase = Status and StatusBaseMask

// Status with channel number appended

Select Case StatusBase

Case &h80 // Note off

T0Time.append TAbsolute

T0Type.append MidiMessage

DataString = chr(Status)

ProcessNoteOff

Case &h90 // Note on

T0Time.append TAbsolute

T0Type.append MidiMessage

DataString = chr(Status)

ProcessNoteOn

End Select

end subroutine

sub ProcessNoteOn

dim NoteValue as uint8

dim VelocityValue as uint8

if (RunningStatus) then

NoteValue = FirstByte

else

NoteValue = bstream.readuint8

NbRead = NbRead + 1

NbTotal = NbTotal + 1

end

VelocityValue = bstream.readuint8

NbRead = NbRead + 1

NbTotal = NbTotal + 1

DataString = DataString + chr(NoteValue) + chr(VelocityValue)

T0Data.append(DataString)

end sub

37