Classic Computer Magazine Archive START VOL. 2 NO. 1 / SUMMER 1987

MUSIC

Save Your Synthesizer Sounds

MIDISAVE, a Casio and Yamaha patch librarian

by TOM BAJORAS


In our ongoing support for the ST and the world of music, we present MIDISAVE--a simple patch librarian that works with most synthesizers, including the Casio series with its offbeat protocols. Join Tom Bajoras of Hybrid Arts as he unravels the intricacies of how synthesizers handle MIDI information. You'll find Tom's MIDISAVE program on your START disk.


File MIDISAVE.ARC on your START disk

Since its introduction in the summer of 1983, MIDI (the "Musical Instrument Digital Interface") has spread like a new religion throughout the music industry to the point where in some trade magazines, the words "MIDI" and "music" are used interchangeably. A survey conducted recently by one of the leading music magazines reveals that 42% of its readers use personal computers in their music, and the other 58% plan to get computers sometime during the next year. Traditionalists may shake their heads in dismay over this state of affairs, but one thing is certain: MIDI is here to stay.

Until the Atari ST computers appeared, a musician who wanted to connect his or her computer to a synthesizer had to buy or build a MIDI interface and connect it to one of the computer's existing I/O ports. Practically all electronic musical instruments--synthesizers, drum machines, sequencers, audio processing effects, and now even mixing boards--have a MIDI interface built into them. The ST is the first computer to have a built-in MIDI interface. This, combined with the ST's outstanding graphics and processor speed, is making the ST the focus of a lot of attention from music software developers.

The success of MIDI can be attributed to its highly standardized communication protocol. A document called the MIDI 1.0 Specification (or "MIDI Spec") serves as the bible for MIDI hardware and software engineers. I don't have space here to go into all the information in the MIDI Spec, but curious readers can get a copy of the spec from the International MIDI Association, 11857 Hartsook Street, North Hollywood, CA 91607. (Editor's note: For more on the MIDI Spec, see Tom Jeffries's "The Ins, Outs and Thrus of MIDI, " START 4, Spring 1987.)

On your START disk you'll find MIDISAVE, a MIDI generic patch librarian written in Alcyon C. It works with many different brands of synthesizers, including all those that follow the MIDI 1.0 Spec, as well as the popular Casio synths that have their own variation on the MIDI protocol. To under stand why it can't work with everysort of MIDI device, we'll have to examine how MIDI lets different musical instruments talk to one another.


INSIDE MIDI

MIDI transmits information in groups of bytes called messages. Each message consists of a status byte followed by a number of data bytes. The most significant bit (MSB) distinguishes between status and data bytes: If a byte's MSB is set then the byte is a status byte, otherwise it is a data byte. Thus, status bytes range (in hexadecimal) from $80 to $FF, while data bytes range from $00 to $7F. There is an extensive vocabulary of status bytes, each having a certain number of data bytes that must follow it. For example, the status byte $90 means "Note On on MIDI channel 1." (Channels are MIDI's way of addressing multiple instruments. Each instrument can be set to one of 16 channels. It will then only respond to the MIDI messages sent to it on the corresponding channel.) The $90 status byte requires two data bytes, so an entire Note On message looks like this:

$90 KK VV

The KK data byte is a key number, ranging from $00 to $7F, and the VV is a velocity value, also ranging from $00 to $7E.

The two status bytes of particular significance to MIDISAVE are $F0 and $F7, which stand for "System Exclusive" and "End of System Exclusive" ("EOX"), respectively. Early on in the history of MIDI, the Specs designers realized that, although standardization is a wonderful thing, not all things can be standardized. There is no problem sending note commands, controller commands, patch changes, and so on from one synthesizer to another, even if the two synthesizers are from two different manufacturers. But when it comes to communicating patch data--that is, the data used by the synthesizer's sound-generating hardware and software to produce a specific sonic timbre--that's a different matter. For example, a Yamaha DX7, a Casio CZ101, and an Oberheim Matrix-12 approach soundmaking in three fundamentally different ways. It is not possible (and may never be possible) to exchange patch data between those instruments.

Of course, it is possible to exchange patch data between two identical instruments, so the MIDI Spec had to be set up in such a way as to allow patch data to be sent over MIDI, while somehow preventing a Yamaha from forcing its patch data down a Casios throat or vice versa. That's where the System Exclusive and EOX status bytes come in. In fact, patch data is rarely called "patch data" anymore; the usual term is now "System Exclusive data."

The $F0 status byte tells the receiving instrument that bytes which follow (until it receives an $F7) comprise a System Exclusive data dump. Here is the format of a System Exclusive dump:

$F0 II. . . $F7

The II byte is a manufacturer's ID Number. For example, $43 means Yamaha, and $04 means Moog. When an instrument receives an $F0 it checks the next byte to see if this message has been sent from another of the same brand of instrument.

