/***************************************************************************
*   NAME:  MIDIOUT.C $Revision: 1.25 $
**  COPYRIGHT:
**  "Copyright (c) 1994,1995 by e-Tek Labs"
**
**       "This software is furnished under a license and may be used,
**       copied, or disclosed only in accordance with the terms of such
**       license and with the inclusion of the above copyright notice.
**       This software or any other copies thereof may not be provided or
**       otherwise made available to any other person. No title to and
**       ownership of the software is hereby transfered."
****************************************************************************
* $Log: midiout.c $
* Revision 1.25  1995/11/14 08:35:17  teckert
* Attempted fix to multiple sysex message problem.
* Revision 1.24  1995/10/24 11:26:34  sdsmith
* Fixes for Windows 95 compatability tests
* Revision 1.23  1995/10/13 17:33:10  mleibow
* Changed patch loader to use iwllist.c and iwllist.h instead of list.c and list.h
* Revision 1.22  1995/09/29 13:45:33  teckert
* Added iwReloadPatchMap and fixed editable patch loading (i.e. now makes sure
* that any non-editable patch with the same patch and bank # is unloaded).
* Revision 1.21  1995/08/03 17:29:10  teckert
* Fixed cakewalk "not enough memory" bug
* Revision 1.20  1995/08/02 16:03:18  teckert
* Initialize current patch settings to piano
* Revision 1.19  1995/07/10 12:40:29  mleibow
* moved patch.c to kernel.
* Revision 1.18  1995/07/05 13:55:13  teckert
* Added critical sections for patch loading
* Revision 1.17  1995/06/28 11:37:40  teckert
* Rewrote patch caching strategy to handle low memory conditions.
* Revision 1.16  1995/06/14 14:46:31  teckert
* Revision 1.15  1995/06/12 16:56:38  teckert
* Fixed a bug in bad_load_cb
* Revision 1.14  1995/05/11 14:11:13  teckert
* Corrected midi sysex callback
* Revision 1.13  1995/04/28 16:03:27  teckert
* Sysex message processing defered with callback
* Revision 1.12  1995/04/24 18:17:14  teckert
* Added full memory condition handling
* Revision 1.11  1995/04/20 06:12:09  mleibow
* Added error message for bad patch config.
* Revision 1.10  1995/04/20 05:57:13  teckert
* Bug Fixes
* Revision 1.9  1995/04/20 03:57:23  mleibow
* Revision 1.8  1995/04/19 18:37:31  teckert
* Updated with new patch.c helper routines
* Revision 1.7  1995/04/19 17:18:36  mleibow
* Rewrote patch loading code
* Revision 1.6  1995/04/04 15:36:29  teckert
* Fixed hardware acquisition code
* Revision 1.5  1995/03/16 10:33:08  teckert
* Removed some UltraSound artifacts
* Revision 1.3  1995/03/01 16:57:19  unknown
* Added file header
* Revision 1.1  1995/02/23 15:14:59  sdsmith
* Initial revision
***************************************************************************/
#include <windows.h>
#include <mmsystem.h>
#include <mmddk.h>
#include <string.h>
#include <iw.h>
#include <os.h>
#include <iwl.h>
#include "midi.h"
#include "patch.h"
#include "interwav.h"

#define IN_MIDIOUT_C
#include "digintr.h"          

#define DisableInterrupts OS_PUSH_DISABLE
#define RestoreInterrupts OS_POPF
BYTE bActiveVoices = 32;
extern struct iw_dma_buff wave_dma_buf;
extern int nKernelLoaded;
extern int nEnabled;
extern char RFAR *pload_buff;
extern int DeferedNotes[MAXDEFEREDNOTES][2];
extern char patch_ed_path[];

void redo_patches_assignments(void);
void bad_load_cb(struct iwu_fast_patch RFAR *, int attempt);
int nLoadStatus = LS_LOADONCACHE;
int load16as8 = 0;
void DeferedNotePlayed(int nChan,int nNote);
void DoQueue(void);

