1007 lines
28 KiB
C++
1007 lines
28 KiB
C++
#include "CKAll.h"
|
|
|
|
|
|
|
|
#define SNDINTERNAL
|
|
/*****************************************************************************
|
|
*
|
|
* pSeq 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 - 1996 Microsoft Corporation. All Rights Reserved.
|
|
*
|
|
******************************************************************************
|
|
*
|
|
*MidiSound::uence.C
|
|
*
|
|
*MidiSound::uencer engine for MIDI player app
|
|
*
|
|
*****************************************************************************/
|
|
|
|
#include <windows.h>
|
|
#include <windowsx.h>
|
|
#include <mmsystem.h>
|
|
#include <limits.h>
|
|
|
|
#include "MidiSound.h"
|
|
|
|
static void FAR PASCAL seqMIDICallback(HMIDISTRM hms, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2);
|
|
static MMRESULT XlatSMFErr(SMFRESULT smfrc);
|
|
|
|
|
|
extern CRITICAL_SECTION gMidiCS;
|
|
|
|
// Fichier de son associe
|
|
MMRESULT MidiSound::SetSoundFileName(const char * name) {
|
|
if (name==NULL) {
|
|
return MCIERR_INVALID_FILE;
|
|
}
|
|
|
|
pSeq->pstrFile=CKStrdup((char *)name);
|
|
return OpenFile();
|
|
}
|
|
|
|
const char* MidiSound::GetSoundFileName() {
|
|
return pSeq->pstrFile;
|
|
}
|
|
|
|
|
|
BOOL MidiSound::IsPlaying()
|
|
{
|
|
return (pSeq->uState==SEQ_S_PLAYING);
|
|
}
|
|
|
|
BOOL MidiSound::IsPaused()
|
|
{
|
|
return (pSeq->uState==SEQ_S_PAUSED);
|
|
}
|
|
|
|
/***************************************************************************
|
|
*
|
|
*MidiSound::AllocBuffers
|
|
*
|
|
* Allocate buffers for pSeq instance.
|
|
*
|
|
* pSeq - TheMidiSound::uencer instance to allocate buffers for.
|
|
*
|
|
* Returns
|
|
* MMSYSERR_NOERROR If the operation was successful.
|
|
*
|
|
* MCIERR_OUT_OF_MEMORY If there is insufficient memory for
|
|
* the requested number and size of buffers.
|
|
*
|
|
*MidiSound::AllocBuffers allocates playback buffers based on the
|
|
* cbBuffer and cBuffer fields of pSeq. cbBuffer specifies the
|
|
* number of bytes in each buffer, and cBuffer specifies the
|
|
* number of buffers to allocate.
|
|
*
|
|
*MidiSound::AllocBuffers must be called before any otherMidiSound::uencer call
|
|
* on a newly alloctedMidiSound::UENCE structure. It must be paired with
|
|
* a call toMidiSound::FreeBuffers, which should be the last call made
|
|
* before theMidiSound::UENCE structure is discarded.
|
|
*
|
|
***************************************************************************/
|
|
MMRESULT MidiSound::AllocBuffers()
|
|
{
|
|
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 = (unsigned char*) 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 = (char*)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;
|
|
}
|
|
|
|
/***************************************************************************
|
|
*
|
|
*MidiSound::FreeBuffers
|
|
*
|
|
* Free buffers for pSeq instance.
|
|
*
|
|
* pSeq - TheMidiSound::uencer instance to free buffers for.
|
|
*
|
|
*MidiSound::FreeBuffers frees all allocated memory belonging to the
|
|
* givenMidiSound::uencer instance pSeq. It must be the last call
|
|
* performed on the instance before it is destroyed.
|
|
*
|
|
****************************************************************************/
|
|
VOID MidiSound::FreeBuffers()
|
|
{
|
|
LPMIDIHDR lpmh;
|
|
|
|
//assert(pSeq != NULL);
|
|
|
|
if (NULL != pSeq->lpbAlloc)
|
|
{
|
|
lpmh = (LPMIDIHDR)pSeq->lpbAlloc;
|
|
//assert(!(lpmh->dwFlags & MHDR_PREPARED));
|
|
|
|
GlobalFreePtr(pSeq->lpbAlloc);
|
|
}
|
|
}
|
|
|
|
/***************************************************************************
|
|
*
|
|
*MidiSound::OpenFile
|
|
*
|
|
* Associates a MIDI file with the givenMidiSound::uencer instance.
|
|
*
|
|
* pSeq - TheMidiSound::uencer instance.
|
|
*
|
|
* Returns
|
|
* MMSYSERR_NOERROR If the operation is successful.
|
|
*
|
|
* MCIERR_UNSUPPORTED_FUNCTION If there is already a file open
|
|
* on pSeq instance.
|
|
*
|
|
* MCIERR_OUT_OF_MEMORY If there was insufficient memory to
|
|
* allocate internal buffers on the file.
|
|
*
|
|
* MCIERR_INVALID_FILE If initial attempts to parse the file
|
|
* failed (such as the file is not a MIDI or RMI file).
|
|
*
|
|
*MidiSound::OpenFile may only be called if there is no currently open file
|
|
* on the instance. It must be paired with a call toMidiSound::CloseFile
|
|
* when operations on pSeq file are complete.
|
|
*
|
|
* The pstrFile field of pSeq contains the name of the file
|
|
* to open. pSeq name will be passed directly to mmioOpen; it may
|
|
* contain a specifcation for a custom MMIO file handler. The task
|
|
* context used for all I/O will be the task which callsMidiSound::OpenFile.
|
|
*
|
|
***************************************************************************/
|
|
MMRESULT MidiSound::OpenFile()
|
|
{
|
|
MMRESULT rc = MMSYSERR_NOERROR;
|
|
SMFOPENFILESTRUCT sofs;
|
|
SMFFILEINFO sfi;
|
|
SMFRESULT smfrc;
|
|
DWORD cbBuffer;
|
|
|
|
//assert(pSeq != NULL);
|
|
|
|
if (pSeq->uState !=SEQ_S_NOFILE)
|
|
{
|
|
return MCIERR_UNSUPPORTED_FUNCTION;
|
|
}
|
|
|
|
//assert(pSeq->pstrFile != NULL);
|
|
|
|
sofs.pstrName = pSeq->pstrFile;
|
|
|
|
smfrc = smfOpenFile(&sofs);
|
|
if (SMF_SUCCESS != smfrc)
|
|
{
|
|
rc = XlatSMFErr(smfrc);
|
|
goto Seq_Open_File_Cleanup;
|
|
}
|
|
|
|
pSeq->hSmf = sofs.hSmf;
|
|
smfGetFileInfo(pSeq->hSmf, &sfi);
|
|
|
|
pSeq->dwTimeDivision = sfi.dwTimeDivision;
|
|
pSeq->tkLength = sfi.tkLength;
|
|
pSeq->cTrk = sfi.dwTracks;
|
|
|
|
/* Track buffers must be big enough to hold the state data returned
|
|
** by smfSeek()
|
|
*/
|
|
cbBuffer = __min(pSeq->cbBuffer, smfGetStateMaxSize());
|
|
|
|
Seq_Open_File_Cleanup:
|
|
if (MMSYSERR_NOERROR != rc)
|
|
CloseFile();
|
|
else
|
|
pSeq->uState =SEQ_S_OPENED;
|
|
|
|
return rc;
|
|
}
|
|
|
|
/***************************************************************************
|
|
*
|
|
*MidiSound::CloseFile
|
|
*
|
|
* Deassociates a MIDI file with the givenMidiSound::uencer instance.
|
|
*
|
|
* pSeq - TheMidiSound::uencer instance.
|
|
*
|
|
* Returns
|
|
* MMSYSERR_NOERROR If the operation is successful.
|
|
*
|
|
* MCIERR_UNSUPPORTED_FUNCTION If theMidiSound::uencer instance is not
|
|
* stopped.
|
|
*
|
|
* A call toMidiSound::CloseFile must be paired with a prior call to
|
|
*MidiSound::OpenFile. All buffers associated with the file will be
|
|
* freed and the file will be closed. TheMidiSound::uencer must be
|
|
* stopped before pSeq call will be accepted.
|
|
*
|
|
***************************************************************************/
|
|
MMRESULT MidiSound::CloseFile()
|
|
{
|
|
LPMIDIHDR lpmh;
|
|
|
|
//assert(pSeq != NULL);
|
|
|
|
if (SEQ_S_OPENED != pSeq->uState)
|
|
return MCIERR_UNSUPPORTED_FUNCTION;
|
|
|
|
if ((HSMF)NULL != pSeq->hSmf)
|
|
{
|
|
smfCloseFile(pSeq->hSmf);
|
|
pSeq->hSmf = (HSMF)NULL;
|
|
}
|
|
|
|
/* If we were prerolled, need to clean up -- have an open MIDI handle
|
|
** and buffers in the ready queue
|
|
*/
|
|
//EnterCriticalSection(&gMidiCS);
|
|
|
|
for (lpmh = pSeq->lpmhFree; lpmh; lpmh = lpmh->lpNext)
|
|
midiOutUnprepareHeader(pSeq->hmidi, lpmh, sizeof(*lpmh));
|
|
|
|
if (pSeq->lpmhPreroll)
|
|
{
|
|
midiOutUnprepareHeader(pSeq->hmidi, pSeq->lpmhPreroll, sizeof(*pSeq->lpmhPreroll));
|
|
(void) GlobalFreePtr(pSeq->lpmhPreroll);
|
|
}
|
|
|
|
if (pSeq->hmidi != NULL)
|
|
{
|
|
// cast by aymeric
|
|
//EnterCriticalSection(&gMidiCS);
|
|
midiStreamClose((HMIDISTRM)pSeq->hmidi);
|
|
pSeq->hmidi = NULL;
|
|
//LeaveCriticalSection(&gMidiCS);
|
|
}
|
|
pSeq->uState =SEQ_S_NOFILE;
|
|
|
|
//LeaveCriticalSection(&gMidiCS);
|
|
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
/***************************************************************************
|
|
*
|
|
*MidiSound::Preroll
|
|
*
|
|
* Prepares the file for playback at the given position.
|
|
*
|
|
* pSeq - TheMidiSound::uencer instance.
|
|
*
|
|
* lpPreroll - Specifies the starting and ending tick
|
|
* positions to play between.
|
|
*
|
|
* Returns
|
|
* MMSYSERR_NOERROR If the operation is successful.
|
|
*
|
|
* MCIERR_UNSUPPORTED_FUNCTION If theMidiSound::uencer instance is not
|
|
* opened or prerolled.
|
|
*
|
|
* Open the device so we can initialize channels.
|
|
*
|
|
* Loop through the tracks. For each track, seek to the given position and
|
|
* send the init data SMF gives us to the handle.
|
|
*
|
|
* Wait for all init buffers to finish.
|
|
*
|
|
* Unprepare the buffers (they're only ever sent here; theMidiSound::uencer
|
|
* engine merges them into a single stream during normal playback) and
|
|
* refill them with the first chunk of data from the track.
|
|
*
|
|
*
|
|
****************************************************************************/
|
|
MMRESULT MidiSound::Preroll()
|
|
{
|
|
pSeq->tkBase=0;
|
|
pSeq->tkEnd=pSeq->tkLength;
|
|
struct tag_preroll prerroll={0,pSeq->tkLength};
|
|
LPPREROLL lpPreroll=&prerroll;
|
|
SMFRESULT smfrc;
|
|
MMRESULT mmrc = MMSYSERR_NOERROR;
|
|
MIDIPROPTIMEDIV mptd;
|
|
LPMIDIHDR lpmh = NULL;
|
|
LPMIDIHDR lpmhPreroll = NULL;
|
|
DWORD cbPrerollBuffer;
|
|
UINT uDeviceID=MIDI_MAPPER ;
|
|
|
|
//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
|
|
//
|
|
//EnterCriticalSection(&gMidiCS);
|
|
pSeq->uState =SEQ_S_RESET;
|
|
midiOutReset(pSeq->hmidi);
|
|
//LeaveCriticalSection(&gMidiCS);
|
|
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:MidiSound::Preroll is equivalent to seek; device might already be open
|
|
//
|
|
if (NULL == pSeq->hmidi)
|
|
{
|
|
uDeviceID = pSeq->uDeviceID;
|
|
uDeviceID = MIDI_MAPPER;
|
|
// cast by aymeric
|
|
if ((mmrc = midiStreamOpen((LPHMIDISTRM)&pSeq->hmidi,
|
|
&uDeviceID,
|
|
1,
|
|
(DWORD)seqMIDICallback,
|
|
0,
|
|
CALLBACK_FUNCTION)) != MMSYSERR_NOERROR)
|
|
{
|
|
pSeq->hmidi = NULL;
|
|
goto Preroll_Cleanup;
|
|
}
|
|
|
|
mptd.cbStruct = sizeof(mptd);
|
|
mptd.dwTimeDiv = pSeq->dwTimeDivision;
|
|
// cast by aymeric
|
|
if ((mmrc = midiStreamProperty(
|
|
(HMIDISTRM)pSeq->hmidi,
|
|
(LPBYTE)&mptd,
|
|
MIDIPROP_SET|MIDIPROP_TIMEDIV)) != MMSYSERR_NOERROR)
|
|
{
|
|
//DPF(1, "midiStreamProperty() -> %04X", (WORD)mmrc);
|
|
//EnterCriticalSection(&gMidiCS);
|
|
midiStreamClose((HMIDISTRM)pSeq->hmidi);
|
|
pSeq->hmidi = NULL;
|
|
mmrc = MCIERR_DEVICE_NOT_READY;
|
|
//LeaveCriticalSection(&gMidiCS);
|
|
goto 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 = (char*)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 ) )
|
|
{
|
|
//DPF(1, "smfSeek() returned %lu", (DWORD)smfrc);
|
|
|
|
GlobalFreePtr(lpmhPreroll);
|
|
pSeq->lpmhPreroll = NULL;
|
|
|
|
mmrc = XlatSMFErr(smfrc);
|
|
goto Preroll_Cleanup;
|
|
}
|
|
else // Try to grow buffer.
|
|
{
|
|
cbPrerollBuffer *= 2;
|
|
lpmh = (LPMIDIHDR)GlobalReAllocPtr( lpmhPreroll, cbPrerollBuffer, 0 );
|
|
if( NULL == lpmh )
|
|
{
|
|
//DPF(2,"seqPreroll - realloc failed, aborting preroll.");
|
|
mmrc = MCIERR_OUT_OF_MEMORY;
|
|
goto Preroll_Cleanup;
|
|
}
|
|
|
|
lpmhPreroll = lpmh;
|
|
lpmhPreroll->lpData = (char*)lpmhPreroll + sizeof(MIDIHDR);
|
|
lpmhPreroll->dwBufferLength = cbPrerollBuffer - sizeof(MIDIHDR);
|
|
|
|
pSeq->lpmhPreroll = lpmhPreroll;
|
|
pSeq->cbPreroll = cbPrerollBuffer;
|
|
}
|
|
}
|
|
} while( SMF_SUCCESS != smfrc );
|
|
|
|
if (!pSeq->lpmhPreroll)
|
|
{
|
|
//to keep access to buffer and free memory before closing
|
|
pSeq->lpmhPreroll = lpmhPreroll;
|
|
pSeq->cbPreroll = cbPrerollBuffer;
|
|
}
|
|
|
|
if (MMSYSERR_NOERROR != (mmrc = midiOutPrepareHeader(pSeq->hmidi, lpmhPreroll, sizeof(MIDIHDR))))
|
|
{
|
|
//DPF(1, "midiOutPrepare(preroll) -> %lu!", (DWORD)mmrc);
|
|
|
|
mmrc = MCIERR_DEVICE_NOT_READY;
|
|
goto Preroll_Cleanup;
|
|
}
|
|
|
|
++pSeq->uBuffersInMMSYSTEM;
|
|
|
|
if (MMSYSERR_NOERROR != (mmrc = midiStreamOut((HMIDISTRM)pSeq->hmidi, lpmhPreroll, sizeof(MIDIHDR))))
|
|
{
|
|
//DPF(1, "midiStreamOut(preroll) -> %lu!", (DWORD)mmrc);
|
|
|
|
mmrc = MCIERR_DEVICE_NOT_READY;
|
|
--pSeq->uBuffersInMMSYSTEM;
|
|
goto Preroll_Cleanup;
|
|
}
|
|
//DPF(3,"seqPreroll: midiStreamOut(0x%x,0x%lx,%u) returned %u.",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)
|
|
{
|
|
//DPF(1, "SFP: smfReadEvents() -> %u", (UINT)smfrc);
|
|
mmrc = XlatSMFErr(smfrc);
|
|
goto Preroll_Cleanup;
|
|
}
|
|
|
|
if (MMSYSERR_NOERROR != (mmrc = midiOutPrepareHeader(pSeq->hmidi, lpmh, sizeof(*lpmh))))
|
|
{
|
|
//DPF(1, "SFP: midiOutPrepareHeader failed");
|
|
goto Preroll_Cleanup;
|
|
}
|
|
|
|
if (MMSYSERR_NOERROR != (mmrc = midiStreamOut((HMIDISTRM)pSeq->hmidi, lpmh, sizeof(*lpmh))))
|
|
{
|
|
//DPF(1, "SFP: midiStreamOut failed");
|
|
goto Preroll_Cleanup;
|
|
}
|
|
|
|
++pSeq->uBuffersInMMSYSTEM;
|
|
|
|
if (SMF_END_OF_FILE == smfrc)
|
|
{
|
|
pSeq->fdwSeq |=SEQ_F_EOF;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Preroll_Cleanup:
|
|
if (MMSYSERR_NOERROR != mmrc)
|
|
{
|
|
pSeq->uState =SEQ_S_OPENED;
|
|
pSeq->fdwSeq &= ~SEQ_F_WAITING;
|
|
}
|
|
else
|
|
{
|
|
pSeq->uState =SEQ_S_PREROLLED;
|
|
}
|
|
|
|
return mmrc;
|
|
}
|
|
|
|
/***************************************************************************
|
|
*
|
|
*MidiSound::Start
|
|
*
|
|
* Starts playback at the current position.
|
|
*
|
|
* pSeq - TheMidiSound::uencer instance.
|
|
*
|
|
* Returns
|
|
* MMSYSERR_NOERROR If the operation is successful.
|
|
*
|
|
* MCIERR_UNSUPPORTED_FUNCTION If theMidiSound::uencer instance is not
|
|
* stopped.
|
|
*
|
|
* MCIERR_DEVICE_NOT_READY If the underlying MIDI device could
|
|
* not be opened or fails any call.
|
|
*
|
|
* TheMidiSound::uencer must be prerolled beforeMidiSound::Start may be called.
|
|
*
|
|
* Just feed everything in the ready queue to the device.
|
|
*
|
|
***************************************************************************/
|
|
MMRESULT MidiSound::Start()
|
|
{
|
|
if (NULL == pSeq)
|
|
{
|
|
return MCIERR_UNSUPPORTED_FUNCTION;
|
|
}
|
|
|
|
if (SEQ_S_PREROLLED != pSeq->uState)
|
|
{
|
|
Preroll();
|
|
}
|
|
|
|
if (SEQ_S_PREROLLED != pSeq->uState)
|
|
{
|
|
//DPF(1, "seqStart(): State is wrong! [%u]", pSeq->uState);
|
|
return MCIERR_UNSUPPORTED_FUNCTION;
|
|
}
|
|
|
|
pSeq->uState =SEQ_S_PLAYING;
|
|
|
|
return midiStreamRestart((HMIDISTRM)pSeq->hmidi);
|
|
}
|
|
|
|
/***************************************************************************
|
|
*
|
|
*MidiSound::Pause
|
|
*
|
|
* Pauses playback of the instance.
|
|
*
|
|
* pSeq - TheMidiSound::uencer instance.
|
|
*
|
|
* Returns
|
|
* MMSYSERR_NOERROR If the operation is successful.
|
|
*
|
|
* MCIERR_UNSUPPORTED_FUNCTION If theMidiSound::uencer instance is not
|
|
* playing.
|
|
*
|
|
* TheMidiSound::uencer must be playing beforeMidiSound::Pause may be called.
|
|
* Pausing theMidiSound::uencer will cause all currently on notes to be turned
|
|
* off. pSeq may cause playback to be slightly inaccurate on restart
|
|
* due to missing notes.
|
|
*
|
|
***************************************************************************/
|
|
MMRESULT MidiSound::Pause()
|
|
{
|
|
//assert(NULL != pSeq);
|
|
|
|
if (SEQ_S_PLAYING != pSeq->uState)
|
|
return MCIERR_UNSUPPORTED_FUNCTION;
|
|
|
|
pSeq->uState =SEQ_S_PAUSED;
|
|
|
|
OutputDebugString("MidiSound::Pause\n");
|
|
|
|
//EnterCriticalSection(&gMidiCS);
|
|
midiStreamPause((HMIDISTRM)pSeq->hmidi);
|
|
//LeaveCriticalSection(&gMidiCS);
|
|
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
/***************************************************************************
|
|
*
|
|
*MidiSound::Restart
|
|
*
|
|
* Restarts playback of an instance after a pause.
|
|
*
|
|
* pSeq - TheMidiSound::uencer instance.
|
|
*
|
|
* Returns
|
|
* MMSYSERR_NOERROR If the operation is successful.
|
|
*
|
|
* MCIERR_UNSUPPORTED_FUNCTION If theMidiSound::uencer instance is not
|
|
* paused.
|
|
*
|
|
* TheMidiSound::uencer must be paused beforeMidiSound::Restart may be called.
|
|
*
|
|
***************************************************************************/
|
|
MMRESULT MidiSound::Restart()
|
|
{
|
|
//assert(NULL != pSeq);
|
|
|
|
switch(pSeq->uState){
|
|
case SEQ_S_PAUSED:
|
|
pSeq->uState =SEQ_S_PLAYING;
|
|
midiStreamRestart((HMIDISTRM)pSeq->hmidi);
|
|
break;
|
|
|
|
case SEQ_S_OPENED:
|
|
Start();
|
|
break;
|
|
}
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
/***************************************************************************
|
|
*
|
|
*MidiSound::Stop
|
|
*
|
|
* Totally stops playback of an instance.
|
|
*
|
|
* pSeq - TheMidiSound::uencer instance.
|
|
*
|
|
* Returns
|
|
* MMSYSERR_NOERROR If the operation is successful.
|
|
*
|
|
* MCIERR_UNSUPPORTED_FUNCTION If theMidiSound::uencer instance is not
|
|
* paused or playing.
|
|
*
|
|
* TheMidiSound::uencer must be paused or playing beforeMidiSound::Stop may be called.
|
|
*
|
|
***************************************************************************/
|
|
MMRESULT MidiSound::Stop()
|
|
{
|
|
//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;
|
|
//EnterCriticalSection(&gMidiCS);
|
|
|
|
OutputDebugString("MidiSound::Stop\n");
|
|
|
|
if(pSeq->hmidi){
|
|
|
|
#if 1 //--- modification by Francisco
|
|
pSeq->mmrcLastErr = midiStreamClose((HMIDISTRM)pSeq->hmidi);
|
|
pSeq->uState = SEQ_S_OPENED;
|
|
pSeq->hmidi = 0;
|
|
pSeq->fdwSeq &= ~SEQ_F_WAITING;
|
|
return MMSYSERR_NOERROR;
|
|
#else //---
|
|
if (MMSYSERR_NOERROR != (pSeq->mmrcLastErr = midiStreamStop((HMIDISTRM)pSeq->hmidi)))
|
|
{
|
|
pSeq->fdwSeq &= ~SEQ_F_WAITING;
|
|
//LeaveCriticalSection(&gMidiCS);
|
|
return MCIERR_DEVICE_NOT_READY;
|
|
}
|
|
|
|
while (pSeq->uBuffersInMMSYSTEM)
|
|
Sleep(0);
|
|
|
|
if(pSeq->hmidi)
|
|
midiStreamClose((HMIDISTRM)pSeq->hmidi);
|
|
#endif
|
|
}
|
|
//LeaveCriticalSection(&gMidiCS);
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
/***************************************************************************
|
|
*
|
|
*MidiSound::Time
|
|
*
|
|
* Determine the current position in playback of an instance.
|
|
*
|
|
* pSeq - TheMidiSound::uencer instance.
|
|
*
|
|
* pTicks - A pointer to a DWORD where the current position
|
|
* in ticks will be returned.
|
|
*
|
|
* Returns
|
|
* MMSYSERR_NOERROR If the operation is successful.
|
|
*
|
|
* MCIERR_DEVICE_NOT_READY If the underlying device fails to report
|
|
* the position.
|
|
*
|
|
* MCIERR_UNSUPPORTED_FUNCTION If theMidiSound::uencer instance is not
|
|
* paused or playing.
|
|
*
|
|
* TheMidiSound::uencer must be paused, playing or prerolled beforeMidiSound::Time
|
|
* may be called.
|
|
*
|
|
***************************************************************************/
|
|
MMRESULT MidiSound::Time(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)
|
|
{
|
|
//DPF(1, "seqTime(): State wrong! [is %u]", 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((HMIDISTRM)pSeq->hmidi, &mmt, sizeof(mmt));
|
|
if (MMSYSERR_NOERROR != mmr)
|
|
{
|
|
//DPF(1, "midiStreamPosition() returned %lu", (DWORD)mmr);
|
|
return MCIERR_DEVICE_NOT_READY;
|
|
}
|
|
|
|
*pTicks += mmt.u.ticks;
|
|
}
|
|
}
|
|
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
/***************************************************************************
|
|
*
|
|
*MidiSound::MillisecsToTicks
|
|
*
|
|
* Given a millisecond offset in the output stream, returns the associated
|
|
* tick position.
|
|
*
|
|
* pSeq - TheMidiSound::uencer instance.
|
|
*
|
|
* msOffset - The millisecond offset into the stream.
|
|
*
|
|
* Returns the number of ticks into the stream.
|
|
*
|
|
***************************************************************************/
|
|
TICKS MidiSound::MillisecsToTicks(DWORD msOffset)
|
|
{
|
|
return smfMillisecsToTicks(pSeq->hSmf, msOffset);
|
|
}
|
|
|
|
/***************************************************************************
|
|
*
|
|
*MidiSound::TicksToMillisecs
|
|
*
|
|
* Given a tick offset in the output stream, returns the associated
|
|
* millisecond position.
|
|
*
|
|
* pSeq - TheMidiSound::uencer instance.
|
|
*
|
|
* tkOffset - The tick offset into the stream.
|
|
*
|
|
* Returns the number of milliseconds into the stream.
|
|
*
|
|
***************************************************************************/
|
|
DWORD MidiSound::TicksToMillisecs(TICKS tkOffset)
|
|
{
|
|
return smfTicksToMillisecs(pSeq->hSmf, tkOffset);
|
|
}
|
|
|
|
/***************************************************************************
|
|
*
|
|
*MidiSound::MIDICallback
|
|
*
|
|
* Called by the system when a buffer is done
|
|
*
|
|
* dw1 - The buffer that has completed playback.
|
|
*
|
|
***************************************************************************/
|
|
static void FAR PASCAL seqMIDICallback(HMIDISTRM hms, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2)
|
|
{
|
|
LPMIDIHDR lpmh = (LPMIDIHDR)dw1;
|
|
PSEQ pSeq;
|
|
MMRESULT mmrc;
|
|
SMFRESULT smfrc;
|
|
|
|
if (uMsg != MOM_DONE)
|
|
return;
|
|
|
|
//assert(NULL != lpmh);
|
|
|
|
pSeq = (PSEQ)(lpmh->dwUser);
|
|
|
|
//assert(pSeq != NULL);
|
|
|
|
--pSeq->uBuffersInMMSYSTEM;
|
|
|
|
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 (MMSYSERR_NOERROR != (mmrc = midiOutUnprepareHeader(pSeq->hmidi, lpmh, sizeof(*lpmh))))
|
|
{
|
|
//DPF(1, "midiOutUnprepareHeader failed inMidiSound::BufferDone! (%lu)", (DWORD)mmrc);
|
|
}
|
|
|
|
if (0 == pSeq->uBuffersInMMSYSTEM)
|
|
{
|
|
//DPF(1, "seqBufferDone: normalMidiSound::uencer shutdown.");
|
|
|
|
/* Totally done! Free device and notify.
|
|
*/
|
|
//EnterCriticalSection(&gMidiCS);
|
|
OutputDebugString("midiStreamClose\n");
|
|
/*
|
|
if(pSeq->hmidi)
|
|
midiStreamClose((HMIDISTRM)pSeq->hmidi);
|
|
*/
|
|
pSeq->hmidi = NULL;
|
|
pSeq->uState =SEQ_S_OPENED;
|
|
pSeq->mmrcLastErr = MMSYSERR_NOERROR;
|
|
pSeq->fdwSeq &= ~SEQ_F_WAITING;
|
|
|
|
//LeaveCriticalSection(&gMidiCS);
|
|
|
|
// lParam indicates whether or not to preroll again. Don't if we were explicitly
|
|
// stopped.
|
|
//
|
|
PostMessage(pSeq->hWnd, MMSG_DONE, (WPARAM)pSeq, (LPARAM)(SEQ_S_STOPPING != pSeq->uState));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
** Not EOF yet; attempt to fill another buffer
|
|
*/
|
|
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:
|
|
//DPF(1, "smfReadEvents returned %lu in callback!", (DWORD)smfrc);
|
|
pSeq->uState =SEQ_S_STOPPING;
|
|
break;
|
|
}
|
|
|
|
if (SMF_SUCCESS == smfrc)
|
|
{
|
|
++pSeq->uBuffersInMMSYSTEM;
|
|
mmrc = midiStreamOut((HMIDISTRM)pSeq->hmidi, lpmh, sizeof(*lpmh));
|
|
if (MMSYSERR_NOERROR != mmrc)
|
|
{
|
|
//DPF(1, "seqBufferDone(): midiStreamOut() returned %lu!", (DWORD)mmrc);
|
|
|
|
--pSeq->uBuffersInMMSYSTEM;
|
|
pSeq->uState =SEQ_S_STOPPING;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/***************************************************************************
|
|
*
|
|
* XlatSMFErr
|
|
*
|
|
* Translates an error from the SMF layer into an appropriate MCI error.
|
|
*
|
|
* smfrc - The return code from any SMF function.
|
|
*
|
|
* Returns
|
|
* A parallel error from the MCI error codes.
|
|
*
|
|
***************************************************************************/
|
|
static MMRESULT 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;
|
|
}
|
|
}
|
|
|
|
|
|
MidiSound::MidiSound(void *hwnd) {
|
|
if ((pSeq = (PSEQ)LocalAlloc(LPTR, sizeof(SEQ))) == NULL)
|
|
return;
|
|
|
|
pSeq->cBuffer = 4;
|
|
pSeq->cbBuffer = 1024;
|
|
pSeq->pstrFile = NULL;
|
|
|
|
if (AllocBuffers() != MMSYSERR_NOERROR)
|
|
return;
|
|
pSeq->hWnd = (HWND)hwnd;
|
|
}
|
|
|
|
MidiSound::~MidiSound() {
|
|
Stop();
|
|
if (pSeq->pstrFile)
|
|
free(pSeq->pstrFile);
|
|
pSeq->pstrFile=NULL;
|
|
FreeBuffers();
|
|
LocalFree(pSeq);
|
|
}
|
|
|
|
|
|
|