(c)  Copyright 1989 Commodore-Amiga, Inc.   All rights reserved.
The information contained herein is subject to change without notice, and 
is provided "as is" without warranty of any kind, either express or implied.  
The entire risk as to the use of this information is assumed by the user.



MIDI Output in Assembler

by Darius Taghavy, CATS


The Amiga's serial.device provides a software interface to the serial port 
which is suitable for a wide variety of purposes.  However, high-performance 
applications such as MIDI drivers may require direct access to the Amiga 
hardware which controls the serial port.  The program listed below shows how 
to send MIDI data over the serial port using direct access to the Amiga's 
Paula chip I/O registers.

It is important to note that MIDI applications which use direct access to 
the serial port I/O registers must first gain exclusive access to the serial 
hardware by opening the misc.resource and allocating MR_SERIALBITS and 
MR_SERIALPORT.  By doing this, the application becomes the "owner" of the 
serial port which prevents other tasks which may be running from interfering 
with serial I/O.

The example program demonstrates direct access to the internal serial port 
through the Paula chip I/O registers and provides a very useful MIDI function:
it turns off all notes.  If you have ever used MIDI software, then you may 
be familiar with the stuck-note phenomenon.  A stuck note is a note that 
sustains forever.  Stuck notes happen because the duration of a note is 
determined by the elapsed time between a NOTE ON and a NOTE OFF event.  If 
a NOTE OFF event ever gets lost you end up with a stuck note.  The MIDI 
standard does provide an ALL NOTES OFF command, however, not all tone 
generators respond to it properly, and not all sequencers provide a way to 
send the command.
 
The example program, named Panic, solves this problem by sending a NOTE ON
command with a velocity of zero for all 128 notes on all 16 channels.  In 
other words, each possible note is turned of individually forcing the 
sustaining culprit in your MIDI system to shut up.

The Panic program is written in assembler to keep the code small and to save 
execution time.  The program also employs the idea of a running status to 
save execution time by reducing the number of bytes it has to send.  In 
accordance with the official MIDI specification, as long as data of the same 
status is being sent (i.e., NOTE ON) we can just keep sending the associated 
data bytes which, in this case, is 2 bytes for pitch and velocity, 
respectively.  There is no need to re-transmit the status byte with every 
message.

In the example code, this is implemented by cycling through the (inner) 
note_loop for 128 times, once for each MIDI note number.  Since the MIDI 
channel information is imbedded in the lower nybble of the status byte, a 
new status byte must be sent when switching to a different channel. This is 
implemented as the (outer) channel_loop which loops 16 times, once for each 
MIDI channel.  Using a running status cuts I/O activity by a third.

Panic demonstrates assembler independent code by using the SECTION CODE
and SECTION DATA directives. All Amiga assemblers will understand this. Panic 
also uses the statement, XDEF _panic, to make it callable from another module 
(say, a C main program).  Manx users, please note that this is much better 
than using the Manx directive public since it is portable between assemblers 
(public is not).  Use XREF for referencing external labels and XDEF for 
making labels available to external modules. 

Panic can be assembled and linked as is. No special assembler or linker flags 
are needed.  The resulting executable is a mere 164 bytes long and only takes 
approximately 1.3 seconds to execute (using MANX 3.6a as and ln).
The length of the transmitted MIDI data itself is 4112 bytes: 

(2 note/velocity bytes * 128 notes) + (1 status byte * 16 channels) 
= 4112 bytes. 

The MIDI transfer rate is defined as 31250 baud or about 3125 MIDI bytes/sec. 
This figure takes into account start and stop bits ( a MIDI byte is 10 bits ).
Hence, time t for transmitting these events in an ideal world (ideal UART and 
crystal) is: 
       
	total number of bytes		4112 bytes
t = ---------------------------- = --------------------- = 1.31584 seconds.
	MIDI transfer rate		3125 bytes/sec

Note that the Panic program assumes the serial baud rate is already set to 
31250 baud and that the misc.resource has been allocated.  As such there are 
two uses for this program.

First, Panic could be used as a utility in conjunction with a sequencer that 
does not have a true all-notes-off feature or that lacks even the generic 
MIDI command \ff<Courier>ALL NOTES OFF\ff<Times>.  In fact, I originally 
wrote Panic because no Amiga sequencer at the time offered this important 
feature.  

When Panic is used this way, it makes sense to not set the serial baud rate 
(your sequencer already took care of it) and to not allocate the serial port 
(the sequencer probably already has exclusive access to it).

A second use for Panic would be to link it with your own code (say, a 
sequencer) which properly allocates the serial port and sets the baud rate.  
I encourage developers to include Panic in their code.  In either case, it 
is important to make sure that other tasks have been locked out from using 
the serial port before hitting the serial port hardware registers directly.

The program is listed on the next page.


;---------------------------------------------------------------- 
; PANIC (c)1990 by Darius Taghavy
;---------------------------------------------------------------- 
;sends note #0-127 with zero (0) velocity on all 16 MIDI channels 
;MANX: as panic.asm; ln panic panic.o 
;---------------------------------------------------------------- 
; this code assumes SERPER register to be set to MIDI baud rate
; it also assumes that we have exclusive access to the serial port
; hardware and related resources


                    SECTION CODE
XDEF               _panic                ;make available as 'C' function

                    SECTION DATA
CUSTOM_CHIPS     equ           $dff000   ;custom chips base address
TBE              equ           5         ;Transmitt Buffer Empty Bit

;Paula serial port register offsets from custom chip base address
SERDAT           equ           $030      ;data output(WRITE ONLY)
SERDATR          equ           $018      ;data/status(READ ONLY)


                    SECTION CODE
_panic              move.l        d2,-(a7)             ;save d2
                    lea           CUSTOM_CHIPS,a0      ;load custom chip base
                    move.w        #15,d1               ;16 MIDI channels
channel_loop        move.w        #127,d2              ;128 MIDI notes

;-------------status byte
                    move.b        d1,d0                ;get MIDI channel
                    andi.b        #$f,d0               ;nibble
                    or.b          #$90,d0              ;Note On Status
                    andi.w        #$ff,d0              ;clear high byte
                    ori.w         #$100,d0             ;set stop bit

1$                  btst.b        #TBE,SERDATR(a0)     ;wait for free serialport
                    beq           1$

                    move.w        d0,SERDAT(a0)        ;send byte

;-------------note number

note_loop           move.b        d2,d0                ;get note number
                    andi.w        #$ff,d0              ;clear high byte
                    ori.w         #$100,d0             ;set stop bit

2$                  btst.b        #TBE,SERDATR(a0)     ;wait for free serialport
                    beq           2$
                    move.w        d0,SERDAT(a0)        ;send pitch byte

;------------velocity = 0

3$                  btst.b        #TBE,SERDATR(a0)     ;wait for free serialport
                    beq           3$
                    
                    move.w        #$100,SERDAT(a0)     ;send zero velocity

                    dbra          d2,note_loop         ;loop 128 times
                    dbra          d1,channel_loop      ;loop 16 times
                    move.l        (a7)+,d2             ;restore d2
                    rts

                    END
