
;--- implements midiOutXXX()

        .386
if ?FLAT
        .MODEL FLAT, stdcall
else
        .MODEL SMALL, stdcall
endif
		option casemap:none
        option proc:private

        include winbase.inc
        include winuser.inc
        include mmsystem.inc
        include winmm.inc
        include macros.inc
        include sb16.inc

?MIDIACTIVE		equ 1
?MIDICHUNKSIZE	equ 64	;the SB can buffer up to 64 bytes for midi?
?DELAY			equ 20	;delay for long msgs in ms

?MPUDEVICE equ 0

;--- device 0 is SB MPU 401 (SB 16+ only)
;--- if ?SBMIDI is set then
;--- device 1 is SB Midi

MIDI_TYPE	equ "MIDI"

		.DATA

g_hmo		dd 0
g_hMOTimer  dd 0	;helper timer for midi long msg output
g_bInit		db 0	;bit 0: 1=MPU has been set to UART mode
					;bit 1: mm helper thread used
g_bCancel	db 0

			align 4

g_MidiProcs MIDIPROCS <>


FMO_UART  	equ 1
FMO_THREAD	equ 2

        .CODE

;--- ebx = MIDIOBJ
;--- if MOM_DONE: esi == MIDIHDR

_midiSendNotification proc dwFunc:dword, dwMsg:dword            
		.if ([ebx].MIDIOBJ.lpfnCallback)
			mov edx, [ebx].MIDIOBJ.dwFlags
			and edx, CALLBACK_TYPEMASK
			.if (dwFunc == MOM_DONE)
				mov ecx, esi
			.else
				xor ecx, ecx
			.endif
			.if (edx == CALLBACK_FUNCTION)
				invoke [ebx].MIDIOBJ.lpfnCallback, ebx, dwFunc, [ebx].MIDIOBJ.dwCallbackInstance, ecx, 0
			.elseif (edx == CALLBACK_WINDOW)
				invoke SendMessage, [ebx].MIDIOBJ.hwnd, dwMsg, ebx, ecx
			.elseif (edx == CALLBACK_THREAD)
				invoke PostThreadMessage, [ebx].MIDIOBJ.threadid, dwMsg, ebx, ecx
			.elseif (edx == CALLBACK_EVENT)
				invoke SetEvent, [ebx].MIDIOBJ.hEvent
			.endif
		.endif
		ret
		align 4
_midiSendNotification endp            

midiOutOpen proc public uses ebx phmo:ptr DWORD, uDeviceID:dword,
		dwCallback:dword, dwCallbackInstance:dword, dwFlags:dword

		@strace <"midiOutOpen(", phmo, ", ", uDeviceID, ", ", dwCallback, ", ", dwCallbackInstance, ", ", dwFlags, ") enter">
if ?MIDIACTIVE
		.if (g_hmo)
   	    	mov eax, MMSYSERR_ALLOCATED
       		jmp exit
        .endif
       	invoke SndInit
		@strace <"midiOutOpen: SndInit called">
        .if (!eax)
			@strace <"midiOutOpen: SndInit failed">
			mov eax, MIDIERR_NODEVICE
            jmp exit
        .endif
		invoke SndSetMidiDevice, uDeviceID, addr g_MidiProcs
		@strace <"midiOutOpen: SndSetMidiDevice called">
if ?SBMIDI
		.if (uDeviceID == 1)
        	jmp check_done
        .endif
endif
        invoke SndGetCaps
        .if (!edx)					;midi supported?
			@strace <"midiOutOpen: SndGetCaps failed">
			mov eax, MIDIERR_NODEVICE
            jmp exit
        .endif
        invoke SndGetMidiPort	;P3xx must be set in BLASTER variable
        .if (eax == -1)
			@strace <"midiOutOpen: SndGetMidiPort failed">
			mov eax, MIDIERR_NODEVICE
            jmp exit
        .endif
