windows-nt/Source/XPSP1/NT/multimedia/media/mciseq/mciseq.c

1158 lines
38 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
/******************************************************************************
Copyright (c) 1985-1999 Microsoft Corporation
Title: mciseq.c - Multimedia Systems Media Control Interface
Sequencer driver for MIDI files.
Version: 1.00
Date: 24-Apr-1992
Author: Greg Simons
------------------------------------------------------------------------------
Change log:
DATE REV DESCRIPTION
----------- ----- -----------------------------------------------------------
24-APR-1990 GregSi Original
1 -OCT-1990 GregSi Merge with MMSEQ
10-MAR-1992 RobinSp Move to Windows NT
*****************************************************************************/
#define UNICODE
//MMSYSTEM
#define MMNOSOUND - Sound support
#define MMNOWAVE - Waveform support
#define MMNOAUX - Auxiliary output support
#define MMNOJOY - Joystick support
//MMDDK
#define NOWAVEDEV - Waveform support
#define NOAUXDEV - Auxiliary output support
#define NOJOYDEV - Joystick support
#include <stdlib.h>
#include <windows.h>
#include <mmsystem.h>
#include <mmddk.h>
#include <string.h>
#include <wchar.h>
#include "mmsys.h"
#include "list.h"
#include "mciseq.h"
#define CONFIG_ID 0xFFFFFFFF // Use this value to identify config. opens
#define GETMOTDWORD(lpd) ((((DWORD)GETMOTWORD(lpd)) << (8 * sizeof(WORD))) + GETMOTWORD((LPBYTE)(lpd) + sizeof(WORD)))
#define ASYNCMESSAGE(w) (((w) == MCI_PLAY) || ((w) == MCI_SEEK))
/***************************************************************************
*
* Globals
*
**************************************************************************/
ListHandle SeqStreamListHandle;
HINSTANCE hInstance;
UINT MINPERIOD; // Minimum timer period supported.
int MIDIConfig (HWND hwndParent);
/***************************************************************************
*
* @doc INTERNAL MCISEQ
*
* @api DWORD | mciDriverEntry | Single entry point for MCI drivers
*
* @parm MCIDEVICEID | wDeviceID | The MCI device ID
*
* @parm UINT | wMessage | The requested action to be performed.
*
* @parm DWORD | dwParam1 | Data for this message. Defined seperately for
* each message
*
* @parm DWORD | dwParam2 | Data for this message. Defined seperately for
* each message
*
* @rdesc Defined separately for each message.
*
* @comm This may not be called at interrupt time.
*
***************************************************************************/
PUBLIC DWORD FAR PASCAL mciDriverEntry (MCIDEVICEID wDeviceID, UINT wMessage,
DWORD_PTR dwParam1, DWORD_PTR dwParam2)
{
pSeqStreamType pStream;
DWORD dwError;
// get the sequence stream handle for the given ID
pStream = (pSeqStreamType) mciGetDriverData (wDeviceID);
// (unless it's irrelevent to the command)
if (!pStream &&
(!((wMessage == MCI_OPEN_DRIVER) || (wMessage == MCI_GETDEVCAPS) ||
(wMessage == MCI_INFO) || (wMessage == MCI_CLOSE_DRIVER))))
return MCIERR_UNSUPPORTED_FUNCTION;
switch (wMessage)
{
case MCI_OPEN_DRIVER:
dwError = msOpen(&pStream, wDeviceID, (DWORD)dwParam1, (LPMCI_OPEN_PARMS)dwParam2);
break;
case MCI_CLOSE_DRIVER: // close file
dwError = msClose(pStream, wDeviceID, (DWORD)dwParam1);
pStream = NULL;
break;
case MCI_PLAY: // play a file (pass thru to sequencer)
dwError = msPlay(pStream, wDeviceID, (DWORD)dwParam1, (LPMCI_PLAY_PARMS)dwParam2);
break;
case MCI_PAUSE:
case MCI_STOP:
if (wMessage == MCI_PAUSE) // remember pause status for "mci_mode"
pStream->bLastPaused = TRUE;
else
pStream->bLastPaused = FALSE;
if (!(dwError = (DWORD)midiSeqMessage(pStream->hSeq, SEQ_STOP, 0L, 0L)))
midiSeqMessage(pStream->hSeq, SEQ_SETPORTOFF, TRUE, 0L);
break;
case MCI_SEEK: // pass thru as song ptr
pStream->bLastPaused = FALSE;
dwError = msSeek(pStream, wDeviceID, (DWORD)dwParam1, (LPMCI_SEEK_PARMS)dwParam2);
break;
case MCI_STATUS:
dwError = msStatus(pStream, wDeviceID, (DWORD)dwParam1, (LPMCI_STATUS_PARMS)dwParam2);
break;
case MCI_GETDEVCAPS:
dwError = msGetDevCaps(pStream, wDeviceID, (DWORD)dwParam1, (LPMCI_GETDEVCAPS_PARMS)dwParam2);
break;
case MCI_INFO: // TBD: use resource for string
dwError = msInfo(pStream, wDeviceID, (DWORD)dwParam1, (LPMCI_INFO_PARMS)dwParam2);
break;
case MCI_SET:
dwError = msSet(pStream, wDeviceID, (DWORD)dwParam1, (LPMCI_SEQ_SET_PARMS)dwParam2);
break;
case MCI_STEP:
case MCI_RECORD:
case MCI_SAVE:
case MCI_CUE:
case MCI_REALIZE:
case MCI_WINDOW:
case MCI_PUT:
case MCI_WHERE:
case MCI_FREEZE:
case MCI_UNFREEZE:
case MCI_LOAD:
case MCI_CUT:
case MCI_COPY:
case MCI_PASTE:
case MCI_UPDATE:
case MCI_DELETE:
case MCI_RESUME:
return MCIERR_UNSUPPORTED_FUNCTION;
default:
//case MCI_SOUND: This is obsolete and has been removed from the public headers
return MCIERR_UNRECOGNIZED_COMMAND;
} // switch
// NOTIFY HANDLED HERE
if (!LOWORD(dwError))
{
MIDISEQINFO seqInfo;
DWORD dwTo;
// first derive info crucial for play abort
if (wMessage == MCI_PLAY)
{
// get info to aid in possible time format conversions (from & to)
midiSeqMessage((HMIDISEQ) pStream->hSeq,
SEQ_GETINFO, (DWORD_PTR)(LPMIDISEQINFO) &seqInfo, 0L);
if (dwParam1 & MCI_TO)
{
// is the user typing in what he believes to be the end?
if (((LPMCI_PLAY_PARMS)dwParam2)->dwTo == CnvtTimeFromSeq(pStream, seqInfo.dwLength, &seqInfo))
dwTo = seqInfo.dwLength; // if so, let him have it
else
dwTo = CnvtTimeToSeq(pStream, // else straight cnvt
((LPMCI_PLAY_PARMS)dwParam2)->dwTo, &seqInfo);
}
else
dwTo = seqInfo.dwLength; // already in native format
}
if (pStream) {
// HANDLE ABORT/SUPERSEDE OF ANY OUTSTANDING DELAYED NOTIFY
if (pStream->hNotifyCB)
{
if (bMutex(wMessage, pStream->wNotifyMsg,
(DWORD)dwParam1 /*flags*/, dwTo, pStream->dwNotifyOldTo))
// if msg cancels old notify (regardless of whether
// it notifies) abort pending notify
Notify(pStream, MCI_NOTIFY_ABORTED);
else if (dwParam1 & MCI_NOTIFY) // else if this one notifies,
// that supersedes old one
Notify(pStream, MCI_NOTIFY_SUPERSEDED);
}
// HANDLE THIS MESSAGE'S NOTIFY
if (dwParam1 & MCI_NOTIFY)
{
// HANDLE THIS NOTIFY
PrepareForNotify(pStream, wMessage,
(LPMCI_GENERIC_PARMS) dwParam2, dwTo);
if (!ASYNCMESSAGE(wMessage) || ((wMessage == MCI_PLAY) && (seqInfo.dwCurrentTick == dwTo)))
Notify(pStream, MCI_NOTIFY_SUCCESSFUL);
}
} else if (dwParam1 & MCI_NOTIFY)
mciDriverNotify((HWND)((LPMCI_GENERIC_PARMS)dwParam2)->dwCallback, wDeviceID, MCI_NOTIFY_SUCCESSFUL);
}
return dwError;
}
/****************************************************************************
*
* Helper Functions
*
***************************************************************************/
PRIVATE BOOL NEAR PASCAL bMutex(UINT wNewMsg, UINT wOldMsg, DWORD wNewFlags,
DWORD dwNewTo, DWORD dwOldTo)
{
switch (wOldMsg)
{
case MCI_PLAY:
switch (wNewMsg)
{
case MCI_STOP:
case MCI_PAUSE:
case MCI_SEEK:
case MCI_CLOSE_DRIVER:
return TRUE;
case MCI_PLAY:
if ((wNewFlags & MCI_FROM) || (dwNewTo != dwOldTo))
return TRUE;
else
return FALSE;
default:
return FALSE;
}
break;
case MCI_SEEK:
switch (wNewMsg)
{
case MCI_CLOSE_DRIVER:
case MCI_SEEK:
return TRUE;
case MCI_PLAY:
if (wNewFlags & MCI_FROM)
return TRUE;
else
return FALSE;
default:
return FALSE;
}
break;
default: // should never get here
return FALSE;
}
}
/***************************************************************************/
PUBLIC VOID FAR PASCAL PrepareForNotify(pSeqStreamType pStream,
UINT wMessage, LPMCI_GENERIC_PARMS lpParms, DWORD dwTo)
/* This function's purpose is to setup for notify in cases where
* an asynchronous message is about to be sent to the sequencer --
* e.g. before a 'play,' where the sequencer must call back to tell you
* it's done (then you, in turn, call back the client).
* This funtion sets up the MCISEQ -> CLIENT interface.
*/
{
// remember this notify's dwCallback, and message
// which notify was for
//mci client's notify callback handle
pStream->hNotifyCB = (HWND)lpParms->dwCallback;
pStream->wNotifyMsg = wMessage; // remember for possible supersed/abort
pStream->dwNotifyOldTo = dwTo; // save to position for possible
// subsequent abort/supersede
}
/****************************************************************************/
PUBLIC VOID NEAR PASCAL EndStreamCycle(SeqStreamType* seqStream)
{
// signal on all tracks' buffers
// as a result, the Stream Cycle process will finish (i.e. die)
// account for cases where stream or part of it wasn't allocated
TrackStreamType* trackStream;
int i;
if (!seqStream)
return;
seqStream->streaming = FALSE; // first let it exit
if (seqStream->trackStreamListHandle == NULLLIST)
return;
// now signal on all to let it escape
trackStream = (TrackStreamType*) List_Get_First(seqStream->trackStreamListHandle);
while(trackStream)
{
for(i = 0; i < NUMHDRS; i++) // signal on all buffers
{
if (seqStream->streamTaskHandle)
{
dprintf2(("about to signal in EndStreamCycle"));
if (seqStream->streamTaskHandle) {
TaskSignal(seqStream->streamTaskHandle, WTM_QUITTASK);
#ifdef WIN32
TaskWaitComplete(seqStream->streamThreadHandle);
#else
Yield();
#endif // WIN32
}
}
else
break;
}
trackStream = (TrackStreamType*) List_Get_Next(seqStream->trackStreamListHandle, trackStream);
}
}
/****************************************************************************/
PUBLIC DWORD NEAR PASCAL EndFileStream(pSeqStreamType pStream)
/* Closes the file and frees all stream memory. Handles cases where
allocation failed part way through. */
{
if (!pStream)
return 0;
EndStreamCycle(pStream);
if (pStream->hSeq)
midiSeqMessage(pStream->hSeq, SEQ_CLOSE, 0L, 0L); //directly close it
if (pStream->trackStreamListHandle != NULLLIST)
{
TrackStreamType *trackStream;
trackStream = (TrackStreamType*) List_Get_First(pStream->trackStreamListHandle);
while (trackStream)
{
int i;
for(i = 0; i < NUMHDRS; i++) // unlock two midihdr buffers for it
{
if (trackStream->fileHeaders[i])
{
#ifdef WIN16
GlobalFreePtr(trackStream->fileHeaders[i]);
#else
GlobalFree(trackStream->fileHeaders[i]);
#endif
}
else
break;
}
trackStream = (TrackStreamType*) List_Get_Next(pStream->trackStreamListHandle, trackStream);
}
List_Destroy(pStream->trackStreamListHandle);
}
List_Deallocate(SeqStreamListHandle, (NPSTR) pStream);
return 0;
}
/*****************************************************************************/
PRIVATE void PASCAL NEAR InitMMIOOpen(LPMMIOPROC pIOProc, LPMMIOINFO lpmmioInfo)
{
_fmemset(lpmmioInfo, 0, sizeof(MMIOINFO));
if (pIOProc)
lpmmioInfo->pIOProc = pIOProc;
else
lpmmioInfo->fccIOProc = FOURCC_DOS;
}
/*****************************************************************************/
PRIVATE HMMIO NEAR PASCAL msOpenFile(LPWSTR szName, LPMMCKINFO lpmmckData, LPMMIOPROC pIOProc)
/* Returns hmmio. This value will be null if failure.
Reads both RIFF and dos midifiles.
Sets the current file position to the start of MIDI data. */
{
#define RMIDFORMTYPE mmioFOURCC('R', 'M', 'I', 'D')
#define DATACKID mmioFOURCC('d', 'a', 't', 'a')
MMIOINFO mmioInfo;
HMMIO hmmio;
MMCKINFO mmckRiff;
InitMMIOOpen(pIOProc, &mmioInfo);
hmmio = mmioOpen(szName, &mmioInfo, MMIO_READ | MMIO_DENYWRITE);
if (hmmio == NULL)
return NULL;
mmckRiff.fccType = RMIDFORMTYPE;
lpmmckData->ckid = DATACKID;
if (mmioDescend(hmmio, &mmckRiff, NULL, MMIO_FINDRIFF) || mmioDescend(hmmio, lpmmckData, &mmckRiff, MMIO_FINDCHUNK))
{
lpmmckData->cksize = mmioSeek(hmmio, 0, SEEK_END);
lpmmckData->fccType = 0;
lpmmckData->dwDataOffset = 0;
lpmmckData->dwFlags = 0;
mmioSeek(hmmio, 0, SEEK_SET);
}
return hmmio;
}
/*****************************************************************************/
PUBLIC DWORD NEAR PASCAL msOpenStream(pSeqStreamType FAR * lppStream, LPCWSTR szName, LPMMIOPROC pIOProc)
/* opens file, sets up streaming variables and buffers, calls routine
to load them and send them to the sequencer. Returns stream handle in
pStream var pointed to by lppStream. Returns error code */
{
#define MAXHDRSIZE 0x100
#define MThd mmioFOURCC('M', 'T', 'h', 'd')
#define MTrk mmioFOURCC('M', 'T', 'r', 'k')
UINT wTracks;
UINT wFormat;
HMMIO hmmio;
SeqStreamType *thisSeqStream = NULL;
TrackStreamType *thisTrackStream;
BYTE fileHeader[MAXHDRSIZE];
int iTrackNum;
DWORD smErrCode, errCode;
MIDISEQOPENDESC open;
MMCKINFO mmckData;
MMCKINFO mmck;
MMIOINFO mmioInfo;
WCHAR szPathName[128];
wcsncpy(szPathName, szName, (sizeof(szPathName)/sizeof(WCHAR)) - 1);
InitMMIOOpen(pIOProc, &mmioInfo);
if (!mmioOpen(szPathName, &mmioInfo, MMIO_PARSE)) {
hmmio = NULL;
errCode = MCIERR_FILE_NOT_FOUND;
goto ERROR_HDLR;
}
// check if it's a RIFF file or not -- if so, descend into RMID chunk
// if not, just open it and assume that it's a MIDI file
hmmio = msOpenFile(szPathName, &mmckData, pIOProc);
if (!hmmio) // open the MIDI file
{
errCode = MCIERR_FILE_NOT_FOUND;
goto ERROR_HDLR;
}
mmck.ckid = MThd;
if (mmioDescend(hmmio, &mmck, &mmckData, MMIO_FINDCHUNK))
{
errCode = MCIERR_INVALID_FILE;
goto ERROR_HDLR;
}
mmck.cksize = GETMOTDWORD(&mmck.cksize);
if (mmck.cksize < 3 * sizeof(WORD))
{
errCode = MCIERR_INVALID_FILE;
goto ERROR_HDLR;
}
// allocate sequence stream structure & its track stream list
if (! (thisSeqStream = (SeqStreamType*) List_Allocate(SeqStreamListHandle)))
{
errCode = MCIERR_OUT_OF_MEMORY;
goto ERROR_HDLR;
}
List_Attach_Tail(SeqStreamListHandle, (NPSTR) thisSeqStream);
thisSeqStream->trackStreamListHandle = NULLLIST;
thisSeqStream->hmmio = hmmio;
thisSeqStream->pIOProc = pIOProc;
lstrcpy(thisSeqStream->szFilename, szPathName);
Yield();
thisSeqStream->dwFileLength = mmckData.cksize;
open.dwLen = min(mmck.cksize, MAXHDRSIZE);
mmioRead(hmmio, (HPSTR)fileHeader, open.dwLen); // read the header info now
wFormat = GETMOTWORD(fileHeader);
wTracks = GETMOTWORD(fileHeader + sizeof(WORD));
if (((wFormat == 0) && (wTracks > 1)) || // illegal format 0
(wFormat > 1)) // illegal format
{
errCode = MCIERR_INVALID_FILE;
goto ERROR_HDLR;
}
thisSeqStream->wPortNum = MIDI_MAPPER;
/* Create a sequence given the header data (will add stream as param) */
open.lpMIDIFileHdr = (LPBYTE) fileHeader; // step over mhdr+len
open.dwCallback = (DWORD_PTR) mciSeqCallback;
open.dwInstance = (DWORD_PTR) thisSeqStream;
open.hStream = (HANDLE)thisSeqStream;
smErrCode = (DWORD)midiSeqMessage(NULL, // open sequence
SEQ_OPEN,
(DWORD_PTR)(LPVOID)&open,
(DWORD_PTR)(LPVOID)&(thisSeqStream->hSeq));
if (smErrCode != MIDISEQERR_NOERROR)
{
// N.B. at this point if failed in sequencer, sequence is invalid
// thisSeqStream->hSeq should be NULL
if (smErrCode == MIDISEQERR_NOMEM)
errCode = MCIERR_OUT_OF_MEMORY;
else
errCode = MCIERR_INVALID_FILE;
goto ERROR_HDLR;
}
thisSeqStream->trackStreamListHandle = List_Create((LONG) sizeof(TrackStreamType),0l);
if (thisSeqStream->trackStreamListHandle == NULLLIST)
{
errCode = MCIERR_OUT_OF_MEMORY; // not a memory problem
goto ERROR_HDLR;
}
mmioAscend(hmmio, &mmck, 0);
// MIDI track data does not have RIFF even byte restriction
if (mmck.cksize & 1L)
mmioSeek(hmmio, -1L, SEEK_CUR);
mmck.ckid = MTrk;
iTrackNum = 0;
while (wTracks-- > 0)
{
int i;
// allocate trackstream record and put it in list
if (! (thisTrackStream = (TrackStreamType*)
List_Allocate(thisSeqStream->trackStreamListHandle)))
{
errCode = MCIERR_OUT_OF_MEMORY;
goto ERROR_HDLR;
}
List_Attach_Tail(thisSeqStream->trackStreamListHandle,
(NPSTR) thisTrackStream);
for(i = 0; i < NUMHDRS; i++) // alloc and lock two midihdr buffers for it
{
if (! (thisTrackStream->fileHeaders[i] = (LPMIDISEQHDR)
#ifdef WIN16
GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE, (LONG) (sizeof(MIDISEQHDR) + BUFFSIZE))))
#else
GlobalAlloc(GPTR, (LONG) (sizeof(MIDISEQHDR) + BUFFSIZE))))
#endif // WIN16
{
errCode = MCIERR_OUT_OF_MEMORY;
goto ERROR_HDLR;
}
thisTrackStream->fileHeaders[i]->lpData =
(LPSTR) (((DWORD_PTR) thisTrackStream->fileHeaders[i]) +
sizeof(MIDISEQHDR));
thisTrackStream->fileHeaders[i]->wTrack = (WORD)iTrackNum;
thisTrackStream->fileHeaders[i]->lpNext = NULL;
thisTrackStream->fileHeaders[i]->wFlags = MIDISEQHDR_DONE;
};
Yield();
// set up to read this track's 'Mtrk' & length
if (mmioDescend(hmmio, &mmck, &mmckData, MMIO_FINDCHUNK))
{
errCode = MCIERR_INVALID_FILE;
goto ERROR_HDLR;
}
mmck.cksize = GETMOTDWORD(&mmck.cksize);
// set up beginning, current, and end
thisTrackStream->beginning = mmck.dwDataOffset;
thisTrackStream->current = thisTrackStream->beginning;
thisTrackStream->end = thisTrackStream->beginning + mmck.cksize - 1;
// minimum track length is 3 bytes
// verify track ends with "End Of Track" meta event
mmioSeek(hmmio, (LONG)thisTrackStream->end - 2, SEEK_SET);
mmioRead(hmmio, (HPSTR)fileHeader, 3L); // read EOT
if ((fileHeader[0] != 0xFF) || (fileHeader[1] != 0x2F) ||
(fileHeader[2] != 0x00))
{
errCode = MCIERR_INVALID_FILE;
goto ERROR_HDLR;
}
mmioAscend(hmmio, &mmck, 0);
// MIDI track data does not have RIFF even byte restriction
if (mmck.cksize & 1L)
mmioSeek(hmmio, -1L, SEEK_CUR);
iTrackNum++;
}
mmioClose(hmmio, 0); // don't need in this task context any longer
hmmio = NULL;
// create cycle task
thisSeqStream->streaming = TRUE;
thisSeqStream->streamTaskHandle = 0; // don't know it yet
if (mmTaskCreate(mciStreamCycle, &thisSeqStream->streamThreadHandle,
(DWORD_PTR)thisSeqStream))
//mmTaskCreate returns 0 if successful
{
errCode = MCIERR_OUT_OF_MEMORY;
goto ERROR_HDLR;
}
thisSeqStream->bLastPaused = FALSE; // never paused
thisSeqStream->hNotifyCB = NULL; // no notify pending
*lppStream = thisSeqStream;
return 0;
ERROR_HDLR:
if (hmmio) // close file if it was opened
mmioClose(hmmio, 0);
if (thisSeqStream)
{
midiSeqMessage((HMIDISEQ) thisSeqStream->hSeq, SEQ_SETPORTOFF, FALSE, 0L);
EndFileStream(thisSeqStream); //dealloc it and everything it owns
}
return errCode;
}
/***************************************************************************/
PUBLIC VOID FAR PASCAL StreamTrackReset(pSeqStreamType pStream, UINT wTrackNum)
/* Find track stream struct within pStream as specified by wTrackNum & reset
it to start over. */
{
TrackStreamType *trackStream;
int iTrackNum;
if (pStream->trackStreamListHandle == NULLLIST)
return;
trackStream = (TrackStreamType*) List_Get_First(pStream->trackStreamListHandle);
iTrackNum = 0;
while ((trackStream) && ((int)wTrackNum != iTrackNum++))
{
trackStream = (TrackStreamType*) List_Get_Next(pStream->trackStreamListHandle, trackStream);
}
if (trackStream)
{
int i;
trackStream->current = trackStream->beginning; // reset stream
// now signal on all buffers that have been blocked on
// (have done bit set)
for(i = 0; i < NUMHDRS; i++) // fill any of these that're MT
if (!(trackStream->fileHeaders[i]->wFlags & MIDISEQHDR_DONE))
{
trackStream->fileHeaders[i]->wFlags |= MIDISEQHDR_DONE; //set it
TaskSignal(pStream->streamTaskHandle, WTM_FILLBUFFER);
}
}
}
/***************************************************************************/
PUBLIC VOID FAR PASCAL _LOADDS mciStreamCycle(DWORD_PTR dwInst)
/* Fills any buffers for this track that are empty. Rule: at any time, the
block count = the number of buffers - number of buffers with done bit clr
(i.e. sent) - 1. Note that this will normally be -1 (asleep). When a
buffer is freed, the done bit is set and we signal (block count++).
Important: when a buffer is available, but we've run out of data to send
on that track, we clr the done bit and block anyway (otherwise we wouldn't
go back to sleep properly). The only thing is that we must properly regain
our original status if the sequence is ever reset. This is accomplished by
signaling on every buffer with its done bit cleared. (This is like
signaling on every buffer that's been sent but not returned + any buffers
ignored becuase there was no data to send on their track.)
Whenever we signal for a buffer, we must be SURE that its done bit is set
-- this is to maintain our rule above. Otherwise it will break badly! */
{
TrackStreamType *trackStream;
SeqStreamType *seqStream = (SeqStreamType*)dwInst;
MMCKINFO mmckData;
HMMIO hmmio;
EnterSeq();
/*
** Make a safe "user" call so that user knows about our thread.
*/
GetDesktopWindow();
if (!seqStream->streamTaskHandle) {
seqStream->streamTaskHandle = mmGetCurrentTask(); // fill it in asap
}
hmmio = msOpenFile(seqStream->szFilename, &mmckData, seqStream->pIOProc); // open the MIDI file
seqStream->hmmio = hmmio;
// block count = 0
// first signal on all
trackStream = (TrackStreamType*) List_Get_First(seqStream->trackStreamListHandle);
while(trackStream)
{
int i;
for(i = 0; i < NUMHDRS; i++) // fill any of these that're MT
{
trackStream->fileHeaders[i]->wFlags |= MIDISEQHDR_DONE; //set it
TaskSignal(seqStream->streamTaskHandle, WTM_FILLBUFFER);
}
trackStream = (TrackStreamType*) List_Get_Next(seqStream->trackStreamListHandle, trackStream);
}
// block count = number of buffers
TaskBlock();
// block count == number of buffers - 1
do
{
trackStream = (TrackStreamType*) List_Get_First(seqStream->trackStreamListHandle);
while ((trackStream) && (seqStream->streaming))
{
int i;
for(i = 0; i < NUMHDRS; i++) // fill any of these that're MT
{
/* if the header isn't being used, fill it and send it to
the sequencer */
if ((trackStream->fileHeaders[i]->wFlags & MIDISEQHDR_DONE) &&
(seqStream->streaming))
{
int iDataToRead;
mmioSeek(seqStream->hmmio, (LONG) trackStream->current, SEEK_SET);
iDataToRead = (int) min((DWORD) BUFFSIZE,
(trackStream->end - trackStream->current) + 1);
trackStream->fileHeaders[i]->wFlags &=
~(MIDISEQHDR_DONE + MIDISEQHDR_EOT + MIDISEQHDR_BOT);
// clr the done beginning and end regardless
if (iDataToRead > 0)
{
if (trackStream->current == trackStream->beginning)
trackStream->fileHeaders[i]->wFlags |=
MIDISEQHDR_BOT; // set the beginning of track flag
mmioRead(seqStream->hmmio,
(HPSTR) trackStream->fileHeaders[i]->lpData, iDataToRead);
trackStream->fileHeaders[i]->dwLength = iDataToRead;
trackStream->current += iDataToRead;
trackStream->fileHeaders[i]->reserved =
((trackStream->current - trackStream->beginning) - 1);
if (trackStream->current > trackStream->end)
trackStream->fileHeaders[i]->wFlags |=
MIDISEQHDR_EOT; // set the end of track flag
if (seqStream->streaming)
midiSeqMessage((HMIDISEQ) seqStream->hSeq, SEQ_TRACKDATA,
(DWORD_PTR) trackStream->fileHeaders[i], 0L); // send it
} // if data to read
while (seqStream->streaming) {
MIDISEQINFO seqInfo;
switch (TaskBlock()) {
case WTM_DONEPLAY:
midiSeqMessage((HMIDISEQ)seqStream->hSeq, SEQ_GETINFO, (DWORD_PTR)(LPMIDISEQINFO)&seqInfo, 0L);
if (!seqInfo.bPlaying)
midiSeqMessage((HMIDISEQ)seqStream->hSeq, SEQ_SETPORTOFF, FALSE, 0L);
continue;
case WTM_QUITTASK:
case WTM_FILLBUFFER:
break;
}
break;
}
//BLOCK even if data wasn't available (buffer still "used")
// when all buffers have been blocked, we'll sleep here
if (seqStream->streaming)
// don't yield to close, 'cause it deallocs seq
Yield(); // yield in case cpu bound
} // if done bit set and streaming
} // for i
if (seqStream->streaming)
trackStream = (TrackStreamType*) List_Get_Next(seqStream->trackStreamListHandle, trackStream);
} // while (trackStream)
} while(seqStream->streaming);
mmioClose(seqStream->hmmio, 0);
seqStream->streamTaskHandle = 0;
LeaveSeq();
}
/***************************************************************************
*
* @doc INTERNAL
*
* @api LRESULT | DriverProc | The entry point for an installable driver.
*
* @parm DWORD | dwDriverId | For most messages, dwDriverId is the DWORD
* value that the driver returns in response to a DRV_OPEN message.
* Each time that the driver is opened, through the DrvOpen API,
* the driver receives a DRV_OPEN message and can return an
* arbitrary, non-zero, value. The installable driver interface
* saves this value and returns a unique driver handle to the
* application. Whenever the application sends a message to the
* driver using the driver handle, the interface routes the message
* to this entry point and passes the corresponding dwDriverId.
*
* This mechanism allows the driver to use the same or different
* identifiers for multiple opens but ensures that driver handles
* are unique at the application interface layer.
*
* The following messages are not related to a particular open
* instance of the driver. For these messages, the dwDriverId
* will always be ZERO.
*
* DRV_LOAD, DRV_FREE, DRV_ENABLE, DRV_DISABLE, DRV_OPEN
*
* @parm UINT | wMessage | The requested action to be performed. Message
* values below DRV_RESERVED are used for globally defined messages.
* Message values from DRV_RESERVED to DRV_USER are used for
* defined driver portocols. Messages above DRV_USER are used
* for driver specific messages.
*
* @parm LPARAM | lParam1 | Data for this message. Defined separately for
* each message
*
* @parm LPARAM | lParam2 | Data for this message. Defined separately for
* each message
*
* @rdesc Defined separately for each message.
*
***************************************************************************/
PUBLIC LRESULT FAR PASCAL _LOADDS DriverProc (DWORD_PTR dwDriverID, HDRVR hDriver, UINT wMessage, LPARAM lParam1, LPARAM lParam2)
{
DWORD_PTR dwRes = 0L;
switch (wMessage)
{
TIMECAPS timeCaps;
// Standard, globally used messages.
case DRV_LOAD:
/*
Sent to the driver when it is loaded. Always the first
message received by a driver.
*/
/*
Find the minimum period we can support
*/
if (timeGetDevCaps(&timeCaps, sizeof(timeCaps)) == MMSYSERR_NOERROR)
{
MINPERIOD = timeCaps.wPeriodMin;
/* create a list of seq streams */
SeqStreamListHandle = List_Create((LONG) sizeof(SeqStreamType), 0L);
// following sets up command table to enable subsequent commands
dwRes = 1L;
}
break;
case DRV_FREE:
/*
Sent to the driver when it is about to be discarded. This
will always be the last message received by a driver before
it is freed.
dwDriverID is 0L.
lParam1 is 0L.
lParam2 is 0L.
Return value is IGNORED.
*/
//dwReturn = midiSeqTerminate();
dwRes = 1L;
break;
case DRV_OPEN:
/*
Sent to the driver when it is opened.
dwDriverID is 0L.
lParam1 is a far pointer to a zero-terminated string
containing the name used to open the driver.
lParam2 is passed through from the drvOpen call.
Return 0L to FAIL the open.
*/
if (!lParam2)
dwRes = CONFIG_ID;
else
{
((LPMCI_OPEN_DRIVER_PARMS)lParam2)->wCustomCommandTable = MCI_TABLE_NOT_PRESENT;
((LPMCI_OPEN_DRIVER_PARMS)lParam2)->wType = MCI_DEVTYPE_SEQUENCER;
dwRes = ((LPMCI_OPEN_DRIVER_PARMS)lParam2)->wDeviceID;
}
break;
case DRV_CLOSE:
/*
Sent to the driver when it is closed. Drivers are unloaded
when the close count reaches zero.
dwDriverID is the driver identifier returned from the
corresponding DRV_OPEN.
lParam1 is passed through from the drvOpen call.
lParam2 is passed through from the drvOpen call.
Return 0L to FAIL the close.
*/
dwRes = 1L;
break;
case DRV_ENABLE:
/*
Sent to the driver when the driver is loaded or reloaded
and whenever windows is enabled. Drivers should only
hook interrupts or expect ANY part of the driver to be in
memory between enable and disable messages
dwDriverID is 0L.
lParam1 is 0L.
lParam2 is 0L.
Return value is ignored.
*/
dwRes = 1L;
break;
case DRV_DISABLE:
/*
Sent to the driver before the driver is freed.
and whenever windows is disabled
dwDriverID is 0L.
lParam1 is 0L.
lParam2 is 0L.
Return value is ignored.
*/
dwRes = 1L;
break;
case DRV_QUERYCONFIGURE:
/*
Sent to the driver so that applications can
determine whether the driver supports custom
configuration. The driver should return a
non-zero value to indicate that configuration
is supported.
dwDriverID is the value returned from the DRV_OPEN
call that must have succeeded before this message
was sent.
lParam1 is passed from the app and is undefined.
lParam2 is passed from the app and is undefined.
return 1L to indicate configuration supported.
*/
dwRes = 1L;
break;
case DRV_CONFIGURE:
/*
Sent to the driver so that it can display a custom
configuration dialog box.
lParam1 is passed from the app. and should contain
the parent window handle in the loword.
lParam2 is passed from the app and is undefined.
return value is undefined.
Drivers should create their own section in
system.ini. The section name should be the driver
name.
*/
if ( lParam1 )
dwRes = MIDIConfig((HWND)LOWORD (lParam1));
else
dwRes = (LRESULT)DRVCNF_CANCEL;
break;
case DRV_INSTALL:
case DRV_REMOVE:
dwRes = DRVCNF_OK;
break;
default:
if (CONFIG_ID != dwDriverID &&
wMessage >= DRV_MCI_FIRST && wMessage <= DRV_MCI_LAST) {
EnterSeq();
dwRes = mciDriverEntry ((MCIDEVICEID)dwDriverID, wMessage,
(DWORD_PTR)lParam1, (DWORD_PTR)lParam2);
LeaveSeq();
}
else
dwRes = (DWORD_PTR)DefDriverProc(dwDriverID, hDriver, wMessage, lParam1, lParam2);
break;
}
return (LRESULT)dwRes;
}
#ifdef WIN32
/**************************************************************************
@doc EXTERNAL
@api BOOL | DllInstanceInit | This procedure is called whenever a
process attaches or detaches from the DLL.
@parm PVOID | hModule | Handle of the DLL.
@parm ULONG | Reason | What the reason for the call is.
@parm PCONTEXT | pContext | Some random other information.
@rdesc The return value is TRUE if the initialisation completed ok,
FALSE if not.
**************************************************************************/
BOOL DllInstanceInit(PVOID hModule, ULONG Reason, PCONTEXT pContext)
{
UNREFERENCED_PARAMETER(pContext);
if (Reason == DLL_PROCESS_ATTACH) {
/*
Initialize our critical section
*/
InitCrit();
hInstance = hModule;
DisableThreadLibraryCalls(hModule);
} else if (Reason == DLL_PROCESS_DETACH) {
DeleteCrit();
}
return TRUE;
}
/************************************************************************/
#endif
/*****************************************************************************
@doc INTERNAL MCISEQ
@api int | MIDIConfig |
@parm HWND | hwndParent |
@rdesc
@comm
*****************************************************************************/
typedef BOOL (WINAPI *SHOWMMCPLPROPSHEETW)(HWND hwndParent,
LPCWSTR szPropSheetID,
LPWSTR szTabName,
LPWSTR szCaption);
int MIDIConfig (HWND hwndParent)
{
static HWND hwndPrevParent = NULL;
WCHAR szCaptionW[ 128 ];
// We need only a unicode version of the caption (for FindWindow()
// and ShowMMCPLPropertySheetW(), which are unicode-enabled).
//
LoadStringW(hInstance,IDS_MIDICAPTION,szCaptionW,cchLENGTH(szCaptionW));
if (hwndPrevParent)
{
BringWindowToTop(FindWindowW(NULL, szCaptionW));
}
else
{
HINSTANCE h;
SHOWMMCPLPROPSHEETW fn;
static TCHAR aszMMSystemW[] = TEXT("MMSYS.CPL");
static char aszShowPropSheetA[] = "ShowMMCPLPropertySheetW";
static WCHAR aszMIDIW[] = L"MIDI";
WCHAR szTabNameW[64];
LoadStringW(hInstance, IDS_MIDITAB, szTabNameW, cchLENGTH(szTabNameW));
h = LoadLibrary (aszMMSystemW);
if (h)
{
fn = (SHOWMMCPLPROPSHEETW)GetProcAddress(h, aszShowPropSheetA);
if (fn)
{
BOOL f;
hwndPrevParent = hwndParent;
f = fn(hwndParent, aszMIDIW, szTabNameW, szCaptionW);
hwndPrevParent = NULL;
}
FreeLibrary(h);
}
}
return DRVCNF_OK;
}