You are here

How To Use Amiga's MIDI Facilities

Amiga Notes
Published June 1995

Something a little different this month, as Paul Overaa provides details of how the Amiga's MIDI facilities can be used from BASIC...

One of the reasons that real‑time Amiga MIDI programming is difficult is that accurate time‑stamping of events often means getting involved with low‑level serial port control, direct use of the timer hardware, and so on. If you want to write your own sequencer program, you have little choice but to come to terms with these Amiga system‑related 'nasties'. In many other cases, however, a much easier alternative is available. Believe it or not, there are many useful MIDI diagnostic utilities and test programs that can be written with just a few lines of BASIC, and I thought this month that an explanation of the fundamental ideas might be useful. Needless to say, it's not possible to provide the whole story in just one instalment, but the ideas which follow should give those of you who would like to experiment some important 'initial footholds'!

MIDI uses serial transmission, and for the examples here, I've chosen to use HiSoft BASIC, with serial port access achieved using the SER: device. This provides high‑level access to the Amiga's serial device, which in turn controls the machine's underlying serial hardware. All you need to remember is that serial device characteristics must be set to suitable values, namely: a baud rate of 31250 with no parity, no handshaking, and just one stop bit. All of these settings can be adjusted from the Amiga's Workbench Preferences program.

The file‑handling approach of HiSoft BASIC is straightforward, and the sequential file handling input/output conventions are that you 'output to a file' or 'input from a file'. This being the case, we can open the Amiga's serial device for sending serial data like this:

OPEN "SER:" FOR OUTPUT AS #1

To ensure that MIDI data is sent straight away (and not buffered), it is better to explicitly set a buffer size of 1 byte, like this:

OPEN "SER:" FOR OUTPUT AS #1 LEN=1

Once the serial device is open, all we need is a way of sending MIDI messages. The easiest way to transmit bytes of MIDI information is to place them in a string variable or string expression. Supposing, for example, that I wished to transmit a MIDI real‑time stop message. This is a single byte whose value is decimal 252 (or FC in hex). The CHR$() function can convert the decimal 252 numeric argument into a 1‑byte character string, and this can then be used in conjunction with the BASIC 'PRINT#' statement like this:

PRINT# 1, CHR$(252);

The result? A MIDI stop message will be transmitted. Note that the semicolon at the end of the statement prevents BASIC from transmitting a terminal newline character. There are several ways of transmitting longer messages, but the easiest approach is just to build up the messages using CHR$() coupled with BASIC's string concatenation operator (+). To transmit a two‑byte program change message, for example, we send a program change status byte followed by the patch number. The general layout for a channel‑n/patch‑p message takes the form as shown below:

Status byte Data byte

1100 nnnn (binary) pppp pppp (binary)

Providing we remember that MIDI channel numbers 1‑16 are actually transmitted as the numbers 0‑15, and patch commands 1‑128 are similarly represented by the numbers 0‑127, it is easy to work out what bytes need to be transmitted. If, for example, we wanted to transmit a patch number = 6 command on MIDI Channel 2, we'd need to incorporate the numbers 5 and 1 respectively into the general message just described.

The binary, hex, and decimal forms of the required numbers are as shown in Table 1. So, the message which needs to be transmitted is this:

PRINT# 1, CHR$(193) + CHR$(5);

Most MIDI programmers prefer to use hex values for status bytes, and in the above case, this would be done by re‑writing the fragment as:

PRINT# 1, CHR$(&Hc1) + CHR$(5);

Why use hex? Because working out decimal values for the status bytes is not only a pain, it also makes it harder to see what the status byte represents. The '1' value in the above status byte C1 hex tells you immediately that the byte refers to a Channel 2 MIDI message, and (once you are MIDI‑literate) the C tells you that the status byte refers to a program change message. The same pieces of information are undoubtedly still there when the status byte is in decimal form... but neither the message type nor the channel number are particularly obvious!

A useful idea, as far as constant values are concerned, is to isolate the characters being transmitted, so that they are no longer clutter the main program code. One way of doing this is to place the required descriptions at the start of the program. The definition written initially as

REM define MIDI message...

message$=CHR$(&Hc1) +CHR$(4)

might, for instance, be used later in the program as:

PRINT# 1, message$;

You don't have to use constant values in the PRINT# expressions. To send a two‑byte message consisting of the numerical values X and Y we could use something along the lines of:

PRINT# 1, CHR$(X) + CHR$(Y);

If we used X=&Hc1 and Y=5, the same program change message described earlier would be transmitted.