check_done:        
        invoke g_MidiProcs.pReset
        mov g_bInit, 0
        .if (!eax)
			@strace <"midiOutOpen: reset Midi failed">
			mov eax, MIDIERR_NODEVICE
            jmp exit
        .endif

       	invoke LocalAlloc, LMEM_FIXED or LMEM_ZEROINIT, sizeof MIDIOBJ
        .if (eax)
           	mov ebx, eax
            mov g_hmo, eax
            mov [ebx].MIDIOBJ.dwType, MIDI_TYPE
            mov ecx, uDeviceID 
            mov eax, dwCallback
            mov edx, dwCallbackInstance
            mov [ebx].MIDIOBJ.uDeviceId, ecx
            mov [ebx].MIDIOBJ.lpfnCallback, eax
            mov [ebx].MIDIOBJ.dwCallbackInstance, edx
            mov eax, dwFlags
            mov ecx, phmo
            mov [ebx].MIDIOBJ.dwFlags, eax
            mov [ecx], ebx
            invoke _midiSendNotification, MOM_OPEN, MM_MOM_OPEN
	        mov eax, MMSYSERR_NOERROR
        .else
           	mov eax, MMSYSERR_NOMEM
        .endif
else
		mov eax, MIDIERR_NODEVICE
endif
exit:        
		@strace <"midiOutOpen(", phmo, ", ", uDeviceID, ", ", dwCallback, ", ", dwCallbackInstance, ", ", dwFlags, ")=", eax, " [hmo=", ebx, "]">
		ret
        align 4
midiOutOpen endp

midiOutClose proc public uses ebx hmo:dword
		mov ebx, hmo
        .if (ebx && (ebx == g_hmo))
        	invoke midiOutReset, ebx
			.if (g_bInit & FMO_THREAD)
                and g_bInit, not FMO_THREAD
            	invoke StopMMThread
            .endif
            invoke _midiSendNotification, MOM_CLOSE, MM_MOM_CLOSE
			invoke LocalFree, ebx
            mov g_hmo, 0
	        mov eax, MMSYSERR_NOERROR
        .else
			mov eax, MMSYSERR_INVALHANDLE
        .endif
		@strace <"midiOutClose(", hmo, ")=", eax>
		ret
        align 4
midiOutClose endp

midiOutPrepareHeader proc public hmo:DWORD, lpMidiOutHdr:DWORD, cbMidiOutHdr:DWORD
		mov ecx, hmo
        .if (ecx && (ecx == g_hmo))
        	mov edx, lpMidiOutHdr
	  	    .if (!([edx].MIDIHDR.dwFlags & MHDR_PREPARED))
    	   		or  [edx].MIDIHDR.dwFlags, MHDR_PREPARED
	        .endif
	        mov eax, MMSYSERR_NOERROR
        .else
			mov eax, MMSYSERR_INVALHANDLE
        .endif
		@strace <"midiOutPrepareHeader(", hmo, ", ", lpMidiOutHdr, ", ", cbMidiOutHdr, ")=", eax>
		ret
        align 4
midiOutPrepareHeader endp

midiOutUnprepareHeader proc public hmo:DWORD, lpMidiOutHdr:DWORD, cbMidiOutHdr:DWORD
		mov ecx, hmo
        .if (ecx && (ecx == g_hmo))
        	mov edx, lpMidiOutHdr
            .if (!([edx].MIDIHDR.dwFlags & MHDR_INQUEUE))
		      	and [edx].MIDIHDR.dwFlags, not MHDR_PREPARED
		        mov eax, MMSYSERR_NOERROR                       	
            .else
				mov eax, MIDIERR_STILLPLAYING
            .endif
        .else
			mov eax, MMSYSERR_INVALHANDLE
        .endif
		@strace <"midiOutUnprepareHeader(", hmo, ", ", lpMidiOutHdr, ", ", cbMidiOutHdr, ")=", eax>
		ret
        align 4
midiOutUnprepareHeader endp

