/*
THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
SOFTWARE CORPORATION ("PARALLAX").  PARALLAX, IN DISTRIBUTING THE CODE TO
END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
IN USING, DISPLAYING,  AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
FREE PURPOSES.  IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES.  THE END-USER UNDERSTANDS
AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.  
COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION.  ALL RIGHTS RESERVED.
*/


#pragma off (unreferenced)
static char rcsid[] = "$Id: winmidi.c 1.12 1996/10/04 17:13:54 samir Exp $";
#pragma on (unreferenced)




#define _WIN32
#define WIN95
#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <mmreg.h>

#include "pstypes.h"
#include "mono.h"
#include "error.h"
#include "mem.h"
#include "winapp.h"
#include "winregs.h"


#include "midifile.h"
#include "midiseq.h"
#include "winmidi.h"


//	----------------------------------------------------------------------------
//	Data
//	----------------------------------------------------------------------------

BOOL 	WMidi_NewStream = 0;

static BOOL				WMidi_initialized = FALSE;
static SEQ				MSeq;
static DWORD			wmidi_volume = 0x80008000;
static BOOL				LoopSong = FALSE;
static BOOL				MidiVolChanges = TRUE;


void wmidi_deamp_song(DWORD vol);
int wmidi_get_tech();
void CALLBACK wmidi_seq_callback(HMIDISTRM hms, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2);


//	----------------------------------------------------------------------------
//	Sequencer Initialization and destruction
//	----------------------------------------------------------------------------

int wmidi_init(uint mmdev)
{
	MIDIOUTCAPS midicaps;
	//BYTE *hdr;
	//DWORD bufsize;
	//int i;
	UINT ui;
	
	memset(&MSeq, 0, sizeof(SEQ));

	WMidi_initialized = TRUE;

//	Initialize stream buffers
	MSeq.uState = SEQ_S_NOFILE;
	MSeq.lpmhFree = NULL;
	MSeq.lpbAlloc = NULL;
	MSeq.hmidi = NULL;

	MSeq.cBuffer = 4;
	MSeq.cbBuffer = 512;

	if (seqAllocBuffers(&MSeq) != MMSYSERR_NOERROR)
    	return FALSE;

//	Setup sequencer some more
	ui = midiOutGetDevCaps(mmdev, &midicaps, sizeof(midicaps));
	if ( ui != MMSYSERR_NOERROR) {
		mprintf((1, "WMIDI:(%x) Unable to find requested device.\n", MSeq.mmrcLastErr));
		return 0;
	}
	
	if ((midicaps.dwSupport & MIDICAPS_VOLUME) || (midicaps.dwSupport & MIDICAPS_LRVOLUME)) 
		MidiVolChanges = TRUE;
	else MidiVolChanges = FALSE;

	MSeq.uDeviceID = mmdev;

//	Now we must do a stupid Microsoft trick to determine whether we are
//	using WAVE TABLE volume or OPL volume.
	MSeq.uMCIDeviceID = wmidi_get_tech();

	logentry("MIDI technology: %d.\n", MSeq.uMCIDeviceID);
	
	return 1;
}
				

void wmidi_close()
{
	//MIDIHDR *hdr;

	Assert(WMidi_initialized == TRUE);

	seqFreeBuffers(&MSeq);
	
	WMidi_initialized = FALSE;
}


int wmidi_get_tech()
{
	HKEY hKey;
	long lres;
	char key[32];	
	char buf[256];
	char *pstr;
	DWORD len,type;


	lres = RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Multimedia\\MIDIMap",
					0, KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE | KEY_EXECUTE,
					&hKey);
	if (lres != ERROR_SUCCESS) return 0;

	len = 32;
	lres = RegQueryValueEx(hKey, "CurrentInstrument", NULL, &type, key, &len);
	if (lres != ERROR_SUCCESS) {
		RegCloseKey(hKey);
		return 0;
	}

	RegCloseKey(hKey);

	strcpy(buf, "System\\CurrentControlSet\\control\\MediaResources\\midi\\");
	strcat(buf, key);
	lres = RegOpenKeyEx(HKEY_LOCAL_MACHINE, buf, 
					0, KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE | KEY_EXECUTE,
					&hKey);
	if (lres != ERROR_SUCCESS) return 0;
	
 	len =128;
	lres = RegQueryValueEx(hKey, "Description", NULL, &type, buf, &len);
	if (lres != ERROR_SUCCESS) {
		RegCloseKey(hKey);
		return 0;
	}
	RegCloseKey(hKey);

