hexen2/H2MP/code/midi.c
2000-11-10 00:00:00 +00:00

613 lines
17 KiB
C

/*
* $Header: /H2 Mission Pack/midi.c 3 2/12/98 12:26a Jmonroe $
*/
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <memory.h>
#include <mmreg.h>
#include "midstuff.h"
#include "midi.h"
#include "quakedef.h"
BOOL bMidiInited,bFileOpen, bPlaying, bBuffersPrepared;
BOOL bPaused, bLooped;
UINT uMIDIDeviceID = MIDI_MAPPER, uCallbackStatus;
int nCurrentBuffer, nEmptyBuffers;
DWORD dwBufferTickLength, dwTempoMultiplier, dwCurrentTempo, dwProgressBytes;
DWORD dwVolumePercent, dwVolCache[NUM_CHANNELS];
HMIDISTRM hStream;
CONVERTINFO ciStreamBuffers[NUM_STREAM_BUFFERS];
// From mstrconv.c
extern INFILESTATE ifs;
// Private to this module...
static HANDLE hBufferReturnEvent;
static void FreeBuffers(void);
void MidiErrorMessageBox(MMRESULT mmr)
{
char temp[1024];
midiOutGetErrorText(mmr,temp,sizeof(temp));
Con_Printf("%s\n",temp);
}
void MIDI_Play_f (void)
{
if (Cmd_Argc () == 2)
{
MIDI_Play(Cmd_Argv(1));
}
}
void MIDI_Stop_f (void)
{
MIDI_Stop();
}
void MIDI_Pause_f (void)
{
MIDI_Pause();
}
void MIDI_Loop_f (void)
{
if (Cmd_Argc () == 2)
{
if (strcmpi(Cmd_Argv(1),"on") == 0 || strcmpi(Cmd_Argv(1),"1") == 0)
MIDI_Loop(1);
else if (strcmpi(Cmd_Argv(1),"off") == 0 || strcmpi(Cmd_Argv(1),"0") == 0)
MIDI_Loop(0);
else if (strcmpi(Cmd_Argv(1),"toggle") == 0)
MIDI_Loop(2);
}
if (bLooped) Con_Printf("MIDI music will be looped\n");
else Con_Printf("MIDI music will not be looped\n");
}
void MIDI_Volume_f (void)
{
if (Cmd_Argc () == 2)
{
dwVolumePercent = atol(Cmd_Argv(1))*65535/100;
midiOutSetVolume(hStream,(dwVolumePercent<<16)+dwVolumePercent);
/* dwVolumePercent = atol(Cmd_Argv(1))*10;
SetAllChannelVolumes(dwVolumePercent);*/
}
else
{
Con_Printf("MIDI volume is %d\n", dwVolumePercent/(65535/100));
}
}
BOOL MIDI_Init(void)
{
MMRESULT mmrRetVal;
hBufferReturnEvent = CreateEvent(NULL,FALSE,FALSE,"Wait For Buffer Return");
if(COM_CheckParm("-nomidi"))
{
bMidiInited = 0;
return FALSE;
}
mmrRetVal = midiStreamOpen(&hStream,&uMIDIDeviceID,(DWORD)1,(DWORD)MidiProc,(DWORD)0,CALLBACK_FUNCTION);
if(mmrRetVal != MMSYSERR_NOERROR )
{
bMidiInited = 0;
MidiErrorMessageBox( mmrRetVal );
return FALSE;
}
Cmd_AddCommand ("midi_play", MIDI_Play_f);
Cmd_AddCommand ("midi_stop", MIDI_Stop_f);
Cmd_AddCommand ("midi_pause", MIDI_Pause_f);
Cmd_AddCommand ("midi_loop", MIDI_Loop_f);
Cmd_AddCommand ("midi_volume", MIDI_Volume_f);
dwTempoMultiplier = 100;
dwVolumePercent = 0xffff;
bFileOpen = FALSE;
bPlaying = FALSE;
bLooped = TRUE;
bPaused = FALSE;
bBuffersPrepared = FALSE;
uCallbackStatus = 0;
bMidiInited = 1;
return TRUE;
}
void MIDI_Play(char *Name)
{
MMRESULT mmrRetVal;
char Temp[100];
char *Data;
if (!bMidiInited) //don't try to play if there is no midi
return;
sprintf(Temp, "midi/%s.mid", Name);
MIDI_Stop();
if (StreamBufferSetup(Temp))
{
Con_Printf("Couldn't load midi file %s\n",Temp);
}
else
{
bFileOpen = TRUE;
Con_Printf("Playing midi file %s\n",Temp);
uCallbackStatus = 0;
mmrRetVal = midiStreamRestart(hStream);
if (mmrRetVal != MMSYSERR_NOERROR)
{
MidiErrorMessageBox(mmrRetVal);
return;
}
midiOutSetVolume(hStream, (dwVolumePercent<<16)+dwVolumePercent);
bPlaying = TRUE;
}
}
void MIDI_Pause(void)
{
if(bPaused)
midiStreamRestart(hStream);
else
midiStreamPause(hStream);
bPaused = !bPaused;
}
void MIDI_Loop(int NewValue)
{
if (NewValue == 2)
bLooped = !bLooped;
else bLooped = NewValue;
}
void MIDI_Stop(void)
{
MMRESULT mmrRetVal;
if(bFileOpen || bPlaying)// || uCallbackStatus != STATUS_CALLBACKDEAD)
{
bPlaying = bPaused = FALSE;
if (uCallbackStatus != STATUS_CALLBACKDEAD && uCallbackStatus != STATUS_WAITINGFOREND)
uCallbackStatus = STATUS_KILLCALLBACK;
mmrRetVal = midiStreamStop(hStream);
if (mmrRetVal != MMSYSERR_NOERROR)
{
MidiErrorMessageBox(mmrRetVal);
return;
}
mmrRetVal = midiOutReset((HMIDIOUT)hStream);
if(mmrRetVal != MMSYSERR_NOERROR)
{
MidiErrorMessageBox(mmrRetVal);
return;
}
if(WaitForSingleObject(hBufferReturnEvent,DEBUG_CALLBACK_TIMEOUT) == WAIT_TIMEOUT)
{
//Con_Printf("Timed out waiting for MIDI callback\n");
uCallbackStatus = STATUS_CALLBACKDEAD;
}
}
if (uCallbackStatus == STATUS_CALLBACKDEAD)
{
uCallbackStatus = 0;
if (bFileOpen)
{
ConverterCleanup();
FreeBuffers();
if (hStream)
{
mmrRetVal = midiStreamClose(hStream);
if (mmrRetVal != MMSYSERR_NOERROR)
{
MidiErrorMessageBox(mmrRetVal);
}
hStream = NULL;
}
bFileOpen = FALSE;
}
}
}
void MIDI_Cleanup(void)
{
MMRESULT mmrRetVal;
MIDI_Stop();
CloseHandle(hBufferReturnEvent);
if(hStream)
{
mmrRetVal = midiStreamClose(hStream);
if(mmrRetVal != MMSYSERR_NOERROR)
{
MidiErrorMessageBox(mmrRetVal);
}
hStream = NULL;
}
}
/*****************************************************************************/
/* FreeBuffers() */
/* */
/* This function unprepares and frees all our buffers -- something we must */
/* do to work around a bug in MMYSYSTEM that prevents a device from playing */
/* back properly unless it is closed and reopened after each stop. */
/*****************************************************************************/
void FreeBuffers(void)
{
DWORD idx;
MMRESULT mmrRetVal;
if(bBuffersPrepared)
{
for(idx=0;idx<NUM_STREAM_BUFFERS;idx++)
{
mmrRetVal = midiOutUnprepareHeader((HMIDIOUT)hStream,&ciStreamBuffers[idx].mhBuffer,sizeof(MIDIHDR));
if(mmrRetVal != MMSYSERR_NOERROR)
{
MidiErrorMessageBox(mmrRetVal);
}
}
bBuffersPrepared = FALSE;
}
// Free our stream buffers...
for(idx=0;idx<NUM_STREAM_BUFFERS;idx++)
if(ciStreamBuffers[idx].mhBuffer.lpData)
{
GlobalFreePtr( ciStreamBuffers[idx].mhBuffer.lpData);
ciStreamBuffers[idx].mhBuffer.lpData = NULL;
}
}
/*****************************************************************************/
/* StreamBufferSetup() */
/* */
/* This function uses the filename stored in the global character array to*/
/* open a MIDI file. Then it goes tabout converting at least the first part of*/
/* that file into a midiStream buffer for playback. */
/*****************************************************************************/
BOOL StreamBufferSetup(char *Name)
{
int nChkErr;
BOOL bFoundEnd = FALSE;
DWORD dwConvertFlag, idx;
MMRESULT mmrRetVal;
MIDIPROPTIMEDIV mptd;
if(!hStream)
{
mmrRetVal = midiStreamOpen(&hStream,&uMIDIDeviceID,(DWORD)1,(DWORD)MidiProc,(DWORD)0,CALLBACK_FUNCTION);
if(mmrRetVal != MMSYSERR_NOERROR)
{
MidiErrorMessageBox(mmrRetVal);
return(TRUE);
}
}
for(idx=0;idx<NUM_STREAM_BUFFERS;idx++)
{
ciStreamBuffers[idx].mhBuffer.dwBufferLength = OUT_BUFFER_SIZE;
ciStreamBuffers[idx].mhBuffer.lpData = GlobalAllocPtr(GHND,OUT_BUFFER_SIZE);
if(ciStreamBuffers[idx].mhBuffer.lpData == NULL)
{
// Buffers we already allocated will be killed by WM_DESTROY
// after we fail on the create by returning with -1
return(-1);
}
}
if(ConverterInit(Name))
return(TRUE);
// Initialize the volume cache array to some pre-defined value
for(idx=0;idx<NUM_CHANNELS;idx++)
dwVolCache[idx] = VOL_CACHE_INIT;
mptd.cbStruct = sizeof(mptd);
mptd.dwTimeDiv = ifs.dwTimeDivision;
mmrRetVal = midiStreamProperty(hStream,(LPBYTE)&mptd,MIDIPROP_SET | MIDIPROP_TIMEDIV);
if(mmrRetVal != MMSYSERR_NOERROR)
{
MidiErrorMessageBox(mmrRetVal);
ConverterCleanup();
return(TRUE);
}
nEmptyBuffers = 0;
dwConvertFlag = CONVERTF_RESET;
for(nCurrentBuffer=0;nCurrentBuffer<NUM_STREAM_BUFFERS;nCurrentBuffer++)
{
// Tell the converter to convert up to one entire buffer's length of output
// data. Also, set a flag so it knows to reset any saved state variables it
// may keep from call to call.
ciStreamBuffers[nCurrentBuffer].dwStartOffset = 0;
ciStreamBuffers[nCurrentBuffer].dwMaxLength = OUT_BUFFER_SIZE;
ciStreamBuffers[nCurrentBuffer].tkStart = 0;
ciStreamBuffers[nCurrentBuffer].bTimesUp = FALSE;
nChkErr = ConvertToBuffer(dwConvertFlag,&ciStreamBuffers[nCurrentBuffer]);
if (nChkErr != CONVERTERR_NOERROR)
{
if (nChkErr == CONVERTERR_DONE)
{
bFoundEnd = TRUE;
}
else
{
DebugPrint("Initial conversion pass failed");
ConverterCleanup();
return(TRUE);
}
}
ciStreamBuffers[nCurrentBuffer].mhBuffer.dwBytesRecorded = ciStreamBuffers[nCurrentBuffer].dwBytesRecorded;
if(!bBuffersPrepared)
{
mmrRetVal = midiOutPrepareHeader((HMIDIOUT)hStream,&ciStreamBuffers[nCurrentBuffer].mhBuffer,sizeof(MIDIHDR));
if(mmrRetVal != MMSYSERR_NOERROR)
{
MidiErrorMessageBox(mmrRetVal);
ConverterCleanup();
return(TRUE);
}
}
mmrRetVal = midiStreamOut(hStream,&ciStreamBuffers[nCurrentBuffer].mhBuffer,sizeof(MIDIHDR));
if(mmrRetVal != MMSYSERR_NOERROR)
{
MidiErrorMessageBox(mmrRetVal);
break;
}
dwConvertFlag = 0;
if(bFoundEnd)
break;
}
bBuffersPrepared = TRUE;
nCurrentBuffer = 0;
return(FALSE);
}
/*****************************************************************************/
/* MidiProc() */
/* */
/* This is the callback handler which continually refills MIDI data buffers*/
/* as they're returned to us from the audio subsystem. */
/*****************************************************************************/
void CALLBACK MidiProc(HMIDIIN hMidi,UINT uMsg,DWORD dwInstance,DWORD dwParam1,DWORD dwParam2)
{
static int nWaitingBuffers = 0;
MIDIEVENT *pme;
MIDIHDR *pmh;
MMRESULT mmrRetVal;
int nChkErr;
switch(uMsg)
{
case MOM_DONE:
if(uCallbackStatus == STATUS_CALLBACKDEAD)
{
return;
}
nEmptyBuffers++;
if(uCallbackStatus == STATUS_WAITINGFOREND)
{
if(nEmptyBuffers < NUM_STREAM_BUFFERS)
{
return;
}
else
{
uCallbackStatus = STATUS_CALLBACKDEAD;
MIDI_Stop();
SetEvent(hBufferReturnEvent);
return;
}
}
// This flag is set whenever the callback is waiting for all buffers to
// come back.
if(uCallbackStatus == STATUS_KILLCALLBACK)
{
// Count NUM_STREAM_BUFFERS-1 being returned for the last time
if(nEmptyBuffers < NUM_STREAM_BUFFERS)
{
return;
}
// Then send a stop message when we get the last buffer back...
else
{
// Change the status to callback dead
uCallbackStatus = STATUS_CALLBACKDEAD;
SetEvent( hBufferReturnEvent );
return;
}
}
dwProgressBytes += ciStreamBuffers[nCurrentBuffer].mhBuffer.dwBytesRecorded;
///////////////////////////////////////////////////////////////////////////////
// Fill an available buffer with audio data again...
if(bPlaying && nEmptyBuffers)
{
ciStreamBuffers[nCurrentBuffer].dwStartOffset = 0;
ciStreamBuffers[nCurrentBuffer].dwMaxLength = OUT_BUFFER_SIZE;
ciStreamBuffers[nCurrentBuffer].tkStart = 0;
ciStreamBuffers[nCurrentBuffer].dwBytesRecorded = 0;
ciStreamBuffers[nCurrentBuffer].bTimesUp = FALSE;
nChkErr = ConvertToBuffer(0,&ciStreamBuffers[nCurrentBuffer]);
if(nChkErr != CONVERTERR_NOERROR)
{
if(nChkErr == CONVERTERR_DONE)
{
// Don't include this one in the count
nWaitingBuffers = NUM_STREAM_BUFFERS - 1;
uCallbackStatus = STATUS_WAITINGFOREND;
return;
}
else
{
Con_Printf( "MidiProc() conversion pass failed!" );
ConverterCleanup();
return;
}
}
ciStreamBuffers[nCurrentBuffer].mhBuffer.dwBytesRecorded = ciStreamBuffers[nCurrentBuffer].dwBytesRecorded;
mmrRetVal = midiStreamOut(hStream,&ciStreamBuffers[nCurrentBuffer].mhBuffer,sizeof(MIDIHDR));
if(mmrRetVal != MMSYSERR_NOERROR )
{
MidiErrorMessageBox( mmrRetVal );
ConverterCleanup();
return;
}
nCurrentBuffer = ( nCurrentBuffer + 1 ) % NUM_STREAM_BUFFERS;
nEmptyBuffers--;
}
break;
case MOM_POSITIONCB:
pmh = (MIDIHDR *)dwParam1;
pme = (MIDIEVENT *)(pmh->lpData + pmh->dwOffset);
if (MIDIEVENT_TYPE( pme->dwEvent ) == MIDI_CTRLCHANGE)
{
if (MIDIEVENT_DATA1( pme->dwEvent ) == MIDICTRL_VOLUME_LSB)
{
DebugPrint( "Got an LSB volume event" );
break;
}
if (MIDIEVENT_DATA1( pme->dwEvent ) != MIDICTRL_VOLUME)
break;
// Mask off the channel number and cache the volume data byte
dwVolCache[ MIDIEVENT_CHANNEL( pme->dwEvent )] = MIDIEVENT_VOLUME( pme->dwEvent );
// Post a message so that the main program knows to counteract
// the effects of the volume event in the stream with its own
// generated event which reflects the proper trackbar position.
/* PostMessage( hWndMain, WM_MSTREAM_UPDATEVOLUME,MIDIEVENT_CHANNEL( pme->dwEvent ), 0L );*/
}
break;
default:
break;
}
return;
}
/****************************************************************************/
/* SetAllChannelVolumes() */
/* */
/* Given a percent in tenths of a percent, sets volume on all channels to */
/* reflect the new value. */
/****************************************************************************/
void SetAllChannelVolumes(DWORD dwVolumePercent)
{
DWORD dwEvent, dwStatus, dwVol, idx;
MMRESULT mmrRetVal;
/* if(!bPlaying)
return;*/
for(idx=0,dwStatus=MIDI_CTRLCHANGE;idx<NUM_CHANNELS;idx++,dwStatus++)
{
dwVol = ( dwVolCache[idx] * dwVolumePercent ) / 1000;
dwEvent = dwStatus | ((DWORD)MIDICTRL_VOLUME << 8) | ((DWORD)dwVol << 16);
mmrRetVal = midiOutShortMsg((HMIDIOUT)hStream, dwEvent);
if(mmrRetVal != MMSYSERR_NOERROR )
{
MidiErrorMessageBox(mmrRetVal);
return;
}
}
}
/****************************************************************************/
/* SetChannelVolume() */
/* */
/* Given a percent in tenths of a percent, sets volume on a specified */
/* channel to reflect the new value. */
/****************************************************************************/
void SetChannelVolume(DWORD dwChannel, DWORD dwVolumePercent)
{
DWORD dwEvent, dwVol;
MMRESULT mmrRetVal;
if(!bPlaying)
return;
dwVol = (dwVolCache[dwChannel] * dwVolumePercent) / 1000;
dwEvent = MIDI_CTRLCHANGE | dwChannel | ((DWORD)MIDICTRL_VOLUME << 8) | ((DWORD)dwVol << 16);
mmrRetVal = midiOutShortMsg((HMIDIOUT)hStream, dwEvent);
if(mmrRetVal != MMSYSERR_NOERROR )
{
MidiErrorMessageBox( mmrRetVal );
return;
}
}
/*
* $Log: /H2 Mission Pack/midi.c $
*
* 3 2/12/98 12:26a Jmonroe
* finished -nomidi to not try to play
*
* 2 1/15/98 10:04p Jmonroe
* added stub mpack menu stuff
*
* 3 4/24/97 3:32p Bgokey
*
* 2 4/24/97 12:13p Bgokey
*
* 1 2/27/97 4:04p Rjohnson
* Initial Revision
*/