midiOutShortMsg proc public hmo:DWORD, dwMsg:dword
		mov ecx, hmo
		.if (ecx && (ecx == g_hmo))
			.if (!(g_bInit & FMO_UART))
				or g_bInit, FMO_UART
                mov al,3Fh
				invoke g_MidiProcs.pWriteCmd
			.endif
			mov eax, dwMsg
			invoke g_MidiProcs.pWriteShortMsg
			mov eax, MMSYSERR_NOERROR
		.else
			mov eax, MMSYSERR_INVALHANDLE
		.endif
		@strace <"midiOutShortMsg(", hmo, ", ", dwMsg, ")=", eax>
		ret
		align 4
midiOutShortMsg endp

;--- inp: ebx=MIDIOBJ
;--- ESI=MIDIHDR
;--- code is serialized by g_csMM

UnlinkMidiHdr proc

        mov ecx, [ebx].MIDIOBJ.pMidiHdr
        xor edx, edx
       	.while (ecx)
			.if (esi == ecx)
            	xor eax, eax
				xchg eax, [esi].MIDIHDR.lpNext
				.if (edx)
					mov [edx].MIDIHDR.lpNext, eax
				.else
					mov [ebx].MIDIOBJ.pMidiHdr, eax
				.endif
                and [esi].MIDIHDR.dwFlags, not MHDR_INQUEUE
				.break  
			.endif
			mov edx, ecx
			mov ecx, [ecx].MIDIHDR.lpNext
		.endw
        ret
        align 4

UnlinkMidiHdr endp

;--- this code is serialized by g_csMM

_midiDequeueHdr proc uses ebx esi

		mov ebx, g_hmo
        mov esi, [ebx].MIDIOBJ.pMidiHdr
		.while (esi)
			mov eax, [esi].MIDIHDR.reserved
            .break .if (eax != [esi].MIDIHDR.dwBufferLength)
            push [esi].MIDIHDR.lpNext
			invoke UnlinkMidiHdr
			or [esi].MIDIHDR.dwFlags, MHDR_DONE
            invoke _midiSendNotification, MOM_DONE, MM_MOM_DONE
            pop esi
        .endw
        ret
        align 4
_midiDequeueHdr endp

;--- this proc is called from inside the multimedia thread
;--- feed the MPU port from the MIDIHDR data
;--- the code runs in the MM critical section!

if ?USEMMTIMER
_midithreadproc proc public uses ebx esi uID:dword, dw1:dword, dwUser:dword, lParam1:dword, lParam2:dword
else
_midithreadproc proc public uses ebx esi
endif

		@strace <"midicallbackA(", uID, ", ", dw1, ", ", dwUser, ")=", eax>
       	mov ebx, g_hmo
        mov esi, [ebx].MIDIOBJ.pMidiHdr
        .if (esi)
			mov eax, [esi].MIDIHDR.reserved
   	        .if (eax < [esi].MIDIHDR.dwBufferLength)
       	    	mov edx, [esi].MIDIHDR.lpData
                mov ecx, [esi].MIDIHDR.dwBufferLength
           	    add edx, eax
   	            sub ecx, eax
       	        .if (ecx > ?MIDICHUNKSIZE)
           	    	mov ecx, ?MIDICHUNKSIZE
                .endif
   	            add [esi].MIDIHDR.reserved, ecx
       	        mov esi, edx
           	    .while (ecx)
                	push ecx
   	            	lodsb
    	            invoke g_MidiProcs.pWriteData
           	        pop ecx
                    dec ecx
                .endw
            .endif
        	invoke _midiDequeueHdr
        .else
           	xor ecx, ecx
            xchg ecx, g_hMOTimer
ife ?USEMMTIMER            
            push ecx
          	invoke CancelWaitableTimer, ecx
            pop eax
           	invoke CloseHandle, eax
else
           	invoke timeKillEvent, ecx
endif
        .endif
		ret
        align 4
_midithreadproc endp

;--- ebx -> MIDIOBJ