//	Okay, look for 'wave' or 'fm' or 'opl'
	pstr = strlwr(buf);	
	if (strstr(pstr, "wave")) return 1;

	return 0;
}


int wmidi_support_volchange() {
	return (int)MidiVolChanges;
}


//	----------------------------------------------------------------------------
//	High level midi song play/stop/pause interface
//	----------------------------------------------------------------------------

int wmidi_init_song(WMIDISONG *song)
{
	DWORD bufsize;
	SMF *smf;
	MMRESULT mmrc;

	if (MSeq.uState != SEQ_S_NOFILE) return 0;

	Assert (song->data != NULL);

   mmrc = seqStop(&MSeq);
	if (mmrc != MMSYSERR_NOERROR)
	{
		mprintf((1, "WMIDI: Stop song failed.\n"));;
		return 0;
	}

	wmidi_close_song();

	if (song->looping) LoopSong = TRUE;
	else LoopSong = FALSE;

	if (smfOpenFile(song->data, song->length, (HSMF *)&smf) != SMF_SUCCESS) return 0;

//	song is confirmed to be a MIDI v1.0 compatible file
	MSeq.hSmf = smf;
	MSeq.dwTimeDivision = smf->dwTimeDivision;
	MSeq.tkLength = smf->tkLength;
	MSeq.cTrk = smf->dwTracks;

	bufsize = min(MSeq.cbBuffer, smfGetStateMaxSize());

	MSeq.uState = SEQ_S_OPENED;
	
	return 1;
}


int wmidi_play()
{
	PREROLL		preroll;
	MMRESULT 	mmrc;
	DWORD 		vol;

	preroll.tkBase = 0;
	preroll.tkEnd = MSeq.tkLength;

	mprintf((1, "wmidi_play: beginning playback.\n"));

	if ((mmrc = seqPreroll(&MSeq, &preroll)) != MMSYSERR_NOERROR) {
		mprintf((1, "wmidi_play: preroll failed! (%x)\n", mmrc));
		return 0;
	}

	
	Assert(MSeq.hmidi != NULL);

//	wmidi_deamp_song(wmidi_volume);

	mmrc = midiOutSetVolume((HMIDIOUT)MSeq.hmidi, wmidi_volume);	
	if (mmrc != MMSYSERR_NOERROR) mprintf((1, "MIDI VOL ERR: %d\n", mmrc));
	midiOutGetVolume((HMIDIOUT)MSeq.hmidi, &vol);

	if (seqStart(&MSeq) != MMSYSERR_NOERROR) return 0; 

	return 1;
}


int wmidi_stop()
{
	if (seqStop(&MSeq) != MMSYSERR_NOERROR) return 0;
	return 1;
}


int wmidi_pause()
{
	if (MSeq.uState != SEQ_S_PAUSED) {
		if (seqPause(&MSeq) != MMSYSERR_NOERROR) return 0;
	}
	return 1;
}
		
int wmidi_resume()
{
	if (MSeq.uState == SEQ_S_PAUSED) {
		if (seqRestart(&MSeq) != MMSYSERR_NOERROR) return 0;
	}
	return 1;
}
		

int wmidi_close_song()
{

	LPMIDIHDR               lpmh;
    
	if (SEQ_S_OPENED != MSeq.uState)
		return MCIERR_UNSUPPORTED_FUNCTION;

	LoopSong = FALSE;
    
	if ((HSMF)NULL != MSeq.hSmf)
 	{
		smfCloseFile(MSeq.hSmf);
		MSeq.hSmf = (HSMF)NULL;
   }

/* take care of any stream buffers or MIDI resources left open by
	the sequencer */

	if (MSeq.hmidi) {

		for (lpmh = MSeq.lpmhFree; lpmh; lpmh = lpmh->lpNext)
			midiOutUnprepareHeader(MSeq.hmidi, lpmh, sizeof(MIDIHDR));

		if (MSeq.lpmhPreroll) 
			midiOutUnprepareHeader(MSeq.hmidi, MSeq.lpmhPreroll, sizeof(MIDIHDR));

		midiStreamClose(MSeq.hmidi);
		MSeq.hmidi = NULL;
	}

	MSeq.uState = SEQ_S_NOFILE;
	
	return 1;
}


