Kart-Public/src/win32ce/win_snd.c
Sryder 6184f91dd3 Add an int to I_PlaySound to tell an interface which channel number SRB2 is using.
I've voided this out on other sound interfaces than SDL Mixer ones because I'm both not sure whether they need it, and not sure how to make them work with it if they do.
2018-10-13 23:01:11 +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.
}