midi programing with xo jo
DESCRIPTION
midi , coco loco and the beachTRANSCRIPT
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
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
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
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
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
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.
Individual types are assigned to NoteOff and NoteOn, although NoteOff is sometimes re-dundant. The same result could be achieved by sending a NoteOn message with a volume ofzero. In fact, this option is often used in practice to minimize the length of MIDI files via therunning status (Sect.7). NoteOff does have a unique function for synthesizers that act on thevalue of the release velocity (the velocity value of the command).
6
Two of the operation bytes (&hA0 and &hD0) are devoted to an effect called aftertouch. Theidea is that after pressing a key, you can press it harder to indicate that you want somethingelse to happen. What something else is depends on the specific hardware. It is hard to imaginethat aftertouch support was added in response to the demands of performers – the only analogyon a real keyboard instrument is the technique of bebung on a clavichord. A cynic might positthat the primary purpose of aftertouch is to add more transducers to increase the value ofhigh-end keyboards. In any case, aftertouch functions could have been merged into a singleoperation 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 the
7
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
options. A complete description of all the options is beyond the scope of this report. Again,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
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. Apercussion sound is generally localized in time and has such a broad spectrum that pitch isindistinguishable. There is a wide variety of percussion sounds, depending on the instrumentor where is it struck.
By convention, MIDI channel &h09 is used exclusively for percussion. The signal to asynthesizer that a different MIDI channel should produce percussion sounds is an XG MSBcontrol message with a value of &h7F or &h7E:
&hB0+ChanNo &h00 &h7F
It is not necessary to send the control message for channel &h09. A program message sets thedrum 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.
9
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
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
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
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
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
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 selections in the listboxes of Fig. 4.
15
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
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
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
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
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 program.
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
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
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
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
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 implies thatthe MIDI messages are sent to an output device in the proper sequence and with the propertiming to sound like music. Sequencing and updates of the interface can be performed with asingle timer (PlayTimer) with a short period (2 ms). The following routines form the core ofthe MIDI player of Fig. 5. The main global variables are:
24
dim Tempo as integer \\ Start tempo in quarter notes/minute
dim PPQuat as integer \\ Pulses per quarter note
dim UsPerQNote,UsPerPulse as double
dim MaxIndex,LastIndex as integer
dim NPTimeMin,NPTimeMax as integer
dim NPTime,TimePrev as double
dim MIDIFilePlaying as boolean
The action event of the Start button intializes variables and starts the timer:
UsPerQNote = 60.0*1000000.0/TempoDefault
UsPerPulse = UsPerQNote/PPQuat
MaxIndex = UBound(T0Time)
NPTimeMin = T0Time(0)
NPTimeMax = T0Time(MaxIndex)
LastIndex = -1
DTime = 1.0/(NPTimeMax-NPTimeMin)
NPTime = NPTimeMin
TimePrev = Microseconds
MIDIFilePlaying = True
PlayTimer.Mode = Timer.ModeMultiple
The timer has the action event:
if (NPTime >= NPTimeMax) then
MidiFilePlaying = False
me.mode = Timer.ModeOff
else
TimeNow = Microseconds
NPTime = NPTime + (TimeNow-TimePrev)/UsPerPulse
SendMIDIEvents(NPTime)
TimePrev = TimeNow
end if
The quantity NPTime is the total number of pulses that have elapsed since playing was initiated.Every 2 ms, the timer updates the interface, calculates NPTime and calls the subroutine. Thecore of the process is the subroutine SendMIDIEvent shown in Table 10. The subroutinesends stored MIDI messages with T0T ime(n) ≤ NPTime, starting after the last messagesent. The efficient process makes little demand on the computer. Although messages aresent in bursts every 2 ms, the effect is imperceptible to the listener. The minimum intervalthat a human can detect is about 25 ms. The Pause/Restart button sets the timer mode toModeOff/ModeMultiple, preserving NPTime. The Stop button sets ModeOff and resets thevariables.
25
Table 10: Subroutine SendMIDIEvent
sub SendMIDIEvent(PTCurrentD as double)
dim n,PTCurrent,NCheck as integer
dim NType as uint8
dim StatusByte,DataByte1,DataByte2 as uint8
dim InLoop as Boolean
PTCurrent = PTCurrentD
InLoop = True
do
n = LastIndex + 1
if (n >= MaxIndex) then
InLoop = False
else
if (T0Time(n) > PTCurrent) then
LastIndex = n - 1
InLoop = False
else
NType = T0Type(n)
select case NType
case MidiMessage
StatusByte = asc(mid(T0Data(n),1,1))
DataByte1 = asc(mid(T0Data(n),2,1))
DataByte2 = asc(mid(T0Data(n),3,1))
MIDIEvent.set StatusByte, DataByte1, DataByte2
NCheck = MIDIOutput.write(MIDIEvent)
case SysEx
SendSysEx(n)
case SysExCont
SendSysExCont(n)
case TempChange
UsPerQNote = val(T0Data(n))
UsPerPulse = UsPerQNote/PPQuat
end select
LastIndex = n
end if
end if
loop until (not InLoop)
26
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
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
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
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
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
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
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
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
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
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
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