static uint funky_vol_log_table [] = {
	0x00000,				// 0
	0x0b000,				// 1
	0x0c000,				// 2
	0x0c800,				// 3
	0x0e000,		 		// 4
	0x0e800,				// 5
	0x0f000,				//	6
	0x0f800,				//	7
	0x0ffff				// 8
};



int wmidi_set_volume(uint volume)
{
	DWORD vol;
	//int i;
	MMRESULT mmrc;
	

	Assert(volume < 128);
	if (volume == 127) volume++;

	volume = volume /16;

	if (!volume) vol = 0;
	else if (MSeq.uMCIDeviceID == 1)				// WAVETABLE 
		vol = funky_vol_log_table[volume]|0x00ff;
	else {												// OPL2/OPL3 and others
		vol = volume * 0x2000;				
		if (vol > 0) vol--;
	}

	vol = (vol<<16)+vol;

	wmidi_volume = vol;


	if (MSeq.hmidi) {
//		wmidi_deamp_song(wmidi_volume);
		mmrc = midiOutSetVolume((HMIDIOUT)MSeq.hmidi, wmidi_volume);	
		if (mmrc != MMSYSERR_NOERROR) mprintf((1, "MIDI VOL ERR: %d\n", mmrc));
		midiOutGetVolume((HMIDIOUT)MSeq.hmidi, &vol);
	}

	return 1;
}


#define MIDI_CTRLCHANGE ((BYTE)0xB0)        // + ctrlr + value
#define MIDICTRL_VOLUME ((BYTE)0x07)
 

void wmidi_deamp_song(DWORD vol)
{
	float ptvol;
	DWORD ptdvol;
	DWORD dwvol, dwstatus, dwevent;
	MMRESULT mmrc;
	int i;	


	vol = vol & 0x0000ffff;

	ptvol = (float)(vol)*100.0/65536.0;
	ptdvol = (DWORD)(ptvol);

	mprintf((0, "Deamp = %d percent.\n", ptdvol));

	for (i = 0, dwstatus = MIDI_CTRLCHANGE; i < 16; i++, dwstatus++)
	{
		dwvol = ( 100 * ptdvol ) / 1000;
	   dwevent = dwstatus | ((DWORD)MIDICTRL_VOLUME << 8)
            | ((DWORD)dwvol << 16);
	  	if(( mmrc= midiOutShortMsg( (HMIDIOUT)MSeq.hmidi, dwevent ))
                            != MMSYSERR_NOERROR )
      {
			mprintf((1, "WINMIDI::BAD MIDI VOLUME CHANGE!\n"));
        	return;
      }
	}
}



/*****************************************************************************
*
*  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
*  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
*  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
*  A PARTICULAR PURPOSE.
*
*  Copyright (C) 1993-1995 Microsoft Corporation. All Rights Reserved.
*
******************************************************************************/


PRIVATE void FAR PASCAL seqMIDICallback(HMIDISTRM hms, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2);
PRIVATE MMRESULT FNLOCAL XlatSMFErr(SMFRESULT smfrc);