HTASK hTask;                    // Task handle for the helper application

	
/****************************************************************************

FUNCTION DEFINITION:
MidiInit - initializes the midi device

DESCRIPTION:
Sets the status for each client entry in the client array to closed and clears
the patch arrays.
Reads in the patch library files and parses them.
Loads the helper application.

RETURNS: void
*/
void MidiInit(void)
{
	struct iwu_fast_patch RFAR *fpi;
        int i, j, rval;
	int nShow[2];
	LPSTR lpstr;

	struct _LOADPARMS {
		WORD      segEnv;         /* child environment  */
		LPSTR     lpszCmdLine;    /* child command tail */
		UINT FAR* lpShow;         /* how to show child  */
		UINT FAR* lpReserved;     /* must be NULL       */
	}lp;

	for(i=0;i<MAXDEFEREDNOTES;i++){
	    DeferedNotes[i][0] = 0xFF;
	}

	nShow[0] = 2;
	nShow[1] = SW_HIDE;
	lp.segEnv = 0;
	lp.lpszCmdLine = (LPSTR)"";
	lp.lpShow = (LPWORD)nShow;
	lp.lpReserved = (LPWORD)NULL;

	rval = iwu_get_patch_config();
	if (rval != IWU_PATCH_OK) {
	    lpstr = (LPSTR)iwu_error_str(rval);
	    MessageBox(NULL, lpstr, "INTERWAV.DRV:", MB_OK);
	    wIWDriverFlags |= IWDF_PATCH_CONFIG_FAIL;
	} else {
	    LoadModule("IWLOADER.EXE",&lp); // I don't even know
	}
        fpi = iwu_find_patch(0,0,0);
	for(i=0;i<MAXMIDICLIENTS;i++){
	    MidiClients[i].bClientStatus = MCS_CLOSED;
            for(j=0;j<16;j++){
                if (j==9) continue;
                MidiClients[i].CurPatch[j] = fpi;
            }
	}
}


/****************************************************************************

FUNCTION DEFINITION:
MidiUninit - frees the patch library memory and closes the helper application
			
DESCRIPTION:
Sends message that unloads the helper application.

RETURNS: void
*/
void MidiUninit(void)
{
	PostAppMessage(hTask,WM_USER+1,0,0l);
}


/****************************************************************************

FUNCTION DEFINITION:
ReloadPatchMap - forces the driver to re-parse the ini file and reload the 
		 patch information.

RETURNS: RPM_OK on success
         RPM_MIDIBUSY if any synth clients are open
         RPM_ERROR if any error occurs during patch loading 
*/
int FAR PASCAL _export __loadds iwReloadPatchMap(void)
{                       
    struct iwu_fast_patch RFAR *fpi;
    char far *lpstr;
    int i,j,rval;
    
	if(BlockMidiClients()){
		iwu_unload_all_patches();
		GetPrivateProfileString((LPCSTR)"patch editor",
					(LPCSTR)"patch_editor_dir",
					(LPCSTR)os_getenv("IWDIR"),
					(LPSTR)patch_ed_path,
					256,
					(LPCSTR)os_getenv("INTERWAVE")); 
		if(patch_ed_path[_fstrlen(patch_ed_path)-1]!='\\'){
		    _fstrcat(patch_ed_path,"\\");
		}
					
		iwu_free_patch_config();
		if((rval = iwu_get_patch_config()) != IWU_PATCH_OK) {
		    lpstr = (LPSTR)iwu_error_str(rval);
		    MessageBox(NULL, lpstr, "INTERWAV.DRV:", MB_OK);
		    wIWDriverFlags |= IWDF_PATCH_CONFIG_FAIL;
		    UnBlockMidiClients();
		    return RPM_ERROR;
		}
		wIWDriverFlags &= ~IWDF_PATCH_CONFIG_FAIL;

	        fpi = iwu_find_patch(0,0,0);
		for(i=0;i<MAXMIDICLIENTS;i++){
		    MidiClients[i].bClientStatus = MCS_CLOSED;
	            for(j=0;j<16;j++){
	                if (j==9) continue;
	                MidiClients[i].CurPatch[j] = fpi;
	            }
		}
		UnBlockMidiClients();
		return RPM_OK;
	}
	return RPM_MIDIBUSY;
}
		

