Dr.Godfried-Willem RAES

Kursus Experimentele Muziek: Boekdeel 1: Algoritmische kompositie

Hogeschool Gent : Departement Muziek & Drama


<Terug naar inhoudstafel kursus>    

1063:

M.I.D.I.- PROCEDURES & FUNKTIES

Zelfbouwinterfaces (cfr.boekdeel 2) - Logotronics
Afspelen van P(i,j) arrays
MPU-UART mode
MPU midi Uit procedure in Basic
Soundblaster - Power Basic - Assembler
MPU midi Uit procedure in assembler
Soundblaster Uit procedure in assembler
MPU-UART procedure in assembler
Midi Input
Midi In/Out
Midi FIFO sturing - dubbele buffers
PowerBasic Console Compiler Soundblaster procedures NEW 1998

1.Universele procedure voor de sturing van Logos midi-interfaces

Deze procedures werken alleen met de interfaces zoals beschreven in boekdeel 2 van kursus. Vanuit het hoofdprogramma of vanuit een ander subprogramma over te dragen variabelen (of konstanten) zijn:

DP het I/O adres van de midi-poort

b% de waarde van het uit te sturen midi-getal

CODE:

SUB Uit (b%)

' dit is een subprogramma dat een byte 'b%' naar het Logos midi-interface doorstuurt het I/O adres van het gebruikte interface is opgeslagen in de variabele of konstante DP

IF DP=&H320 THEN

OUT &H320,b% : OUT &H322,0

Z#=ZZ#^ZZ# :' dit is een dummy op de strobe-puls te verlengen .Het kan ook in hardware geimplementeerd worden met een monoflop.

OUT &H322,1

WHILE INP(&H321) AND 128 : WEND

' wacht tot de UART klaar is met de verwerking

EXIT SUB

ENDIF

IF DP<> &H2FA THEN

OUT DP,b% : OUT DP+2,0: A$="_": OUT DP+2,1

WHILE INP(DP+1) AND 128 : WEND

ELSE

OUT &H2FA,b%

DO : LOOP UNTIL INP(&H2FB) AND 64

ENDIF

END SUB

Heb je slechts 1 machine met slechts 1 interface, dan kan je voor DP uiteraard ook een konstante waarde invoeren en deze procedure sterk inkorten. Heb je bijvoorbeeld je midi-interface geinstalleerd op adres &H380 dan wordt dit :

SUB Uit (b%)

OUT &H380, b% : OUT &H382, 0 : A$="***" : OUT &H382, 1

WHILE INP(&H381) AND 128 : WEND

END SUB

Een subprogramma dat zelf het adres van een logos-midi-interface op een willekeurige machine opzoekt en uittest, staat ter beschikking op de harddisk in de klas, zoals overigens ook alle nog aktuele procedures en programmas (en de laatste versies daarvan) waarvan sprake doorheen deze kursus.


2. Universeel subprogramma voor het weergeven van P(i,j) arrays via midi

Door het hoofdprogramma over te dragen variabelen :

P(I,J) het partituur-array zelf