/***************************************************************************
*  
* seqAllocBuffers
*
* Allocate buffers for this instance.
*
***************************************************************************/
MMRESULT FNLOCAL seqAllocBuffers(
    PSEQ                    pSeq)
{
    DWORD                   dwEachBufferSize;
    DWORD                   dwAlloc;
    UINT                    i;
    LPBYTE                  lpbWork;

    Assert(pSeq != NULL);

    pSeq->uState    = SEQ_S_NOFILE;
    pSeq->lpmhFree  = NULL;
    pSeq->lpbAlloc  = NULL;
    pSeq->hSmf      = (HSMF)NULL;
    
    /* First make sure we can allocate the buffers they asked for
    */
    dwEachBufferSize = sizeof(MIDIHDR) + (DWORD)(pSeq->cbBuffer);
    dwAlloc          = dwEachBufferSize * (DWORD)(pSeq->cBuffer);
    
    pSeq->lpbAlloc = GlobalAllocPtr(GMEM_MOVEABLE|GMEM_SHARE, dwAlloc);
    if (NULL == pSeq->lpbAlloc)
        return MCIERR_OUT_OF_MEMORY;

    /* Initialize all MIDIHDR's and throw them into a free list
    */
    pSeq->lpmhFree = NULL;

    lpbWork = pSeq->lpbAlloc;
    for (i=0; i < pSeq->cBuffer; i++)
    {
        ((LPMIDIHDR)lpbWork)->lpNext            = pSeq->lpmhFree;

        ((LPMIDIHDR)lpbWork)->lpData            = lpbWork + sizeof(MIDIHDR);
        ((LPMIDIHDR)lpbWork)->dwBufferLength    = pSeq->cbBuffer;
        ((LPMIDIHDR)lpbWork)->dwBytesRecorded   = 0;
        ((LPMIDIHDR)lpbWork)->dwUser            = (DWORD)(UINT)pSeq;
        ((LPMIDIHDR)lpbWork)->dwFlags           = 0;

        pSeq->lpmhFree = (LPMIDIHDR)lpbWork;

        lpbWork += dwEachBufferSize;
    }

    return MMSYSERR_NOERROR;
}

/***************************************************************************
*  
* seqFreeBuffers
*
* Free buffers for this instance.
*
****************************************************************************/
VOID FNLOCAL seqFreeBuffers(
    PSEQ                    pSeq)
{
    LPMIDIHDR               lpmh;
    
    Assert(pSeq != NULL);

    if (NULL != pSeq->lpbAlloc)
    {
        lpmh = (LPMIDIHDR)pSeq->lpbAlloc;
        Assert(!(lpmh->dwFlags & MHDR_PREPARED));
        
        GlobalFreePtr(pSeq->lpbAlloc);
    }
}


