ew-engine/hq engine src/Midi.c
2006-10-08 00:00:00 +00:00

682 lines
19 KiB
C

/*
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
/*
* $Header: /H2 Mission Pack/midi.c 3 2/12/98 12:26a Jmonroe $
*/
#include "quakedef.h"
//#define USE_MIDI
#ifdef USE_MIDI
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <memory.h>
#include <mmreg.h>
#include "midstuff.h"
#include "midi.h"
char current_midi[64];
// evil evil evil evil - raven forgot to add prototypes for these...
void MIDI_Restart_f (void);
void MIDI_Play(char *Name);
void MIDI_Pause(void);
void MIDI_Loop(int NewValue);
void MIDI_Stop(void);
// ugh! hungarian notation! someone's gonna burn in hell for this. bring back the days
// when all variables were called either 'fred' or 'x'
BOOL bMidiInited,bFileOpen, bPlaying, bBuffersPrepared;
BOOL bPaused, bLooped;
UINT uMIDIDeviceID = MIDI_MAPPER, uCallbackStatus;
int nCurrentBuffer, nEmptyBuffers;
DWORD dwBufferTickLength, dwCurrentTempo, dwProgressBytes;
DWORD dwVolCache[NUM_CHANNELS];
// the use of the word 'percent' here is misleading, since it's actually a 0 to 65535 scale...
cvar_t midi_volumepercent = {"midi_volumepercent", "65535", true};
cvar_t miditempo = {"miditempo", "100", true};
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_Restart_f (void)
{
MIDI_Play (current_midi);
}
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);
}
else
{
Con_Printf ("usage: midi_loop <on|off|toggle>\n");
return;
}
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)
{
if (atol (Cmd_Argv (1)) > 10.0)
{
Con_Printf ("Use a 0 to 10 range, please\n");
return;
}
else
{
Cvar_SetValue ("midi_volumepercent", atol(Cmd_Argv(1))*6553.5);
midiOutSetVolume((HMIDIOUT) hStream,((DWORD) midi_volumepercent.value<<16)+midi_volumepercent.value);
}
}
else Con_Printf("MIDI volume is %0.1f\n", midi_volumepercent.value/6553.5);
}
void MIDI_Tempo_f (void)
{
if (Cmd_Argc () == 2)
{
Cvar_SetValue ("miditempo", atol(Cmd_Argv(1)));
MIDI_Restart_f ();
}
else Con_Printf("MIDI tempo is %0.1f\n", miditempo.value);
}
void MIDI_ChangeVolume (void)
{
midiOutSetVolume((HMIDIOUT) hStream,((DWORD) midi_volumepercent.value<<16)+midi_volumepercent.value);
}
void MIDI_Init(void)
{
MMRESULT mmrRetVal;
Con_Printf ("MIDI_Init - Starting midi sound system\n");
hBufferReturnEvent = CreateEvent(NULL,FALSE,FALSE,"Wait For Buffer Return");
// register this now to prevent errors with midi_volumepercent not being registered
Cvar_RegisterVariable (&midi_volumepercent);
if(COM_CheckParm("-nomidi"))
{
bMidiInited = 0;
return;
}
if(COM_CheckParm("-nomusic"))
{
bMidiInited = 0;
return;
}
mmrRetVal = midiStreamOpen(&hStream,&uMIDIDeviceID,(DWORD)1,(DWORD)MidiProc,(DWORD)0,CALLBACK_FUNCTION);
if(mmrRetVal != MMSYSERR_NOERROR )
{
bMidiInited = 0;
Con_Printf ("MIDI_Init Error - Could not initialize MIDI Audio\n");
//MidiErrorMessageBox( mmrRetVal );
return;
}
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);
Cmd_AddCommand ("midi_restart", MIDI_Restart_f);
Cmd_AddCommand ("midi_tempo", MIDI_Tempo_f);
Cvar_RegisterVariable (&miditempo);
bFileOpen = FALSE;
bPlaying = FALSE;
bLooped = TRUE;
bPaused = FALSE;
bBuffersPrepared = FALSE;
uCallbackStatus = 0;
bMidiInited = 1;
return;
}
void MIDI_Play(char *Name)
{
MMRESULT mmrRetVal;
char Temp[100];
if (!bMidiInited) //don't try to play if there is no midi
return;
sprintf(Temp, "music/%s.mid", Name);
// save out the current track name (in bare format) for restarts. etc.
// this is an evil way of doing it - i *know* i should be using strcpy,
// but what the fuck...
sprintf(current_midi, "%s", Name);
MIDI_Stop();
if (StreamBufferSetup(Temp))
{
Con_DPrintf("Couldn't load midi file %s\n",Temp);
}
else
{
bFileOpen = TRUE;
Con_DPrintf("Playing midi file %s\n",Temp);
uCallbackStatus = 0;
mmrRetVal = midiStreamRestart(hStream);
if (mmrRetVal != MMSYSERR_NOERROR)
{
MidiErrorMessageBox(mmrRetVal);
return;
}
midiOutSetVolume((HMIDIOUT) hStream, ((DWORD)midi_volumepercent.value<<16)+midi_volumepercent.value);
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;
}
}
#endif