_midiQueueHdr proc pMidiHdr:LPMIDIHDR, cbMidiOutHdr:dword
			mov edx, pMidiHdr
   	        mov [edx].MIDIHDR.lpNext, 0
       	    mov [edx].MIDIHDR.reserved, 0
            or [edx].MIDIHDR.dwFlags, MHDR_INQUEUE
        	and [edx].MIDIHDR.dwFlags, not MHDR_DONE
            invoke EnterCriticalSection, addr g_csMM
       	    mov ecx, [ebx].MIDIOBJ.pMidiHdr
            .if (ecx)
   	        	.while ([ecx].MIDIHDR.lpNext)
       	        	mov ecx, [ecx].MIDIHDR.lpNext
                .endw
   	            mov [ecx].MIDIHDR.lpNext, edx
            .else
   	        	mov [ebx].MIDIOBJ.pMidiHdr, edx
            .endif
            invoke LeaveCriticalSection, addr g_csMM
			ret
_midiQueueHdr endp

midiOutLongMsg proc public uses ebx hmo:DWORD, pMidiHdr:LPMIDIHDR, cbMidiOutHdr:dword

local	filetime:FILETIME

		mov ebx, hmo
        .if (ebx && (ebx == g_hmo))
        	.if (!(g_bInit & FMO_UART))
            	or g_bInit, FMO_UART
                mov al,3Fh
            	invoke g_MidiProcs.pWriteCmd
            .endif
            invoke _midiQueueHdr, pMidiHdr, cbMidiOutHdr
            mov eax, 1
            .if (!g_hMOTimer)
	            invoke EnterCriticalSection, addr g_csMM
				.if (!g_hMOTimer)
if ?USEMMTIMER
	            	invoke timeSetEvent, ?DELAY, 0, offset _midithreadproc, ebx, TIME_PERIODIC or TIME_CALLBACK_FUNCTION
    	            mov g_hMOTimer, eax
else                
	               	invoke CreateWaitableTimer, 0, 0, 0
    	            .if (eax)
						mov g_hMOTimer, eax
						mov eax, ?DELAY*1000*10
						neg eax
						cdq
						mov filetime.dwLowDateTime, eax
						mov filetime.dwHighDateTime, edx
						mov ecx, ?DELAY
						invoke SetWaitableTimer, g_hMOTimer, addr filetime, \
							ecx, 0, 0, 0
	                .endif
endif                
                .endif
    	        invoke LeaveCriticalSection, addr g_csMM
                .if (!g_hMOTimer)
	   	            mov eax, MMSYSERR_NOMEM
    	            jmp exit
        	    .endif
			.endif
            
           	.if (!(g_bInit & FMO_THREAD))
                or g_bInit, FMO_THREAD
               	invoke StartMMThread
                .if (!eax)
	   	            mov eax, MMSYSERR_NOMEM
                    jmp exit
                .endif
            .endif
if 1            
			invoke _midithreadproc, g_hMOTimer, 0, ebx, 0, 0
;            invoke Sleep,0
endif            
	        mov eax, MMSYSERR_NOERROR
        .else
			mov eax, MMSYSERR_INVALHANDLE
        .endif
exit:   
		@strace <"midiOutLongMsg(", hmo, ", ", pMidiHdr, ", ", cbMidiOutHdr, ")=", eax>
		ret
        align 4
midiOutLongMsg endp

midiOutReset proc public uses ebx esi hmo:DWORD
		mov ebx, hmo
        .if (ebx && (ebx == g_hmo))
        	invoke g_MidiProcs.pReset
            and g_bInit, not FMO_UART
            .if (eax)
            	invoke EnterCriticalSection, addr g_csMM
        	    mov esi, [ebx].MIDIOBJ.pMidiHdr
		       	.while (esi)
    	            push [esi].MIDIHDR.lpNext
        	        invoke UnlinkMidiHdr
			  		or [esi].MIDIHDR.dwFlags, MHDR_DONE
    	            pop esi
				.endw
            	invoke LeaveCriticalSection, addr g_csMM
		        mov eax, MMSYSERR_NOERROR
            .else
			  	mov eax, MMSYSERR_NODRIVER
            .endif
        .else
		  	mov eax, MMSYSERR_INVALHANDLE
        .endif
		@strace <"midiOutReset(", hmo, ")=", eax>
		ret
        align 4