EINDE de grootste waarde van I ( dit is ook te vinden als EINDE=UBOUND(P%,1)

CODE:

 

SUB MPLAY

DEFLNG K,T

CLS

DO :K$=INKEY$ : LOOP UNTIL K$="" : ' maak de keyboard buffer leeg...

LOCATE 15, 10 : PRINT " Want to hear this piece ? (Y/N) "; : K$ = INPUT$(1)

PRINT K$

K$= UCASE$(K$)

IF K$="Y" THEN

CLS

LOCATE 16, 10 : PRINT " Tempo ( M.M.-getal) ?:"; INPUT MM

TMP# = 60 / (MM*8) :' dit klopt alleen wanneer de teleenheid in het array de tweeendertigste noot is !

FOR I= 0 TO EINDE

T0#= TIMER ' kijk op de klok

FOR J = 1 TO 8 STEP 2

IF P(I, J) > 0 THEN ' als er een midi-code staat in het array ...

Uit (144 + (( J - 1) \ 2))

Uit (P(I, J))

Uit (P(I, J + 1)) ' hier wordt 3x de midi-uit procedure gebruikt

ENDIF

NEXT J

DO UNTIL TIMER - T0# >= TMP# : LOOP ' kijk op de klok tot de gevraagde duur verstreken is...

NEXT I

ENDIF

ENDSUB

Hoewel ik een programma schreef en ter beschikking stel dat arrays en files op een meer gesofistikeerde wijze kan weergeven en in realtime beinvloeden, raad ik de studenten toch aan deze of een soortgelijke beperkte procedure in elk komponeerprogramma op te nemen, omdat dit het debuggen erg kan versnellen en omdat de opname van het gehele 'Player' programma teveel geheugenruimte zou innemen.

De aanpassing van de hierboven gegeven kode voor het extended-P formaat zal de studenten allicht geen grote moeite kosten. Doe het eens, als oefening.


3. Procedure voor de initialisatie in UART-modus van MPU401 en MusicQuest interfaces

' ****************

' * MPUUART.BAS

' ****************

' Deze programmamodule verzorgt de noodzakelijk instelling van het MPU401,of MusicQuest interface in 'domme' UART-modus. Het moet gerund worden na eventueel gebruik van MBATCH, Windows of CakePro...

DEFINT A-Z

CONST DP = &H330

CONST CP = &H331: ' bit 7= byte to be read - negative logic

' bit 6= mcc ready - negative logic

' bits 5-0= not used (always 1!)

INITUART:

' als bit 7 1 is, is er geen te lezen byte:

IF INP(CP) AND 128 THEN

WAIT &H331, 64, 64 'check bit 6 - must be 0

OUT CP, &H3F: ' = 0011 1111 (63) =UART-command

ELSE

' als bit 7 0 is moet er een byte ingelezen worden:

WHILE INP(CP) < 128

dummy = INP(DP)

WEND

GOTO INITUART

END IF

END


4. Universele Midi-out routines in Basic voor MPU401 e.d.

COMMON SHARED Dp%

DECLARE SUB Uit (byte%)

 

SUB Uit (byte%)

' routine te gebruiken wanneer er ook MIDI-in verwacht kan worden:

IF Dp% = &H330 THEN

IF INP(&H331) AND 128 THEN

WAIT &H331, 64, 64: OUT &H330, byte%: EXIT SUB

ELSE

WHILE INP(&H331) < 128: dummy%= INP(&H330): WEND :' het midi-in byte gooien we weg...

WAIT &H331, 64, 64: OUT &H330, byte%: EXIT SUB

END IF

END IF

' Logotronics interfaces:

IF Dp% = &H2FA THEN

OUT &H2FA, byte%: DO: LOOP UNTIL INP(&H2FB) AND 64: EXIT SUB

WHILE INP(&H2FB) < 64: WEND: ' werkt ook zonder op de T1000 of andere trage XT komputers!

END IF

IF Dp% = &H378 THEN

' this is for 80386SX + 80486DX

OUT &H378, byte%: OUT &H37A, 0: ZZ# = ZZ# ^ ZZ#: OUT &H37A, 1

WHILE INP(&H379) AND 128: WEND

EXIT SUB

END IF

IF Dp% = &H320 THEN

' this is for Abulafia (80386DX -33MHz) and faster machines

OUT &H320, byte%: OUT &H322, 0: ZZ# = TIMER

DO: LOOP UNTIL TIMER - ZZ# > .00001

OUT &H322, 1

WHILE INP(&H321) AND 128: WEND

EXIT SUB

ELSE

OUT Dp%, byte%: OUT Dp% + 2, 0: t$ = "_": OUT Dp% + 2, 1

WHILE INP(Dp% + 1) AND 128: WEND

END IF

END SUB

Merk op dat deze procedure in een programma net zo eenvoudig te gebruiken is als de midi-sturing op een oude Atari ST. We kunnen immers -zonder gebruik te maken van CALL- rechtstreeks schrijven:

Uit 144 : Uit 60 : Uit 127

en onze centrale do klinkt in ff. We brengen haar tot stilte via:

Uit 144 : Uit 60 : Uit 0

of, wat op hetzelfde neerkomt:

Uit 128 : Uit 60 : Uit 0

  • (Opmerking: in BC7 evenals in Power Basic mogen de haakjes rond de uit te sturen waarden ook weggelaten worden zoals hier getoond. In QB4.0 waren ze nog van belang. Wanneer de waarden tussen haakjes worden geplaatst dan wordt een kopie ervan doorgegeven naar de procedure, wat uiteraard alleen betekenis heeft wanneer het om een variabele gaat. Zonder haakjes, en wanneer het een variabele betreft, wordt eigenlijk het adres in het geheugen van de betreffende variabele overgedragen. Het gevolg hiervan is, dat de procedure de waarde van die variabele kan wijzigen. Met variabelen tussen haakjes is dit niet mogelijk.).

  • 6. Procedures voor Soundblaster midi interfaces en assembler procedures in Power Basic

    De hier gegeven procedures zijn geschreven voor opname in Power Basic programma's. In deze Basic versie is het mogelijk assembler taal rechtstreeks in de bronkode op te nemen. Dit laat de hoogst haalbare kommunikatiesnelheden toe. We hebben deze kodevoorbeelden uitvoerig getest op soundblasterkaarten voorzien van midi-interfaces. Vanaf de soundblaster kaart 'SB16' kan het midi interface gebruikt en geadresseerd worden zoals hiervoor getoond , en op dezelfde adressen als gebruikelijk voor MPU-kompatibele kaarten. Oudere versies moeten gebruik maken van de SBuit procedure die we verder eveneens laten volgen. Deze SBuit procedure werkt ook op de nieuwste Soundblasterkaarten. Wanneer je dus schrijft voor machines waarin je een soundblasterkaart als aanwezig veronderstelt, dan kan je volstaan met uitsluitend deze procedures en vervalt bovendien de noodzaak om het interface eerst in een MPU401 kompatibele UART modus om te schakelen. Bovendien zijn er nog enkele extra voordelen verbonden aan het gebruik van het soundblaster midi-interface in 'native' mode: het laat 'time-stamping' toe en het is sneller dan de MPU-mode. Voorbeelden van het gebruik met time-stamping zullen binnenkort aan deze paragrafen worden toegevoegd. Ze zijn trouwens sedert 1998 standaard opgenomen in de nieuwste versies van <GMT>. (Godfried's multitasker).

    6.1: Een in assembler gekodeerde Uit procedure voor midi , in MPU-UART mode:

    Deze procedure werkt alleen in UART-mode! (zie verder voor een procedure waarmee UART mode wordt ingesteld). Ze is minstens 20 maal sneller dan eerder gegeven procedures in Basic. Wees echter uiterst aandachtig bij het overtypen van deze kode. De uitroeptekens gaan telkens de assemblerinstrukties vooraf. Kommentaren toegevoegd aan regels in assembler moeten beginnen met ; (punt-komma) en niet met ' of :' zoals in Basic. Er is geen syntax-help beschikbaar voor inline assembler. Een foutje leidt dan ook bijna steeds tot een gecrashte komputer... Wie graag de mogelijkheden van assembler en/of de how and why's van deze procedures wil onderzoeken, verwijzen we naar de Professional developper kit for programmers voor de Soundblaster kaarten evenals naar -uiteraard- een goed boek over 80xx86 processoren en hun instruktieset. Deze materie valt echter beslist buiten het bestek van deze kursus.

    %Madr = &H330 :' konstante voor het I/O adres van het midi interface

    SUB Uit (BYVAL b%) PUBLIC
    ' code for MPU in UART mode - tested. 1998 o.k.for PB3.5
    ! push ax
    ! push dx
    ! mov dx, %Madr
    ; numbers follow basic conventions
    ! inc dx
    ; Madr + 1 = status port
    UitBusy:
    ' we set labels here as in Basic ...
    ! in al, dx
    ; read status port
    ! test al, &H40
    ! jnz UitBusy
    ; jump if not ready
    ! mov ax, b%
    ; we do: mov ax, b% and further use only lsb in al. This serves as a filter
    ! dec dx
    ; set back to base port adress
    ! out dx, al
    ; this outputs the byte
    ! pop dx
    ! pop ax
    END SUB

    6.2: Een in assembler gekodeerde Uit routine voor soundblaster kaarten (normal-mode, niet in UART /MPU mode)


    %SBadr = &H220 :' konstante voor het basis I/O adres van de soundblaster kaart

    SUB SBuit (BYVAL b%)
    ' code for soundblaster midi-out - this does not require entering UART mode prior to a first call.
    ! push ax
    ! push dx
    ! mov dx, %SBadr
    ! add dl, &H0C
    ; status port/ data port
    SbBusy0:
    ! in al, dx
    ! test al, &H80
    ! jnz SbBusy0
    ; dsp should be ready to accept a command. The highest bit should become zero

    ! mov al, &H38
    ; midi-out command
    ! out dx, al
    ; send midi-out command
    SbBusy1:
    ! in al, dx
    ! test al, &H80
    ! jnz SbBusy1
    ; again, check dsp busy...

    ! mov al, b%
    ! out dx, al
    ; output the byte
    ! pop dx
    ! pop ax
    END SUB

    6.3: Een procedure om een MPU-kompatibel midi interface in of uit UART-modus te schakelen:
    Deze procedure vraagt een parameter: 1 schakelt UART modus in, 0 schakelt dit weer uit.


    SUB MpuUart (BYVAL state%)
    ' state% sets ON (1) or OFF (0)
    ! push cx
    ; we only use cx here. ax, dx are used in MpuCommand
    ! cmp state%, 0
    ; compare parameter to 0 (OFF-state)
    ! je TurnOff
    ; if we have to leave UART, jump to the corresponding label in the procedure

    MpuCommand &H03F
    ' basic call to the assembler procedure (zie verder)

    ! sub cx, cx
    ; try max.65536 times...
    ! jmp short ExitSetUart
    ; jump to a nearby label
    TurnOff:
    MpuCommand &HFF
    UARTmodeOK:
    ! mov ax, 0
    ; return 0 in ax as a sign of success. However, we cannot retrieve this value on exit
    ExitSetUART:
    ! pop cx
    END SUB

    In deze procedure komt een call voor naar een algemenere procedure -volledig in assembler- waarmee kommando-bytes naar het MPU interface moeten worden gestuurd:

    SUB MpuCommand (BYVAL b%)
    ' this procedure sends a command byte to the MPU midi-port.
    ! push ax
    ! push dx
    ! mov dx, %Madr
    ! inc dx
    Busy1:
    ! in al, dx
    ! test al, &H40
    ! jnz Busy1
    ! mov al, b%
    ! out dx, al
    ! pop dx
    ! pop ax
    END SUB


    7. Procedures voor midi-input

    De hier gegeven procedure kodeert een funktie die een via de midi-ingang aangeboden midi-byte retourneert. Ze kan -eens opgenomen in je eigen programma- gebruikt worden zoals elke andere funktie eigen aan Basic zelf en volgt ook geheel eenzelfde syntax. De funktie hier gegeven werkt alleen voor MPU401 en daarmee kompatibele interfaces (MusicQuest en latere Soundblasterkaarten) die eerst in UART modus werden geschakeld met de hiervoor gegeven programmaprocedure.

    COMMON SHARED DP AS INTEGER

    DECLARE FUNCTION Min% (b%)

    DP = &H330

    Voorbeeld van kode zoals die in een hoofdprogrammamodule zou kunnen voorkomen, en waarmee de funktie kan worden getest:

    DO

    byte = Min%

    IF byte >= 0 AND byte < &HFF THEN PRINT byte; " ";

    LOOP

    'en hier volgt de kode van de funktie zelf:

    FUNCTION Min%

    IF DP = &H330 THEN

    IF INP(&H331) < 128 THEN Min% = INP(&H330) ELSE Min% = -1

    EXIT FUNCTION

    END IF

    END FUNCTION

    Met deze funktie zijn geen hoge doorvoersnelheden te halen. Wanneer dat nodig is zullen we een buffer in software moeten implementeren.Dit wordt uitvoerig behandeld wanneer we aan het ontwerpen van echte multitaskers toe zullen zijn. Daarbij zal overigens het gebruiken van assembler procedures erg nuttig blijken.

     

    Kode voor gebruik in kombinatie met Power Basic en in-line assembler geven we alvast in twee varianten:

    1.- een versie voor gebruik van een interface in MPU-kompatibele UART modus (dit kan dus ook een soundblaster inferface zijn, geinitialiseerd in MPU-UART modus zoals hoger gezien).


    FUNCTION MiIn%() PUBLIC
    ' Read a byte from the midi port
    ! push ax
    ; we need ax to receive the return value
    ! push dx
    ! mov dx, %Madr
    ! inc dx
    ; set port to &H331
    BusyMiIn1:
    ! in al, dx
    ! test al, &H080
    ; o.k. to read? = IF al AND &H80 THEN
    ! jnz NoMiIn
    ; jump if not zero- nothing to read

    ! dec dx
    ; set back to data port
    ! in al, dx
    ; read data byte, clear midi interrupt
    ! jmp short EindeMiIn
    NoMiIn:
    ! mov ax, -1
    EindeMiIn:
    ! mov FUNCTION, ax
    ! pop dx
    ! pop ax
    END FUNCTION

    2.- een versie voor gebruik met een Soundblaster kaart in de voor die kaart gewone midi-modus. (geen UART, en bereikbaar via het basis I/O adres van de kaart, bijna steeds &H220)


    FUNCTION SBmidiRead%() PUBLIC
    ' reads a byte from a soundblaster
    ' midi input port, in polling mode.

    ! push ax
    ! push dx
    ! mov dx, %SBadr
    ; %SBadr is a declared constant. &H220
    ! add dl, &H0C
    ; status port/ data port
    SbBusy00:
    ! in al, dx
    ; read status port
    ! test al, &H80
    ; ready for output?
    ! jnz SbBusy00
    ; dsp should be ready to accept a command

    ! mov al, &H30
    ; midi-in command: read command for polling mode
    ! out dx, al
    ; send midi-in command

    ! mov dx, %SBadr
    ; reset to base adress
    ! add dl, &H0E
    ; status port
    SbBusy01:
    ! in al, dx
    ; read status port
    ! test al, &H080
    ; midi data available?
    ! jz SbBusy01
    ; if not, ...

    ! mov dx, %SBadr
    ; reset to base adress
    ! add dl, &H0A
    ; set to data port
    ! in al, dx
    ; read data byte
    ! mov ah, al
    ; save it...

    ! mov dx, %SBadr
    ; reset to base adress
    ! add dl, &H0C
    ; set to status / data port

    sbBusy02:
    ! in al, dx
    ; read status port
    ! test al, &H080
    ; ready for output ?
    ! jnz sbBusy02

    ! mov al, &H030
    ; this is the midi-in command
    ! out dx, al
    ; turn off the midi-in command

    ! mov al, ah
    ; retrieve data byte read
    ! mov ah, 0
    ; mask msb

    ! mov FUNCTION, ax
    ; this is the method to return a register value in the function
    ! pop dx
    ! pop ax
    END FUNCTION


    8.Gekombineerde Midi-IN en Midi-Out procedure:

    ' MAIN module:

    COMMON SHARED i%

    COMMON SHARED Fifo%()

    DECLARE SUB IO (b%)

    DIM SHARED Fifo%(0 TO &H7FF)

    CONST dummybyte = &H100

     

    ' PROCEDURE:

    SUB IO (b%)

    IF (NOT (INP(&H331) OR 127) AND 128) THEN

    DO

    Fifo%(i%) = INP(&H330):

    i% = ABS(NOT i%) AND &H7FF

    LOOP UNTIL INP(&H331) AND 128

    END IF

    IF NOT b% AND dummybyte THEN WAIT &H331, 64, 64: OUT &H330, b%

    END SUB


    9. Midi- I/O procedure met dubbele fifo-buffers:

    ' Dit is Microsoft Basic PDS kode.

    ' in de MAIN-module:

    REM $DYNAMIC

    CONST DP= &H330

    CONST SP=&H331

    CONST MidiBuffer = &H3FF

    COMMON SHARED FifoIn%()

    COMMON SHARED FifoUit%()

    COMMON SHARED i%,j%,ii%,jj%

    DECLARE SUB Midi ()

    DIM SHARED FifoIn%(0 TO MidiBuffer)

    DIM SHARED FifoUit%(0 TO MidiBuffer)

    'Procedure:

    SUB Midi

    DO

    WHILE INP(SP) < 128

    FifoIn%(ii%)= INP(DP) :

    ii%= (ii%+1) AND MidiBuffer

    WEND

    IF jj% <> j% THEN

    WAIT SP,64,64

    OUT DP, FifoUit%(jj%): jj%= (jj%+1) AND MidiBuffer

    LOOP UNTIL jj%=j%

    END SUB

     


    10. Midi- I/O procedures met dubbele fifo-buffers voor PBcc:

    Kodevoorbeelden geschreven en getest in Power Basic Console Compiler versie 1.0 (07/1998).

    Sedert 1998 moeten we de studenten eigenlijk afraden nog langer naar kompatibiliteit met het hoogbejaarde Roland MPU401 midi interface te zoeken. Wanneer gelijktijdig midi-input en output nodig is in een programma -en dit is nu eenmaal zo goed als steeds het geval in programma die we schrijven voor interaktieve kompositie en uitvoeringspraktijk- , dan laat het MPU interface -dat intern gebruik maakt van een voor hedendaagse normen erg trage en primitieve 8-bit microprocessor, verstek gaan.

    De moderne Soundblaster-kaarten hebben een DSP processor aan boord evenals een heleboel geheugen, waarmee in hardware FIFO's werden voorzien voor midi I/O, maar ook -uiteraard- voor digitale audio I/O.

    De eerste taak die we krijgen bij het opzetten van een eigen programma, bestaat er veelal in de DSP op de soundcard te resetten. We gaan er immers van uit dat onder Windows95, 98 of NT wordt gewerkt vandaag en dat dus de mogelijkheid bestaat dat het interface zich in een willekeurige toestand bevindt op het moment dat we er in ons eigen programma gebruik van willen maken.

    De nieuwste 32-bit compiler van Power Basic beschikt echter niet over enkele traditionele Basic kommandos waarmee we rechtstreeks naar de registers van de DSP chip kunnen schrijven. Daarom hebben we onderstaande kode welhaast volledig geschreven in inline assembler.

    10.1.: Resetting the SoundBlaster DSP chip:

    ' Dr.Godfried-Willem RAES: SoundBlaster RESET procedure written in assembler...
    ' This can be compiled under PowerBasic Console Compiler V1.0
    $DIM ALL
    %SBadr = &H220  :' make sure this corresponds to your hardware settings...
    DECLARE FUNCTION DSPreset? () AS BYTE
    
    FUNCTION PBMAIN () AS LONG
       LOCAL a?
        DO
           a? = DSPreset?
           PRINT a?
           IF INKEY$ <> "" THEN EXIT DO
        LOOP UNTIL a? = 170
    END FUNCTION
    
    FUNCTION DSPreset? ()
       LOCAL t!
                        ' sends a RESET to the SoundBlaster DSP chip
            ! push ax
            ! push cx
            ! push dx
                        ' first we check the DSP for a ready to write...
            ! mov dx, %SBadr
            ! add dl, &H0C
    rstlabel1:
            ! in al, dx
            ! test al, &H80
            ! jnz rstlabel1
    
            ! mov dx, %SBadr
            ! add dl, 6                ; set to DSP reset register
            ! mov al, 1                ; prepare to write a 1 to the reset port
            ! out dx, al               ; output it
            ! sub al, al               ; delay loop
    SbDSPdelay:
            ! dec al
            ! jnz SbDSPdelay           ; should be 3 microseconds...
            ! mov ax, 0
            ! out dx, al               ; output a zero now
            ! sub cx,cx                ; max. 65536 tries
    SbDSPempt:
            ! mov dx, %SBadr           ; set I/O adres back
            ! add dl, &H0E             ; set to read-buffer status port
            ! in al, dx                ; read it
            ! or al, al                ; data available?
            ! jns SbDSPnxt             ; bit 7 clear, try again
            ! sub dl,4                 ; set to data-port
            ! in al,dx                 ; read data returned
            ! cmp al,&HAA              ; receive code for successfull reset ? (=&HAA)
            ! je SbDSPresetOK          ; if al = &HAA then goto SbDSPresetOK
    SbDSPnxt:
            ! loop SbDSPempt           ; if no success, try again...
    
    SbDSPresetOK:
            ! mov FUNCTION, al          ; return the value for success in the basic function
            ! pop dx
            ! pop cx
            ! pop ax
    END FUNCTION
    10.2.: Midi I/O using  the SoundBlaster DSP chip:
    In de hieronderstaande kode, ontleend aan de PBcc versie van <GMT> maken we gebruik van de intrinsieke DSP UART funktie van de Soundblaster kaart. Dit mag niet worden verward met de MPU-UART modus, die door deze kaarten nog steeds wordt ondersteund.
    ' first we declare all constants: 
    %Madr = &H330 :' base I/O adres for mpu interfaces 
    %SBadr = &H220 :' base adress for soundblaster cards 
    %Mirq = &H5 :' Midi hardware interrupt number 
    %MidiBuffer = &H1FFF :' size for midi fifo-buffers
    GLOBAL MiBuf AS STRING * %MidiBuffer   :' buffer for raw incoming midi data
    GLOBAL MuBuf AS STRING * %MidiBuffer   :' buffer for midi output data
    GLOBAL MidiMode AS BYTE            :' 0 = MPU401 mode
                                       :' 1 = SB-Mode UART
                                       :' 2 = SB-standard mode
                                       :' 3 = Use RS232-port               
    SUB InitMidi
       LOCAL k$,  retval?
            MuBuf = STRING$(255, %MidiBuffer)
            MiBuf = STRING$(255, %MidiBuffer)
    
        'MidiMode =                   :' sets UART mode  - global variable
                                      :' 2 = soundblaster normal mode
                                      :' 1 = soundblaster midi UART mode
                                      :' 0 = MPU-UART mode
        PRINT "MidiMode? (Choose 1 with SB-cards)";
        DO: k$=INKEY$: LOOP UNTIL k$>="0" AND k$<="2"
        MidiMode = VAL(k$)
        SELECT CASE MidiMode
               CASE 0
                    ' MPU-UART mode  - midi-input is shaky in this mode...
                    MidiTimeStamp = 0
                    SHELL "mpuuart.exe", 1
                                       ' uses shell command to external programm
                    CLS
               CASE 1
                    ' Soundblaster DSP polling UART-mode
                    ' THIS IS THE ONLY MODE WHERIN WE DO NOT MISS BYTES...
                    retval? = DSPreset?
                    SELECT CASE retval?
                           CASE 170
                                ' 170 = &HAA = &B10101010
                                $DEBUG PRINT "Soundblaster DSP reset O.K."
                           CASE ELSE
                                PRINT "SB reset failed... "; retval?
                    END SELECT
             
                            ' SB UART polling mode midi I/O
                            ' DSPcommand &H34    
                            ' sends a command to the SoundBlaster DSP chip
                                ! push ax
                                ! push dx
                                ! mov dx, %SBadr
                                ! add dl, &H0C              ; set to DSP write command/data adres
    SbDSPstIm10:
                                ! in al, dx
                                ! test al, &H80
                                ! jnz SbDSPstIm10
                                ! mov al, &H34              ; send the DSP-command
                                ! out dx, al
                                ! pop dx
                                ! pop ax
                            $DEBUG PRINT "Soundblaster DSP midi I/O in use  - mode &H34"
    
                CASE 2
                    ' Soundblaster normal mode : requires In/Out switching
                        '     DSPcommand &H30  :' standard SB polling mode midi IN
                        '     DSPcommand &H38  :' standard midi output mode
                    retval? = DSPreset?
                    SELECT CASE retval?
                           CASE &HAA
                                $DEBUG PRINT "Soundblaster DSP reset O.K."
                           CASE ELSE
                                PRINT "SB reset failed... "; retval?
                    END SELECT
                    MidiTimeStamp = 0
                CASE ELSE
                    ' other modes to be implemented later...
        END SELECT
    
    END SUB                               
    ' ----------------------------------------------------------------------
    SUB Uit(b%)
           ' writes midioutput to the output buffer in string-format:
            IF b% > 254 THEN EXIT SUB
            MuBuf = CHR$(b%) + MuBuf
    END SUB
    
    ' ----------------------------------------------------------------------
    SUB MidiUit
       LOCAL b?, wpp%
                                    ' this is a Task in a multitasker. It should be called periodically.
                                    ' writes the midi output buffer MuBuf to the midi-port.
                                    ' Search the first occurence of the ASC(255) character.
                                    ' This is one byte beyond the last byte to send out.
                                    ' If the buffer is empty wpp% would be 1
        wpp% = INSTR (1,MuBuf,CHR$(255))
                                    ' if wpp%=0 than we have buffer-overflow (ni ASC(255) character
                                    ' found in the entire buffer!
        ' we now have the pointer to the first byte to send out through midi...
        wpp% = wpp% - 1
        'IF wpp% < 0 THEN
        '   LOCATE 18,1: PRINT "Buffer overflow"
        '   EXIT SUB  :' buffer overflow error
        'END IF
        
    DO
           IF wpp% THEN
             b? = ASC(MID$(MuBuf, wpp%,1))
             MID$(MuBuf, wpp%, 1) = CHR$(255)
             wpp% = wpp% - 1
           ELSE
              EXIT SUB
           END IF
    
           SELECT CASE MidiMode
               CASE 0
                           ! push ax
                           ! push dx
                           ! mov dx, %Madr           ; numbers follow basic conventions
                           ! inc dx                  ; Madr + 1 = status port
    UitBusy:
                           ! in al, dx               ; read status port
                           ! test al, &H40
                           ! jnz UitBusy             ; jump if not ready
                           ! mov al, b?              ; and further use only lsb in al
                           ! dec dx                  ; set back to base port adress
                           ! out dx, al              ; this outputs the byte
                           ! pop dx
                           ! pop ax
               CASE 1
    
                           ! push ax
                           ! push dx
                           ! mov dx, %SBadr
                           ! add dl, &H0C           ; set to dsp read/write port
    Sbrdy:
                           ! in al, dx              ; check bit 7 - writebuffer status
                           ! test al, &H80
                           ! jnz Sbrdy              ; jump if not 0
    
                           ! mov al, b?             ; read the data into ax
                           ! out dx, al             ; output the byte to the dsp port
                           ! pop dx
                           ! pop ax
               CASE 2
                    ' prepare DSP for output - we send DSPcommand &H38
                    ' DSPcommand &H38
                           ! push ax
                           ! push dx
                           ! mov dx, %SBadr
                           ! add dl, &H0C           ; status port/ data port
    SbBusy20:
                           ! in al, dx
                           ! test al, &H80
                           ! jnz SbBusy20            ; dsp should be ready to accept a command
    
                           ! mov al, &H38           ; midi-out command
                           ! out dx, al             ; send midi-out command
    SbBusy21:
                           ! in al, dx
                           ! test al, &H80
                           ! jnz SbBusy21
                           ! mov al, b?              ; now we send the midi byte
                           ! out dx, al
                           ! pop dx
                           ! pop ax
    
               CASE ELSE
                    ' not implemented yet
        END SELECT
     LOOP UNTIL wpp% = %False
    END SUB
    
    '-----------------------------------------------------------------------
    SUB MidiIn
                    ' this task polls the midi port and writes incoming data to Mibuf.
       LOCAL Inbyte?
       Inbyte? = 255
       SELECT CASE MidiMode
              CASE 0
    
                    ' The polling frequency should correspond to the midi Baudrate
                            ' If we would write the adress of this procedure to the
                            ' interrupt vector table, we would have a real interrupt handler...
                            ' The poke- adress for this interrupt vektor can be calculated
                            ' as : MidiIrqPointer% = %IrqNr * 4
                            ' The adres of PolMidiIn is :....
                            ' So we can do: Poke MidiIrqPointer, PolMidiIn_adrespointer
                    ! push ax
                    ! push dx
                    ! mov dx, %Madr         ; set to MPU adres
                    ! inc dx                ; set to status port adres
                    ! in al,dx              ; read status
                    ! test al, &H80         ; if bit 7 is 0, we have data to read, if 1 there is no data available
                    ! jnz polmidiend        ; jump if not zero: jump out if test returns 1, or no-data to read
                    ! dec dx                ; set to data-port again
                    ! in al,dx              ; read input byte, clear interrupt
                    ! mov Inbyte?, al       ; put the byte into the Basic variable
    polmidiend:
                    ! pop dx
                    ! pop ax
              CASE 1
                     ' soundblaster mode - this works fine
                    ! push ax
                    ! push dx
                    ! mov dx, %SBadr
                    ! add dl, &H0E          ; set to read-buffer status port
                    ! in al, dx             ; read the readbuffer status port
                    ! test al, &H80         ; als bit 7 = 1 = data, 0= geen data
                    ! jz polSBmidiend       ; indien 0, verlaat procedure
                    ! sub dl, 4             ; set to read data port &HE-4=&HA
                    ! in al, dx             ; read inbound DSP data
                    ! mov Inbyte?, al       ; move data into Basic variable
    polSBmidiend:
                    ! pop dx
                    ! pop ax
    
            CASE 2
                    ' first we send DSPCommand &H30
                    ' This works, but we are missing bytes...
                    ! push ax
                    ! push dx
                    ! mov dx, %SBadr
                    ! add dl, &H0C              ; set to DSP write command/data adres
    SbDSP2Min:
                    ! in al, dx
                    ! test al, &H80
                    ! jnz SbDSP2Min             ; wait until ready
                    ! mov al, &H30              ; send the DSP-command
                    ! out dx, al
                       ' now we can wait until the DSP is ready again...
                    ! mov dx, %SBadr
                    ! add dl, &H0E          ; set to read-buffer status port
                    ! in al, dx             ; read the readbuffer status port
                    ! test al, &H80
                    ! jz polSB2mnd
                    ! sub dl, 4             ; set to read data port &HE-4=&HA
                    ! in al, dx             ; read inbound DSP data
                    ! mov Inbyte?, al
    polSB2mnd:
                    ! pop ax
                    ! pop dx
            CASE ELSE
    END SELECT
                     ' now, if a byte was received, we can write in to MiBuf
        IF Inbyte? < 248 THEN
           Mibuf = CHR$(Inbyte?) + Mibuf
        END IF
        IF MidiTimeStamp AND 1 THEN
           ' in MPU mode we could write the midi-timing bytes here...
           TiBuf = RIGHT$("000000"+ HEX$(msbTime?)+ HEX$(midTime?) + HEX$(lsbTime?),6)+ TiBuf
        END IF
    END SUB
    '---------------------------------------------------------------------------
                                 

    Algemene opmerking:

    Deze midifunkties, waarbij de hardware rechtstreeks aangesproken wordt zijn zelfs in gekompileerd Basic uiterst snel. Het programmeren in andere hoge talen zoals C levert geen substantiele tijdwinst op. Alleen assembler kan hier winst opleveren. Onder Windows in protected mode en onder OS2 leveren zij echter grote problemen op, omdat daar elk rechtstreeks aanspreken van de hardware zowat taboe is. Bovendien, zelfs al zou het toch kunnen, dan nog zijn deze multitasking operating systems nog steeds aanzienlijk trager dan het rechtstreeks werken onder DOS.

    Wie werkt onder QBX V7.1 kan weliswaar zijn programmas kompileren voor gebruik op OS2 machines, maar moet dan wel instrukties zoals WAIT, INP, OUT, PEEK, POKE, VARPTR, BLOAD, BSAVE grotendeels missen! Het zelf ontwerpen van midi-software is dan niet meer eenvoudig doenbaar. Wanneer de hardware leverancier van de midi-kaart een bruikbare library ter beschikking stelt, is het nog enigszins doenbaar. Zonder zoiets vergt een en ander het doorworstelen van zo'n 20000 paginas technische dokumentatie rond Windows DLL's en OS2-references.

    Sedert 1997 raden we studenten dan ook aan over te schakelen op het erg degelijke Power Basic, dat hardware access ook in protected mode en onder Windows ten volle mogelijk maakt. De diefstal die Bill Gates met zijn Windows en Visual Basic pleegde door zich het unieke gebruiksrecht van al onze PC hardware toe te eigenen, kan op deze wijze wat worden gekompenseerd.

    In 1998 kwam van Power Basic een nieuwe 32-bit compiler beschikbaar, die in ruime mate de snelheid van C++ overtreft. De hiermee gekompileerde software draait uitsluitend onder 32-bit OS's zoals Win95, Win98 en WinNT. De resultaten doen de hiervoor gemaakte opmerkingen wel wat vervagen.

    In de nieuwste versies van ons <GMT> programmeertaal zijn zowat alle denkbare procedures en funkties in verband met de aansturing van midi hardware opgenomen. Voor up to date informatie verwijzen we dan ook naar deze software en de dokumentatie erbij.


    Filedate: [950501] /2005-10-17

    Terug naar inhoudstafel kursus: <Index Kursus> Naar homepage dr.Godfried-Willem RAES Naar midi-standaard beschrijving