/***************************************************************************
*  
* seqPreroll
*
* Prepares the file for playback at the given position.
*
****************************************************************************/
MMRESULT FNLOCAL seqPreroll(
    PSEQ                    pSeq,
    LPPREROLL               lpPreroll)
{
    SMFRESULT           smfrc;
    MMRESULT            mmrc        = MMSYSERR_NOERROR;
    MIDIPROPTIMEDIV     mptd;
    LPMIDIHDR           lpmh = NULL;
    LPMIDIHDR           lpmhPreroll = NULL;
    DWORD               cbPrerollBuffer;
    UINT                uDeviceID;

    Assert(pSeq != NULL);

    pSeq->mmrcLastErr = MMSYSERR_NOERROR;

    if (pSeq->uState != SEQ_S_OPENED &&
        pSeq->uState != SEQ_S_PREROLLED)
        return MCIERR_UNSUPPORTED_FUNCTION;

	pSeq->tkBase = lpPreroll->tkBase;
	pSeq->tkEnd = lpPreroll->tkEnd;

    if (pSeq->hmidi)
    {
        // Recollect buffers from MMSYSTEM back into free queue
        //
        pSeq->uState = SEQ_S_RESET;
        midiOutReset(pSeq->hmidi);

		while (pSeq->uBuffersInMMSYSTEM)
			Sleep(0);
    }
    
    pSeq->uBuffersInMMSYSTEM = 0;
    pSeq->uState = SEQ_S_PREROLLING;
    
    //
    // We've successfully opened the file and all of the tracks; now
    // open the MIDI device and set the time division.
    //
    // NOTE: seqPreroll is equivalent to seek; device might already be open
    //
    if (NULL == pSeq->hmidi)
    {
        uDeviceID = pSeq->uDeviceID;
        if ((mmrc = midiStreamOpen(&pSeq->hmidi,
                                   &uDeviceID,
                                   1,
                                   (DWORD)seqMIDICallback,
                                   0,
                                   CALLBACK_FUNCTION)) != MMSYSERR_NOERROR)
        {
            pSeq->hmidi = NULL;
            goto seq_Preroll_Cleanup;
        }
        
        mptd.cbStruct  = sizeof(mptd);
        mptd.dwTimeDiv = pSeq->dwTimeDivision;
        if ((mmrc = midiStreamProperty(
                                       (HMIDI)pSeq->hmidi,
                                       (LPBYTE)&mptd,
                                       MIDIPROP_SET|MIDIPROP_TIMEDIV)) != MMSYSERR_NOERROR)
        {
            mprintf((1, "midiStreamProperty() -> %04X\n", (WORD)mmrc));
            midiStreamClose(pSeq->hmidi);
            pSeq->hmidi = NULL;
            mmrc = MCIERR_DEVICE_NOT_READY;
            goto seq_Preroll_Cleanup;
        }
    }

    mmrc = MMSYSERR_NOERROR;

    //
    //  Allocate a preroll buffer.  Then if we don't have enough room for
    //  all the preroll info, we make the buffer larger.  
    //
    if (!pSeq->lpmhPreroll)
    {
        cbPrerollBuffer = 4096;
        lpmhPreroll = (LPMIDIHDR)GlobalAllocPtr(GMEM_MOVEABLE|GMEM_SHARE,
                                                            cbPrerollBuffer);
    }
    else
    {
        cbPrerollBuffer = pSeq->cbPreroll;
        lpmhPreroll = pSeq->lpmhPreroll;
    }

    lpmhPreroll->lpNext            = pSeq->lpmhFree;
    lpmhPreroll->lpData            = (LPBYTE)lpmhPreroll + sizeof(MIDIHDR);
    lpmhPreroll->dwBufferLength    = cbPrerollBuffer - sizeof(MIDIHDR);
    lpmhPreroll->dwBytesRecorded   = 0;
    lpmhPreroll->dwUser            = (DWORD)(UINT)pSeq;
    lpmhPreroll->dwFlags           = 0;

    do
    {
        smfrc = smfSeek(pSeq->hSmf, pSeq->tkBase, lpmhPreroll);
        if( SMF_SUCCESS != smfrc )
        {
            if( ( SMF_NO_MEMORY != smfrc )  ||
                ( cbPrerollBuffer >= 32768L ) )
            {
                mprintf((1, "smfSeek() returned %lu\n", (DWORD)smfrc));

                GlobalFreePtr(lpmhPreroll);
                pSeq->lpmhPreroll = NULL;

                mmrc = XlatSMFErr(smfrc);
                goto seq_Preroll_Cleanup;
            }
            else   //  Try to grow buffer.
            {
                cbPrerollBuffer *= 2;
                lpmh = (LPMIDIHDR)GlobalReAllocPtr( lpmhPreroll, cbPrerollBuffer, 0 );
                if( NULL == lpmh )
                {
                    mprintf((1,"seqPreroll - realloc failed, aborting preroll.\n"));
                    mmrc = MCIERR_OUT_OF_MEMORY;
                    goto seq_Preroll_Cleanup;
                }

                lpmhPreroll = lpmh;
                lpmhPreroll->lpData = (LPBYTE)lpmhPreroll + sizeof(MIDIHDR);
                lpmhPreroll->dwBufferLength = cbPrerollBuffer - sizeof(MIDIHDR);

                pSeq->lpmhPreroll = lpmhPreroll;
                pSeq->cbPreroll = cbPrerollBuffer;
            }
        }
    } while( SMF_SUCCESS != smfrc );

    if (MMSYSERR_NOERROR != (mmrc = midiOutPrepareHeader(pSeq->hmidi, lpmhPreroll, sizeof(MIDIHDR))))
    {
        mprintf((1, "midiOutPrepare(preroll) -> %lu!\n", (DWORD)mmrc));

        mmrc = MCIERR_DEVICE_NOT_READY;
        goto seq_Preroll_Cleanup;
    }

    ++pSeq->uBuffersInMMSYSTEM;

    if (MMSYSERR_NOERROR != (mmrc = midiStreamOut(pSeq->hmidi, lpmhPreroll, sizeof(MIDIHDR))))
    {
        mprintf((1, "midiStreamOut(preroll) -> %lu!\n", (DWORD)mmrc));

        mmrc = MCIERR_DEVICE_NOT_READY;
        --pSeq->uBuffersInMMSYSTEM;
        goto seq_Preroll_Cleanup;
    }
    mprintf((1,"seqPreroll: midiStreamOut(0x%x,0x%lx,%u) returned %u.\n",pSeq->hmidi,lpmhPreroll,sizeof(MIDIHDR),mmrc));

    pSeq->fdwSeq &= ~SEQ_F_EOF;
    while (pSeq->lpmhFree)
    {
        lpmh = pSeq->lpmhFree;
        pSeq->lpmhFree = lpmh->lpNext;

        smfrc = smfReadEvents(pSeq->hSmf, lpmh, pSeq->tkEnd);
        if (SMF_SUCCESS != smfrc && SMF_END_OF_FILE != smfrc)
        {
            mprintf((1, "SFP: smfReadEvents() -> %u\n", (UINT)smfrc));
            mmrc = XlatSMFErr(smfrc);
            goto seq_Preroll_Cleanup;
        }

        if (MMSYSERR_NOERROR != (mmrc = midiOutPrepareHeader(pSeq->hmidi, lpmh, sizeof(*lpmh))))
        {
            mprintf((1, "SFP: midiOutPrepareHeader failed\n"));
            goto seq_Preroll_Cleanup;
        }

        if (MMSYSERR_NOERROR != (mmrc = midiStreamOut(pSeq->hmidi, lpmh, sizeof(*lpmh))))
        {
            mprintf((1, "SFP: midiStreamOut failed\n"));
            goto seq_Preroll_Cleanup;
        }

        ++pSeq->uBuffersInMMSYSTEM; 

        if (SMF_END_OF_FILE == smfrc)
        {
            pSeq->fdwSeq |= SEQ_F_EOF;
            break;
        }
    } 

seq_Preroll_Cleanup:
    if (MMSYSERR_NOERROR != mmrc)
    {
        pSeq->uState = SEQ_S_OPENED;
        pSeq->fdwSeq &= ~SEQ_F_WAITING;
    }
    else
    {
        pSeq->uState = SEQ_S_PREROLLED;
    }

    return mmrc;
}

