2000 ;============================================================================ ; C MIDI ROUTINES FOR MPU-401 (DUMB UART MODE) and SB MIDI ;============================================================================ ; ; int do_midi_init(int iface_type, int portadr, int irq); ; iface_type: 0 = MPU, 1 = SB MIDI ; returns 1 on success, else 0 ; ; void midi_exit(void); ; ; int midi_put(unsigned char midibyte); ; returns 1 on success; ; ; void midi_putl(char *msgbuf, int len, int tickdelay); ; ; unsigned long midi_get(void); ; returns timestamp in upper three bytes, data in lowest byte ; ; int midi_set_tempo(int bpm, int ppqn); ; returns resulting tick frequency in Hz ; ;=========================================================================== OPTION PROC:PRIVATE DOSSEG .MODEL SMALL .386 MPU401 EQU 0 SBMIDI EQU 1 MPU_RESET EQU 0FFh MPU_UART EQU 03Fh MPU_DRR EQU 01000000b ;MPU Data Read Ready, active low. MPU_DSR EQU 10000000b ;MPU Data Set Ready, active low. loop_delay EQU 0FFFFh ;Delay for timeouts. .DATA PUBLIC _user_midiroute, _miditicks _user_midiroute dword 0 _miditicks dword 0 bufhead word 0 buftail word 0 hz18 word 18 orig_cntr word 0 tmr_count word 0ffffh in_isr word 0 _isrflag word 0 .DATA? iface_type word ? old_int8_vect dword ? outdelay word ? midibuf dword 1024 dup (?) isr_seg word ? isr_offs word ? irqnum byte ? ;Interrupt number irqvec byte ? ;Interrupt vector irqmask byte ? ;8259 interrupt mask mpu_data_port word ? ;MPU data port address. mpu_command_port LABEL WORD ;MPU command port address. mpu_status_port word ? ;MPU status port address. mpu_command byte ? sb_reset word ? ;Placeholders for SB port addresses sb_read_data word ? sb_write_data word ? sb_data_avail word ? ;=========================================================================== .CODE PUBLIC _do_midi_init PUBLIC _midi_exit PUBLIC _midi_put PUBLIC _midi_putl PUBLIC _midi_get PUBLIC _midi_set_tempo old_int_vect DWORD 0 ;Must be placed in code ;segment for proper access ;from ISR mpu_isr PROC FAR ASSUME CS:_TEXT, DS:@data sti push ax push dx push ds pushf mov ax, @data mov ds, ax mov dx, mpu_status_port ;Check if interrupt generated in al, dx ;by the MPU test al, MPU_DSR jnz jmp_to_old ;No, jump to original handler popf push di next: mov dx, mpu_data_port ;Read MPU data byte in al, dx call queue_mididata ;Store in buffer mov dx, mpu_status_port ;Check for more bytes waiting in al, dx ;in the MPU hardware buffer test al, MPU_DSR jz next ;Yes, read next byte cli mov al, 20h ; EOI cmp irqnum, 8 jb not_hi_mpuirq out 0a0h, al not_hi_mpuirq: out 020h, al sti pop di pop ds pop dx pop ax iret jmp_to_old: popf pop ds pop dx pop ax jmp old_int_vect ;jump to old handler mpu_isr ENDP sbmidi_isr PROC FAR ASSUME CS:_TEXT, DS:@data sti push ax push dx push di push ds mov ax, @data mov ds, ax mov dx, sb_read_data ;read MIDI byte in al, dx call queue_mididata ;Store in buffer mov dx, sb_data_avail ;Acknowledge DSP interrupt in al, dx ;by reading from status port cli mov al, 20h ;Signal End of Interrupt cmp irqnum, 8 jb not2 out 0a0h, al not2: out 20h, al sti pop ds pop di pop dx pop ax iret sbmidi_isr ENDP queue_mididata PROC mov di, bufhead mov edx, _miditicks ;store timestamp... shl edx, 8 mov dword ptr midibuf[di], edx mov byte ptr midibuf[di], al ;and MIDI byte add di, 4 cmp di, 4096 jb no_wrap xor di, di no_wrap: mov bufhead, di ret queue_mididata ENDP timer_isr PROC FAR ASSUME CS:_TEXT, DS:@data push ax push ds mov ax, @data mov ds, ax cmp in_isr, 1 jnz not_in_isr mov _isrflag, 1 not_in_isr: mov in_isr, 1 mov ax, tmr_count ;Time to call orig ISR? add orig_cntr, ax jnc no_orig ;No pushf call old_int8_vect ;Call original timer ISR jmp skip_EOI no_orig: mov al, 20h ;Make it possible for MPU out 20h, al ;interrupt to occur inside ISR skip_EOI: inc _miditicks ;Update tick value cmp _user_midiroute, 0 ;User routine installed? jz end_of_ISR pusha ;Yes, call it push es call _user_midiroute pop es popa end_of_ISR: mov in_isr, 0 pop ds pop ax iret timer_isr ENDP inst_timer_isr PROC mov ax, 3508h ;H„mta vektorn till INT 08h int 21h mov word ptr[old_int8_vect], bx ;Org_Rutinens OFFSET h„mtas mov word ptr[old_int8_vect + 2], es ;Segment sparas push ds mov ax, 2508h ;Install TIMER ISR push cs pop ds mov dx, offset timer_isr int 21h pop ds push es xor dx, dx ;Wait for system timer to tick mov es, dx ;for synchronization mov dx, word ptr es:[46Ch] wait_tick1: cmp word ptr es:[46Ch], dx jz short wait_tick1 mov al, 34h ;Mode 2 out 43h, al xor ax, ax jmp short $+2 out 40h, al jmp short $+2 out 40h, al pop es ret inst_timer_isr ENDP rest_timer_isr PROC push es xor dx, dx ;Wait for system timer to tick mov es, dx ;for synchronization mov dx, word ptr es:[46Ch] wait_tick2: cmp word ptr es:[46Ch], dx jz short wait_tick2 cli mov al, 36h ;Restore timer 0 out 43h, al xor ax, ax jmp short $+2 out 40h, al jmp short $+2 out 40h, al push ds ;Restore old INT 8 mov ax, 2508h mov dx, word ptr[old_int8_vect] mov ds, word ptr[old_int8_vect + 2] int 21h pop ds sti pop es ret rest_timer_isr ENDP inst_midi_isr PROC mov al, 08h mov cl, irqnum cmp cl, 8 ;Obtain IRQ vector and mask jb not_hi_irq2 add al, 068h ;High IRQ vectors start at sub cl, 8 ;70h... not_hi_irq2: add al, cl mov irqvec, al ;Store vector mov al, 1 ;Build IRQ mask... shl al, cl mov irqmask, al ;and store it mov ah, 035h mov al, irqvec ;IRQ level to get int 21h mov word ptr cs:[old_int_vect], bx ;Save old vector mov word ptr cs:[old_int_vect+2], es push ds ;Save DS mov ah, 025h mov al, irqvec mov dx, isr_offs ;ISR offset mov bx, isr_seg mov ds, bx ;ISR segment int 21h ;Set new vector pop ds ;Restore DS mov dx, 21h cmp irqnum, 8 jb not_hi_irq3 add dx, 80h not_hi_irq3: in al, dx ;Read 8259 mask jmp short $+2 ;Delay for fast processors mov ah, irqmask ;Get new mask not ah ;Invert for ANDing and al, ah ;Reset IRQ bit out dx, al ;Enable IRQ ret inst_midi_isr ENDP rest_midi_isr PROC cli mov dx, 21h cmp irqnum, 8 jb not_hi_irq4 add dx, 80h not_hi_irq4: in al, dx ;Read 8259 mask jmp short $+2 ;Delay for fast processors or al, irqmask ;Set IRQ mask bit out dx, al ;Mask IRQ off sti push ds mov ah, 25h mov al, irqvec mov dx, word ptr cs:[old_int_vect] mov ds, word ptr cs:[old_int_vect+2] int 21h ;Restore old vector pop ds ret rest_midi_isr ENDP mpu_send_command PROC mov cx, loop_delay mov dx, mpu_status_port sc_1: in al, dx test al, MPU_DRR jz sc_2 loop sc_1 xor ax, ax jmp short sc_6 sc_2: cli mov al, mpu_command ;Send command byte out dx, al sc_3: mov cx, loop_delay mov dx, mpu_status_port sc_4: in al, dx test al, MPU_DSR jz sc_5 loop sc_4 xor ax, ax ;Timeout jmp short sc_6 sc_5: mov dx, mpu_data_port in al, dx cmp al, 0feh ;Acknowledge byte? jnz sc_3 ;No, discard and try again mov ax, 1 ;Yes, return 1 for success sc_6: sti ;Restore interrupts ret mpu_send_command ENDP sb_reset_dsp PROC push si mov dx, sb_reset mov al, 1 out dx, al mov cx, 0ffh del: loop del mov al, 0 out dx, 1531 al mov dx, sb_data_avail poll: in al, dx ;Check for data available or al, al jns poll mov dx, sb_read_data in al, dx ;Check for ready byte cmp al, 0aah jnz poll pop si ret sb_reset_dsp ENDP ;============================================================================ ;============================================================================ ;============================================================================ _do_midi_init PROC push bp mov bp, sp push cx push si push di push es cli mov _user_midiroute, 0 ;Reset these for safety mov _miditicks, 0 mov bufhead, 0 mov buftail, 0 mov orig_cntr, 0 mov tmr_count, 0ffffh mov ax, [bp + 4] ;Interface type mov iface_type, ax mov ax, [bp + 8] ;IRQ number cmp ax, 2 jnz not_irq2 mov ax, 9 ;IRQ2 is really IRQ 9 not_irq2: mov irqnum, al mov ax, [bp + 6] ;Base address cmp iface_type, SBMIDI jz sbmidi_init mov mpu_data_port, ax ;Store MPU data and inc ax ;status port addresses mov mpu_status_port, ax mov mpu_command, MPU_RESET ;Reset MPU call mpu_send_command cmp ax, 0 jz err_exit ;Return 0 if timeout mov mpu_command, MPU_UART ;Enter MPU UART mode call mpu_send_command mov isr_seg, SEG mpu_isr mov isr_offs, OFFSET mpu_isr jmp inst_midi sbmidi_init: add ax, 6 ;Store SB port addresses mov sb_reset, ax add ax, 4 mov sb_read_data, ax add ax, 2 mov sb_write_data, ax add ax, 2 mov sb_data_avail, ax call sb_reset_dsp mov isr_seg, SEG sbmidi_isr mov isr_offs, OFFSET sbmidi_isr inst_midi: call inst_midi_isr sti call inst_timer_isr ;Install timer ISR push 192 ;Initial resolution 192 ppqn push 120 ;Initial tempo 120 bpm call _midi_set_tempo add sp, 4 mov edx, _miditicks ;Wait for timer to tick tsync: cmp edx, _miditicks ;once with the new tempo jz tsync cmp iface_type, SBMIDI jnz over_sbmidi_uart mov dx, sb_write_data wd1: in al, dx or al, al js wd1 mov al, 35h ;SBMIDI UART interrupt mode out dx, al over_sbmidi_uart: mov ax, 1 err_exit: pop es pop di pop si pop cx pop bp ret _do_midi_init ENDP _midi_exit PROC push si cmp iface_type, SBMIDI jz sbmidi_exit mov cx, loop_delay mov dx, mpu_status_port sc_7: in al, dx test al, MPU_DRR jz sc_8 loop sc_7 jmp short sc_9 sc_8: cli mov al, MPU_RESET ; Exit UART mode out dx, al sti sc_9: jmp over_sbmidi_exit sbmidi_exit: call sb_reset_dsp over_sbmidi_exit: call rest_midi_isr call rest_timer_isr pop si ret _midi_exit ENDP _midi_put PROC push bp mov bp, sp push cx cmp iface_type, SBMIDI jz sbmidi_put mov cx, loop_delay mov dx, mpu_status_port sd_1: in al, dx test al, MPU_DRR jz send_d loop sd_1 xor ax, ax jmp short sd_exit send_d: cli mov al, byte ptr[bp+4] ;Get data byte mov dx, mpu_data_port out dx, al ;Send it mov ax, 1 ;Set AX for success sd_exit: sti jmp over_sbmidi_put sbmidi_put: mov dx, sb_write_data wd3: in al, dx or al, al js wd3 mov al, byte ptr[bp+4] out dx, al mov ax, 1 over_sbmidi_put: pop cx pop bp ret _midi_put ENDP _midi_putl PROC push bp mov bp, sp push cx push di push es mov ax, @data mov es, ax mov di, [bp+4] mov cx, [bp+6] mov ax, [bp+8] mov outdelay, ax moutlp: mov al, byte ptr es:[di] push ax call _midi_put add sp, 2 mov eax, _miditicks dly: mov edx, _miditicks sub edx, eax cmp dx, outdelay jb dly inc di loop moutlp pop es pop di pop cx pop bp ret _midi_putl ENDP _midi_get PROC push di mov ax, 0 mov dx, 0 mov di, buftail ;Byte waiting? cmp di, bufhead jz notavail mov al, byte ptr midibuf[di] ;return MIDI byte in al inc di mov ah, byte ptr midibuf[di] ;return timestamp in dx and ah inc di mov dx, word ptr midibuf[di] add di, 2 cmp di, 4096 jb no_wrap2 xor di, di no_wrap2: mov buftail, di ;update buffer tail notavail: pop di ret _midi_get ENDP _midi_set_tempo PROC push bp mov bp, sp push bx push cx push es xor ebx, ebx mov bx, [bp+4] ;tempo xor edx, edx mov eax, 60000000 ;Divide 60000000 with bpm value div ebx ;to get usecs/quarter note xor edx, edx mov bx, [bp + 6] ;ppqn div ebx ;Divide usecs/quarter note with ppqn ;to get usecs/tick mov ebx, eax xor edx, edx mov eax, 1000000 ;Divide 1000000 with usecs/tick div ebx ;to get tick frequency in Hz mov bx, ax ;Tick frequency -> BX mov ax, 34DCh ;Divide 1193180 with tick freq to mov dx, 12h ;obtain new timer count div bx mov cx, ax ;Timer count -> CX xor dx, dx ;Wait for system timer to tick mov es, dx mov dx, word ptr es:[46Ch] wait_tick3: cmp word ptr es:[46Ch], dx jz short wait_tick3 cli ;Load new timer count mov al, cl out 40h, al mov al, ch jmp short $+2 out 40h, al mov tmr_count, cx ;Update tmr_count sti mov ax, bx ;Return tick frequency in AX pop es pop cx pop bx pop bp ret _midi_set_tempo ENDP END . 0