midiOutReset endp

midiOutGetDevCapsA proc public uDeviceId:dword, lpMidiOutCaps:ptr, cbMidiOutCaps:dword
		
        mov ecx, uDeviceId
if ?SBMIDI        
        .if ((ecx == 0) || (ecx == 1) || (ecx == MIDI_MAPPER))
else        
        .if ((ecx == 0) || (ecx == MIDI_MAPPER))
endif        
	       	invoke SndInit
	        .if (eax)
            	.if (uDeviceId != 1)
		  	        invoke SndGetCaps
    		        .if (edx)
		    	        invoke SndGetMidiPort
                	    .if (eax != -1)
                       		jmp error
                        .endif
                    .endif
                .endif
               	mov edx, lpMidiOutCaps
				mov ecx, cbMidiOutCaps
				mov [edx].MIDIOUTCAPSA.wMid, 0
				mov [edx].MIDIOUTCAPSA.wPid, 0
				mov [edx].MIDIOUTCAPSA.vDriverVersion, 100h
if ?SBMIDI                  
                .if ((uDeviceId == 0) || (uDeviceId == MIDI_MAPPER))
                   	mov ecx, CStr("HX SB MPU-401")
                .else
                  	mov ecx, CStr("HX SB MIDI")
                .endif
else                    
               	mov ecx, CStr("HX SB MPU-401")
endif                       
				invoke lstrcpy, addr [edx].MIDIOUTCAPSA.szPname, ecx
				mov edx, lpMidiOutCaps
				mov [edx].MIDIOUTCAPSA.wTechnology, MOD_SYNTH
				mov [edx].MIDIOUTCAPSA.wVoices, 128
				mov [edx].MIDIOUTCAPSA.wNotes, 128
				mov [edx].MIDIOUTCAPSA.wChannelMask, -1
				mov [edx].MIDIOUTCAPSA.dwSupport, 0
				mov eax, MMSYSERR_NOERROR
				jmp exit
            .endif
        .endif
error:        
		mov eax, MMSYSERR_NODRIVER
exit:        
		@strace <"midiOutGetDevCapsA(", uDeviceId, ")=", eax>
		ret
        align 4
midiOutGetDevCapsA endp

;--- 1 or 2 devices, first is SB Midi

midiOutGetNumDevs proc public uses ebx

if ?MIDIACTIVE		
		xor ebx, ebx
       	invoke SndInit
        .if (eax)
	        invoke SndGetCaps
    	    .if (edx)				;midi supported?
		        invoke SndGetMidiPort
		        .if (eax != -1)
                	inc ebx
if ?SBMIDI        
		        	inc ebx
endif            
                .endif
            .endif
        .endif
endif        
       	mov eax, ebx
		@strace <"midiOutGetNumDevs()=", eax>
		ret
        align 4
midiOutGetNumDevs endp

midiOutSetVolume proc public hmo:DWORD, dwVolume:DWORD
		mov eax, MMSYSERR_INVALHANDLE
		@strace <"midiOutSetVolume(", hmo, ", ", dwVolume, ")=", eax, " *** unsupp ***">
		ret
        align 4
midiOutSetVolume endp

midiOutGetVolume proc public hmo:dword, pVol:ptr DWORD
		mov eax, MMSYSERR_INVALHANDLE
		@strace <"midiOutGetVolume(", hmo, ", ", pVol, ")=", eax, " *** unsupp ">
		ret
        align 4
midiOutGetVolume endp

midiOutGetErrorTextA proc public mmrError:dword, pszText:ptr BYTE, cchText:DWORD
		mov eax, MMSYSERR_NODRIVER
		@strace <"midiOutGetErrorTextA(", mmrError, ", ", pszText, ", ", cchText, ")=", eax>
		ret
        align 4
midiOutGetErrorTextA endp

		end