int nBlockMidiSynth = 0;

/****************************************************************************

FUNCTION DEFINITION:
BlockMidiClients - Blocks midi synth clients from opening, fails if any are open
			
RETURNS: 1, on success: 0, on failure
*/
int BlockMidiClients(void)
{
    int wClientIndex;
    
    if(nBlockMidiSynth){
        nBlockMidiSynth++;
	return 1;
    }    

    for( wClientIndex=0 ; wClientIndex<MAXMIDICLIENTS ; wClientIndex++ )
	if( MidiClients[wClientIndex].bClientStatus != MCS_CLOSED )
		break;
			
    if( wClientIndex == MAXMIDICLIENTS ){
        nBlockMidiSynth = 1;
        return 1;
    }

    return 0;
}


/****************************************************************************

FUNCTION DEFINITION:
UnBlockMidiClients - allows midi synth clients
			
RETURNS: void
*/
void UnBlockMidiClients(void)
{
    if(nBlockMidiSynth){
        nBlockMidiSynth--;
    }
}


/****************************************************************************

FUNCTION DEFINITION:
RegisterHelperApp - lets the Helper Application register its task with us

RETURNS: void
*/
void FAR PASCAL _loadds RegisterHelperApp(
	HTASK hTaskParam)
{
	hTask = hTaskParam;
}
		

/****************************************************************************

FUNCTION DEFINITION:
GetLibPointer - exports the patch library pointer for a debugging app.

RETURNS: void
*/
void FAR PASCAL _loadds GetLibPointer( void far *pbuf, int nsize )
{
    if(nsize < sizeof(void far *))
        return;
    *((void far * far *)pbuf) = (void far *)plib;
}