/***************************************************************************
*  
* seqStart
*
* Starts playback at the current position.
*       
***************************************************************************/
MMRESULT FNLOCAL seqStart(
    PSEQ                    pSeq)
{
    Assert(NULL != pSeq);

    if (SEQ_S_PREROLLED != pSeq->uState)
    {
        mprintf((1, "seqStart(): State is wrong! [%u]\n", pSeq->uState));
        return MCIERR_UNSUPPORTED_FUNCTION;
    }

    pSeq->uState = SEQ_S_PLAYING;

    return midiStreamRestart(pSeq->hmidi);
}

/***************************************************************************
*  
* seqPause
*
***************************************************************************/
MMRESULT FNLOCAL seqPause(
    PSEQ                    pSeq)
{
    Assert(NULL != pSeq);
    
    if (SEQ_S_PLAYING != pSeq->uState)
        return MCIERR_UNSUPPORTED_FUNCTION;

    pSeq->uState = SEQ_S_PAUSED;
    midiStreamPause(pSeq->hmidi);
    
    return MMSYSERR_NOERROR;
}

/***************************************************************************
*  
* seqRestart
*
***************************************************************************/
MMRESULT FNLOCAL seqRestart(
    PSEQ                    pSeq)
{
    Assert(NULL != pSeq);

    if (SEQ_S_PAUSED != pSeq->uState)
        return MCIERR_UNSUPPORTED_FUNCTION;

    pSeq->uState = SEQ_S_PLAYING;
    midiStreamRestart(pSeq->hmidi);

    return MMSYSERR_NOERROR;
}

