SRB2/src/win32ce/win_snd.c
2018-10-14 10:14:07 +01:00

2408 lines
68 KiB
C

// Emacs style mode select -*- C++ -*-
//-----------------------------------------------------------------------------
//
// Copyright (C) 1998-2000 by DooM Legacy Team.
//
// 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.
//-----------------------------------------------------------------------------
/// \file
/// \brief interface level code for sound
///
/// Uses the midiStream* Win32 functions to play MIDI data
/// with low latency and low processor overhead.
#include "../doomdef.h"
#include "win_main.h"
#include <mmsystem.h>
#define DIRECTSOUND_VERSION 0x0600 /* version 6.0 */
#define DIRECTINPUT_VERSION 0x0700
#define DXVERSION_NTCOMPATIBLE 0x0300
#ifdef _MSC_VER
#pragma warning(disable : 4201)
#endif
#include <dsound.h>
#include "../command.h"
#include "../i_sound.h"
#include "../s_sound.h"
#include "../i_system.h"
#include "../m_argv.h"
#include "../w_wad.h"
#include "../z_zone.h"
#include "../doomstat.h"
#include "dx_error.h"
#include "mid2strm.h"
#ifdef HW3SOUND
#include "../hardware/hw3dsdrv.h"
#include "../hardware/hw3sound.h"
#include "win_dll.h"
#endif
#ifndef SURROUND
#define SURROUND // comment out this to disable the SurroundSound code
#endif
#ifdef SURROUND
ATTRNOINLINE static FUNCNOINLINE void CopyAndInvertMemory(void *dest, void *src, int bytes); //Can't inline ASM that haves lables
#endif
#define FMODSOUND // comment out this to disable MOD/IT/MP3/OGG music playback
///////////////////////////////////////////////////////////
#ifdef FMODSOUND
#ifdef __MINGW32__
#include <FMOD/fmod.h>
#else
#include <fmod.h>
#endif
#ifdef FSOUND_INIT_DONTLATENCYADJUST //Alam: why didn't I think about this before? :)
#define FSOUND_Stream_OpenFile(name,mode,length) FSOUND_Stream_Open(name,mode,0,length)
#else
#define FSOUND_INIT_DONTLATENCYADJUST 0
#endif
#ifdef __MINGW32__
#include <FMOD/fmod_errors.h>
#else
#include <fmod_errors.h> /* optional */
#endif
//#include <conio.h>
#endif
/////////////////////////////////////////////////////////
//#define TESTCODE // remove this for release version
/* briefly described here for convenience:
typedef struct {
WORD wFormatTag; // WAVE_FORMAT_PCM is the only format accepted for DirectSound:
// this tag indicates Pulse Code Modulation (PCM), an uncompressed format
// in which each samples represents the amplitude of the signal at the time
// of sampling.
WORD nChannels; // either one (mono) or two (stereo)
DWORD nSamplesPerSec; // the sampling rate, or frequency, in hertz.
// Typical values are 11,025, 22,050, and 44,100
DWORD nAvgBytesPerSec; // nAvgBytesPerSec is the product of nBlockAlign and nSamplesPerSec
WORD nBlockAlign; // the number of bytes required for each complete sample, for PCM formats
// is equal to (wBitsPerSample * nChannels / 8).
WORD wBitsPerSample; // gives the size of each sample, generally 8 or 16 bits
WORD cbSize; // cbSize gives the size of any extra fields required to describe a
// specialized wave format. This member is always zero for PCM formats.
} WAVEFORMATEX;
*/
// Tails 11-21-2002
#ifdef FMODSOUND
static FMUSIC_MODULE *mod = NULL;
static int fsoundchannel = -1;
static int fsoundfreq = 0;
static int fmodvol = 127;
static FSOUND_STREAM *fmus = NULL;
#endif
// --------------------------------------------------------------------------
// DirectSound stuff
// --------------------------------------------------------------------------
static LPDIRECTSOUND DSnd = NULL;
static LPDIRECTSOUNDBUFFER DSndPrimary = NULL; ;
// Stack sounds means sounds put on top of each other, since DirectSound can't play
// the same sound buffer at different locations at the same time, we need to dupli-
// cate existing buffers to play multiple instances of the same sound in the same
// time frame. A duplicate sound is freed when it is no more used. The priority that
// comes from the s_sound engine, is kept so that the lowest priority sounds are
// stopped to make place for the new sound, unless the new sound has a lower priority
// than all playing sounds, in which case the sound is not started.
#define MAXSTACKSOUNDS 32 // this is the absolute number of sounds that
// can play simultaneously, whatever the value
// of cv_numChannels
typedef struct {
LPDIRECTSOUNDBUFFER lpSndBuf;
#ifdef SURROUND
// judgecutor:
// Need for produce surround sound
LPDIRECTSOUNDBUFFER lpSurround;
#endif
int priority;
boolean duplicate;
} StackSound_t;
static StackSound_t StackSounds[MAXSTACKSOUNDS];
// --------------------------------------------------------------------------
// Fill the DirectSoundBuffer with data from a sample, made separate so that
// sound data cna be reloaded if a sound buffer was lost.
// --------------------------------------------------------------------------
static boolean CopySoundData (LPDIRECTSOUNDBUFFER dsbuffer, LPBYTE data, DWORD length)
{
LPVOID lpvAudio1; // receives address of lock start
DWORD dwBytes1; // receives number of bytes locked
LPVOID lpvAudio2; // receives address of lock start
DWORD dwBytes2; // receives number of bytes locked
HRESULT hr;
// Obtain memory address of write block.
hr = IDirectSoundBuffer_Lock (dsbuffer, 0, length, &lpvAudio1, &dwBytes1, &lpvAudio2, &dwBytes2, 0);
// If DSERR_BUFFERLOST is returned, restore and retry lock.
if (hr == DSERR_BUFFERLOST)
{
hr = IDirectSoundBuffer_Restore (dsbuffer);
if (FAILED (hr))
I_Error("Restore fail on %x, %s\n",dsbuffer,DXErrorToString(hr));
hr = IDirectSoundBuffer_Lock (dsbuffer, 0, length, &lpvAudio1, &dwBytes1, &lpvAudio2, &dwBytes2, 0);
if (FAILED (hr))
I_Error("Lock fail(2) on %x, %s\n",dsbuffer,DXErrorToString(hr));
}
else
if (FAILED (hr))
I_Error("Lock fail(1) on %x, %s\n",dsbuffer,DXErrorToString(hr));
// copy wave data into the buffer (note: dwBytes1 should equal to dsbdesc->dwBufferBytes ...)
CopyMemory (lpvAudio1, data, dwBytes1);
if (dwBytes2 && lpvAudio2)
CopyMemory(lpvAudio2, data+dwBytes1, dwBytes2);
// finally, unlock the buffer
hr = IDirectSoundBuffer_Unlock (dsbuffer, lpvAudio1, dwBytes1, lpvAudio2, dwBytes2);
if (FAILED (hr))
I_Error("Unlock fail on %x, %s\n",dsbuffer,DXErrorToString(hr));
return true;
}
#ifdef SURROUND
// judgecutor:
// Hmmm... May be this function is not too good...
static void CopyAndInvertMemory(void *dest, void *src, int bytes)
{
#ifdef __GNUC__
__asm__("CAIM:;lodsb;neg %%al;stosb;loop CAIM;"::"c"(bytes),"D"(dest),"S"(src): "memory","cc");
#else
_asm
{
push esi
push edi
push ecx
mov ecx,bytes
mov esi,src
mov edi,dest
a:
lodsb
neg al
stosb
loop a
pop ecx
pop edi
pop esi
}
#endif
}
// judgecutor:
// Like normal CopySoundData but sound data will be inverted
static boolean CopyAndInvertSoundData(LPDIRECTSOUNDBUFFER dsbuffer, LPBYTE data, DWORD length)
{
LPVOID lpvAudio1 = NULL; // receives address of lock start
DWORD dwBytes1 = 0; // receives number of bytes locked
LPVOID lpvAudio2 = NULL;
DWORD dwBytes2 = 0;
HRESULT hr;
// Obtain memory address of write block.
hr = IDirectSoundBuffer_Lock (dsbuffer, 0, length, &lpvAudio1, &dwBytes1, &lpvAudio2, &dwBytes2, 0);
// If DSERR_BUFFERLOST is returned, restore and retry lock.
if (hr == DSERR_BUFFERLOST)
{
hr = IDirectSoundBuffer_Restore (dsbuffer);
if (FAILED (hr))
I_Error("CopyAndInvert: Restore fail on %x, %s\n",dsbuffer,DXErrorToString(hr));
hr = IDirectSoundBuffer_Lock (dsbuffer, 0, length, &lpvAudio1, &dwBytes1, &lpvAudio2, &dwBytes2, 0);
if (FAILED (hr))
I_Error("CopyAndInvert: Lock fail(2) on %x, %s\n",dsbuffer,DXErrorToString(hr));
} else if (FAILED (hr))
I_Error("CopyAndInvetrt: Lock fail(1) on %x, %s\n",dsbuffer,DXErrorToString(hr));
// copy wave data into the buffer (note: dwBytes1 should equal to dsbdesc->dwBufferBytes ...)
CopyAndInvertMemory (lpvAudio1, data, dwBytes1);
if (dwBytes2 && lpvAudio2)
CopyAndInvertMemory(lpvAudio2, data+dwBytes1, dwBytes2);
hr = IDirectSoundBuffer_Unlock (dsbuffer, lpvAudio1, dwBytes1, lpvAudio2, dwBytes2);
if (FAILED (hr))
I_Error("CopyAndInvert: Unlock fail on %x, %s\n",dsbuffer,DXErrorToString(hr));
return false;
}
#endif
static DWORD sound_buffer_flags = DSBCAPS_CTRLPAN |
DSBCAPS_CTRLVOLUME |
DSBCAPS_STICKYFOCUS |
//DSBCAPS_LOCSOFTWARE |
DSBCAPS_STATIC;
// --------------------------------------------------------------------------
// raw2DS : convert a raw sound data, returns a LPDIRECTSOUNDBUFFER
// --------------------------------------------------------------------------
// dsdata points a 4 UINT16 header:
// +0 : value 3 what does it mean?
// +2 : sample rate, either 11025 or 22050.
// +4 : number of samples, each sample is a single byte since it's 8bit
// +6 : value 0
//
#ifdef SURROUND
// judgecutor:
// We need an another function definition for supporting the surround sound
// Invert just cause to copy an inverted sound data
static LPDIRECTSOUNDBUFFER raw2DS(LPBYTE *dsdata, size_t len, UINT8 invert)
#else
static LPDIRECTSOUNDBUFFER raw2DS(LPBYTE *dsdata, size_t len)
#endif
{
HRESULT hr;
WAVEFORMATEX wfm;
DSBUFFERDESC dsbdesc;
LPDIRECTSOUNDBUFFER dsbuffer;
// initialise WAVEFORMATEX structure describing the wave format
ZeroMemory (&wfm, sizeof (WAVEFORMATEX));
wfm.wFormatTag = WAVE_FORMAT_PCM;
wfm.nChannels = 1;
wfm.nSamplesPerSec = (dsdata[3]<<8)+dsdata[2]; //mostly 11025, but some at 22050.
wfm.wBitsPerSample = 8;
wfm.nBlockAlign = (WORD)(wfm.wBitsPerSample / 8 * wfm.nChannels);
wfm.nAvgBytesPerSec = wfm.nSamplesPerSec * wfm.nBlockAlign;
// Set up DSBUFFERDESC structure.
ZeroMemory (&dsbdesc, sizeof (DSBUFFERDESC));
dsbdesc.dwSize = sizeof (DSBUFFERDESC);
/* dsbdesc.dwFlags = DSBCAPS_CTRLPAN |
DSBCAPS_CTRLVOLUME |
DSBCAPS_STICKYFOCUS |
//DSBCAPS_LOCSOFTWARE |
DSBCAPS_STATIC
| DSBCAPS_CTRLFREQUENCY; // This one for pitching
*/
dsbdesc.dwFlags = sound_buffer_flags;
dsbdesc.dwBufferBytes = len-8;
dsbdesc.lpwfxFormat = &wfm; // pointer to WAVEFORMATEX structure
// Create the sound buffer
hr = IDirectSound_CreateSoundBuffer (DSnd, &dsbdesc, &dsbuffer, NULL);
if (hr == DSERR_CONTROLUNAVAIL)
{
CONS_Printf("\tSoundBufferCreate error - a buffer control is not available.\n\tTrying to disable frequency control.\n");
sound_buffer_flags &= ~DSBCAPS_CTRLFREQUENCY;
dsbdesc.dwFlags = sound_buffer_flags;
hr = IDirectSound_CreateSoundBuffer (DSnd, &dsbdesc, &dsbuffer, NULL);
}
if (FAILED(hr))
I_Error("CreateSoundBuffer() FAILED: %s\n", DXErrorToString(hr));
#ifdef SURROUND
if (invert)
// just invert a sound data for producing the surround sound
CopyAndInvertSoundData(dsbuffer, (LPBYTE)dsdata + 8, dsbdesc.dwBufferBytes);
else
// Do a normal operation
#endif
// fill the DirectSoundBuffer waveform data
CopySoundData (dsbuffer, (LPBYTE)dsdata + 8, dsbdesc.dwBufferBytes);
return dsbuffer;
}
// --------------------------------------------------------------------------
// This function loads the sound data from the WAD lump, for single sound.
// --------------------------------------------------------------------------
void *I_GetSfx (sfxinfo_t * sfx)
{
LPBYTE dssfx;
if (sfx->lumpnum < 0)
sfx->lumpnum = S_GetSfxLumpNum (sfx);
#ifdef HW3SOUND
if (hws_mode != HWS_DEFAULT_MODE)
return HW3S_GetSfx(sfx);
#endif
sfx->length = W_LumpLength (sfx->lumpnum);
// PU_CACHE because the data is copied to the DIRECTSOUNDBUFFER, the one here will not be used
dssfx = (LPBYTE) W_CacheLumpNum (sfx->lumpnum, PU_CACHE);
#ifdef SURROUND
// Make a normal (not inverted) sound buffer
return (void *)raw2DS (dssfx, sfx->length, FALSE);
#else
// return the LPDIRECTSOUNDBUFFER, which will be stored in S_sfx[].data
return (void *)raw2DS (dssfx, sfx->length);
#endif
}
// --------------------------------------------------------------------------
// Free all allocated resources for a single sound
// --------------------------------------------------------------------------
void I_FreeSfx (sfxinfo_t *sfx)
{
LPDIRECTSOUNDBUFFER dsbuffer;
if (sfx->lumpnum < 0)
return;
#ifdef HW3SOUND
if (hws_mode != HWS_DEFAULT_MODE)
{
HW3S_FreeSfx(sfx);
}
else
#endif
{
//CONS_Printf("I_FreeSfx(%d)\n", sfx->lumpnum);
// free DIRECTSOUNDBUFFER
dsbuffer = (LPDIRECTSOUNDBUFFER) sfx->data;
if (dsbuffer)
{
size_t i;
for (i = 0; i < MAXSTACKSOUNDS; i++)
{
if (StackSounds[i].lpSndBuf == dsbuffer)
{
StackSounds[i].lpSndBuf = NULL;
#ifdef SURROUND
if (StackSounds[i].lpSurround)
{
IDirectSoundBuffer_Stop(StackSounds[i].lpSurround);
IDirectSoundBuffer_Release(StackSounds[i].lpSurround);
}
StackSounds[i].lpSurround = NULL;
#endif
}
}
IDirectSoundBuffer_Stop (dsbuffer);
IDirectSoundBuffer_Release (dsbuffer);
}
}
sfx->data = NULL;
sfx->lumpnum = -1;
}
// --------------------------------------------------------------------------
// Set the global volume for sound effects
// --------------------------------------------------------------------------
void I_SetSfxVolume(INT32 volume)
{
int vol;
HRESULT hr;
if (nosound || !sound_started)
return;
// use the last quarter of volume range
if (volume)
vol = (volume * ((DSBVOLUME_MAX-DSBVOLUME_MIN)/4)) / 31 +
(DSBVOLUME_MAX - ((DSBVOLUME_MAX-DSBVOLUME_MIN)/4));
else
vol = DSBVOLUME_MIN; // make sure 0 is silence
//CONS_Printf("setvolume to %d\n", vol);
hr = IDirectSoundBuffer_SetVolume (DSndPrimary, vol);
//if (FAILED(hr))
// CONS_Printf("setvolumne failed\n");
}
// --------------------------------------------------------------------------
// Update the volume for a secondary buffer, make sure it was created with
// DSBCAPS_CTRLVOLUME
// --------------------------------------------------------------------------
static void I_UpdateSoundVolume (LPDIRECTSOUNDBUFFER lpSnd, int volume)
{
HRESULT hr;
volume = (volume * ((DSBVOLUME_MAX-DSBVOLUME_MIN)/4)) / 256 +
(DSBVOLUME_MAX - ((DSBVOLUME_MAX-DSBVOLUME_MIN)/4));
hr = IDirectSoundBuffer_SetVolume (lpSnd, volume);
//if (FAILED(hr))
// CONS_Printf("\2SetVolume FAILED\n");
}
// --------------------------------------------------------------------------
// Update the panning for a secondary buffer, make sure it was created with
// DSBCAPS_CTRLPAN
// --------------------------------------------------------------------------
#define DSBPAN_RANGE (DSBPAN_RIGHT-(DSBPAN_LEFT))
#define SEP_RANGE 256 //Doom sounds pan range 0-255 (128 is centre)
static void I_UpdateSoundPanning (LPDIRECTSOUNDBUFFER lpSnd, int sep)
{
HRESULT hr;
hr = IDirectSoundBuffer_SetPan (lpSnd, (sep * DSBPAN_RANGE)/SEP_RANGE - DSBPAN_RIGHT);
//if (FAILED(hr))
// CONS_Printf("SetPan FAILED for sep %d pan %d\n", sep, (sep * DSBPAN_RANGE)/SEP_RANGE - DSBPAN_RIGHT);
}
// search a free slot in the stack, free it if needed
static int GetFreeStackNum(int newpriority)
{
int lowestpri = 256,lowestprihandle = 0;
int i;
// DirectSound can't play multiple instances of the same sound buffer
// unless they are duplicated, so if the sound buffer is in use, make a duplicate
for (i = 0; i < MAXSTACKSOUNDS; i++)
{
// find a free 'playing sound slot' to use
if (StackSounds[i].lpSndBuf == NULL)
{
//CONS_Printf("\t\tfound free slot %d\n", i);
return i;
}
else if (!I_SoundIsPlaying(i)) // check for sounds that finished playing, and can be freed
{
//CONS_Printf("\t\tfinished sound in slot %d\n", i);
//stop sound and free the 'slot'
I_StopSound (i);
// we can use this one since it's now freed
return i;
}
else if (StackSounds[i].priority < lowestpri) //remember lowest priority sound
{
lowestpri = StackSounds[i].priority;
lowestprihandle = i;
}
}
// the maximum of sounds playing at the same time is reached, if we have at least
// one sound playing with a lower priority, stop it and replace it with the new one
//CONS_Printf("\t\tall slots occupied..\n");
if (newpriority >= lowestpri)
{
I_StopSound (lowestprihandle);
return lowestprihandle;
//CONS_Printf(" kicking out lowest priority slot: %d pri: %d, my priority: %d\n",
// handle, lowestpri, priority);
}
return -1;
}
#ifdef SURROUND
static LPDIRECTSOUNDBUFFER CreateInvertedSound(int id)
{
lumpnnum_t lumpnum;
LBYPTE dsdata;
lumpnum = S_sfx[id].lumpnum;
if (lumpnum < 0)
lumpnum = S_GetSfxLumpNum (&S_sfx[id]);
dsdata = W_CacheLumpNum (lumpnum, PU_CACHE);
return raw2DS(dsdata, S_sfx[id].length, TRUE);
}
#endif
// Calculate internal pitch from Doom pitch
#if 0
static float recalc_pitch(int doom_pitch)
{
return doom_pitch < NORMAL_PITCH ?
(float)(doom_pitch + NORMAL_PITCH) / (NORMAL_PITCH * 2)
:(float)doom_pitch / (float)NORMAL_PITCH;
}
#endif
// --------------------------------------------------------------------------
// Start the given S_sfx[id] sound with given properties (panning, volume..)
// --------------------------------------------------------------------------
INT32 I_StartSound (sfxenum_t id,
INT32 vol,
INT32 sep,
INT32 pitch,
INT32 priority,
INT32 channel)
{
HRESULT hr;
LPDIRECTSOUNDBUFFER dsbuffer;
DWORD dwStatus;
int handle;
int i;
//DWORD freq;
#ifdef SURROUND
LPDIRECTSOUNDBUFFER dssurround;
#endif
(void)channel;
if (nosound)
return -1;
//CONS_Printf("I_StartSound:\n\t\tS_sfx[%d]\n", id);
handle = GetFreeStackNum(priority);
if (handle < 0)
return -1;
//CONS_Printf("\t\tusing handle %d\n", handle);
// if the original buffer is playing, duplicate it (DirectSound specific)
// else, use the original buffer
dsbuffer = (LPDIRECTSOUNDBUFFER) S_sfx[id].data;
IDirectSoundBuffer_GetStatus (dsbuffer, &dwStatus);
if (dwStatus & (DSBSTATUS_PLAYING | DSBSTATUS_LOOPING))
{
//CONS_Printf("\t\toriginal sound S_sfx[%d] is playing, duplicating.. ", id);
hr = IDirectSound_DuplicateSoundBuffer(DSnd, (LPDIRECTSOUNDBUFFER) S_sfx[id].data, &dsbuffer);
if (FAILED(hr))
{
//CONS_Printf("Cound't duplicate sound buffer\n");
// re-use the original then..
dsbuffer = (LPDIRECTSOUNDBUFFER) S_sfx[id].data;
// clean up stacksounds info
for (i = 0; i < MAXSTACKSOUNDS; i++)
if (handle != i &&
StackSounds[i].lpSndBuf == dsbuffer)
{
StackSounds[i].lpSndBuf = NULL;
}
}
// stop the duplicate or the re-used original
IDirectSoundBuffer_Stop (dsbuffer);
}
//judgecutor: Sound pitching
#if 0
if (cv_rndsoundpitch.value)
{
// At first reset the buffer back to original frequency
hr = IDirectSoundBuffer_SetFrequency(dsbuffer, DSBFREQUENCY_ORIGINAL);
if (SUCCEEDED (hr))
{
IDirectSoundBuffer_GetFrequency(dsbuffer, &freq);
// Now pitch it
freq *= recalc_pitch(pitch);
IDirectSoundBuffer_SetFrequency(dsbuffer, freq);
}
else
cv_rndsoundpitch = 0;
}
#else
pitch = 0;
#endif
// store information on the playing sound
StackSounds[handle].lpSndBuf = dsbuffer;
StackSounds[handle].priority = priority;
StackSounds[handle].duplicate = (dsbuffer != (LPDIRECTSOUNDBUFFER)S_sfx[id].data);
//CONS_Printf("StackSounds[%d].lpSndBuf is %s\n", handle, StackSounds[handle].lpSndBuf == NULL ? "Null":"valid");
//CONS_Printf("StackSounds[%d].priority is %d\n", handle, StackSounds[handle].priority);
//CONS_Printf("StackSounds[%d].duplicate is %s\n", handle, StackSounds[handle].duplicate ? "TRUE":"FALSE");
I_UpdateSoundVolume (dsbuffer, vol);
#ifdef SURROUND
// Prepare the surround sound buffer
// Use a normal sound data for the left channel (with pan == 0)
// and an inverted sound data for the right channel (with pan == 255)
dssurround = CreateInvertedSound(id);
// Surround must be pitched too
#if 0
if (cv_rndsoundpitch.value)
IDirectSoundBuffer_SetFrequency(dssurround, freq);
#endif
if (sep == -128)
{
I_UpdateSoundPanning(dssurround, 255);
I_UpdateSoundVolume(dssurround, vol);
I_UpdateSoundPanning(dsbuffer, 0);
IDirectSoundBuffer_SetCurrentPosition(dssurround, 0);
}
else
// Perform normal operation
#endif
I_UpdateSoundPanning (dsbuffer, sep);
IDirectSoundBuffer_SetCurrentPosition (dsbuffer, 0);
hr = IDirectSoundBuffer_Play (dsbuffer, 0, 0, 0);
if (hr == DSERR_BUFFERLOST)
{
//CONS_Printf("buffer lost\n");
// restores the buffer memory and all other settings for the buffer
hr = IDirectSoundBuffer_Restore (dsbuffer);
if (SUCCEEDED (hr))
{
LPBYTE dsdata;
// reload sample data here
int lumpnum = S_sfx[id].lumpnum;
if (lumpnum < 0)
lumpnum = S_GetSfxLumpNum (&S_sfx[id]);
dsdata = W_CacheLumpNum (lumpnum, PU_CACHE);
// Well... Data lenght must be -8!!!
CopySoundData (dsbuffer, dsdata + 8, S_sfx[id].length - 8);
// play
hr = IDirectSoundBuffer_Play (dsbuffer, 0, 0, 0);
}
else
I_Error("I_StartSound : ->Restore FAILED, %s",DXErrorToString(hr));
}
#ifdef SURROUND
if (sep == -128)
{
hr = IDirectSoundBuffer_Play (dssurround, 0, 0, 0);
//CONS_Printf("Surround playback\n");
if (hr == DSERR_BUFFERLOST)
{
// restores the buffer memory and all other settings for the surround buffer
hr = IDirectSoundBuffer_Restore (dssurround);
if (SUCCEEDED (hr))
{
LPBYTE dsdata;
lumpnumt_t lumpnum = S_sfx[id].lumpnum;
if (lumpnum < 0)
lumpnum = S_GetSfxLumpNum (&S_sfx[id]);
dsdata = W_CacheLumpNum (lumpnum, PU_CACHE);
CopyAndInvertSoundData (dssurround, (LPBYTE)dsdata + 8, S_sfx[id].length - 8);
hr = IDirectSoundBuffer_Play (dssurround, 0, 0, 0);
}
else
I_Error("I_StartSound : ->Restore FAILED, %s",DXErrorToString(hr));
}
}
StackSounds[handle].lpSurround = dssurround;
#endif
// Returns a handle
return handle;
}
// --------------------------------------------------------------------------
// Stop a sound if it is playing,
// free the corresponding 'playing sound slot' in StackSounds[]
// --------------------------------------------------------------------------
void I_StopSound (INT32 handle)
{
LPDIRECTSOUNDBUFFER dsbuffer;
HRESULT hr;
if (nosound || handle < 0)
return;
//CONS_Printf("I_StopSound (%d)\n", handle);
dsbuffer = StackSounds[handle].lpSndBuf;
hr = IDirectSoundBuffer_Stop (dsbuffer);
// free duplicates of original sound buffer (DirectSound hassles)
if (StackSounds[handle].duplicate)
{
//CONS_Printf("\t\trelease a duplicate..\n");
IDirectSoundBuffer_Release (dsbuffer);
}
#ifdef SURROUND
// Stop and release the surround sound buffer
dsbuffer = StackSounds[handle].lpSurround;
if (dsbuffer != NULL)
{
IDirectSoundBuffer_Stop(dsbuffer);
IDirectSoundBuffer_Release(dsbuffer);
}
StackSounds[handle].lpSurround = NULL;
#endif
StackSounds[handle].lpSndBuf = NULL;
}
// --------------------------------------------------------------------------
// Returns whether the sound is currently playing or not
// --------------------------------------------------------------------------
INT32 I_SoundIsPlaying(INT32 handle)
{
LPDIRECTSOUNDBUFFER dsbuffer;
DWORD dwStatus;
if (nosound || handle == -1)
return FALSE;
dsbuffer = StackSounds[handle].lpSndBuf;
if (dsbuffer)
{
IDirectSoundBuffer_GetStatus (dsbuffer, &dwStatus);
if (dwStatus & (DSBSTATUS_PLAYING | DSBSTATUS_LOOPING))
return TRUE;
}
return FALSE;
}
// --------------------------------------------------------------------------
// Update properties of a sound currently playing
// --------------------------------------------------------------------------
void I_UpdateSoundParams(INT32 handle,
INT32 vol,
INT32 sep,
INT32 pitch)
{
LPDIRECTSOUNDBUFFER dsbuffer;
#ifdef SURROUND
LPDIRECTSOUNDBUFFER dssurround;
DWORD dwStatus;
DWORD pos;
boolean surround_inuse = FALSE;
#endif
if (nosound)
return;
pitch = 0; /// \todo pitch setup
dsbuffer = StackSounds[handle].lpSndBuf;
#ifdef SURROUND
if (dsbuffer == NULL)
return;
dssurround = StackSounds[handle].lpSurround;
if (dssurround)
{
IDirectSoundBuffer_GetStatus(dssurround, &dwStatus);
surround_inuse = (dwStatus & (DSBSTATUS_PLAYING | DSBSTATUS_LOOPING));
}
// If pan changed to stereo...
if (sep != -128)
{
if (surround_inuse)
{
IDirectSoundBuffer_Stop(dssurround);
surround_inuse = FALSE;
}
}
else if (!surround_inuse) // Just update volumes and start the surround if need
{
I_UpdateSoundVolume(dssurround, vol);
I_UpdateSoundPanning(dsbuffer, 0);
IDirectSoundBuffer_GetCurrentPosition(dsbuffer, &pos, NULL);
IDirectSoundBuffer_SetCurrentPosition(dssurround, pos);
IDirectSoundBuffer_Play(dssurround, 0, 0, 0);
surround_inuse = TRUE;
}
else
I_UpdateSoundVolume(dssurround, vol);
I_UpdateSoundVolume(dsbuffer, vol);
if (!surround_inuse)
I_UpdateSoundPanning(dsbuffer, sep);
#else
if (dsbuffer)
{
I_UpdateSoundVolume (dsbuffer, vol);
I_UpdateSoundPanning (dsbuffer, sep);
}
#endif
}
//
// Shutdown DirectSound
//
void I_ShutdownSound(void)
{
int i;
CONS_Printf("I_ShutdownSound()\n");
#ifdef HW3SOUND
if (hws_mode != HWS_DEFAULT_MODE)
{
HW3S_Shutdown();
Shutdown3DSDriver();
return;
}
#endif
// release any temporary 'duplicated' secondary buffers
for (i = 0; i < MAXSTACKSOUNDS; i++)
if (StackSounds[i].lpSndBuf)
// stops the sound and release it if it is a duplicate
I_StopSound (i);
if (DSnd)
{
IDirectSound_Release(DSnd);
DSnd = NULL;
}
}
// ==========================================================================
// Startup DirectSound
// ==========================================================================
void I_StartupSound(void)
{
HRESULT hr;
LPDIRECTSOUNDBUFFER lpDsb;
DSBUFFERDESC dsbdesc;
WAVEFORMATEX wfm;
int cooplevel;
int frequency;
#ifdef HW3SOUND
const char *sdrv_name = NULL;
snddev_t snddev;
#endif
sound_started = false;
if (dedicated)
return;
if (nosound)
return;
// Secure and configure sound device first.
CONS_Printf("I_StartupSound: ");
// frequency of primary buffer may be set at cmd-line
if (M_CheckParm ("-freq") && M_IsNextParm())
{
frequency = atoi(M_GetNextParm());
CONS_Printf(" requested frequency of %d hz\n", frequency);
CV_SetValue(&cv_samplerate,frequency);
}
else
frequency = cv_samplerate.value;
// Set cooperative level
// Cooperative sound with other applications can be requested at cmd-line
if (M_CheckParm("-coopsound"))
cooplevel = DSSCL_PRIORITY;
else
cooplevel = DSSCL_EXCLUSIVE;
#ifdef HW3SOUND
if (M_CheckParm("-ds3d"))
{
hws_mode = HWS_DS3D;
sdrv_name = "s_ds3d.dll";
}
#if 1
else if (M_CheckParm("-fmod3d"))
{
hws_mode = HWS_FMOD3D;
sdrv_name = "s_fmod.dll";
}
else if (M_CheckParm("-sounddriver") && M_IsNextParm())
{
hws_mode = HWS_OTHER;
sdrv_name = M_GetNextParm();
}
#else
else if (M_CheckParm("-sounddriver") && M_IsNextParm())
{
hws_mode = HWS_OTHER;
sdrv_name = M_GetNextParm();
}
else if (!M_CheckParm("-nosd"))
{
hws_mode = HWS_FMOD3D;
sdrv_name = "s_fmod.dll";
}
#endif
// There must be further sound drivers (such as A3D and EAX)!!!
if (hws_mode != HWS_DEFAULT_MODE && sdrv_name != NULL)
{
if (Init3DSDriver(sdrv_name))
{
//nosound = true;
snddev.sample_rate = frequency;
snddev.bps = 16;
snddev.numsfxs = NUMSFX;
snddev.cooplevel = cooplevel;
snddev.hWnd = hWndMain;
if (HW3S_Init(I_Error, &snddev))
{
CONS_Printf("Using external sound driver %s\n", sdrv_name);
I_AddExitFunc(I_ShutdownSound);
return;
}
// Falls back to default sound system
CONS_Printf("Not using external sound driver %s\n", sdrv_name);
HW3S_Shutdown();
Shutdown3DSDriver();
}
hws_mode = HWS_DEFAULT_MODE;
}
#endif
// Create DirectSound, use the default sound device
hr = DirectSoundCreate(NULL, &DSnd, NULL);
if (FAILED(hr))
{
CONS_Printf(" DirectSoundCreate FAILED\n"
" there is no sound device or the sound device is under\n"
" the control of another application\n");
nosound = true;
return;
}
// register exit code, now that we have at least DirectSound to close
I_AddExitFunc(I_ShutdownSound);
hr = IDirectSound_SetCooperativeLevel (DSnd, hWndMain, cooplevel);
if (FAILED(hr))
{
CONS_Printf(" SetCooperativeLevel FAILED\n");
nosound = true;
return;
}
// Set up DSBUFFERDESC structure.
ZeroMemory (&dsbdesc, sizeof (DSBUFFERDESC));
dsbdesc.dwSize = sizeof (DSBUFFERDESC);
dsbdesc.dwFlags = DSBCAPS_PRIMARYBUFFER |
DSBCAPS_CTRLVOLUME;
dsbdesc.dwBufferBytes = 0; // Must be 0 for primary buffer
dsbdesc.lpwfxFormat = NULL; // Must be NULL for primary buffer
// Set up structure for the desired format
ZeroMemory (&wfm, sizeof (WAVEFORMATEX));
wfm.wFormatTag = WAVE_FORMAT_PCM;
wfm.nChannels = 2; //STEREO SOUND!
wfm.nSamplesPerSec = frequency;
wfm.wBitsPerSample = 16;
wfm.nBlockAlign = (WORD)(wfm.wBitsPerSample / 8 * wfm.nChannels);
wfm.nAvgBytesPerSec = wfm.nSamplesPerSec * wfm.nBlockAlign;
// Gain access to the primary buffer
hr = IDirectSound_CreateSoundBuffer (DSnd, &dsbdesc, &lpDsb, NULL);
if (FAILED(hr))
{
CONS_Printf("CreateSoundBuffer FAILED: %s (ErrNo %d)\n", DXErrorToString(hr), hr);
nosound = true;
return;
}
// Set the primary buffer to the desired format,
// but only if we are allowed to do it
if (cooplevel >= DSSCL_PRIORITY)
{
if (SUCCEEDED (hr))
{
// Set primary buffer to the desired format. If this fails,
// we'll just ignore and go with the default.
hr = IDirectSoundBuffer_SetFormat (lpDsb, &wfm);
if (FAILED(hr))
CONS_Printf("I_StartupSound : couldn't set primary buffer format.\n");
else
CV_SetValue(&cv_samplerate,wfm.nSamplesPerSec);
}
// move any on-board sound memory into a contiguous block
// to make the largest portion of free memory available.
CONS_Printf(" Compacting onboard sound-memory...");
hr = IDirectSound_Compact (DSnd);
CONS_Printf(" %s\n", SUCCEEDED(hr) ? "Done\n" : "Failed\n"));
}
// set the primary buffer to play continuously, for performance
// "... this method will ensure that the primary buffer is playing even when no secondary
// buffers are playing; in that case, silence will be played. This can reduce processing
// overhead when sounds are started and stopped in sequence, because the primary buffer
// will be playing continuously rather than stopping and starting between secondary buffers."
hr = IDirectSoundBuffer_Play (lpDsb, 0, 0, DSBPLAY_LOOPING);
if (FAILED (hr))
CONS_Printf(" Primary buffer continuous play FAILED\n");
#ifdef DEBUGSOUND
{
DSCAPS DSCaps;
DSCaps.dwSize = sizeof (DSCAPS);
hr = IDirectSound_GetCaps (DSnd, &DSCaps);
if (SUCCEEDED (hr))
{
if (DSCaps.dwFlags & DSCAPS_CERTIFIED)
CONS_Printf("This driver has been certified by Microsoft\n");
if (DSCaps.dwFlags & DSCAPS_EMULDRIVER)
CONS_Printf("No driver with DirectSound support installed (no hardware mixing)\n");
if (DSCaps.dwFlags & DSCAPS_PRIMARY16BIT)
CONS_Printf("Supports 16-bit primary buffer\n");
if (DSCaps.dwFlags & DSCAPS_PRIMARY8BIT)
CONS_Printf("Supports 8-bit primary buffer\n");
if (DSCaps.dwFlags & DSCAPS_SECONDARY16BIT)
CONS_Printf("Supports 16-bit, hardware-mixed secondary buffers\n");
if (DSCaps.dwFlags & DSCAPS_SECONDARY8BIT)
CONS_Printf("Supports 8-bit, hardware-mixed secondary buffers\n");
CONS_Printf("Maximum number of hardware buffers: %d\n", DSCaps.dwMaxHwMixingStaticBuffers);
CONS_Printf("Size of total hardware memory: %d\n", DSCaps.dwTotalHwMemBytes);
CONS_Printf("Size of free hardware memory= %d\n", DSCaps.dwFreeHwMemBytes);
CONS_Printf("Play Cpu Overhead (%% cpu cycles): %d\n", DSCaps.dwPlayCpuOverheadSwBuffers);
}
else
CONS_Printf(" couldn't get sound device caps.\n");
}
#endif
// save pointer to the primary DirectSound buffer for volume changes
DSndPrimary = lpDsb;
ZeroMemory (StackSounds, sizeof (StackSounds));
CONS_Printf("sound initialised.\n");
sound_started = true;
}
// ==========================================================================
//
// MUSIC API using MidiStream
//
// ==========================================================================
#define SPECIAL_HANDLE_CLEANMIDI -1999 // tell I_StopSong() to do a full (slow) midiOutReset() on exit
static BOOL bMusicStarted;
static UINT uMIDIDeviceID, uCallbackStatus;
static HMIDISTRM hStream;
static HANDLE hBufferReturnEvent; // for synch between the callback thread and main program thread
// (we need to synch when we decide to stop/free stream buffers)
static int nCurrentBuffer = 0, nEmptyBuffers;
static BOOL bBuffersPrepared = FALSE;
static DWORD dwVolCache[MAX_MIDI_IN_TRACKS];
DWORD dwVolumePercent; // accessed by win_main.c
// this is accessed by mid2strm.c conversion code
BOOL bMidiLooped = FALSE;
static BOOL bMidiPlaying = FALSE;
static BOOL bMidiPaused = FALSE;
static CONVERTINFO ciStreamBuffers[NUM_STREAM_BUFFERS];
#define STATUS_KILLCALLBACK 100 // Signals that the callback should die
#define STATUS_CALLBACKDEAD 200 // Signals callback is done processing
#define STATUS_WAITINGFOREND 300 // Callback's waiting for buffers to play
#define DEBUG_CALLBACK_TIMEOUT 2000 // Wait 2 seconds for callback
// faB: don't freeze the main code if we debug..
#define VOL_CACHE_INIT 127 // for dwVolCache[]
static BOOL bMidiCanSetVolume; // midi caps
static void Mid2StreamFreeBuffers(void);
static void CALLBACK MidiStreamCallback (HMIDIIN hMidi, UINT uMsg, DWORD dwInstance,
DWORD dwParam1, DWORD dwParam2);
static BOOL StreamBufferSetup(LPBYTE pMidiData, size_t iMidiSize);
// -------------------
// MidiErrorMessageBox
// Calls the midiOutGetErrorText() function and displays the text which
// corresponds to a midi subsystem error code.
// -------------------
static void MidiErrorMessageBox(MMRESULT mmr)
{
char szTemp[256] = "";
/*szTemp[0] = '\2'; //white text to stand out*/
if((MMSYSERR_NOERROR == midiOutGetErrorTextA(mmr, szTemp/*+1*/, sizeof (szTemp))) && *szTemp)
CONS_Printf("%s\n",szTemp);
/*MessageBox (GetActiveWindow(), szTemp+1, "LEGACY",
MB_OK | MB_ICONSTOP);*/
//wsprintf(szDebug, "Midi subsystem error: %s", szTemp);
}
// ----------------
// I_InitAudioMixer
// ----------------
#ifdef TESTCODE
void I_InitAudioMixer (void)
{
UINT cMixerDevs = mixerGetNumDevs();
CONS_Printf("%d mixer devices available\n", cMixerDevs);
}
#endif
// -----------
// I_InitDigMusic
// Startup Digital device for streaming output
// -----------
void I_InitDigMusic(void)
{
if (dedicated)
nodigimusic = true;
else
CONS_Printf("I_InitDigMusic()\n");
#ifdef FMODSOUND
if (!nodigimusic)
{
// Tails 11-21-2002
if (FSOUND_GetVersion() < FMOD_VERSION)
{
//I_Error("FMOD Error : You are using the wrong DLL version!\nYou should be using FMOD %s\n", "FMOD_VERSION");
CONS_Printf("FMOD Error : You are using the wrong DLL version!\nYou should be using FMOD %s\n", "FMOD_VERSION");
nodigimusic = true;
}
/*
INITIALIZE
*/
#if 1
if (!FSOUND_SetHWND(hWndMain))
{
// I_Error("FMOD(Init,FSOUND_SetHWND): %s\n", FMOD_ErrorString(FSOUND_GetError()));
//FSOUND_SetOutput(FSOUND_OUTPUT_DSOUND);
}
//else
#endif
if (!FSOUND_Init(44100, 32, FSOUND_INIT_DONTLATENCYADJUST))
{
//I_Error("FMOD(Init,FSOUND_Init): %s\n", FMOD_ErrorString(FSOUND_GetError()));
CONS_Printf("FMOD(Init,FSOUND_Init): %s\n", FMOD_ErrorString(FSOUND_GetError()));
nodigimusic = true;
}
else
I_AddExitFunc(I_ShutdownDigMusic);
}
#endif
}
// -----------
// I_InitMIDIMusic
// Startup Midi device for streaming output
// -----------
void I_InitMIDIMusic(void)
{
DWORD idx;
MMRESULT mmrRetVal;
UINT cMidiDevs;
MIDIOUTCAPS MidiOutCaps;
const char *szTechnology;
bMusicStarted = false;
if (dedicated)
nomidimusic = true;
else
CONS_Printf("I_InitMIDIMusic()\n");
if (nomidimusic)
return;
// check out number of MIDI devices available
//
cMidiDevs = midiOutGetNumDevs();
if (!cMidiDevs)
{
CONS_Printf("No MIDI devices available, music is disabled\n");
nomidimusic = true;
return;
}
#ifdef DEBUGMIDISTREAM
else
{
CONS_Printf("%d MIDI devices available\n", cMidiDevs);
}
#endif
if (M_CheckParm("-winmidi") && M_IsNextParm())
uMIDIDeviceID = atoi(M_GetNextParm());
else
uMIDIDeviceID = MIDI_MAPPER;
// get MIDI device caps
//
if ((mmrRetVal = midiOutGetDevCaps (uMIDIDeviceID, &MidiOutCaps, sizeof (MIDIOUTCAPS))) !=
MMSYSERR_NOERROR)
{
CONS_Printf("midiOutGetCaps FAILED : \n");
MidiErrorMessageBox (mmrRetVal);
}
else
{
CONS_Printf("MIDI product name: %s\n", MidiOutCaps.szPname);
switch (MidiOutCaps.wTechnology)
{
case MOD_FMSYNTH: szTechnology = "FM Synth"; break;
case MOD_MAPPER: szTechnology = "Microsoft MIDI Mapper"; break;
case MOD_MIDIPORT: szTechnology = "MIDI hardware port"; break;
case MOD_SQSYNTH: szTechnology = "Square wave synthesizer"; break;
case MOD_SYNTH: szTechnology = "Synthesizer"; break;
default: szTechnology = "unknown"; break;
}
CONS_Printf("MIDI technology: %s\n", szTechnology);
CONS_Printf("MIDI caps:\n");
if (MidiOutCaps.dwSupport & MIDICAPS_CACHE)
CONS_Printf("-Patch caching\n");
if (MidiOutCaps.dwSupport & MIDICAPS_LRVOLUME)
CONS_Printf("-Separate left and right volume control\n");
if (MidiOutCaps.dwSupport & MIDICAPS_STREAM)
CONS_Printf("-Direct support for midiStreamOut()\n");
if (MidiOutCaps.dwSupport & MIDICAPS_VOLUME)
CONS_Printf("-Volume control\n");
bMidiCanSetVolume = ((MidiOutCaps.dwSupport & MIDICAPS_VOLUME)!=0);
}
#ifdef TESTCODE
I_InitAudioMixer ();
#endif
// ----------------------------------------------------------------------
// Midi2Stream initialization
// ----------------------------------------------------------------------
// create event for synch'ing the callback thread to main program thread
// when we will need it
hBufferReturnEvent = CreateEvent(NULL, FALSE, FALSE,
TEXT("SRB2 Midi Playback: Wait For Buffer Return"));
if (!hBufferReturnEvent)
{
I_GetLastErrorMsgBox();
nomidimusic = true;
return;
}
if ((mmrRetVal = midiStreamOpen(&hStream,
&uMIDIDeviceID,
(DWORD)1, (DWORD_PTR)MidiStreamCallback/*NULL*/,
(DWORD)0,
CALLBACK_FUNCTION /*CALLBACK_NULL*/)) != MMSYSERR_NOERROR)
{
CONS_Printf("I_RegisterSong: midiStreamOpen FAILED\n");
MidiErrorMessageBox(mmrRetVal);
nomidimusic = true;
return;
}
// stream buffers are initially unallocated (set em NULL)
for (idx = 0; idx < NUM_STREAM_BUFFERS; idx++)
ZeroMemory (&ciStreamBuffers[idx].mhBuffer, sizeof (MIDIHDR));
// ----------------------------------------------------------------------
// register exit code
I_AddExitFunc(I_ShutdownMIDIMusic);
bMusicStarted = true;
}
// ---------------
// I_InitMusic
// ---------------
void I_InitMusic(void)
{
I_InitDigMusic();
I_InitMIDIMusic();
}
// ---------------
// I_ShutdownDigMusic
// ---------------
void I_ShutdownDigMusic(void)
{
CONS_Printf("I_ShutdownDigMusic: \n");
#ifdef FMODSOUND
if (!nodigimusic && FSOUND_GetError() != FMOD_ERR_UNINITIALIZED)
{
if (FSOUND_GetError() != FMOD_ERR_NONE && FSOUND_GetError() != FMOD_ERR_CHANNEL_ALLOC && FSOUND_GetError() != FMOD_ERR_MEDIAPLAYER)
if (devparm) CONS_Printf("FMOD(Shutdown,Unknown): %s\n", FMOD_ErrorString(FSOUND_GetError()));
if (mod)
{
if (FMUSIC_IsPlaying(mod))
if (!FMUSIC_StopSong(mod))
if (devparm) CONS_Printf("FMOD(Shutdown,FMUSIC_StopSong): %s\n", FMOD_ErrorString(FSOUND_GetError()));
if (!FMUSIC_FreeSong(mod))
if (devparm) CONS_Printf("FMOD(Shutdown,FMUSIC_FreeSong): %s\n", FMOD_ErrorString(FSOUND_GetError()));
}
if (fmus)
{
if (FSOUND_IsPlaying(fsoundchannel))
if (!FSOUND_Stream_Stop(fmus))
if (devparm) CONS_Printf("FMOD(Shutdown,FSOUND_Stream_Stop): %s\n", FMOD_ErrorString(FSOUND_GetError()));
if (!FSOUND_Stream_Close(fmus))
if (devparm) CONS_Printf("FMOD(Shutdown,FSOUND_Stream_Close): %s\n", FMOD_ErrorString(FSOUND_GetError()));
}
FSOUND_Close();
remove("fmod.tmp"); // Delete the temp file
//if (!FSOUND_StopSound(FSOUND_ALL))
//if (FSOUND_GetError() != FMOD_ERR_MEDIAPLAYER) CONS_Printf("FMOD(Shutdown,FSOUND_StopSound): %s\n", FMOD_ErrorString(FSOUND_GetError()));
//FMUSIC_StopAllSongs();
//if (FSOUND_GetError() != FMOD_ERR_NONE && FSOUND_GetError() != FMOD_ERR_MEDIAPLAYER) CONS_Printf("FMOD(Shutdown,FMUSIC_StopAllSongs): %s\n", FMOD_ErrorString(FSOUND_GetError()));
}
#endif
}
// ---------------
// I_ShutdownMIDIMusic
// ---------------
void I_ShutdownMIDIMusic(void)
{
DWORD idx;
MMRESULT mmrRetVal;
HGLOBAL lp = NULL;
CONS_Printf("I_ShutdownMIDIMusic: \n");
if (nomidimusic)
return;
if (!bMusicStarted)
return;
if (hStream)
{
I_StopSong (SPECIAL_HANDLE_CLEANMIDI);
}
Mid2StreamConverterCleanup();
Mid2StreamFreeBuffers();
// Free our stream buffers
for (idx = 0; idx < NUM_STREAM_BUFFERS; idx++)
{
if (ciStreamBuffers[idx].mhBuffer.lpData)
{
//GlobalFreePtr(ciStreamBuffers[idx].mhBuffer.lpData);
lp = GlobalPtrHandle(ciStreamBuffers[idx].mhBuffer.lpData);
GlobalUnlock(lp);
GlobalFree(lp);
ciStreamBuffers[idx].mhBuffer.lpData = NULL;
}
}
if (hStream)
{
if ((mmrRetVal = midiStreamClose(hStream)) != MMSYSERR_NOERROR)
MidiErrorMessageBox(mmrRetVal);
hStream = NULL;
}
CloseHandle(hBufferReturnEvent);
bMusicStarted = false;
}
// ---------------
// I_ShutdownMusic
// ---------------
void I_ShutdownMusic(void)
{
if (!nodigimusic)
I_ShutdownDigMusic();
if (!nomidimusic)
I_ShutdownMIDIMusic();
}
// --------------------
// SetAllChannelVolumes
// Given a percent in tenths of a percent, sets volume on all channels to
// reflect the new value.
// --------------------
static void SetAllChannelVolumes(DWORD dwVolumePercent)
{
DWORD dwEvent, dwStatus, dwVol, idx;
MMRESULT mmrRetVal;
if (!bMidiPlaying)
return;
for (idx = 0, dwStatus = MIDI_CTRLCHANGE; idx < MAX_MIDI_IN_TRACKS; idx++, dwStatus++)
{
dwVol = (dwVolCache[idx] * dwVolumePercent) / 1000;
//CONS_Printf("channel %d vol %d\n", idx, dwVol);
dwEvent = dwStatus | ((DWORD)MIDICTRL_VOLUME << 8)
| ((DWORD)dwVol << 16);
if ((mmrRetVal = midiOutShortMsg((HMIDIOUT)hStream, dwEvent))
!= MMSYSERR_NOERROR)
{
MidiErrorMessageBox(mmrRetVal);
return;
}
}
}
// ----------------
// I_SetMusicVolume
// Set the midi output volume
// ----------------
void I_SetMIDIMusicVolume(INT32 volume)
{
MMRESULT mmrRetVal;
int iVolume;
if (nomidimusic)
return;
if (bMidiCanSetVolume)
{
// method A
// current volume is 0-31, we need 0-0xFFFF in each word (left/right channel)
iVolume = (volume << 11) | (volume << 27);
if ((mmrRetVal = midiOutSetVolume ((HMIDIOUT)(size_t)uMIDIDeviceID, iVolume)) != MMSYSERR_NOERROR)
{
CONS_Printf("I_SetMusicVolume: couldn't set volume\n");
MidiErrorMessageBox(mmrRetVal);
}
}
else
{
// method B
dwVolumePercent = (volume * 1000) / 32;
SetAllChannelVolumes (dwVolumePercent);
}
}
void I_SetDigMusicVolume(INT32 volume)
{
#ifdef FMODSOUND
if (volume != -1)
fmodvol = (volume<<3)+(volume>>2);
if (!nodigimusic)
{
if (FSOUND_GetError() != FMOD_ERR_NONE && FSOUND_GetError() != FMOD_ERR_CHANNEL_ALLOC && FSOUND_GetError() != FMOD_ERR_MEDIAPLAYER)
if (devparm) CONS_Printf("FMOD(Volume,Unknown): %s\n", FMOD_ErrorString(FSOUND_GetError()));
if (mod)
{
if (FMUSIC_GetType(mod) != FMUSIC_TYPE_NONE)
{
if (!FMUSIC_SetMasterVolume(mod, fmodvol) && devparm)
CONS_Printf("FMOD(Volume,FMUSIC_SetMasterVolume): %s\n",
FMOD_ErrorString(FSOUND_GetError()));
}
else if (devparm)
CONS_Printf("FMOD(Volume,FMUSIC_GetType): %s\n", FMOD_ErrorString(FSOUND_GetError()));
}
if (fmus)
{
if (!FSOUND_SetVolume(fsoundchannel, fmodvol))
if (devparm) CONS_Printf("FMOD(Volume,FSOUND_SetVolume): %s\n", FMOD_ErrorString(FSOUND_GetError()));
}
}
#else
(void)volume;
#endif
}
// ----------
// I_PlaySong
// Note: doesn't use the handle, would be useful to switch between mid's after
// some trigger (would do several RegisterSong, then PlaySong the chosen one)
// ----------
boolean I_PlaySong(INT32 handle, INT32 bLooping)
{
MMRESULT mmrRetVal;
if (nomidimusic)
return false;
#ifdef DEBUGMIDISTREAM
CONS_Printf("I_PlaySong: looping %d\n", bLooping);
#endif
// unpause the song first if it was paused
if (bMidiPaused)
I_PauseSong(handle);
// Clear the status of our callback so it will handle
// MOM_DONE callbacks once more
uCallbackStatus = 0;
bMidiPlaying = FALSE;
if ((mmrRetVal = midiStreamRestart(hStream)) != MMSYSERR_NOERROR)
{
MidiErrorMessageBox(mmrRetVal);
Mid2StreamConverterCleanup();
Mid2StreamFreeBuffers();
midiStreamClose(hStream);
//I_Error("I_PlaySong: midiStreamRestart error");
midiStreamOpen(&hStream, &uMIDIDeviceID, (DWORD)1,
(DWORD_PTR)MidiStreamCallback/*NULL*/,
(DWORD)0, CALLBACK_FUNCTION /*CALLBACK_NULL*/);
}
else bMidiPlaying = TRUE;
bMidiLooped = bLooping;
return bMidiPlaying;
}
// -----------
// I_PauseSong
// calls midiStreamPause() to pause the midi playback
// -----------
void I_PauseSong (INT32 handle)
{
(void)handle;
#ifdef FMODSOUND
if (!nodigimusic)
{
if (FSOUND_GetError() != FMOD_ERR_NONE && FSOUND_GetError() != FMOD_ERR_CHANNEL_ALLOC && FSOUND_GetError() != FMOD_ERR_MEDIAPLAYER)
if (devparm) CONS_Printf("FMOD(Pause,Unknown): %s\n", FMOD_ErrorString(FSOUND_GetError()));
if (mod)
{
if (!FMUSIC_GetPaused(mod))
if (!FMUSIC_SetPaused(mod, true))
if (devparm) CONS_Printf("FMOD(Pause,FMUSIC_SetPaused): %s\n", FMOD_ErrorString(FSOUND_GetError()));
}
if (fmus)
{
if (!FSOUND_GetPaused(fsoundchannel))
if (!FSOUND_SetPaused(fsoundchannel, true))
if (devparm) CONS_Printf("FMOD(Pause,FSOUND_SetPaused): %s\n", FMOD_ErrorString(FSOUND_GetError()));
}
}
#endif
if (nomidimusic)
return;
#ifdef DEBUGMIDISTREAM
CONS_Printf("I_PauseSong: \n");
#endif
if (!bMidiPaused)
{
midiStreamPause(hStream);
bMidiPaused = true;
}
}
// ------------
// I_ResumeSong
// un-pause the midi song with midiStreamRestart
// ------------
void I_ResumeSong (INT32 handle)
{
(void)handle;
#ifdef FMODSOUND
if (!nodigimusic)
{
if (FSOUND_GetError() != FMOD_ERR_NONE && FSOUND_GetError() != FMOD_ERR_CHANNEL_ALLOC && FSOUND_GetError() != FMOD_ERR_MEDIAPLAYER)
if (devparm) CONS_Printf("FMOD(Resume,Unknown): %s\n", FMOD_ErrorString(FSOUND_GetError()));
if (mod != NULL)
{
if (FMUSIC_GetPaused(mod))
if (!FMUSIC_SetPaused(mod, false))
if (devparm) CONS_Printf("FMOD(Resume,FMUSIC_SetPaused): %s\n", FMOD_ErrorString(FSOUND_GetError()));
}
if (fmus != NULL)
{
if (FSOUND_GetPaused(fsoundchannel))
if (!FSOUND_SetPaused(fsoundchannel, false))
if (devparm) CONS_Printf("FMOD(Resume,FSOUND_SetPaused): %s\n", FMOD_ErrorString(FSOUND_GetError()));
}
}
#endif
if (nomidimusic)
return;
#ifdef DEBUGMIDISTREAM
CONS_Printf("I_ResumeSong: \n");
#endif
if (bMidiPaused)
{
midiStreamRestart(hStream);
bMidiPaused = false;
}
}
// ----------
// I_StopSong
// ----------
// faB: -1999 is a special handle here, it means we stop the midi when exiting
// Legacy, this will do a midiOutReset() for a more 'sure' midi off.
void I_StopSong(INT32 handle)
{
MMRESULT mmrRetVal;
if (nomidimusic)
return;
#ifdef DEBUGMIDISTREAM
CONS_Printf("I_StopSong: \n");
#endif
if (bMidiPlaying || (uCallbackStatus != STATUS_CALLBACKDEAD))
{
bMidiPlaying = bMidiPaused = FALSE;
if (uCallbackStatus != STATUS_CALLBACKDEAD &&
uCallbackStatus != STATUS_WAITINGFOREND)
uCallbackStatus = STATUS_KILLCALLBACK;
//CONS_Printf("a: %d\n",I_GetTime());
if ((mmrRetVal = midiStreamStop(hStream)) != MMSYSERR_NOERROR)
{
MidiErrorMessageBox(mmrRetVal);
return;
}
//faB: if we don't call midiOutReset() seems we have to stop the buffers
// ourselves (or it doesn't play anymore)
if (!bMidiPaused && (handle != SPECIAL_HANDLE_CLEANMIDI))
{
midiStreamPause(hStream);
}
//CONS_Printf("b: %d\n",I_GetTime());
else
//faB: this damn call takes 1 second and a half !!! still do it on exit
// to be sure everything midi is cleaned as much as possible
if (handle == SPECIAL_HANDLE_CLEANMIDI)
{
if ((mmrRetVal = midiOutReset((HMIDIOUT)hStream)) != MMSYSERR_NOERROR)
{
MidiErrorMessageBox(mmrRetVal);
return;
}
}
//CONS_Printf("c: %d\n",I_GetTime());
// Wait for the callback thread to release this thread, which it will do by
// calling SetEvent() once all buffers are returned to it
if ((devparm)
&& (WaitForSingleObject(hBufferReturnEvent, DEBUG_CALLBACK_TIMEOUT)
== WAIT_TIMEOUT))
{
// Note, this is a risky move because the callback may be genuinely busy, but
// when we're debugging, it's safer and faster than freezing the application,
// which leaves the MIDI device locked up and forces a system reset...
CONS_Printf("Timed out waiting for MIDI callback\n");
uCallbackStatus = STATUS_CALLBACKDEAD;
}
//CONS_Printf("d: %d\n",I_GetTime());
}
if (uCallbackStatus == STATUS_CALLBACKDEAD)
{
uCallbackStatus = 0;
Mid2StreamConverterCleanup();
Mid2StreamFreeBuffers();
//faB: we could close the stream here and re-open later to avoid
// a little quirk in mmsystem (see DirectX6 mstream note)
midiStreamClose(hStream);
midiStreamOpen(&hStream, &uMIDIDeviceID, (DWORD)1,
(DWORD_PTR)MidiStreamCallback/*NULL*/,
(DWORD)0, CALLBACK_FUNCTION /*CALLBACK_NULL*/);
}
}
void I_StopDigSong(void)
{
#ifdef FMODSOUND
if (!nodigimusic)
{
if (FSOUND_GetError() != FMOD_ERR_NONE && FSOUND_GetError() != FMOD_ERR_INVALID_PARAM && FSOUND_GetError() != FMOD_ERR_CHANNEL_ALLOC && FSOUND_GetError() != FMOD_ERR_MEDIAPLAYER)
if (devparm) CONS_Printf("FMOD(Stop,Unknown): %s\n", FMOD_ErrorString(FSOUND_GetError()));
if (mod)
{
if (FMUSIC_IsPlaying(mod))
{
if (!FMUSIC_StopSong(mod))
if (devparm) CONS_Printf("FMOD(Stop,FMUSIC_StopSong): %s\n", FMOD_ErrorString(FSOUND_GetError()));
}
}
if (fmus)
{
if (FSOUND_IsPlaying(fsoundchannel))
{
if (!FSOUND_Stream_Stop(fmus))
if (devparm) CONS_Printf("FMOD(Stop,FSOUND_Stream_Stop): %s\n", FMOD_ErrorString(FSOUND_GetError()));
}
}
//if (!FSOUND_StopSound(FSOUND_ALL))
//if (FSOUND_GetError() != FMOD_ERR_MEDIAPLAYER) CONS_Printf("FMOD(Stop,FSOUND_StopSound): %s\n", FMOD_ErrorString(FSOUND_GetError()));
//FMUSIC_StopAllSongs();
//if (FSOUND_GetError() != FMOD_ERR_NONE && FSOUND_GetError() != FMOD_ERR_MEDIAPLAYER) CONS_Printf("FMOD(Stop,FMUSIC_StopAllSongs): %s\n", FMOD_ErrorString(FSOUND_GetError()));
}
#endif
}
void I_UnRegisterSong(INT32 handle)
{
handle = 0;
if (nomidimusic)
return;
//faB: we might free here whatever is allocated per-music
// (but we don't cause I hate malloc's)
Mid2StreamConverterCleanup();
#ifdef DEBUGMIDISTREAM
CONS_Printf("I_UnregisterSong: \n");
#endif
}
int I_SetSongSpeed(unsigned int speed)
{
#ifdef FMODSOUND
if (music_disabled || nodigimusic)
return 0; //there no music or FMOD is not loaded
if((!fmus || !FSOUND_IsPlaying(fsoundchannel)) && (!mod || !FMUSIC_IsPlaying(mod)))
return 0; //there no FMOD music playing
if (speed == 0)
return 1; //yes, we can set the speed
if (fmus)
{
if (FSOUND_IsPlaying(fsoundchannel)
&& !FSOUND_SetFrequency(fsoundchannel,(int)(((float)speed*(float)fsoundfreq)/100.0f)))
{
if (devparm)
CONS_Printf("FMOD(ChangeSpeed,FSOUND_SetFrequency): %s\n", FMOD_ErrorString(FSOUND_GetError()));
}
else
return 1;
}
else if (mod)
{
if (FMUSIC_IsPlaying(mod)
&& !FMUSIC_SetMasterSpeed(mod,(float)speed/100.0f))
{
if (devparm)
CONS_Printf("FMOD(ChangeSpeed,FMUSIC_SetMasterSpeed): %s\n", FMOD_ErrorString(FSOUND_GetError()));
}
else
return 1;
}
#else
(void)speed;
#endif
return 0;
}
// Special FMOD support Tails 11-21-2002
boolean I_StartDigSong(const char *musicname, INT32 looping)
{
#ifdef FMODSOUND
char filename[9];
void *data;
int lumpnum;
if (FSOUND_GetError() != FMOD_ERR_NONE && FSOUND_GetError() != FMOD_ERR_CHANNEL_ALLOC &&
FSOUND_GetError() != FMOD_ERR_MEDIAPLAYER && FSOUND_GetError() != FMOD_ERR_INVALID_PARAM)
if (devparm) CONS_Printf("FMOD(Start,Unknown): %s\n", FMOD_ErrorString(FSOUND_GetError()));
if (fmus)
{
if (FSOUND_IsPlaying(fsoundchannel))
if (!FSOUND_Stream_Stop(fmus))
if (devparm) CONS_Printf("FMOD(Start,FSOUND_Stream_Stop): %s\n", FMOD_ErrorString(FSOUND_GetError()));
if (!FSOUND_Stream_Close(fmus))
if (devparm) CONS_Printf("FMOD(Start,FSOUND_Stream_Close): %s\n", FMOD_ErrorString(FSOUND_GetError()));
fsoundchannel = -1;
fmus = NULL;
}
if (mod)
{
if (FMUSIC_IsPlaying(mod))
if (!FMUSIC_StopSong(mod))
if (devparm) CONS_Printf("FMOD(Start,FMUSIC_StopSong): %s\n", FMOD_ErrorString(FSOUND_GetError()));
if (!FMUSIC_FreeSong(mod))
if (devparm) CONS_Printf("FMOD(Start,FMUSIC_FreeSong): %s\n", FMOD_ErrorString(FSOUND_GetError()));
mod = NULL;
}
//if (!FSOUND_StopSound(FSOUND_ALL))
//if (FSOUND_GetError() != FMOD_ERR_MEDIAPLAYER) CONS_Printf("FMOD(Start,FSOUND_StopSound): %s\n", FMOD_ErrorString(FSOUND_GetError()));
//FMUSIC_StopAllSongs();
//if (FSOUND_GetError() != FMOD_ERR_NONE && FSOUND_GetError() != FMOD_ERR_MEDIAPLAYER) CONS_Printf("FMOD(Start,FMUSIC_StopAllSongs): %s\n", FMOD_ErrorString(FSOUND_GetError()));
// Create the filename we need
sprintf(filename, "o_%s", musicname);
lumpnum = W_CheckNumForName(filename);
if (lumpnum == -1)
{
// Graue 02-29-2004: don't worry about missing music, there might still be a MIDI
return false; // No music found. Oh well!
}
data = W_CacheLumpName (filename, PU_CACHE);
I_SaveMemToFile (data, W_LumpLength(lumpnum), "fmod.tmp");
Z_Free(data);
mod = FMUSIC_LoadSong("fmod.tmp");
if (FSOUND_GetError() != FMOD_ERR_NONE)
{
if (FSOUND_GetError() != FMOD_ERR_FILE_FORMAT)
if (devparm) CONS_Printf("FMOD(Start,FMUSIC_LoadSong): %s\n", FMOD_ErrorString(FSOUND_GetError()));
if (mod)
{
if (FMUSIC_IsPlaying(mod))
if (!FMUSIC_StopSong(mod))
if (devparm) CONS_Printf("FMOD(Start,FMUSIC_StopSong): %s\n", FMOD_ErrorString(FSOUND_GetError()));
if (!FMUSIC_FreeSong(mod))
if (devparm) CONS_Printf("FMOD(Start,FMUSIC_FreeSong): %s\n", FMOD_ErrorString(FSOUND_GetError()));
mod = NULL;
}
}
if (mod)
{
if (!FMUSIC_SetLooping(mod, (signed char)looping))
{
if (devparm) CONS_Printf("FMOD(Start,FMUSIC_SetLooping): %s\n", FMOD_ErrorString(FSOUND_GetError()));
}
// else if (FMUSIC_GetType(mod) == FMUSIC_TYPE_MOD || FMUSIC_GetType(mod) == FMUSIC_TYPE_S3M)
// {
// if (!FMUSIC_SetPanSeperation(mod, 0.85f)) /* 15% crossover */
// CONS_Printf("FMOD(Start,FMUSIC_SetPanSeperation): %s\n", FMOD_ErrorString(FSOUND_GetError()));
// }
else if (!FMUSIC_SetPanSeperation(mod, 0.0f))
{
if (devparm) CONS_Printf("FMOD(Start,FMUSIC_SetPanSeperation): %s\n", FMOD_ErrorString(FSOUND_GetError()));
}
}
else
{
fmus = FSOUND_Stream_OpenFile("fmod.tmp", ((looping) ? (FSOUND_LOOP_NORMAL) : (0)),0);
if (fmus == NULL)
{
if (devparm) CONS_Printf("FMOD(Start,FSOUND_Stream_Open): %s\n", FMOD_ErrorString(FSOUND_GetError()));
return false;
}
}
// Scan the Ogg Vorbis file for the COMMENT= field for a custom loop point
if (fmus && looping)
{
int scan;
char *dataum;
char looplength[64];
unsigned int loopstart = 0;
int newcount = 0;
const int freq = 44100;
int lumplength = W_LumpLength(lumpnum);
int length = FSOUND_Stream_GetLengthMs(fmus);
if (length == 0)
{
if (devparm) CONS_Printf("FMOD(Start,FSOUND_Stream_GetLengthMs): %s\n", FMOD_ErrorString(FSOUND_GetError()));
}
else
{
//freq = FSOUND_GetFrequency(fsoundchannel);
data = W_CacheLumpName (filename, PU_CACHE);
dataum = (char *)data;
for (scan = 0;scan < lumplength; scan++)
{
if (*dataum++ == 'C'){
if (*dataum++ == 'O'){
if (*dataum++ == 'M'){
if (*dataum++ == 'M'){
if (*dataum++ == 'E'){
if (*dataum++ == 'N'){
if (*dataum++ == 'T'){
if (*dataum++ == '='){
if (*dataum++ == 'L'){
if (*dataum++ == 'O'){
if (*dataum++ == 'O'){
if (*dataum++ == 'P'){
if (*dataum++ == 'P'){
if (*dataum++ == 'O'){
if (*dataum++ == 'I'){
if (*dataum++ == 'N'){
if (*dataum++ == 'T'){
if (*dataum++ == '=')
{
while (*dataum != 1 && newcount != 63)
{
looplength[newcount++] = *dataum++;
}
looplength[newcount] = '\n';
loopstart = atoi(looplength);
}
else
dataum--;}
else
dataum--;}
else
dataum--;}
else
dataum--;}
else
dataum--;}
else
dataum--;}
else
dataum--;}
else
dataum--;}
else
dataum--;}
else
dataum--;}
else
dataum--;}
else
dataum--;}
else
dataum--;}
else
dataum--;}
else
dataum--;}
else
dataum--;}
else
dataum--;}
}
Z_Free(data);
}
if (loopstart > 0)
{
const unsigned int loopend = (unsigned int)((freq/1000.0f)*length-(freq/1000.0f));
//const unsigned int loopend = (((freq/2)*length)/500)-8;
if (!FSOUND_Stream_SetLoopPoints(fmus, loopstart, loopend) && devparm)
CONS_Printf("FMOD(Start,FSOUND_Stream_SetLoopPoints): %s\n",
FMOD_ErrorString(FSOUND_GetError()));
}
}
/*
PLAY SONG
*/
if (mod)
{
if (FMUSIC_PlaySong(mod))
{
fsoundchannel = -1;
I_SetDigMusicVolume(-1);
return true;
}
else
{
if (devparm)
CONS_Printf("FMOD(Start,FMUSIC_PlaySong): %s\n",
FMOD_ErrorString(FSOUND_GetError()));
return false;
}
}
if (fmus)
{
fsoundchannel = FSOUND_Stream_PlayEx(FSOUND_FREE, fmus, NULL, TRUE);
if (fsoundchannel == -1)
{
if (devparm)
CONS_Printf("FMOD(Start,FSOUND_Stream_PlayEx): %s\n",
FMOD_ErrorString(FSOUND_GetError()));
return false;
}
else if (!FSOUND_SetPaused(fsoundchannel, FALSE))
{
if (devparm)
CONS_Printf("FMOD(Start,FSOUND_SetPaused): %s\n",
FMOD_ErrorString(FSOUND_GetError()));
return false;
}
I_SetDigMusicVolume(-1);
fsoundfreq = FSOUND_GetFrequency(fsoundchannel);
return true;
}
#else
(void)musicname;
(void)looping;
#endif
return false;
/////////////////////////////////////////////////////////////////////////////////
}
// --------------
// I_RegisterSong
// Prepare a song for playback
// - setup midi stream buffers, and activate the callback procedure
// which will continually fill the buffers with new data
// --------------
INT32 I_RegisterSong(void *data, int len)
{
char *pMidiFileData = NULL; // MIDI music buffer to be played or NULL
if (nomidimusic)
return 1;
if (!data || !len)
return 0;
#ifdef DEBUGMIDISTREAM
CONS_Printf("I_RegisterSong: \n");
#endif
// check for MID format file
if (!memcmp(data, "MThd", 4))
pMidiFileData = data;
else
{
CONS_Printf("Music lump is not MID music format\n");
return 0;
}
#ifdef DEBUGMIDISTREAM
I_SaveMemToFile(pMidiFileData, len, "debug.mid");
#endif
// setup midi stream buffer
if (StreamBufferSetup((LPBYTE)pMidiFileData, len))
{
Mid2StreamConverterCleanup();
I_Error("I_RegisterSong: StreamBufferSetup FAILED");
}
return 1;
}
// -----------------
// StreamBufferSetup
// This function uses the filename stored in the global character array to
// open a MIDI file. Then it goes about converting at least the first part of
// that file into a midiStream buffer for playback.
// -----------------
// -----------------
// StreamBufferSetup
// - returns TRUE if a problem occurs
// -----------------
static BOOL StreamBufferSetup(LPBYTE pMidiData, size_t iMidiSize)
{
MMRESULT mmrRetVal;
MIDIPROPTIMEDIV mptd;
BOOL bFoundEnd = FALSE;
int dwConvertFlag, nChkErr, idx;
#ifdef DEBUGMIDISTREAM
if (hStream == NULL)
I_Error("StreamBufferSetup: hStream is NULL!");
#endif
// pause midi stream before manipulating the buffers
midiStreamPause(hStream);
// allocate the stream buffers (only once)
for (idx = 0; idx < NUM_STREAM_BUFFERS; idx++)
{
ciStreamBuffers[idx].mhBuffer.dwBufferLength = OUT_BUFFER_SIZE;
if (!ciStreamBuffers[idx].mhBuffer.lpData)
{
ciStreamBuffers[idx].mhBuffer.lpData = GlobalAllocPtr(GHND, OUT_BUFFER_SIZE);
if (!ciStreamBuffers[idx].mhBuffer.lpData)
return FALSE;
}
}
// returns TRUE in case of conversion error
if (Mid2StreamConverterInit(pMidiData, iMidiSize))
return TRUE;
// Initialize the volume cache array to some pre-defined value
for (idx = 0; idx < MAX_MIDI_IN_TRACKS; idx++)
dwVolCache[idx] = VOL_CACHE_INIT;
mptd.cbStruct = sizeof (mptd);
mptd.dwTimeDiv = ifs.dwTimeDivision;
if ((mmrRetVal = midiStreamProperty(hStream, (LPBYTE)&mptd, MIDIPROP_SET|MIDIPROP_TIMEDIV))
!= MMSYSERR_NOERROR)
{
MidiErrorMessageBox(mmrRetVal);
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;
if ((nChkErr = Mid2StreamConvertToBuffer(dwConvertFlag, &ciStreamBuffers[nCurrentBuffer]))
!= CONVERTERR_NOERROR)
{
if (nChkErr == CONVERTERR_DONE)
bFoundEnd = TRUE;
else
{
CONS_Printf("StreamBufferSetup: initial conversion pass failed\n");
return TRUE;
}
}
ciStreamBuffers[nCurrentBuffer].mhBuffer.dwBytesRecorded
= ciStreamBuffers[nCurrentBuffer].dwBytesRecorded;
if (!bBuffersPrepared)
{
if ((mmrRetVal = midiOutPrepareHeader((HMIDIOUT)hStream,
&ciStreamBuffers[nCurrentBuffer].mhBuffer, sizeof (MIDIHDR))) != MMSYSERR_NOERROR)
{
MidiErrorMessageBox(mmrRetVal);
return TRUE;
}
}
if ((mmrRetVal = midiStreamOut(hStream, &ciStreamBuffers[nCurrentBuffer].mhBuffer,
sizeof (MIDIHDR))) != MMSYSERR_NOERROR)
{
MidiErrorMessageBox(mmrRetVal);
break;
}
dwConvertFlag = 0;
if (bFoundEnd)
break;
}
bBuffersPrepared = TRUE;
nCurrentBuffer = 0;
// MIDI volume
dwVolumePercent = (cv_midimusicvolume.value * 1000) / 32;
if (hStream)
SetAllChannelVolumes(dwVolumePercent);
return FALSE;
}
// ----------------
// SetChannelVolume
// Call here delayed by MIDI stream callback, to adapt the volume event of the
// midi stream to our own set volume percentage.
// ----------------
void I_SetMidiChannelVolume(DWORD dwChannel, DWORD dwVolumePercent)
{
DWORD dwEvent, dwVol;
MMRESULT mmrRetVal;
if (!bMidiPlaying)
return;
dwVol = (dwVolCache[dwChannel] * dwVolumePercent) / 1000;
dwEvent = MIDI_CTRLCHANGE|dwChannel|((DWORD)MIDICTRL_VOLUME << 8)|((DWORD)dwVol << 16);
if ((mmrRetVal = midiOutShortMsg((HMIDIOUT)hStream, dwEvent)) != MMSYSERR_NOERROR)
{
#ifdef DEBUGMIDISTREAM
MidiErrorMessageBox(mmrRetVal);
#endif
return;
}
}
// ------------------
// MidiStreamCallback
// This is the callback handler which continually refills MIDI data buffers
// as they're returned to us from the audio subsystem.
// ------------------
static void CALLBACK MidiStreamCallback(HMIDIIN hMidi, UINT uMsg, DWORD dwInstance,
DWORD dwParam1, DWORD dwParam2)
{
MMRESULT mmrRetVal;
int nChkErr;
MIDIEVENT* pme;
MIDIHDR* pmh;
hMidi = NULL;
dwParam1 = dwParam2 = dwInstance = 0;
switch (uMsg)
{
case MOM_DONE:
// dwParam1 is LPMIDIHDR
if (uCallbackStatus == STATUS_CALLBACKDEAD)
return;
nEmptyBuffers++;
// we reached end of song, but we wait until all the buffers are returned
if (uCallbackStatus == STATUS_WAITINGFOREND)
{
if (nEmptyBuffers < NUM_STREAM_BUFFERS)
return;
else
{
// stop the song when end reached (was not looping)
uCallbackStatus = STATUS_CALLBACKDEAD;
SetEvent(hBufferReturnEvent);
I_StopSong(0);
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 (bMidiPlaying && nEmptyBuffers)
{
ciStreamBuffers[nCurrentBuffer].dwStartOffset = 0;
ciStreamBuffers[nCurrentBuffer].dwMaxLength = OUT_BUFFER_SIZE;
ciStreamBuffers[nCurrentBuffer].tkStart = 0;
ciStreamBuffers[nCurrentBuffer].dwBytesRecorded = 0;
ciStreamBuffers[nCurrentBuffer].bTimesUp = FALSE;
if ((nChkErr = Mid2StreamConvertToBuffer(0, &ciStreamBuffers[nCurrentBuffer]))
!= CONVERTERR_NOERROR)
{
if (nChkErr == CONVERTERR_DONE)
{
// Don't include this one in the count
uCallbackStatus = STATUS_WAITINGFOREND;
return;
}
else
{
// We're not in the main thread, so we can't call I_Error() now.
// Log the error message out, and post exit message.
CONS_Printf("MidiStreamCallback(): conversion pass failed!\n");
PostMessage(hWndMain, WM_CLOSE, 0, 0);
return;
}
}
ciStreamBuffers[nCurrentBuffer].mhBuffer.dwBytesRecorded
= ciStreamBuffers[nCurrentBuffer].dwBytesRecorded;
if ((mmrRetVal = midiStreamOut(hStream, &ciStreamBuffers[nCurrentBuffer].mhBuffer,
sizeof (MIDIHDR))) != MMSYSERR_NOERROR)
{
MidiErrorMessageBox(mmrRetVal);
Mid2StreamConverterCleanup();
return;
}
nCurrentBuffer = (nCurrentBuffer + 1) % NUM_STREAM_BUFFERS;
nEmptyBuffers--;
}
break;
case MOM_POSITIONCB:
pmh = (MIDIHDR*)(size_t)dwParam1;
pme = (MIDIEVENT*)(pmh->lpData + pmh->dwOffset);
if (MIDIEVENT_TYPE(pme->dwEvent) == MIDI_CTRLCHANGE)
{
#ifdef DEBUGMIDISTREAM
if (MIDIEVENT_DATA1(pme->dwEvent) == MIDICTRL_VOLUME_LSB)
{
CONS_Printf("Got an LSB volume event\n");
PostMessage(hWndMain, WM_CLOSE, 0, 0); // can't I_Error() here
break;
}
#endif
// this is meant to respond to our own intention, from mid2strm.c
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);
// call SetChannelVolume() later to adjust MIDI volume control message to our
// own current volume level.
PostMessage(hWndMain, WM_MSTREAM_UPDATEVOLUME,
MIDIEVENT_CHANNEL(pme->dwEvent), 0L);
}
break;
default:
break;
}
return;
}
// ---------------------
// Mid2StreamFreeBuffers
// 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.
// ---------------------
static void Mid2StreamFreeBuffers(void)
{
DWORD idx;
MMRESULT mmrRetVal;
if (bBuffersPrepared)
{
for (idx = 0; idx < NUM_STREAM_BUFFERS; idx++)
{
if ((mmrRetVal = midiOutUnprepareHeader((HMIDIOUT)hStream,
&ciStreamBuffers[idx].mhBuffer, sizeof (MIDIHDR))) != MMSYSERR_NOERROR)
{
MidiErrorMessageBox(mmrRetVal);
}
}
bBuffersPrepared = FALSE;
}
// Don't free the stream buffers here, but rather allocate them once at startup,
// and free them at shutdown.
}