/****************************************************************************

FUNCTION DEFINITION:
MessageCallback - callback entry point from helper application

DESCRIPTION:
Gets called from the helper application in response to a PostCallbackMsg() call.
Will always be in the foreground so file access is OK.

RETURNS: void
*/
void FAR PASCAL _loadds MessageCallback(
	WORD wParam,            // Message Code, indicates required action
	LONG lParam)            // Message specific data
{        
	struct iwu_fast_patch RFAR *fpi;
	int nClient,nChan,nBank,nProgram,nNote,nVel,nIsMelodic;
								
	// Beware of callbacks received after drivers are shut down     
	if(!nKernelLoaded)
		return;                                            
		
	// First parse the lParam                                  
        if(wParam == MC_NOTEON || wParam == MC_NOTEOFF){
		nClient = (int)HIBYTE(HIWORD(lParam))/16;
		nChan = (int)HIBYTE(HIWORD(lParam))%16;
		nBank = (int)LOBYTE(HIWORD(lParam));
		nNote = (int)HIBYTE(LOWORD(lParam));
		nVel = (int)LOBYTE(LOWORD(lParam));
	}
	
	switch(wParam){
		case MC_LOADPATCHES: 
		    iwu_load_marked_patches(pload_buff, PLOAD_BSIZE, bad_load_cb, load16as8);
		    iwu_clear_marked_patches();
		    break;
		case MC_LOADMELODICPATCHES:
		    for(nClient = 0;nClient < MAXMIDICLIENTS;nClient++){
			for(nChan = 0;nChan<16;nChan++){
			    if(nChan == 0x09)
				continue;       
			    ENTER;
			    fpi = MidiClients[nClient].CurPatch[nChan];
			    LEAVE;
			    if (fpi && !(fpi->flags & IWU_PATCH_LOADED)) {
                                iwu_load_patch(fpi->id.h.program, fpi->id.h.bank, pload_buff, PLOAD_BSIZE, bad_load_cb, load16as8);
			    }
			    if (fpi && (fpi->flags & IWU_PATCH_LOADED)) {
				iw_midi_change_program(nClient*16+nChan,fpi->p);
			    }
			}           
		    }		
		    break;
		case MC_NOTEON:
			if(nChan == 0x09){      // If its the drum channel
				fpi = iwu_find_patch(nNote+128,nBank,0);
				// Cache the patch if its not cached (mark as load pending)
				if (fpi && (fpi->flags & IWU_PATCH_LOADED)) {
					iw_midi_note_on(nClient*16+nChan,nNote,nVel,fpi->p,1);
				}
			}else{                  // If its a melodic channel
				// If the patch is loaded play the note
				fpi = MidiClients[nClient].CurPatch[nChan];
				if (fpi && (fpi->flags & IWU_PATCH_LOADED)) {
					iw_midi_note_on(nClient*16+nChan,nNote,nVel,NULL,1);
				}
			}
			DeferedNotePlayed(nClient*16+nChan,nNote);
			break;
		case MC_NOTEOFF:
			iw_midi_note_off(nClient*16+nChan,nNote);
			break;
		case MC_DOSYSEX:         
			nClient = (int)lParam;
			DoSysexMessage(nClient);              
			wIWDriverFlags &= ~IWDF_SYSEXPENDING;
			wIWDriverFlags |= IWDF_INFOREGROUND;
			DoQueue();
			wIWDriverFlags &= ~IWDF_INFOREGROUND;
			break;
			
	}
}
		
		
/****************************************************************************

FUNCTION DEFINITION:
StrCatInt - adds the integer (in ascii format) to the end of a string

DESCRIPTION:
Takes a positive integer (0 - 999) and converts it to an ascii string then
concatenates it to the end of the string.

RETURNS: void
*/
void StrCatInt(
	char far *Dest,             // a pointer to the string
	int nNumber)                // the number to add (0 - 999)
{
	char buf[4];
	int i,n;
	int place;

	if(nNumber > 999)nNumber = 999;     
	if(nNumber < 0)nNumber = 0;
		
	for(i=0,place=100;place;place/=10){
		n = nNumber / place;
		if(n){
			buf[i++] = '0' + n;
			nNumber -= n * place;
		}
		else if(i || place==1){
			buf[i++] = '0';
		}
	}
	buf[i] = 0;

	while(*Dest)Dest++;
	for(i=0;i<4 && buf[i];i++){
		*Dest = buf[i];
		Dest++;
	}
	*Dest = 0;
}

/****************************************************************************

FUNCTION DEFINITION:
modGetDevCaps - Get the capabilities of the port.

DESCRIPTION:
Fills the capabilities structure and copies it to the destination

RETURNS: void
*/
void FAR PASCAL modGetDevCaps(
	WORD wID,                       // 0 for Synth, 1 for Port
	LPBYTE lpCaps,                  // Far pointer to a MIDIOUTCAPS structure.
	WORD wSize)                     // Size of the MIDIOUTCAPS structure.
{
	MIDIOUTCAPS mc;

	if(wID==1){
		mc.wMid = MM_ETEK;
		mc.wPid = MM_INTERWAVE_MIDIOUT;
		mc.vDriverVersion = DRIVER_VERSION;
		mc.wTechnology = MOD_MIDIPORT;
		mc.wVoices = 0;                   // not used for ports
		mc.wNotes = 0;                    // not used for ports
		mc.wChannelMask = 0xFFFF;         // all channels
		// Totally bogus workaround for brain dead mciseq...
		mc.dwSupport = MIDICAPS_CACHE;
		//    mc.dwSupport = 0L;
		LoadString(ghModule, STR_IWAVE_MIDIOUT, (LPSTR)&(mc.szPname), sizeof(mc.szPname));
	}else{
		mc.wMid = MM_ETEK;
		mc.wPid = MM_INTERWAVE_MIDISYNTH;
		//  mc.wPid = 17;
		mc.vDriverVersion = DRIVER_VERSION;
		mc.wTechnology = MOD_SYNTH;
		mc.wVoices = (WORD) (bActiveVoices - 2);
		mc.wNotes  = (WORD) (bActiveVoices - 2);
		mc.wChannelMask = 0xFFFF;         // all channels
		//    mc.dwSupport = 0L;
		mc.dwSupport = MIDICAPS_CACHE | MIDICAPS_VOLUME;
		LoadString(ghModule, STR_IWAVE_MIDI_SYNTH, (LPSTR)&(mc.szPname), sizeof(mc.szPname));
	}

	_fmemcpy(lpCaps, (unsigned char far *)&mc, min(wSize,sizeof(mc)));
}