/***************************************************************************
*  
* seqStop
*
* Totally stops playback of an instance.
*
***************************************************************************/
MMRESULT FNLOCAL seqStop(
    PSEQ                    pSeq)
{
    Assert(NULL != pSeq);

    /* Automatic success if we're already stopped
    */
    if (SEQ_S_PLAYING != pSeq->uState &&
        SEQ_S_PAUSED != pSeq->uState)
    {
        pSeq->fdwSeq &= ~SEQ_F_WAITING;
        return MMSYSERR_NOERROR;
    }

    pSeq->uState = SEQ_S_STOPPING;
    pSeq->fdwSeq |= SEQ_F_WAITING;
    
    if (MMSYSERR_NOERROR != (pSeq->mmrcLastErr = midiStreamStop(pSeq->hmidi)))
    {
        mprintf((1, "midiOutStop() returned %lu in seqStop()!\n", (DWORD)pSeq->mmrcLastErr));
        
        pSeq->fdwSeq &= ~SEQ_F_WAITING;
        return MCIERR_DEVICE_NOT_READY;
    }

	while (pSeq->uBuffersInMMSYSTEM)
		Sleep(0);
    
    return MMSYSERR_NOERROR;
}

/***************************************************************************
*  
* seqTime
*
***************************************************************************/
MMRESULT FNLOCAL seqTime(
    PSEQ                    pSeq,
    PTICKS                  pTicks)
{
    MMRESULT                mmr;
    MMTIME                  mmt;
    
    Assert(pSeq != NULL);

    if (SEQ_S_PLAYING != pSeq->uState &&
        SEQ_S_PAUSED != pSeq->uState &&
        SEQ_S_PREROLLING != pSeq->uState &&
        SEQ_S_PREROLLED != pSeq->uState &&
        SEQ_S_OPENED != pSeq->uState)
    {
        mprintf((1, "seqTime(): State wrong! [is %u]\n", pSeq->uState));
        return MCIERR_UNSUPPORTED_FUNCTION;
    }

    *pTicks = 0;
    if (SEQ_S_OPENED != pSeq->uState)
    {
        *pTicks = pSeq->tkBase;
        if (SEQ_S_PREROLLED != pSeq->uState)
        {
            mmt.wType = TIME_TICKS;
            mmr = midiStreamPosition(pSeq->hmidi, &mmt, sizeof(mmt));
            if (MMSYSERR_NOERROR != mmr)
            {
                mprintf((1, "midiStreamPosition() returned %lu\n", (DWORD)mmr));
                return MCIERR_DEVICE_NOT_READY;
            }

            *pTicks += mmt.u.ticks;
        }
    }

    return MMSYSERR_NOERROR;
}
                              
/***************************************************************************
*  
* seqMillisecsToTicks
*
* Given a millisecond offset in the output stream, returns the associated
* tick position.
*
***************************************************************************/
TICKS FNLOCAL seqMillisecsToTicks(
    PSEQ                    pSeq,
    DWORD                   msOffset)
{
    return smfMillisecsToTicks(pSeq->hSmf, msOffset);
}

/***************************************************************************
*  
* seqTicksToMillisecs
*
* Given a tick offset in the output stream, returns the associated
* millisecond position.
*
***************************************************************************/
DWORD FNLOCAL seqTicksToMillisecs(
    PSEQ                    pSeq,
    TICKS                   tkOffset)
{
    return smfTicksToMillisecs(pSeq->hSmf, tkOffset);
}

/***************************************************************************
*  
* seqMIDICallback
*
* Called by the system when a buffer is done
*
* dw1                       - The buffer that has completed playback.
*
***************************************************************************/