If not, the receiver ignores the rest of the message until the $F7. If the manufacturer's ID is the same as the receiver's, then usually some additional information must be supplied in the following bytes to tell whether this message has been sent from the same model number as the receiver's. Again, if this does not match, the receiver ignores the rest of the message until the $F7. The " . . ." above indicates that between the manufacturer's ID number and the EOX status byte there can be any number of data bytes. Different instruments will require a different number of bytes to describe a patch. In general, more complex instruments require more bytes. The only rule is that each data byte must be between $00 and $7F.

In its most basic form, a "patch librarian" is a program that can save and load System Exclusive data to and from a disk drive, and transmit and receive System Exclusive data over MIDI. Commercial patch librarians fall into one of two categories: patch librarian/editorsare designed to work with a specific brand of synthesizer, and generic patch librariansare designed to work with a large number of different synthesizers.

MIDISAVE is in the second category. At first glance you might wonder why someone would want a patch librarian that can work with only one brand of synthesizer. That means for each synthesizer you own, you must buy a separate program. The explanation is simple: generalization means loss of power. Dedicated patch librarians typically include powerful full-screen editors for modifying System Exclusive data. To cut costs, synthesizer manufacturers try to minimize the number of controls and indicators on their instruments' front panels. This results in more instrument for the money, but it also results in less user-friendliness. Editing a patch on a synthesizer that has only two switches and one LCD is a bit like using a word processor which allows you to see only one word at a time.

The more sophisticated patch librarian/editors provide full-screen editors plus graphical analysis of wave shapes, envelopes, keyboard scaling curves, and operator outputs. A generic patch librarian, on the other hand, cannot be so powerful, because the bytes between $F0 and $F7 cannot be assigned any meaning. You might be able to change those bytes (any decent disk utility program can do that), but you'd have to be a MIDI hacker at heart to obtain specific results.


USING MIDISAVE

MIDISAVE is a command-oriented program. Type the command after the "%" prompt, and enter it with the Return or Enter key. The Control-C command requires no [RETURN] or [ENTER].

There are only ten commands:

    LIST (filenames)
    LOAD filename
    SAVE filename
    ERASE filename
    HELP
    CHANNEL n
    PATCH n
    CLOAD filename
    CSAVE filename
    Control-C

You can type in the first nine commands with any combination of lower- and upper-case letters. For example, "help", "HELP" and "HElp" are all equivalent. Control-C, of course is just the C key pressed while you're holding down the Control key.

When the program loads, it automatically executes the HELP command.

(A) LIST (filenames)

This command lists the files on the disk. The optional (filenames) portion of the command specifies a file or group of files to be listed. Wild-card characters can be used within (filenames): "*" means any number of any characters, and "?" means any one character. Omitting (filenames) is equivalent to typing "LIST *.*".

It's also possible to list files on other than the default disk. (The "default disk" is the one from which you booted MIDISAVE.TOS.) For example:

    LIST B:*.*--list all files on drive B

The LIST command will not work properly unless MIDISAVE.TOS is in the root directory.

Type Control-S to pause the output from the LIST command, and Control-Q to continue.

(B) LOAD filename

Loads a file from disk to synthesizer. To load a file from other than the default disk drive use a drive designator prefix ("A:","B:", etc.).

(C) SAVE filename

Saves a System Exclusive data dump from the synthesizer to a disk file. MIDISAVE will go into a wait mode until either a valid system exclusive data dump is sent to it, or you type on the ST's keyboard. Typing a key aborts the command. To find out how to send a system exclusive data dump, you will have to consult your synthesizer's owner's manual. Some synthesizers, such as Casio synthesizers, cannot send System Exclusive data from their front panel; the SAVE and LOAD commands will not work for them. However, see the CLOAD, CSAVE, CHANNEL, and PATCH commands below.

The SAVE command will fail, with an error message, if the file specified already exists on the disk. Either try again with a different file name, or erase the file and try again using the same file name.

(D) ERASE filename

Removes a specified file from the disk.

(E) HELP

Lists the ten available commands and gives a brief description of each. When you load MIDISAVE it executes the HELP command automatically.

(F) CHANNEL n

Sets the MIDI channel (n = 1-16) for subsequent CLOAD and CSAVE commands (see below). If MIDISAVE's MIDI channel number does not match the Casio's, the ST and the Casio will not be able to communicate, resulting in error messages when the CLOAD and CSAVE commands are attempted.

(G) PATCH n