void bad_load_cb(struct iwu_fast_patch RFAR *fpi_being_loaded, int attempt)
{
    struct iwu_fast_patch RFAR*fpi;     /* search index */
    struct patch_node RFAR*fffn;
    struct fff_file RFAR*fff;
    struct patch_lib RFAR*pl;
    int i,nChan,nClearPercFlags = 0;
    
    if(attempt==2){       
    	switch (nLoadStatus) {
    	    case LS_LOADONCACHE:
    	        nLoadStatus = LS_LOADONASSIGN;
    	        break;
    	    case LS_LOADONASSIGN:             
    	        nLoadStatus = LS_LOADONPLAY;
    	    case LS_LOADONPLAY:
    	        nClearPercFlags = 1;
    	        break;
    	}
    }               
    for (pl=plib; pl != 0; pl = pl->next) {
	for (fffn=(struct patch_node RFAR *)pl->fff_list.head; fffn; fffn = fffn->next) {
	    fff = fffn->data;
	    for (fpi = fff->patches, i=0; i < fff->npatches; i++, fpi++) {
	        if ( fpi == fpi_being_loaded ) continue;
		if (fpi->flags & IWU_PATCH_LOADED) {
		    if (fpi->id.h.program > 128){
		    	if ( nClearPercFlags ) {
		    	    fpi->flags &= ~IWU_PATCH_NEEDED;
		    	}
			if ( (nLoadStatus==LS_LOADONPLAY) ||
			     (nLoadStatus==LS_LOADONASSIGN && !(fpi->flags & IWU_PATCH_NEEDED)) ||
			     (!fpi->ud && nLoadStatus!=LS_LOADONCACHE) ) {
			    iwu_unload_patch(fpi->id.h.program,fpi->id.h.bank); 
			}			 
		    }else{
		    	ENTER;
			for(nChan = 0;nChan < 16*MAXMIDICLIENTS;nChan++){
			    if(MidiClients[nChan/16].CurPatch[nChan%16]==fpi){
			        break;
			    }
			}
			LEAVE;
			if ( (nChan == 16*MAXMIDICLIENTS) &&	// patch is not assigned
			     (nLoadStatus!=LS_LOADONCACHE || !fpi->ud) ){
				iwu_unload_patch(fpi->id.h.program,fpi->id.h.bank);     
			}
		    }
		}
	    }
	}
    }
}