PRIVATE void FAR PASCAL seqMIDICallback(HMIDISTRM hms, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2)
{
	LPMIDIHDR					lpmh		= (LPMIDIHDR)dw1;
    PSEQ                    pSeq;
    MMRESULT                mmrc;
    SMFRESULT               smfrc;
	//DWORD vol;

	if (uMsg != MOM_DONE)
		return;

	Assert(NULL != lpmh);

    pSeq = (PSEQ)(lpmh->dwUser);

    Assert(pSeq != NULL);

    --pSeq->uBuffersInMMSYSTEM;
    
	if (pSeq->uState == SEQ_S_NOFILE) 
		mprintf((1, "seqCallback: No file!!\n"));

    if (SEQ_S_RESET == pSeq->uState)
    {
        // We're recollecting buffers from MMSYSTEM
        //
		if (lpmh != pSeq->lpmhPreroll)
		{
        	lpmh->lpNext   = pSeq->lpmhFree;
        	pSeq->lpmhFree = lpmh;
		}

        return;
    }
    

    if ((SEQ_S_STOPPING == pSeq->uState) || (pSeq->fdwSeq & SEQ_F_EOF))
    {
        /*
        ** Reached EOF, just put the buffer back on the free
        ** list 
        */
		if (lpmh != pSeq->lpmhPreroll)
		{
        	lpmh->lpNext   = pSeq->lpmhFree;
        	pSeq->lpmhFree = lpmh;
		}
			if (!pSeq->hmidi) 
				mprintf((1, "seqCallback: NULL MIDI HANDLE.\n"));

        if (MMSYSERR_NOERROR != (mmrc = midiOutUnprepareHeader(pSeq->hmidi, lpmh, sizeof(*lpmh))))
        {
            mprintf((1, "midiOutUnprepareHeader failed in seqBufferDone! (%lu)\n", (DWORD)mmrc));
        }

        if (0 == pSeq->uBuffersInMMSYSTEM)
        {
				int stop_loop=0;
            mprintf((1, "seqBufferDone: normal sequencer shutdown.\n"));
            
            /* Totally done! Free device and notify.
            */
            midiStreamClose(pSeq->hmidi);
            
				if ((SEQ_S_STOPPING == pSeq->uState) && (pSeq->fdwSeq & SEQ_F_EOF) && LoopSong) {
					stop_loop = 1;
					mprintf((1, "seqMIDICallback: cancelling loop.\n"));
				}
            pSeq->hmidi = NULL;
            pSeq->uState = SEQ_S_OPENED;
            pSeq->mmrcLastErr = MMSYSERR_NOERROR;
            pSeq->fdwSeq &= ~SEQ_F_WAITING;

				if ((pSeq->fdwSeq & SEQ_F_EOF) && LoopSong && !stop_loop)
					wmidi_play(); 
        }
    }
    else
    {
        /*
        ** Not EOF yet; attempt to fill another buffer
        */
		  DWORD vol;

        smfrc = smfReadEvents(pSeq->hSmf, lpmh, pSeq->tkEnd);
        
        switch(smfrc)
        {
            case SMF_SUCCESS:
                break;

            case SMF_END_OF_FILE:
                pSeq->fdwSeq |= SEQ_F_EOF;
                smfrc = SMF_SUCCESS;
                break;

            default:
                mprintf((1, "smfReadEvents returned %lu in callback!\n", (DWORD)smfrc));
                pSeq->uState = SEQ_S_STOPPING;
                break;
        }

        if (SMF_SUCCESS == smfrc)
        {
            ++pSeq->uBuffersInMMSYSTEM;

				Assert(pSeq->hmidi != NULL);

//				wmidi_deamp_song(wmidi_volume);
				mmrc = midiOutSetVolume((HMIDIOUT)MIDI_MAPPER, wmidi_volume);	
				if (mmrc != MMSYSERR_NOERROR) mprintf((1, "MIDI VOL ERR: %d\n", mmrc));
				midiOutGetVolume((HMIDIOUT)MIDI_MAPPER, &vol);

            mmrc = midiStreamOut(pSeq->hmidi, lpmh, sizeof(*lpmh));
				WMidi_NewStream++;
            if (MMSYSERR_NOERROR != mmrc)
            {
                mprintf((1, "seqBufferDone(): midiStreamOut() returned %lu!\n", (DWORD)mmrc));
                
                --pSeq->uBuffersInMMSYSTEM;
                pSeq->uState = SEQ_S_STOPPING;
         --pSeq->uBuffersInMMSYSTEM;
                pSeq->uState = SEQ_S_STOPPING;
            }
        }
    }

}

/***************************************************************************
*  
* XlatSMFErr
*
* Translates an error from the SMF layer into an appropriate MCI error.
*
***************************************************************************/
PRIVATE MMRESULT FNLOCAL XlatSMFErr(
    SMFRESULT               smfrc)
{
    switch(smfrc)
    {
        case SMF_SUCCESS:
            return MMSYSERR_NOERROR;

        case SMF_NO_MEMORY:
            return MCIERR_OUT_OF_MEMORY;

        case SMF_INVALID_FILE:
        case SMF_OPEN_FAILED:
        case SMF_INVALID_TRACK:
            return MCIERR_INVALID_FILE;

        default:
            return MCIERR_UNSUPPORTED_FUNCTION;
    }
}