Sets the source or destination patch number (n = 0-99) for subsequent CLOAD and CSAVE commands (see below). Each model number within Casio's line of CZ synthesizers uses a slightly different numbering system for patches:

  • For a CZ-101 or CZ-1000: Presets 1-16 correspond to n = 0-15. Internal patches 1-16 correspond to n = 32-47. Cartridge patches correspond to n = 64-79.

  • For a CZ-3000 or CZ-5000: Presets A1-D8 correspond to n = 0-31. Internal patches A1-D8 correspond to n = 32-63.

  • For a CZ-1: Internal patches A1-H8 correspond to n = 0-63.

  • For a CZ-230S: Internal patches 0-99 correspond to n = 0-99.

When using the patch command, keep in mind that you cannot select a preset patch as the destination for the CLOAD command. Also, Casio synthesizers other than the CZ-101 and CZ-1000 do not allow MIDI access to the cartridge patches.

(H) CSAVE filename

Casio synthesizers use a more complicated method of System Exclusive communication. A few other synthesizers, most notably certain Roland instruments, also use non-standard methods; unfortunately MIDISAVE will not work for them, although a completely professional generic patch librarian would. Casio's method involves a handshaking dialog between the synthesizer and the computer. Instead of you telling the synthesizer to dump its data by pressing a button on its front panel, you must send a "dump request" command over MIDI. The Casio then transmits the equivalent of "Are you sure you want me to dump my system exclusive data?" to which the computer must respond with a message meaning "Yes, I'm sure. Now do it!" Only then does the Casio transmit the data for a single patch. A patch number and a MIDI channel number are embedded in the dump request, so the Casio knows which patch to dump; this is why CSAVE must be preceeded by the PATCH and CHANNEL commands.

(I) CLOAD filename

The same handshaking procedure that complicates Casio-to-computer communication also complicates computer-to-Casio communication. The handshaking goes something like this: The computer asks "Do you want to receive some system exclusive data?" and the Casio responds "If you insist." Of course the computer counters with "Of course I insist! Here it comes. . " The transmit request includes a destination patch number and channel number.

(J) Control-c

Causes an immediate exit from the program. Warning: You will not be given a chance to change your mind!


THE PROGRAM

The only external file used by the program's source is OSBIND.H, which defines GEMDOS functions for screen, keyboard, disk, and MIDI I/O.

Ten #define statements define the values returned from the getcmd() function. An eleventh #define statement defines a macro used by the parsing function.

There are four global arrays: The cmdline[] array contains each command line entered by the user in response to the "%" prompt. The token1[] and token2[] arrays contain the output from the parser. For example, if the user typed "Hello world" cmdline[] would contain "Hello world", and after parsing token1[] would contain "Hello" and token2[] would contain "world". The filebuf[] array contains MIDI data.

The "typedef struct dta" statement creates the data type DTAPTR returned from GEMDOS's Fgetdta() function. The data type DTAPTR points to a 44-byte structure created by GEMDOS's Fsfirst() and Fsnext() functions.

main()

As with any C program, execution begins with main(). The main() function here consists of an infinite loop. However, the Cconrs() and Cconws() functions intercept the Control-C command and abort from the program, giving us a "free" command to exit MIDISAVE. The while(1) loop branches to one of ten command handlers based on the value of the local variable cmd, and then calls function getcmd() to redefine the value of cmd. At the start of main() cmd is set to HELP so that on the first time through the while(1) loop the do_help() command handler will be executed.

getcmd()

The getcmd() function first erases any previous command in the cmdline[] array. Then it displays the cursor at the start of the next line. Note throughout this program that every Cconws() call includes the line-feed-plus-carriage-return sequence, "\n\r". Calling Cconrs(cmdline) fills cmdline[] with a new string whenever the user types [RETURN] or [ENTER]. Before calling Cconrs(), the first byte in cmdline[] is set to 80 to tell Cconrs() the maximum number of characters to accept. The prompt and input command line sequence is inside a do . .while() loop, so that entering a null command will cause an immediate prompt for another command. Once a non-null command line is entered, getcmd() calls lc_to_uc() to convert lower case letters in the command line to upper case. This allows commands to be typed with any combination of lower- and upper-case letters. Next, getcmd() calls parse() to parse the command line into two tokens. The first token is compared to each legal command string. If a match is found, the corresponding #defined value is returned. If no match is found, the BADCMD value is returned.

parse()

The parsing function first erases the previous two tokens. It then searches from left to right through the command line. When a non-space is encountered it begins copying the command line into the first token. It copies until it encounters a space. Then it continues searching until it finds another non-space at which point it begins copying the command line into the second token. It copies until it encounters either a space or the end of the command line.

do_load()

The do_load() command handler is called in response to the LOAD filename or CLOAD filename commands. First, it checks for a file name following the "LOAD" or "CLOAD" portion of the command. We open the specified file, and then we call Fsfirst() to determine the file's length. A file longer than 50000 bytes won't fit into the filebuf[] array, so in that case we write an error message, close the file, and return to main(). Otherwise we read the file, and transmit the contents of filebuf[] over MIDI. Finally, we close the file and return to main().