The variable approach is very useful when used as part of a BASIC FOR/NEXT loop. To send all 128 channel program change messages on MIDI channel 3, we could use a loop like this:

X=&Hc2

FOR Y=0 to 127

PRINT# 1, CHR$(X) + CHR$(Y);

NEXT Y

On the other hand, to send the program change patch 5 message on all sixteen MIDI channels, we could use a loop which modifies the status byte value:

FOR X = &HC0 TO &HCF

PRINT# 1, CHR$(X) + CHR$(4);

NEXT X

This is fine for illustration purposes, but in general, it is better to use meaningful variable names. In a real MIDI program, for example, a twin loop to send all program change numbers on all channels might be written as something like:

FOR STATUS = &HC0 TO &HCF

FOR PATC= 0 to 127

PRINT# 1, CHR$(STATUS) + CHR$(PATCH);

NEXT PATCH

NEXT STATUS

As well as binary to hex, and hex to decimal conversion, all potential MIDI programmers need to be confident about extracting part‑values from a byte. Given a channel message status byte, for instance, you'll often need to be able to identify the channel and the message type. Channel numbers can be obtained from a status byte by masking out the upper four bits of a byte by ANDing with &HF, like this:

channel=ASC(status$) AND &HF

Similarly, masking out the lower four bits (by ANDing with &HF0) will give the isolated MIDI message class in the top four bits of the number:

messagetype=ASC(status$) AND &HF0

Sometimes, the alternative situation will occur, and you'll want to build up a status byte from the channel and message type values. In this case, the values need to be combined by ORing. So to create and send a Note‑On status byte, we would logically OR &H90 with the channel number, and transmit the value using this type of code:

PRINT# 1,CHR$(&H90 OR channel);

To transmit a complete Note‑On message, we'd follow the status byte with a note number and a velocity value, thus:

PRINT# 1,CHR$(&H90 OR channel) +CHR$(note)+CHR$(velocity);

The string part of these types of fragments are generally useful, and easily turned into user‑defined functions. Here's one which sends a complete MIDI Note‑On message on a specified channel, this time using a fixed velocity value of 64:

DEF FNNoteOn$(note,ch)=CHR$(&H90 OR (ch‑1))+CHR$(note)+CHR$(64)

Subtracting one from the channel number in the above example is just a convenience for the program user — it allows conventional 1‑16 channel numbers to be used, rather than the internal representations (the values 0‑15) used by the program itself. Here's the alternative function to turn a note off:

DEF FNNoteOff$(note,ch)=CHR$(&H80 OR (ch‑1))+CHR$(note)+CHR$(64)

It should be obvious from these discussions that once you know how to transmit one type of MIDI message, you can apply the same principles to any MIDI message. Having to work with binary and hex numbers takes a bit of getting used to if you have not encountered them before, but the solution if you have any difficulties in this area is to practice. Have a look in your synthesizer's MIDI implementation chart or manual to see what types of messages your synth can recognise, and then write a few simple test programs to turn notes on and off or send patch commands. A little experimenting should help you get to grips with these somewhat alien number forms in no time!

Table 1

STATUS BYTE

DATA BYTE

Prog Change

Channel

Patch Number

1100

0001 (binary)

0000 0101 (binary)

C

1 (hex)

05 (hex)

193 (decimal)

5 (decimal)

Amiga News In Brief

  • NEW CATALOGUE
    Silica Systems have launched a dedicated Amiga catalogue containing offers and details of new products. For details, contact Silica at 1‑4 The Mews, Hatherty Road, Sidcup, Kent DA14 4DX (or phone on 0181 309 1111).
  • FRAUD SQUAD INVESTIGATES
    It has recently been reported that two companies that deal with Amiga products, Total Computer Supplies and WTS Electronics, have been the subject of an investigation by the Bedfordshire Police Fraud Squad over alleged claims that goods ordered and paid for have not been supplied. Both companies have said that the problems are connected with Amiga stock supply shortages, and that in most cases, customers have been informed of possible long delays in supplying certain items. Bearing in mind the current Commodore liquidation situation, such delays are certainly understandable, but surely the proper thing for any company to do when it finds that it is unable to fulfil a particular order within a reasonable time span is to refund the customer's money. That keeps the customer happy, and, as an added bonus, ensures that they don't go running off to the local fraud squad making complaints!
  • ICPUG SPECIAL OFFER
    The Independent Commodore Product Users Group (ICPUG) are currently offering cut‑price membership (£16.50 for the UK). Members get advice and expert help, free PD software, and a copy of the club's journal every month. Telephone ICPUG on 01235 815725 (after 8.30 pm) for details.