/****************************************************************************

FUNCTION DEFINITION:
CachePatches - loads the patches specified by the MODM_CACHEPATCHES message

DESCRIPTION:
Marks requested patches as chached and if nLoadStatus is LS_LOADONCACHE for melodics
or LS_LOADONASSIGN for percussives the patches are loaded.

RETURNS: MMSYSERR_XXX error code
*/
DWORD CachePatches(
	int nClient,                    // index into the client array
	LPPATCHARRAY lpPatchArray,      // pointer to an array that indicates requested patches
	WORD wBank,                     // bank to use
	WORD wFlags,                    // specifies cache mode
	int nMelodic)                   // 1 for melodic, 0 for drum
{
	int nPatch,nPnum;
	struct iwu_fast_patch RFAR *fpi;
	DWORD result;
	
	if(wFlags == MIDI_CACHE_ALL || wFlags == MIDI_CACHE_BESTFIT){   // query and uncache don't load patches
		for( nPatch = 0 ; nPatch<128 ; nPatch++ ){
			// if patch is requested and isn't loaded try to load it
			if( lpPatchArray[nPatch] & (nMelodic?~(1<<9):(1<<9)) ){
				if(!nMelodic) {
				    nPnum = nPatch + 128;
				} else {
				    nPnum = nPatch;
				}
				fpi = iwu_find_patch(nPnum,wBank,0);
				if(fpi){
				    fpi->ud |= (1<<nClient);    
				    if ( (nLoadStatus==LS_LOADONCACHE) ||
				         (nLoadStatus==LS_LOADONASSIGN && !nMelodic) )
					iwu_mark_patch(nPnum,wBank);
				}
			}   // if patch is required
		}           // for each patch
		if ( (nLoadStatus==LS_LOADONCACHE) ||
		     (nLoadStatus==LS_LOADONASSIGN && !nMelodic) ) {
		    iwu_load_marked_patches(pload_buff, PLOAD_BSIZE, bad_load_cb, load16as8);
		    iwu_clear_marked_patches();
		    ENTER;
		    redo_patches_assignments();  // MCI un-pause kludge
		    LEAVE;
		}
	}                   // if cache-all or cache-best-fit

#if 0
	// Clear patches if we gave up on a cache-all attempt
	if( wFlags == MIDI_CACHE_ALL && patch_load_failed){
		for(n = 0;n < nPatch;n++){
			if( lpPatchArray[nPatch] & (nMelodic?~(1<<9):(1<<9)) ){
				if(!nMelodic) {
				    nPnum = nPatch + 128;
				} else {
				    nPnum = nPatch;
				}
				fpi = iwu_find_patch(nPnum,wBank,0);
				if (fpi) fpi->ud &= ~(1<<nClient);
			}
		}
	}
#endif
	
	// Unload Specified patches for uncache message
	if( wFlags == MIDI_UNCACHE ) {
		for( nPatch = 0; nPatch<128 ; nPatch++) {
			if( lpPatchArray[nPatch] & (nMelodic?~(1<<9):(1<<9)) ){
				if(!nMelodic) {
				    nPnum = nPatch + 128;
				} else {
				    nPnum = nPatch;
				}
				fpi = iwu_find_patch(nPnum,wBank,0);
				if (fpi) {
					fpi->ud &= ~(1<<nClient);
					if (!fpi->ud) {
						iwu_unload_patch(nPnum,wBank);
					}
				}
			}
		}
	}
		
	// Now report the result
	if( wFlags == MIDI_CACHE_QUERY || wFlags == MIDI_CACHE_BESTFIT){
	    for( nPatch = 0 ; nPatch<128 ; nPatch++ ){
		if (nMelodic) {
		    fpi = iwu_find_patch(nPatch, wBank,0);
		} else {
		    fpi = iwu_find_patch(nPatch+128, wBank,0);
		}
                if(fpi && fpi->ud){
                    if (!lpPatchArray[nPatch]){
                        lpPatchArray[nPatch] = 1;
                    }
                } else {
                    lpPatchArray[nPatch] = 0;
                }
	    }
	    result = MMSYSERR_NOERROR;
	} else if( wFlags == MIDI_UNCACHE ) {
	    _fmemset(lpPatchArray, 0, sizeof(lpPatchArray));
	    result = MMSYSERR_NOERROR; 
	} else {               // successfully loaded all patches, patcharray need not be changed
	    result = MMSYSERR_NOERROR;
	}
		
	return result;
}