do_save()

The do_save() command handler is called in response to the SAVE filename or CSAVE filename commands. First, it checks for a file name following the "SAVE" or "CSAVE" portion of the command. Next, we attempt to open the specified file. If this succeeds, then the file exists, and we write an error message, close the file and return to main(). This means that the SAVE command cannot overwrite a file. If we find that the specified file does not yet exist, then we create it. Next, we call receive() (c_receive() for the CSAVE command) to fill filebuf[] with system exclusive data. The receive() or c_receive() function returns the number of bytes received over MIDI. If this number is zero, then the user has opted to abort the SAVE command by typing a key on the ST's keyboard while receive() was waiting for MIDI bytes. If receive() returns a number greater than zero and less than 50000 then we write the contents of filebuf[] to the newly created file close the file, and return to main().

do_erase()

The do_erase() command handler is called in response to the ERASE filename command. It checks for a file name following the "ERASE" portion of the command. If there is one, we delete the specified file and return to main().

do_help()

The do_help() command handler is called in response to the HELP command. A series of Cconws() calls writes a summary of all available commands on the screen.

do_patch()

The do_patch() command handler is called in response to the PATCH n command. The n portion of the command is converted into a number and stored into the handshaking commands for Casio communication.

do_channel()

The do_channel() command handler is called in response to the CHANNEL n command. The n portion of the command is converted into a number and stored into the hand shaking commands for Casio communication.

do_badcmd()

The do_badcmd() command handler writes the error message "Unrecognizable Command" for an illegal token1[] returned from parse().

do_list()

The do_list() command handler is called in response to the LIST (filenames) command. First, we check for the (filenames) portion of the command. If there is none, we use "**" for that portion. Next, we check for files matching the specified (filenames). If there is at least one matching file, we write that file's name. Then we loop, calling Fsnext() until it returns a non-zero number. Each time we call Fsnext() it puts another file name matching the specified (filename) into the DTA created by Fgetdta().

receive()

First, we find pointers to the heads and tails of the MIDI and Keyboard input queues. When head and tail are not equal, there is a byte available from that device, and we can read it with the Bconin() function. Checking head against tail is faster than calling Bconstat(). For MIDI I/O we need to be as fast as possible to avoid losing bytes at the end of a long System Exclusive data dump.

The loop1 loop checks the Keyboard and MIDI input queues until either is not empty. If the keyboard queue is not empty, then we return a long zero in D0 to request an abort from do_save(). If the MIDI queue is not empty, we read a byte and check for $F0. We continue with loop1 until either a key is typed or an $F0 is received. When we receive an $F0, we put it into filebuf[] and set our byte count to 1. Then we start the loop2 loop.

The loop2 loop checks the Keyboard and MIDI input queues until either is not empty. If the keyboard queue is not empty, then we return a long zero in D0 to request an abort from do_save(). If the MIDI queue is not empty, we read a byte and check for $FE or $F8. The $FE status byte is called "Active Sensing," a MIDI instrument's way of saying "Here I am!" The $F8 status byte is called "MIDI Clock." MIDI drum machines and sequencers use it to synchronize themselves. For our purposes, $FE and $F8 bytes can be treated as if they do not exist. Any other status byte is converted into an EOX status byte, $F7, and terminates loop2. If the received byte is a data byte it is appended to filebuf[], the byte count is incremented, and we continue with loop2.

If the byte count exceeds 49999, then we return a long 50000 in D0 to request an overflow error message from do_save().

c_receive()

The c_receive() function is essentially the same as the receive() function, except that the proper handshaking for Casio communication is done first.


REVERB

As generic patch librarians go, MIDISAVE is very primitive. (I wrote it in one evening.) It can't even begin to compare with a good, full-featured generic patch librarian. On the other hand, you could pay up to $150 for some commercially-available generic patch librarians no more capable than MIDISAVE. For novice programmers, MIDISAVE demonstrates effectively the general principles of communicating over MIDI. More advanced programmers will have fun modifying the program to work with a wider range of instruments.


REFERENCES

  • MIDI 1.0 Specificationfrom The International MIDI Association, 11857 Hartsook Street, N. Hollywood, CA 91607.

  • Keyboard Magazine, January 1986 (entire issue devoted to MIDI). Any issue after 1983 of the following magazines: Keyboard, Electronic Musician, Music Technology, or Keyboards, Computers and Software.

  • The MIDI Book: Using MIDI and Related Interfaces,by Steve de Furia with Joe Scacciaferro, published by Third Earth Productions, dist. by Hal Leonard.

  • MIDI For Musicians,by Craig Anderton, published by Music Sales Corp., 24 E. 22nd St., New York, NY 10010.