mirror of
https://github.com/ZDoom/qzdoom-gpl.git
synced 2024-12-12 13:12:14 +00:00
50b0340a56
position when evicting sounds, because restarting the sound system causes the DSP clock to restart at 0, so start times that were recorded before the reset are no longer applicable after the reset. - Fixed: S_StopChannel() always set the channel's actor to NULL, eliminating origin information when resetting the sound system. SVN r1949 (trunk)
2705 lines
77 KiB
C++
2705 lines
77 KiB
C++
/*
|
||
** fmodsound.cpp
|
||
** System interface for sound; uses FMOD Ex.
|
||
**
|
||
**---------------------------------------------------------------------------
|
||
** Copyright 1998-2009 Randy Heit
|
||
** All rights reserved.
|
||
**
|
||
** Redistribution and use in source and binary forms, with or without
|
||
** modification, are permitted provided that the following conditions
|
||
** are met:
|
||
**
|
||
** 1. Redistributions of source code must retain the above copyright
|
||
** notice, this list of conditions and the following disclaimer.
|
||
** 2. Redistributions in binary form must reproduce the above copyright
|
||
** notice, this list of conditions and the following disclaimer in the
|
||
** documentation and/or other materials provided with the distribution.
|
||
** 3. The name of the author may not be used to endorse or promote products
|
||
** derived from this software without specific prior written permission.
|
||
**
|
||
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
**---------------------------------------------------------------------------
|
||
**
|
||
*/
|
||
|
||
// HEADER FILES ------------------------------------------------------------
|
||
|
||
#ifdef _WIN32
|
||
#define WIN32_LEAN_AND_MEAN
|
||
#include <windows.h>
|
||
#include <mmsystem.h>
|
||
extern HWND Window;
|
||
#define USE_WINDOWS_DWORD
|
||
#else
|
||
#define FALSE 0
|
||
#define TRUE 1
|
||
#endif
|
||
#ifdef __APPLE__
|
||
#include <stdlib.h>
|
||
#else
|
||
#include <malloc.h>
|
||
#endif
|
||
|
||
#include "templates.h"
|
||
#include "fmodsound.h"
|
||
#include "c_cvars.h"
|
||
#include "i_system.h"
|
||
#include "i_music.h"
|
||
#include "v_text.h"
|
||
#include "v_video.h"
|
||
#include "v_palette.h"
|
||
#include "cmdlib.h"
|
||
|
||
// MACROS ------------------------------------------------------------------
|
||
|
||
// killough 2/21/98: optionally use varying pitched sounds
|
||
#define PITCH(freq,pitch) (snd_pitched ? ((freq)*(pitch))/128.f : float(freq))
|
||
|
||
// Just some extra for music and whatever
|
||
#define NUM_EXTRA_SOFTWARE_CHANNELS 1
|
||
|
||
#define MAX_CHANNELS 256
|
||
|
||
#define SPECTRUM_SIZE 256
|
||
|
||
// TYPES -------------------------------------------------------------------
|
||
|
||
struct FEnumList
|
||
{
|
||
const char *Name;
|
||
int Value;
|
||
};
|
||
|
||
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
|
||
|
||
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
|
||
|
||
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
|
||
|
||
static int Enum_NumForName(const FEnumList *list, const char *name);
|
||
static const char *Enum_NameForNum(const FEnumList *list, int num);
|
||
|
||
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
|
||
|
||
EXTERN_CVAR (String, snd_output)
|
||
EXTERN_CVAR (Float, snd_sfxvolume)
|
||
EXTERN_CVAR (Float, snd_musicvolume)
|
||
EXTERN_CVAR (Int, snd_buffersize)
|
||
EXTERN_CVAR (Int, snd_samplerate)
|
||
EXTERN_CVAR (Bool, snd_pitched)
|
||
EXTERN_CVAR (Int, snd_channels)
|
||
|
||
extern int sfx_empty;
|
||
|
||
// PUBLIC DATA DEFINITIONS -------------------------------------------------
|
||
|
||
ReverbContainer *ForcedEnvironment;
|
||
|
||
CVAR (Int, snd_driver, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
||
CVAR (Int, snd_buffercount, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
||
CVAR (Bool, snd_hrtf, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
||
CVAR (Bool, snd_waterreverb, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
||
CVAR (String, snd_resampler, "Linear", CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
||
CVAR (String, snd_speakermode, "Auto", CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
||
CVAR (String, snd_output_format, "PCM-16", CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
||
CVAR (String, snd_midipatchset, "", CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
||
CVAR (Bool, snd_profile, false, 0)
|
||
|
||
// Underwater low-pass filter cutoff frequency. Set to 0 to disable the filter.
|
||
CUSTOM_CVAR (Float, snd_waterlp, 250, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
|
||
{
|
||
// Clamp to the DSP unit's limits.
|
||
if (*self < 10 && *self != 0)
|
||
{
|
||
self = 10;
|
||
}
|
||
else if (*self > 22000)
|
||
{
|
||
self = 22000;
|
||
}
|
||
}
|
||
|
||
// PRIVATE DATA DEFINITIONS ------------------------------------------------
|
||
|
||
static const ReverbContainer *PrevEnvironment;
|
||
static bool ShowedBanner;
|
||
|
||
// The rolloff callback is called during FMOD::Sound::play, so we need this
|
||
// global variable to contain the sound info during that time for the
|
||
// callback.
|
||
static FRolloffInfo *GRolloff;
|
||
static float GDistScale;
|
||
|
||
// In the below lists, duplicate entries are for user selection. When
|
||
// queried, only the first one for the particular value is shown.
|
||
static const FEnumList OutputNames[] =
|
||
{
|
||
{ "Auto", FMOD_OUTPUTTYPE_AUTODETECT },
|
||
{ "Default", FMOD_OUTPUTTYPE_AUTODETECT },
|
||
{ "No sound", FMOD_OUTPUTTYPE_NOSOUND },
|
||
|
||
// Windows
|
||
{ "DirectSound", FMOD_OUTPUTTYPE_DSOUND },
|
||
{ "DSound", FMOD_OUTPUTTYPE_DSOUND },
|
||
{ "Windows Multimedia", FMOD_OUTPUTTYPE_WINMM },
|
||
{ "WinMM", FMOD_OUTPUTTYPE_WINMM },
|
||
{ "WaveOut", FMOD_OUTPUTTYPE_WINMM },
|
||
{ "OpenAL", FMOD_OUTPUTTYPE_OPENAL },
|
||
{ "WASAPI", FMOD_OUTPUTTYPE_WASAPI },
|
||
{ "ASIO", FMOD_OUTPUTTYPE_ASIO },
|
||
|
||
// Linux
|
||
{ "OSS", FMOD_OUTPUTTYPE_OSS },
|
||
{ "ALSA", FMOD_OUTPUTTYPE_ALSA },
|
||
{ "ESD", FMOD_OUTPUTTYPE_ESD },
|
||
{ "SDL", 666 },
|
||
|
||
// Mac
|
||
{ "Sound Manager", FMOD_OUTPUTTYPE_SOUNDMANAGER },
|
||
{ "Core Audio", FMOD_OUTPUTTYPE_COREAUDIO },
|
||
|
||
{ NULL, 0 }
|
||
};
|
||
|
||
static const FEnumList SpeakerModeNames[] =
|
||
{
|
||
{ "Mono", FMOD_SPEAKERMODE_MONO },
|
||
{ "Stereo", FMOD_SPEAKERMODE_STEREO },
|
||
{ "Quad", FMOD_SPEAKERMODE_QUAD },
|
||
{ "Surround", FMOD_SPEAKERMODE_SURROUND },
|
||
{ "5.1", FMOD_SPEAKERMODE_5POINT1 },
|
||
{ "7.1", FMOD_SPEAKERMODE_7POINT1 },
|
||
{ "Prologic", FMOD_SPEAKERMODE_PROLOGIC },
|
||
{ "1", FMOD_SPEAKERMODE_MONO },
|
||
{ "2", FMOD_SPEAKERMODE_STEREO },
|
||
{ "4", FMOD_SPEAKERMODE_QUAD },
|
||
{ NULL, 0 }
|
||
};
|
||
|
||
static const FEnumList ResamplerNames[] =
|
||
{
|
||
{ "No Interpolation", FMOD_DSP_RESAMPLER_NOINTERP },
|
||
{ "NoInterp", FMOD_DSP_RESAMPLER_NOINTERP },
|
||
{ "Linear", FMOD_DSP_RESAMPLER_LINEAR },
|
||
{ "Cubic", FMOD_DSP_RESAMPLER_CUBIC },
|
||
{ "Spline", FMOD_DSP_RESAMPLER_SPLINE },
|
||
{ NULL, 0 }
|
||
};
|
||
|
||
static const FEnumList SoundFormatNames[] =
|
||
{
|
||
{ "None", FMOD_SOUND_FORMAT_NONE },
|
||
{ "PCM-8", FMOD_SOUND_FORMAT_PCM8 },
|
||
{ "PCM-16", FMOD_SOUND_FORMAT_PCM16 },
|
||
{ "PCM-24", FMOD_SOUND_FORMAT_PCM24 },
|
||
{ "PCM-32", FMOD_SOUND_FORMAT_PCM32 },
|
||
{ "PCM-Float", FMOD_SOUND_FORMAT_PCMFLOAT },
|
||
{ "GCADPCM", FMOD_SOUND_FORMAT_GCADPCM },
|
||
{ "IMAADPCM", FMOD_SOUND_FORMAT_IMAADPCM },
|
||
{ "VAG", FMOD_SOUND_FORMAT_VAG },
|
||
{ "XMA", FMOD_SOUND_FORMAT_XMA },
|
||
{ "MPEG", FMOD_SOUND_FORMAT_MPEG },
|
||
{ NULL, 0 }
|
||
};
|
||
|
||
static const char *OpenStateNames[] =
|
||
{
|
||
"Ready",
|
||
"Loading",
|
||
"Error",
|
||
"Connecting",
|
||
"Buffering",
|
||
"Seeking",
|
||
"Streaming"
|
||
};
|
||
|
||
// CODE --------------------------------------------------------------------
|
||
|
||
//==========================================================================
|
||
//
|
||
// Enum_NumForName
|
||
//
|
||
// Returns the value of an enum name, or -1 if not found.
|
||
//
|
||
//==========================================================================
|
||
|
||
static int Enum_NumForName(const FEnumList *list, const char *name)
|
||
{
|
||
while (list->Name != NULL)
|
||
{
|
||
if (stricmp(list->Name, name) == 0)
|
||
{
|
||
return list->Value;
|
||
}
|
||
list++;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// Enum_NameForNum
|
||
//
|
||
// Returns the name of an enum value. If there is more than one name for a
|
||
// value, on the first one in the list is returned. Returns NULL if there
|
||
// was no match.
|
||
//
|
||
//==========================================================================
|
||
|
||
static const char *Enum_NameForNum(const FEnumList *list, int num)
|
||
{
|
||
while (list->Name != NULL)
|
||
{
|
||
if (list->Value == num)
|
||
{
|
||
return list->Name;
|
||
}
|
||
list++;
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// The container for a streaming FMOD::Sound, for playing music.
|
||
//
|
||
//==========================================================================
|
||
|
||
class FMODStreamCapsule : public SoundStream
|
||
{
|
||
public:
|
||
FMODStreamCapsule(FMOD::Sound *stream, FMODSoundRenderer *owner, const char *url)
|
||
: Owner(owner), Stream(NULL), Channel(NULL),
|
||
UserData(NULL), Callback(NULL), URL(url), Ended(false)
|
||
{
|
||
SetStream(stream);
|
||
}
|
||
|
||
FMODStreamCapsule(void *udata, SoundStreamCallback callback, FMODSoundRenderer *owner)
|
||
: Owner(owner), Stream(NULL), Channel(NULL),
|
||
UserData(udata), Callback(callback), Ended(false)
|
||
{}
|
||
|
||
~FMODStreamCapsule()
|
||
{
|
||
if (Channel != NULL)
|
||
{
|
||
Channel->stop();
|
||
}
|
||
if (Stream != NULL)
|
||
{
|
||
Stream->release();
|
||
}
|
||
}
|
||
|
||
void SetStream(FMOD::Sound *stream)
|
||
{
|
||
float frequency;
|
||
|
||
Stream = stream;
|
||
|
||
// As this interface is for music, make it super-high priority.
|
||
if (FMOD_OK == stream->getDefaults(&frequency, NULL, NULL, NULL))
|
||
{
|
||
stream->setDefaults(frequency, 1, 0, 0);
|
||
}
|
||
}
|
||
|
||
bool Play(bool looping, float volume)
|
||
{
|
||
FMOD_RESULT result;
|
||
|
||
if (URL.IsNotEmpty())
|
||
{ // Net streams cannot be looped, because they cannot be seeked.
|
||
looping = false;
|
||
}
|
||
Stream->setMode((looping ? FMOD_LOOP_NORMAL : FMOD_LOOP_OFF) | FMOD_SOFTWARE | FMOD_2D);
|
||
result = Owner->Sys->playSound(FMOD_CHANNEL_FREE, Stream, true, &Channel);
|
||
if (result != FMOD_OK)
|
||
{
|
||
return false;
|
||
}
|
||
Channel->setChannelGroup(Owner->MusicGroup);
|
||
Channel->setSpeakerMix(1, 1, 1, 1, 1, 1, 1, 1);
|
||
Channel->setVolume(volume);
|
||
// Ensure reverb is disabled.
|
||
FMOD_REVERB_CHANNELPROPERTIES reverb = { 0, };
|
||
if (FMOD_OK == Channel->getReverbProperties(&reverb))
|
||
{
|
||
reverb.Room = -10000;
|
||
Channel->setReverbProperties(&reverb);
|
||
}
|
||
Channel->setPaused(false);
|
||
Ended = false;
|
||
JustStarted = true;
|
||
Starved = false;
|
||
Loop = looping;
|
||
Volume = volume;
|
||
return true;
|
||
}
|
||
|
||
void Stop()
|
||
{
|
||
if (Channel != NULL)
|
||
{
|
||
Channel->stop();
|
||
Channel = NULL;
|
||
}
|
||
}
|
||
|
||
bool SetPaused(bool paused)
|
||
{
|
||
if (Channel != NULL)
|
||
{
|
||
return FMOD_OK == Channel->setPaused(paused);
|
||
}
|
||
return false;
|
||
}
|
||
|
||
unsigned int GetPosition()
|
||
{
|
||
unsigned int pos;
|
||
|
||
if (Channel != NULL && FMOD_OK == Channel->getPosition(&pos, FMOD_TIMEUNIT_MS))
|
||
{
|
||
return pos;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
bool IsEnded()
|
||
{
|
||
bool is;
|
||
FMOD_OPENSTATE openstate = FMOD_OPENSTATE_MAX;
|
||
bool starving;
|
||
|
||
if (Stream == NULL)
|
||
{
|
||
return true;
|
||
}
|
||
if (FMOD_OK != Stream->getOpenState(&openstate, NULL, &starving))
|
||
{
|
||
openstate = FMOD_OPENSTATE_ERROR;
|
||
}
|
||
if (openstate == FMOD_OPENSTATE_ERROR)
|
||
{
|
||
if (Channel != NULL)
|
||
{
|
||
Channel->stop();
|
||
Channel = NULL;
|
||
}
|
||
return true;
|
||
}
|
||
if (Channel != NULL && (FMOD_OK != Channel->isPlaying(&is) || is == false))
|
||
{
|
||
return true;
|
||
}
|
||
if (Ended)
|
||
{
|
||
Channel->stop();
|
||
Channel = NULL;
|
||
return true;
|
||
}
|
||
if (URL.IsNotEmpty() && !JustStarted && openstate == FMOD_OPENSTATE_READY)
|
||
{
|
||
// Reconnect the stream, since it seems to have stalled.
|
||
// The only way to do this appears to be to completely recreate it.
|
||
FMOD_RESULT result;
|
||
|
||
Channel->stop();
|
||
Stream->release();
|
||
Channel = NULL;
|
||
Stream = NULL;
|
||
Owner->Sys->setStreamBufferSize(64*1024, FMOD_TIMEUNIT_RAWBYTES);
|
||
// Open the stream asynchronously, so we don't hang the game while trying to reconnect.
|
||
// (It would be nice to do the initial open asynchronously as well, but I'd need to rethink
|
||
// the music system design to pull that off.)
|
||
result = Owner->Sys->createSound(URL, (Loop ? FMOD_LOOP_NORMAL : FMOD_LOOP_OFF) | FMOD_SOFTWARE | FMOD_2D |
|
||
FMOD_CREATESTREAM | FMOD_NONBLOCKING, NULL, &Stream);
|
||
JustStarted = true;
|
||
Owner->Sys->setStreamBufferSize(16*1024, FMOD_TIMEUNIT_RAWBYTES);
|
||
return result != FMOD_OK;
|
||
}
|
||
if (JustStarted && openstate == FMOD_OPENSTATE_STREAMING)
|
||
{
|
||
JustStarted = false;
|
||
}
|
||
if (JustStarted && Channel == NULL && openstate == FMOD_OPENSTATE_READY)
|
||
{
|
||
return !Play(Loop, Volume);
|
||
}
|
||
if (starving != Starved)
|
||
{ // Mute the sound if it's starving.
|
||
Channel->setVolume(starving ? 0 : Volume);
|
||
Starved = starving;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
void SetVolume(float volume)
|
||
{
|
||
if (Channel != NULL && !Starved)
|
||
{
|
||
Channel->setVolume(volume);
|
||
}
|
||
Volume = volume;
|
||
}
|
||
|
||
// Sets the position in ms.
|
||
bool SetPosition(unsigned int ms_pos)
|
||
{
|
||
return FMOD_OK == Channel->setPosition(ms_pos, FMOD_TIMEUNIT_MS);
|
||
}
|
||
|
||
// Sets the order number for MOD formats.
|
||
bool SetOrder(int order_pos)
|
||
{
|
||
return FMOD_OK == Channel->setPosition(order_pos, FMOD_TIMEUNIT_MODORDER);
|
||
}
|
||
|
||
FString GetStats()
|
||
{
|
||
FString stats;
|
||
FMOD_OPENSTATE openstate;
|
||
unsigned int percentbuffered;
|
||
unsigned int position;
|
||
bool starving;
|
||
float volume;
|
||
float frequency;
|
||
bool paused;
|
||
bool isplaying;
|
||
|
||
if (FMOD_OK == Stream->getOpenState(&openstate, &percentbuffered, &starving))
|
||
{
|
||
stats = (openstate <= FMOD_OPENSTATE_STREAMING ? OpenStateNames[openstate] : "Unknown state");
|
||
stats.AppendFormat(",%3d%% buffered, %s", percentbuffered, starving ? "Starving" : "Well-fed");
|
||
}
|
||
if (Channel == NULL)
|
||
{
|
||
stats += ", not playing";
|
||
}
|
||
if (Channel != NULL && FMOD_OK == Channel->getPosition(&position, FMOD_TIMEUNIT_MS))
|
||
{
|
||
stats.AppendFormat(", %d", position);
|
||
if (FMOD_OK == Stream->getLength(&position, FMOD_TIMEUNIT_MS))
|
||
{
|
||
stats.AppendFormat("/%d", position);
|
||
}
|
||
stats += " ms";
|
||
}
|
||
if (Channel != NULL && FMOD_OK == Channel->getVolume(&volume))
|
||
{
|
||
stats.AppendFormat(", %d%%", int(volume * 100));
|
||
}
|
||
if (Channel != NULL && FMOD_OK == Channel->getPaused(&paused) && paused)
|
||
{
|
||
stats += ", paused";
|
||
}
|
||
if (Channel != NULL && FMOD_OK == Channel->isPlaying(&isplaying) && isplaying)
|
||
{
|
||
stats += ", playing";
|
||
}
|
||
if (Channel != NULL && FMOD_OK == Channel->getFrequency(&frequency))
|
||
{
|
||
stats.AppendFormat(", %g Hz", frequency);
|
||
}
|
||
if (JustStarted)
|
||
{
|
||
stats += " JS";
|
||
}
|
||
if (Ended)
|
||
{
|
||
stats += " XX";
|
||
}
|
||
return stats;
|
||
}
|
||
|
||
static FMOD_RESULT F_CALLBACK PCMReadCallback(FMOD_SOUND *sound, void *data, unsigned int datalen)
|
||
{
|
||
FMOD_RESULT result;
|
||
FMODStreamCapsule *self;
|
||
|
||
result = ((FMOD::Sound *)sound)->getUserData((void **)&self);
|
||
if (result != FMOD_OK || self == NULL || self->Callback == NULL || self->Ended)
|
||
{
|
||
// Contrary to the docs, this return value is completely ignored.
|
||
return FMOD_OK;
|
||
}
|
||
if (!self->Callback(self, data, datalen, self->UserData))
|
||
{
|
||
self->Ended = true;
|
||
}
|
||
return FMOD_OK;
|
||
}
|
||
|
||
static FMOD_RESULT F_CALLBACK PCMSetPosCallback(FMOD_SOUND *sound, int subsound, unsigned int position, FMOD_TIMEUNIT postype)
|
||
{
|
||
// This is useful if the user calls Channel::setPosition and you want
|
||
// to seek your data accordingly.
|
||
return FMOD_OK;
|
||
}
|
||
|
||
private:
|
||
FMODSoundRenderer *Owner;
|
||
FMOD::Sound *Stream;
|
||
FMOD::Channel *Channel;
|
||
void *UserData;
|
||
SoundStreamCallback Callback;
|
||
FString URL;
|
||
bool Ended;
|
||
bool JustStarted;
|
||
bool Starved;
|
||
bool Loop;
|
||
float Volume;
|
||
};
|
||
|
||
//==========================================================================
|
||
//
|
||
// The interface the game uses to talk to FMOD.
|
||
//
|
||
//==========================================================================
|
||
|
||
FMODSoundRenderer::FMODSoundRenderer()
|
||
{
|
||
InitSuccess = Init();
|
||
}
|
||
|
||
FMODSoundRenderer::~FMODSoundRenderer()
|
||
{
|
||
Shutdown();
|
||
}
|
||
|
||
bool FMODSoundRenderer::IsValid()
|
||
{
|
||
return InitSuccess;
|
||
}
|
||
|
||
#ifdef _MSC_VER
|
||
//==========================================================================
|
||
//
|
||
// CheckException
|
||
//
|
||
//==========================================================================
|
||
|
||
#define FACILITY_VISUALCPP ((LONG)0x6d)
|
||
#define VcppException(sev,err) ((sev) | (FACILITY_VISUALCPP<<16) | err)
|
||
|
||
static int CheckException(DWORD code)
|
||
{
|
||
if (code == VcppException(ERROR_SEVERITY_ERROR,ERROR_MOD_NOT_FOUND) ||
|
||
code == VcppException(ERROR_SEVERITY_ERROR,ERROR_PROC_NOT_FOUND))
|
||
{
|
||
return EXCEPTION_EXECUTE_HANDLER;
|
||
}
|
||
return EXCEPTION_CONTINUE_SEARCH;
|
||
}
|
||
|
||
#endif
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: Init
|
||
//
|
||
//==========================================================================
|
||
|
||
bool FMODSoundRenderer::Init()
|
||
{
|
||
FMOD_RESULT result;
|
||
unsigned int version;
|
||
FMOD_SPEAKERMODE speakermode;
|
||
FMOD_SOUND_FORMAT format;
|
||
FMOD_DSP_RESAMPLER resampler;
|
||
FMOD_INITFLAGS initflags;
|
||
int samplerate;
|
||
int driver;
|
||
|
||
int eval;
|
||
|
||
SFXPaused = 0;
|
||
DSPLocked = false;
|
||
MusicGroup = NULL;
|
||
SfxGroup = NULL;
|
||
PausableSfx = NULL;
|
||
SfxConnection = NULL;
|
||
WaterLP = NULL;
|
||
WaterReverb = NULL;
|
||
PrevEnvironment = DefaultEnvironments[0];
|
||
DSPClock.AsOne = 0;
|
||
ChannelGroupTargetUnit = NULL;
|
||
SfxReverbHooked = false;
|
||
SfxReverbPlaceholder = NULL;
|
||
OutputPlugin = 0;
|
||
|
||
Printf("I_InitSound: Initializing FMOD\n");
|
||
|
||
// Create a System object and initialize.
|
||
#ifdef _MSC_VER
|
||
__try {
|
||
#endif
|
||
result = FMOD::System_Create(&Sys);
|
||
#ifdef _MSC_VER
|
||
}
|
||
__except(CheckException(GetExceptionCode()))
|
||
{
|
||
Sys = NULL;
|
||
Printf(TEXTCOLOR_ORANGE"Failed to load fmodex"
|
||
#ifdef _WIN64
|
||
"64"
|
||
#endif
|
||
".dll\n");
|
||
return false;
|
||
}
|
||
#endif
|
||
if (result != FMOD_OK)
|
||
{
|
||
Sys = NULL;
|
||
Printf(TEXTCOLOR_ORANGE"Failed to create FMOD system object: Error %d\n", result);
|
||
return false;
|
||
}
|
||
|
||
result = Sys->getVersion(&version);
|
||
if (result != FMOD_OK)
|
||
{
|
||
Printf(TEXTCOLOR_ORANGE"Could not validate FMOD version: Error %d\n", result);
|
||
return false;
|
||
}
|
||
|
||
const char *wrongver = NULL;
|
||
if (version < 0x42000)
|
||
{
|
||
wrongver = "an old";
|
||
}
|
||
#if FMOD_VERSION < 0x42700
|
||
else if ((version & 0xFFFF00) > 0x42600)
|
||
#else
|
||
else if ((version & 0xFFFF00) > (FMOD_VERSION & 0xFFFF00))
|
||
#endif
|
||
{
|
||
wrongver = "a new";
|
||
}
|
||
if (wrongver != NULL)
|
||
{
|
||
Printf (" "TEXTCOLOR_ORANGE"Error! You are using %s version of FMOD (%x.%02x.%02x).\n"
|
||
" "TEXTCOLOR_ORANGE"This program was built for version %x.%02x.%02x\n",
|
||
wrongver,
|
||
version >> 16, (version >> 8) & 255, version & 255,
|
||
FMOD_VERSION >> 16, (FMOD_VERSION >> 8) & 255, FMOD_VERSION & 255);
|
||
return false;
|
||
}
|
||
ActiveFMODVersion = version;
|
||
|
||
if (!ShowedBanner)
|
||
{
|
||
Printf("FMOD Sound System, copyright <20> Firelight Technologies Pty, Ltd., 1994-2009.\n");
|
||
Printf("Loaded FMOD version %x.%02x.%02x\n", version >> 16, (version >> 8) & 255, version & 255);
|
||
ShowedBanner = true;
|
||
}
|
||
#ifdef _WIN32
|
||
if (OSPlatform == os_WinNT4)
|
||
{
|
||
// The following was true as of FMOD 3. I don't know if it still
|
||
// applies to FMOD Ex, nor do I have an NT 4 install anymore, but
|
||
// there's no reason to get rid of it yet.
|
||
//
|
||
// If running Windows NT 4, we need to initialize DirectSound before
|
||
// using WinMM. If we don't, then FSOUND_Close will corrupt a
|
||
// heap. This might just be the Audigy's drivers--I don't know why
|
||
// it happens. At least the fix is simple enough. I only need to
|
||
// initialize DirectSound once, and then I can initialize/close
|
||
// WinMM as many times as I want.
|
||
//
|
||
// Yes, using WinMM under NT 4 is a good idea. I can get latencies as
|
||
// low as 20 ms with WinMM, but with DirectSound I need to have the
|
||
// latency as high as 120 ms to avoid crackling--quite the opposite
|
||
// from the other Windows versions with real DirectSound support.
|
||
|
||
static bool inited_dsound = false;
|
||
|
||
if (!inited_dsound)
|
||
{
|
||
if (Sys->setOutput(FMOD_OUTPUTTYPE_DSOUND) == FMOD_OK)
|
||
{
|
||
if (Sys->init(1, FMOD_INIT_NORMAL, 0) == FMOD_OK)
|
||
{
|
||
inited_dsound = true;
|
||
Sleep(50);
|
||
Sys->close();
|
||
}
|
||
Sys->setOutput(FMOD_OUTPUTTYPE_WINMM);
|
||
}
|
||
}
|
||
}
|
||
#endif
|
||
|
||
#ifndef _WIN32
|
||
// Try to load SDL output plugin
|
||
result = Sys->setPluginPath(progdir); // Should we really look for it in the program directory?
|
||
result = Sys->loadPlugin("liboutput_sdl.so", &OutputPlugin);
|
||
if (result != FMOD_OK)
|
||
{
|
||
OutputPlugin = 0;
|
||
}
|
||
#endif
|
||
|
||
// Set the user specified output mode.
|
||
eval = Enum_NumForName(OutputNames, snd_output);
|
||
if (eval >= 0)
|
||
{
|
||
if (eval == 666 && OutputPlugin != 0)
|
||
{
|
||
result = Sys->setOutputByPlugin(OutputPlugin);
|
||
}
|
||
else
|
||
{
|
||
result = Sys->setOutput(FMOD_OUTPUTTYPE(eval));
|
||
}
|
||
if (result != FMOD_OK)
|
||
{
|
||
Printf(TEXTCOLOR_BLUE"Setting output type '%s' failed. Using default instead. (Error %d)\n", *snd_output, result);
|
||
eval = FMOD_OUTPUTTYPE_AUTODETECT;
|
||
Sys->setOutput(FMOD_OUTPUTTYPE_AUTODETECT);
|
||
}
|
||
}
|
||
|
||
result = Sys->getNumDrivers(&driver);
|
||
#ifdef unix
|
||
if (result == FMOD_OK)
|
||
{
|
||
// On Linux, FMOD defaults to OSS. If OSS is not present, it doesn't
|
||
// try ALSA; it just fails. We'll try for it, but only if OSS wasn't
|
||
// explicitly specified for snd_output.
|
||
if (driver == 0 && eval == FMOD_OUTPUTTYPE_AUTODETECT)
|
||
{
|
||
FMOD_OUTPUTTYPE output;
|
||
if (FMOD_OK == Sys->getOutput(&output))
|
||
{
|
||
if (output == FMOD_OUTPUTTYPE_OSS)
|
||
{
|
||
Printf(TEXTCOLOR_BLUE"OSS could not be initialized. Trying ALSA.\n");
|
||
Sys->setOutput(FMOD_OUTPUTTYPE_ALSA);
|
||
result = Sys->getNumDrivers(&driver);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
#endif
|
||
if (result == FMOD_OK)
|
||
{
|
||
if (driver == 0)
|
||
{
|
||
Printf(TEXTCOLOR_ORANGE"No working sound devices found. Try a different snd_output?\n");
|
||
return false;
|
||
}
|
||
if (snd_driver >= driver)
|
||
{
|
||
Printf(TEXTCOLOR_BLUE"Driver %d does not exist. Using 0.\n", *snd_driver);
|
||
driver = 0;
|
||
}
|
||
else
|
||
{
|
||
driver = snd_driver;
|
||
}
|
||
result = Sys->setDriver(driver);
|
||
}
|
||
result = Sys->getDriver(&driver);
|
||
result = Sys->getDriverCaps(driver, &Driver_Caps, &Driver_MinFrequency, &Driver_MaxFrequency, &speakermode);
|
||
if (result != FMOD_OK)
|
||
{
|
||
Printf(TEXTCOLOR_BLUE"Could not ascertain driver capabilities. Some things may be weird. (Error %d)\n", result);
|
||
// Fill in some default to pretend it worked. (But as long as we specify a valid driver,
|
||
// can this call actually fail?)
|
||
Driver_Caps = 0;
|
||
Driver_MinFrequency = 4000;
|
||
Driver_MaxFrequency = 48000;
|
||
speakermode = FMOD_SPEAKERMODE_STEREO;
|
||
}
|
||
|
||
// Set the user selected speaker mode.
|
||
eval = Enum_NumForName(SpeakerModeNames, snd_speakermode);
|
||
if (eval >= 0)
|
||
{
|
||
speakermode = FMOD_SPEAKERMODE(eval);
|
||
}
|
||
result = Sys->setSpeakerMode(speakermode);
|
||
if (result != FMOD_OK)
|
||
{
|
||
Printf(TEXTCOLOR_BLUE"Could not set speaker mode to '%s'. (Error %d)\n", *snd_speakermode, result);
|
||
}
|
||
|
||
// Set software format
|
||
eval = Enum_NumForName(SoundFormatNames, snd_output_format);
|
||
format = eval >= 0 ? FMOD_SOUND_FORMAT(eval) : FMOD_SOUND_FORMAT_PCM16;
|
||
eval = Enum_NumForName(ResamplerNames, snd_resampler);
|
||
resampler = eval >= 0 ? FMOD_DSP_RESAMPLER(eval) : FMOD_DSP_RESAMPLER_LINEAR;
|
||
samplerate = clamp<int>(snd_samplerate, Driver_MinFrequency, Driver_MaxFrequency);
|
||
if (samplerate == 0 || snd_samplerate == 0)
|
||
{ // Creative's ASIO drivers report the only supported frequency as 0!
|
||
if (FMOD_OK != Sys->getSoftwareFormat(&samplerate, NULL, NULL, NULL, NULL, NULL))
|
||
{
|
||
samplerate = 48000;
|
||
}
|
||
}
|
||
if (samplerate != snd_samplerate && snd_samplerate != 0)
|
||
{
|
||
Printf(TEXTCOLOR_BLUE"Sample rate %d is unsupported. Trying %d.\n", *snd_samplerate, samplerate);
|
||
}
|
||
result = Sys->setSoftwareFormat(samplerate, format, 0, 0, resampler);
|
||
if (result != FMOD_OK)
|
||
{
|
||
Printf(TEXTCOLOR_BLUE"Could not set mixing format. Defaults will be used. (Error %d)\n", result);
|
||
}
|
||
|
||
// Set software channels according to snd_channels
|
||
result = Sys->setSoftwareChannels(snd_channels + NUM_EXTRA_SOFTWARE_CHANNELS);
|
||
if (result != FMOD_OK)
|
||
{
|
||
Printf(TEXTCOLOR_BLUE"Failed to set the preferred number of channels. (Error %d)\n", result);
|
||
}
|
||
|
||
if (Driver_Caps & FMOD_CAPS_HARDWARE_EMULATED)
|
||
{ // The user has the 'Acceleration' slider set to off!
|
||
// This is really bad for latency!
|
||
Printf (TEXTCOLOR_BLUE"Warning: The sound acceleration slider has been set to off.\n");
|
||
Printf (TEXTCOLOR_BLUE"Please turn it back on if you want decent sound.\n");
|
||
result = Sys->setDSPBufferSize(1024, 10); // At 48khz, the latency between issuing an fmod command and hearing it will now be about 213ms.
|
||
}
|
||
else if (snd_buffersize != 0 || snd_buffercount != 0)
|
||
{
|
||
int buffersize = snd_buffersize ? snd_buffersize : 1024;
|
||
int buffercount = snd_buffercount ? snd_buffercount : 4;
|
||
result = Sys->setDSPBufferSize(buffersize, buffercount);
|
||
}
|
||
else
|
||
{
|
||
result = FMOD_OK;
|
||
}
|
||
if (result != FMOD_OK)
|
||
{
|
||
Printf(TEXTCOLOR_BLUE"Setting DSP buffer size failed. (Error %d)\n", result);
|
||
}
|
||
|
||
// Try to init
|
||
initflags = FMOD_INIT_NORMAL;
|
||
if (snd_hrtf)
|
||
{
|
||
initflags |= FMOD_INIT_SOFTWARE_HRTF;
|
||
}
|
||
if (snd_profile)
|
||
{
|
||
initflags |= FMOD_INIT_ENABLE_PROFILE;
|
||
}
|
||
for (;;)
|
||
{
|
||
result = Sys->init(MAX(*snd_channels, MAX_CHANNELS), initflags, 0);
|
||
if (result == FMOD_ERR_OUTPUT_CREATEBUFFER)
|
||
{
|
||
// Possible causes of a buffer creation failure:
|
||
// 1. The speaker mode selected isn't supported by this soundcard. Force it to stereo.
|
||
// 2. The output format is unsupported. Force it to 16-bit PCM.
|
||
// 3. ???
|
||
result = Sys->getSpeakerMode(&speakermode);
|
||
if (result == FMOD_OK &&
|
||
speakermode != FMOD_SPEAKERMODE_STEREO &&
|
||
FMOD_OK == Sys->setSpeakerMode(FMOD_SPEAKERMODE_STEREO))
|
||
{
|
||
Printf(TEXTCOLOR_RED" Buffer creation failed. Retrying with stereo output.\n");
|
||
continue;
|
||
}
|
||
result = Sys->getSoftwareFormat(&samplerate, &format, NULL, NULL, &resampler, NULL);
|
||
if (result == FMOD_OK &&
|
||
format != FMOD_SOUND_FORMAT_PCM16 &&
|
||
FMOD_OK == Sys->setSoftwareFormat(samplerate, FMOD_SOUND_FORMAT_PCM16, 0, 0, resampler))
|
||
{
|
||
Printf(TEXTCOLOR_RED" Buffer creation failed. Retrying with PCM-16 output.\n");
|
||
continue;
|
||
}
|
||
}
|
||
#ifdef _WIN32
|
||
else if (result == FMOD_ERR_OUTPUT_INIT)
|
||
{
|
||
FMOD_OUTPUTTYPE output;
|
||
result = Sys->getOutput(&output);
|
||
if (result == FMOD_OK && output != FMOD_OUTPUTTYPE_DSOUND)
|
||
{
|
||
Printf(TEXTCOLOR_BLUE" Init failed for output type %s. Retrying with DirectSound.\n",
|
||
Enum_NameForNum(OutputNames, output));
|
||
if (FMOD_OK == Sys->setOutput(FMOD_OUTPUTTYPE_DSOUND))
|
||
{
|
||
continue;
|
||
}
|
||
}
|
||
}
|
||
#endif
|
||
break;
|
||
}
|
||
if (result != FMOD_OK)
|
||
{ // Initializing FMOD failed. Cry cry.
|
||
Printf(TEXTCOLOR_ORANGE" System::init returned error code %d\n", result);
|
||
return false;
|
||
}
|
||
|
||
// Create channel groups
|
||
result = Sys->createChannelGroup("Music", &MusicGroup);
|
||
if (result != FMOD_OK)
|
||
{
|
||
Printf(TEXTCOLOR_ORANGE" Could not create music channel group. (Error %d)\n", result);
|
||
return false;
|
||
}
|
||
|
||
result = Sys->createChannelGroup("SFX", &SfxGroup);
|
||
if (result != FMOD_OK)
|
||
{
|
||
Printf(TEXTCOLOR_ORANGE" Could not create sfx channel group. (Error %d)\n", result);
|
||
return false;
|
||
}
|
||
|
||
result = Sys->createChannelGroup("Pausable SFX", &PausableSfx);
|
||
if (result != FMOD_OK)
|
||
{
|
||
Printf(TEXTCOLOR_ORANGE" Could not create pausable sfx channel group. (Error %d)\n", result);
|
||
return false;
|
||
}
|
||
|
||
result = SfxGroup->addGroup(PausableSfx);
|
||
if (result != FMOD_OK)
|
||
{
|
||
Printf(TEXTCOLOR_BLUE" Could not attach pausable sfx to sfx channel group. (Error %d)\n", result);
|
||
}
|
||
|
||
// Create DSP units for underwater effect
|
||
result = Sys->createDSPByType(FMOD_DSP_TYPE_LOWPASS, &WaterLP);
|
||
if (result != FMOD_OK)
|
||
{
|
||
Printf(TEXTCOLOR_BLUE" Could not create underwater lowpass unit. (Error %d)\n", result);
|
||
}
|
||
else
|
||
{
|
||
result = Sys->createDSPByType(FMOD_DSP_TYPE_REVERB, &WaterReverb);
|
||
if (result != FMOD_OK)
|
||
{
|
||
Printf(TEXTCOLOR_BLUE" Could not create underwater reverb unit. (Error %d)\n", result);
|
||
}
|
||
}
|
||
|
||
// Connect underwater DSP unit between PausableSFX and SFX groups, while
|
||
// retaining the connection established by SfxGroup->addGroup().
|
||
if (WaterLP != NULL)
|
||
{
|
||
FMOD::DSP *sfx_head, *pausable_head;
|
||
|
||
result = SfxGroup->getDSPHead(&sfx_head);
|
||
if (result == FMOD_OK)
|
||
{
|
||
result = sfx_head->getInput(0, &pausable_head, &SfxConnection);
|
||
if (result == FMOD_OK)
|
||
{
|
||
// The placeholder mixer is for reference to where to connect the SFX
|
||
// reverb unit once it gets created.
|
||
result = Sys->createDSPByType(FMOD_DSP_TYPE_MIXER, &SfxReverbPlaceholder);
|
||
if (result == FMOD_OK)
|
||
{
|
||
// Replace the PausableSFX->SFX connection with
|
||
// PausableSFX->ReverbPlaceholder->SFX.
|
||
result = SfxReverbPlaceholder->addInput(pausable_head, NULL);
|
||
if (result == FMOD_OK)
|
||
{
|
||
FMOD::DSPConnection *connection;
|
||
result = sfx_head->addInput(SfxReverbPlaceholder, &connection);
|
||
if (result == FMOD_OK)
|
||
{
|
||
sfx_head->disconnectFrom(pausable_head);
|
||
SfxReverbPlaceholder->setActive(true);
|
||
SfxReverbPlaceholder->setBypass(true);
|
||
// The placeholder now takes the place of the pausable_head
|
||
// for the following connections.
|
||
pausable_head = SfxReverbPlaceholder;
|
||
SfxConnection = connection;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
SfxReverbPlaceholder->release();
|
||
SfxReverbPlaceholder = NULL;
|
||
}
|
||
}
|
||
result = WaterLP->addInput(pausable_head, NULL);
|
||
WaterLP->setActive(false);
|
||
WaterLP->setParameter(FMOD_DSP_LOWPASS_CUTOFF, snd_waterlp);
|
||
WaterLP->setParameter(FMOD_DSP_LOWPASS_RESONANCE, 2);
|
||
if (WaterReverb != NULL)
|
||
{
|
||
FMOD::DSPConnection *dry;
|
||
result = WaterReverb->addInput(pausable_head, &dry);
|
||
if (result == FMOD_OK)
|
||
{
|
||
result = dry->setMix(0.1f);
|
||
if (result == FMOD_OK)
|
||
{
|
||
result = WaterReverb->addInput(WaterLP, NULL);
|
||
if (result == FMOD_OK)
|
||
{
|
||
result = sfx_head->addInput(WaterReverb, NULL);
|
||
if (result == FMOD_OK)
|
||
{
|
||
WaterReverb->setParameter(FMOD_DSP_REVERB_ROOMSIZE, 0.001f);
|
||
WaterReverb->setParameter(FMOD_DSP_REVERB_DAMP, 0.2f);
|
||
WaterReverb->setActive(false);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
result = sfx_head->addInput(WaterLP, NULL);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
LastWaterLP = snd_waterlp;
|
||
|
||
// Find the FMOD Channel Group Target Unit. To completely eliminate sound
|
||
// while the program is deactivated, we can deactivate this DSP unit, and
|
||
// all audio processing will cease. This is not directly exposed by the
|
||
// API but can be easily located by getting the master channel group and
|
||
// tracing its single output, since it is known to hook up directly to the
|
||
// Channel Group Target Unit. (See FMOD Profiler for proof.)
|
||
FMOD::ChannelGroup *master_group;
|
||
result = Sys->getMasterChannelGroup(&master_group);
|
||
if (result == FMOD_OK)
|
||
{
|
||
FMOD::DSP *master_head;
|
||
|
||
result = master_group->getDSPHead(&master_head);
|
||
if (result == FMOD_OK)
|
||
{
|
||
result = master_head->getOutput(0, &ChannelGroupTargetUnit, NULL);
|
||
if (result != FMOD_OK)
|
||
{
|
||
ChannelGroupTargetUnit = NULL;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (FMOD_OK != Sys->getSoftwareFormat(&OutputRate, NULL, NULL, NULL, NULL, NULL))
|
||
{
|
||
OutputRate = 48000; // Guess, but this should never happen.
|
||
}
|
||
Sys->set3DSettings(0.5f, 96.f, 1.f);
|
||
Sys->set3DRolloffCallback(RolloffCallback);
|
||
snd_sfxvolume.Callback ();
|
||
return true;
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: Shutdown
|
||
//
|
||
//==========================================================================
|
||
|
||
void FMODSoundRenderer::Shutdown()
|
||
{
|
||
if (Sys != NULL)
|
||
{
|
||
if (MusicGroup != NULL)
|
||
{
|
||
MusicGroup->release();
|
||
MusicGroup = NULL;
|
||
}
|
||
if (PausableSfx != NULL)
|
||
{
|
||
PausableSfx->release();
|
||
PausableSfx = NULL;
|
||
}
|
||
if (SfxGroup != NULL)
|
||
{
|
||
SfxGroup->release();
|
||
SfxGroup = NULL;
|
||
}
|
||
if (WaterLP != NULL)
|
||
{
|
||
WaterLP->release();
|
||
WaterLP = NULL;
|
||
}
|
||
if (WaterReverb != NULL)
|
||
{
|
||
WaterReverb->release();
|
||
WaterReverb = NULL;
|
||
}
|
||
if (SfxReverbPlaceholder != NULL)
|
||
{
|
||
SfxReverbPlaceholder->release();
|
||
SfxReverbPlaceholder = NULL;
|
||
}
|
||
|
||
Sys->close();
|
||
if (OutputPlugin != 0)
|
||
{
|
||
Sys->unloadPlugin(OutputPlugin);
|
||
OutputPlugin = 0;
|
||
}
|
||
Sys->release();
|
||
Sys = NULL;
|
||
}
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: GetOutputRate
|
||
//
|
||
//==========================================================================
|
||
|
||
float FMODSoundRenderer::GetOutputRate()
|
||
{
|
||
return (float)OutputRate;
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: PrintStatus
|
||
//
|
||
//==========================================================================
|
||
|
||
void FMODSoundRenderer::PrintStatus()
|
||
{
|
||
FMOD_OUTPUTTYPE output;
|
||
FMOD_SPEAKERMODE speakermode;
|
||
FMOD_SOUND_FORMAT format;
|
||
FMOD_DSP_RESAMPLER resampler;
|
||
int driver;
|
||
int samplerate;
|
||
int numoutputchannels;
|
||
int num2d, num3d, total;
|
||
unsigned int bufferlength;
|
||
int numbuffers;
|
||
|
||
Printf ("Loaded FMOD version: "TEXTCOLOR_GREEN"%x.%02x.%02x\n", ActiveFMODVersion >> 16,
|
||
(ActiveFMODVersion >> 8) & 255, ActiveFMODVersion & 255);
|
||
if (FMOD_OK == Sys->getOutput(&output))
|
||
{
|
||
Printf ("Output type: "TEXTCOLOR_GREEN"%s\n", Enum_NameForNum(OutputNames, output));
|
||
}
|
||
if (FMOD_OK == Sys->getSpeakerMode(&speakermode))
|
||
{
|
||
Printf ("Speaker mode: "TEXTCOLOR_GREEN"%s\n", Enum_NameForNum(SpeakerModeNames, speakermode));
|
||
}
|
||
if (FMOD_OK == Sys->getDriver(&driver))
|
||
{
|
||
char name[256];
|
||
if (FMOD_OK != Sys->getDriverInfo(driver, name, sizeof(name), NULL))
|
||
{
|
||
strcpy(name, "Unknown");
|
||
}
|
||
Printf ("Driver: "TEXTCOLOR_GREEN"%d"TEXTCOLOR_NORMAL" ("TEXTCOLOR_ORANGE"%s"TEXTCOLOR_NORMAL")\n", driver, name);
|
||
DumpDriverCaps(Driver_Caps, Driver_MinFrequency, Driver_MaxFrequency);
|
||
}
|
||
if (FMOD_OK == Sys->getHardwareChannels(&num2d, &num3d, &total))
|
||
{
|
||
Printf (TEXTCOLOR_YELLOW "Hardware 2D channels: "TEXTCOLOR_GREEN"%d\n", num2d);
|
||
Printf (TEXTCOLOR_YELLOW "Hardware 3D channels: "TEXTCOLOR_GREEN"%d\n", num3d);
|
||
Printf (TEXTCOLOR_YELLOW "Total hardware channels: "TEXTCOLOR_GREEN"%d\n", total);
|
||
}
|
||
if (FMOD_OK == Sys->getSoftwareFormat(&samplerate, &format, &numoutputchannels, NULL, &resampler, NULL))
|
||
{
|
||
Printf (TEXTCOLOR_LIGHTBLUE "Software mixer sample rate: "TEXTCOLOR_GREEN"%d\n", samplerate);
|
||
Printf (TEXTCOLOR_LIGHTBLUE "Software mixer format: "TEXTCOLOR_GREEN"%s\n", Enum_NameForNum(SoundFormatNames, format));
|
||
Printf (TEXTCOLOR_LIGHTBLUE "Software mixer channels: "TEXTCOLOR_GREEN"%d\n", numoutputchannels);
|
||
Printf (TEXTCOLOR_LIGHTBLUE "Software mixer resampler: "TEXTCOLOR_GREEN"%s\n", Enum_NameForNum(ResamplerNames, resampler));
|
||
}
|
||
if (FMOD_OK == Sys->getDSPBufferSize(&bufferlength, &numbuffers))
|
||
{
|
||
Printf (TEXTCOLOR_LIGHTBLUE "DSP buffers: "TEXTCOLOR_GREEN"%u samples x %d\n", bufferlength, numbuffers);
|
||
}
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: DumpDriverCaps
|
||
//
|
||
//==========================================================================
|
||
|
||
void FMODSoundRenderer::DumpDriverCaps(FMOD_CAPS caps, int minfrequency, int maxfrequency)
|
||
{
|
||
Printf (TEXTCOLOR_OLIVE " Min. frequency: "TEXTCOLOR_GREEN"%d\n", minfrequency);
|
||
Printf (TEXTCOLOR_OLIVE " Max. frequency: "TEXTCOLOR_GREEN"%d\n", maxfrequency);
|
||
Printf (" Features:\n");
|
||
if (caps == 0) Printf(TEXTCOLOR_OLIVE " None\n");
|
||
if (caps & FMOD_CAPS_HARDWARE) Printf(TEXTCOLOR_OLIVE " Hardware mixing\n");
|
||
if (caps & FMOD_CAPS_HARDWARE_EMULATED) Printf(TEXTCOLOR_OLIVE " Hardware acceleration is turned off!\n");
|
||
if (caps & FMOD_CAPS_OUTPUT_MULTICHANNEL) Printf(TEXTCOLOR_OLIVE " Multichannel\n");
|
||
if (caps & FMOD_CAPS_OUTPUT_FORMAT_PCM8) Printf(TEXTCOLOR_OLIVE " PCM-8");
|
||
if (caps & FMOD_CAPS_OUTPUT_FORMAT_PCM16) Printf(TEXTCOLOR_OLIVE " PCM-16");
|
||
if (caps & FMOD_CAPS_OUTPUT_FORMAT_PCM24) Printf(TEXTCOLOR_OLIVE " PCM-24");
|
||
if (caps & FMOD_CAPS_OUTPUT_FORMAT_PCM32) Printf(TEXTCOLOR_OLIVE " PCM-32");
|
||
if (caps & FMOD_CAPS_OUTPUT_FORMAT_PCMFLOAT) Printf(TEXTCOLOR_OLIVE " PCM-Float");
|
||
if (caps & (FMOD_CAPS_OUTPUT_FORMAT_PCM8 | FMOD_CAPS_OUTPUT_FORMAT_PCM16 | FMOD_CAPS_OUTPUT_FORMAT_PCM24 | FMOD_CAPS_OUTPUT_FORMAT_PCM32 | FMOD_CAPS_OUTPUT_FORMAT_PCMFLOAT))
|
||
{
|
||
Printf("\n");
|
||
}
|
||
if (caps & FMOD_CAPS_REVERB_EAX2) Printf(TEXTCOLOR_OLIVE " EAX2");
|
||
if (caps & FMOD_CAPS_REVERB_EAX3) Printf(TEXTCOLOR_OLIVE " EAX3");
|
||
if (caps & FMOD_CAPS_REVERB_EAX4) Printf(TEXTCOLOR_OLIVE " EAX4");
|
||
if (caps & FMOD_CAPS_REVERB_EAX5) Printf(TEXTCOLOR_OLIVE " EAX5");
|
||
if (caps & FMOD_CAPS_REVERB_I3DL2) Printf(TEXTCOLOR_OLIVE " I3DL2");
|
||
if (caps & (FMOD_CAPS_REVERB_EAX2 | FMOD_CAPS_REVERB_EAX3 | FMOD_CAPS_REVERB_EAX4 | FMOD_CAPS_REVERB_EAX5 | FMOD_CAPS_REVERB_I3DL2))
|
||
{
|
||
Printf("\n");
|
||
}
|
||
if (caps & FMOD_CAPS_REVERB_LIMITED) Printf("TEXTCOLOR_OLIVE Limited reverb\n");
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: PrintDriversList
|
||
//
|
||
//==========================================================================
|
||
|
||
void FMODSoundRenderer::PrintDriversList()
|
||
{
|
||
int numdrivers;
|
||
int i;
|
||
char name[256];
|
||
|
||
if (FMOD_OK == Sys->getNumDrivers(&numdrivers))
|
||
{
|
||
for (i = 0; i < numdrivers; ++i)
|
||
{
|
||
if (FMOD_OK == Sys->getDriverInfo(i, name, sizeof(name), NULL))
|
||
{
|
||
Printf("%d. %s\n", i, name);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: GatherStats
|
||
//
|
||
//==========================================================================
|
||
|
||
FString FMODSoundRenderer::GatherStats()
|
||
{
|
||
int channels;
|
||
float dsp, stream, update, geometry, total;
|
||
FString out;
|
||
|
||
channels = 0;
|
||
total = update = geometry = stream = dsp = 0;
|
||
Sys->getChannelsPlaying(&channels);
|
||
#if FMOD_VERSION >= 0x42501
|
||
// We were built with an FMOD with the geometry parameter.
|
||
if (ActiveFMODVersion >= 0x42501)
|
||
{ // And we are running with an FMOD that includes it.
|
||
FMOD_System_GetCPUUsage((FMOD_SYSTEM *)Sys, &dsp, &stream, &geometry, &update, &total);
|
||
}
|
||
else
|
||
{ // And we are running with an FMOD that does not include it.
|
||
// Cast the function to the appropriate type and call through the cast,
|
||
// since the headers are for the newer version.
|
||
((FMOD_RESULT (F_API *)(FMOD_SYSTEM *, float *, float *, float *, float *))
|
||
FMOD_System_GetCPUUsage)((FMOD_SYSTEM *)Sys, &dsp, &stream, &update, &total);
|
||
}
|
||
#else
|
||
// Same as above, except the headers we used do not include the geometry parameter.
|
||
if (ActiveFMODVersion >= 0x42501)
|
||
{
|
||
((FMOD_RESULT (F_API *)(FMOD_SYSTEM *, float *, float *, float *, float *, float *))
|
||
FMOD_System_GetCPUUsage)((FMOD_SYSTEM *)Sys, &dsp, &stream, &geometry, &update, &total);
|
||
}
|
||
else
|
||
{
|
||
FMOD_System_GetCPUUsage((FMOD_SYSTEM *)Sys, &dsp, &stream, &update, &total);
|
||
}
|
||
#endif
|
||
|
||
out.Format ("%d channels,"TEXTCOLOR_YELLOW"%5.2f"TEXTCOLOR_NORMAL"%% CPU "
|
||
"(DSP:"TEXTCOLOR_YELLOW"%2.2f"TEXTCOLOR_NORMAL"%% "
|
||
"Stream:"TEXTCOLOR_YELLOW"%2.2f"TEXTCOLOR_NORMAL"%% "
|
||
"Geometry:"TEXTCOLOR_YELLOW"%2.2f"TEXTCOLOR_NORMAL"%% "
|
||
"Update:"TEXTCOLOR_YELLOW"%2.2f"TEXTCOLOR_NORMAL"%%)",
|
||
channels, total, dsp, stream, geometry, update);
|
||
return out;
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: SetSfxVolume
|
||
//
|
||
//==========================================================================
|
||
|
||
void FMODSoundRenderer::SetSfxVolume(float volume)
|
||
{
|
||
SfxGroup->setVolume(volume);
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: SetMusicVolume
|
||
//
|
||
//==========================================================================
|
||
|
||
void FMODSoundRenderer::SetMusicVolume(float volume)
|
||
{
|
||
MusicGroup->setVolume(volume);
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: CreateStream
|
||
//
|
||
// Creates a streaming sound that receives PCM data through a callback.
|
||
//
|
||
//==========================================================================
|
||
|
||
SoundStream *FMODSoundRenderer::CreateStream (SoundStreamCallback callback, int buffbytes, int flags, int samplerate, void *userdata)
|
||
{
|
||
FMODStreamCapsule *capsule;
|
||
FMOD::Sound *sound;
|
||
FMOD_RESULT result;
|
||
FMOD_CREATESOUNDEXINFO exinfo;
|
||
FMOD_MODE mode;
|
||
int sample_shift;
|
||
int channel_shift;
|
||
|
||
InitCreateSoundExInfo(&exinfo);
|
||
capsule = new FMODStreamCapsule (userdata, callback, this);
|
||
|
||
mode = FMOD_2D | FMOD_OPENUSER | FMOD_LOOP_NORMAL | FMOD_SOFTWARE | FMOD_CREATESTREAM | FMOD_OPENONLY;
|
||
sample_shift = (flags & (SoundStream::Bits32 | SoundStream::Float)) ? 2 : (flags & SoundStream::Bits8) ? 0 : 1;
|
||
channel_shift = (flags & SoundStream::Mono) ? 0 : 1;
|
||
|
||
// Chunk size of stream update in samples. This will be the amount of data
|
||
// passed to the user callback.
|
||
exinfo.decodebuffersize = buffbytes >> (sample_shift + channel_shift);
|
||
|
||
// Number of channels in the sound.
|
||
exinfo.numchannels = 1 << channel_shift;
|
||
|
||
// Length of PCM data in bytes of whole song (for Sound::getLength).
|
||
// This pretends it's extremely long.
|
||
exinfo.length = ~0u;
|
||
|
||
// Default playback rate of sound. */
|
||
exinfo.defaultfrequency = samplerate;
|
||
|
||
// Data format of sound.
|
||
if (flags & SoundStream::Float)
|
||
{
|
||
exinfo.format = FMOD_SOUND_FORMAT_PCMFLOAT;
|
||
}
|
||
else if (flags & SoundStream::Bits32)
|
||
{
|
||
exinfo.format = FMOD_SOUND_FORMAT_PCM32;
|
||
}
|
||
else if (flags & SoundStream::Bits8)
|
||
{
|
||
exinfo.format = FMOD_SOUND_FORMAT_PCM8;
|
||
}
|
||
else
|
||
{
|
||
exinfo.format = FMOD_SOUND_FORMAT_PCM16;
|
||
}
|
||
|
||
// User callback for reading.
|
||
exinfo.pcmreadcallback = FMODStreamCapsule::PCMReadCallback;
|
||
|
||
// User callback for seeking.
|
||
exinfo.pcmsetposcallback = FMODStreamCapsule::PCMSetPosCallback;
|
||
|
||
// User data to be attached to the sound during creation. Access via Sound::getUserData.
|
||
exinfo.userdata = capsule;
|
||
|
||
result = Sys->createSound(NULL, mode, &exinfo, &sound);
|
||
if (result != FMOD_OK)
|
||
{
|
||
delete capsule;
|
||
return NULL;
|
||
}
|
||
capsule->SetStream(sound);
|
||
return capsule;
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: OpenStream
|
||
//
|
||
// Creates a streaming sound from a file on disk.
|
||
//
|
||
//==========================================================================
|
||
|
||
SoundStream *FMODSoundRenderer::OpenStream(const char *filename_or_data, int flags, int offset, int length)
|
||
{
|
||
FMOD_MODE mode;
|
||
FMOD_CREATESOUNDEXINFO exinfo;
|
||
FMOD::Sound *stream;
|
||
FMOD_RESULT result;
|
||
bool url;
|
||
|
||
InitCreateSoundExInfo(&exinfo);
|
||
mode = FMOD_SOFTWARE | FMOD_2D | FMOD_CREATESTREAM;
|
||
if (flags & SoundStream::Loop)
|
||
{
|
||
mode |= FMOD_LOOP_NORMAL;
|
||
}
|
||
if (offset == -1)
|
||
{
|
||
mode |= FMOD_OPENMEMORY;
|
||
offset = 0;
|
||
}
|
||
exinfo.length = length;
|
||
exinfo.fileoffset = offset;
|
||
if ((*snd_midipatchset)[0] != '\0')
|
||
{
|
||
exinfo.dlsname = snd_midipatchset;
|
||
}
|
||
|
||
url = (offset == 0 && length == 0 && strstr(filename_or_data, "://") > filename_or_data);
|
||
if (url)
|
||
{
|
||
// Use a larger buffer for URLs so that it's less likely to be effected
|
||
// by hiccups in the data rate from the remote server.
|
||
Sys->setStreamBufferSize(64*1024, FMOD_TIMEUNIT_RAWBYTES);
|
||
}
|
||
result = Sys->createSound(filename_or_data, mode, &exinfo, &stream);
|
||
if (url)
|
||
{
|
||
// Restore standard buffer size.
|
||
Sys->setStreamBufferSize(16*1024, FMOD_TIMEUNIT_RAWBYTES);
|
||
}
|
||
if (result == FMOD_ERR_FORMAT && exinfo.dlsname != NULL)
|
||
{
|
||
// FMOD_ERR_FORMAT could refer to either the main sound file or
|
||
// to the DLS instrument set. Try again without special DLS
|
||
// instruments to see if that lets it succeed.
|
||
exinfo.dlsname = NULL;
|
||
result = Sys->createSound(filename_or_data, mode, &exinfo, &stream);
|
||
if (result == FMOD_OK)
|
||
{
|
||
Printf("%s is an unsupported format.\n", *snd_midipatchset);
|
||
}
|
||
}
|
||
if (result == FMOD_OK)
|
||
{
|
||
return new FMODStreamCapsule(stream, this, url ? filename_or_data : NULL);
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: StartSound
|
||
//
|
||
//==========================================================================
|
||
|
||
FISoundChannel *FMODSoundRenderer::StartSound(SoundHandle sfx, float vol, int pitch, int flags, FISoundChannel *reuse_chan)
|
||
{
|
||
FMOD_RESULT result;
|
||
FMOD_MODE mode;
|
||
FMOD::Channel *chan;
|
||
float freq;
|
||
|
||
if (FMOD_OK == ((FMOD::Sound *)sfx.data)->getDefaults(&freq, NULL, NULL, NULL))
|
||
{
|
||
freq = PITCH(freq, pitch);
|
||
}
|
||
else
|
||
{
|
||
freq = 0;
|
||
}
|
||
|
||
GRolloff = NULL; // Do 2D sounds need rolloff?
|
||
result = Sys->playSound(FMOD_CHANNEL_FREE, (FMOD::Sound *)sfx.data, true, &chan);
|
||
if (FMOD_OK == result)
|
||
{
|
||
result = chan->getMode(&mode);
|
||
|
||
if (result != FMOD_OK)
|
||
{
|
||
assert(0);
|
||
mode = FMOD_SOFTWARE;
|
||
}
|
||
mode = (mode & ~FMOD_3D) | FMOD_2D;
|
||
if (flags & SNDF_LOOP)
|
||
{
|
||
mode = (mode & ~FMOD_LOOP_OFF) | FMOD_LOOP_NORMAL;
|
||
}
|
||
chan->setMode(mode);
|
||
chan->setChannelGroup((flags & SNDF_NOPAUSE) ? SfxGroup : PausableSfx);
|
||
if (freq != 0)
|
||
{
|
||
chan->setFrequency(freq);
|
||
}
|
||
chan->setVolume(vol);
|
||
if (!HandleChannelDelay(chan, reuse_chan, flags & (SNDF_ABSTIME | SNDF_LOOP), freq))
|
||
{
|
||
chan->stop();
|
||
return NULL;
|
||
}
|
||
if (flags & SNDF_NOREVERB)
|
||
{
|
||
FMOD_REVERB_CHANNELPROPERTIES reverb = { 0, };
|
||
if (FMOD_OK == chan->getReverbProperties(&reverb))
|
||
{
|
||
reverb.Room = -10000;
|
||
chan->setReverbProperties(&reverb);
|
||
}
|
||
}
|
||
chan->setPaused(false);
|
||
return CommonChannelSetup(chan, reuse_chan);
|
||
}
|
||
|
||
//DPrintf ("Sound %s failed to play: %d\n", sfx->name.GetChars(), result);
|
||
return NULL;
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: StartSound3D
|
||
//
|
||
//==========================================================================
|
||
|
||
CVAR(Float, snd_3dspread, 180, 0)
|
||
|
||
FISoundChannel *FMODSoundRenderer::StartSound3D(SoundHandle sfx, SoundListener *listener, float vol,
|
||
FRolloffInfo *rolloff, float distscale,
|
||
int pitch, int priority, const FVector3 &pos, const FVector3 &vel,
|
||
int channum, int flags, FISoundChannel *reuse_chan)
|
||
{
|
||
FMOD_RESULT result;
|
||
FMOD_MODE mode;
|
||
FMOD::Channel *chan;
|
||
float freq;
|
||
float def_freq, def_vol, def_pan;
|
||
int numchans;
|
||
int def_priority;
|
||
|
||
if (FMOD_OK == ((FMOD::Sound *)sfx.data)->getDefaults(&def_freq, &def_vol, &def_pan, &def_priority))
|
||
{
|
||
freq = PITCH(def_freq, pitch);
|
||
// Change the sound's default priority before playing it.
|
||
((FMOD::Sound *)sfx.data)->setDefaults(def_freq, def_vol, def_pan, clamp(def_priority - priority, 1, 256));
|
||
}
|
||
else
|
||
{
|
||
freq = 0;
|
||
def_priority = -1;
|
||
}
|
||
|
||
// Play it.
|
||
GRolloff = rolloff;
|
||
GDistScale = distscale;
|
||
|
||
// Experiments indicate that playSound will ignore priorities and always succeed
|
||
// as long as the parameters are set properly. It will first try to kick out sounds
|
||
// with the same priority level but has no problem with kicking out sounds at
|
||
// higher priority levels if it needs to.
|
||
result = Sys->playSound(FMOD_CHANNEL_FREE, (FMOD::Sound *)sfx.data, true, &chan);
|
||
|
||
// Then set the priority back.
|
||
if (def_priority >= 0)
|
||
{
|
||
((FMOD::Sound *)sfx.data)->setDefaults(def_freq, def_vol, def_pan, def_priority);
|
||
}
|
||
|
||
// Reduce volume of stereo sounds, because each channel will be summed together
|
||
// and is likely to be very similar, resulting in an amplitude twice what it
|
||
// would have been had it been mixed to mono.
|
||
if (FMOD_OK == ((FMOD::Sound *)sfx.data)->getFormat(NULL, NULL, &numchans, NULL))
|
||
{
|
||
if (numchans > 1)
|
||
{
|
||
vol *= 0.5f;
|
||
}
|
||
}
|
||
|
||
if (FMOD_OK == result)
|
||
{
|
||
result = chan->getMode(&mode);
|
||
if (result != FMOD_OK)
|
||
{
|
||
mode = FMOD_3D | FMOD_SOFTWARE;
|
||
}
|
||
if (flags & SNDF_LOOP)
|
||
{
|
||
mode = (mode & ~FMOD_LOOP_OFF) | FMOD_LOOP_NORMAL;
|
||
}
|
||
mode = SetChanHeadSettings(listener, chan, pos, !!(flags & SNDF_AREA), mode);
|
||
chan->setMode(mode);
|
||
chan->setChannelGroup((flags & SNDF_NOPAUSE) ? SfxGroup : PausableSfx);
|
||
|
||
if (freq != 0)
|
||
{
|
||
chan->setFrequency(freq);
|
||
}
|
||
chan->setVolume(vol);
|
||
if (mode & FMOD_3D)
|
||
{
|
||
chan->set3DAttributes((FMOD_VECTOR *)&pos[0], (FMOD_VECTOR *)&vel[0]);
|
||
chan->set3DSpread(snd_3dspread);
|
||
}
|
||
if (!HandleChannelDelay(chan, reuse_chan, flags & (SNDF_ABSTIME | SNDF_LOOP), freq))
|
||
{
|
||
chan->stop();
|
||
return NULL;
|
||
}
|
||
if (flags & SNDF_NOREVERB)
|
||
{
|
||
FMOD_REVERB_CHANNELPROPERTIES reverb = { 0, };
|
||
if (FMOD_OK == chan->getReverbProperties(&reverb))
|
||
{
|
||
reverb.Room = -10000;
|
||
chan->setReverbProperties(&reverb);
|
||
}
|
||
}
|
||
chan->setPaused(false);
|
||
chan->getPriority(&def_priority);
|
||
FISoundChannel *schan = CommonChannelSetup(chan, reuse_chan);
|
||
schan->Rolloff = *rolloff;
|
||
return schan;
|
||
}
|
||
|
||
GRolloff = NULL;
|
||
//DPrintf ("Sound %s failed to play: %d\n", sfx->name.GetChars(), result);
|
||
return 0;
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: HandleChannelDelay
|
||
//
|
||
// If the sound is restarting, seek it to its proper place. Returns false
|
||
// if the sound would have ended.
|
||
//
|
||
// Otherwise, record its starting time, and return true.
|
||
//
|
||
//==========================================================================
|
||
|
||
bool FMODSoundRenderer::HandleChannelDelay(FMOD::Channel *chan, FISoundChannel *reuse_chan, int flags, float freq) const
|
||
{
|
||
if (reuse_chan != NULL)
|
||
{ // Sound is being restarted, so seek it to the position
|
||
// it would be in now if it had never been evicted.
|
||
QWORD_UNION nowtime;
|
||
chan->getDelay(FMOD_DELAYTYPE_DSPCLOCK_START, &nowtime.Hi, &nowtime.Lo);
|
||
|
||
// If abstime is set, the sound is being restored, and
|
||
// the channel's start time is actually its seek position.
|
||
if (flags & SNDF_ABSTIME)
|
||
{
|
||
unsigned int seekpos = reuse_chan->StartTime.Lo;
|
||
if (seekpos > 0)
|
||
{
|
||
chan->setPosition(seekpos, FMOD_TIMEUNIT_PCM);
|
||
}
|
||
reuse_chan->StartTime.AsOne = QWORD(nowtime.AsOne - seekpos * OutputRate / freq);
|
||
}
|
||
else
|
||
{
|
||
QWORD difftime = nowtime.AsOne - reuse_chan->StartTime.AsOne;
|
||
if (difftime > 0)
|
||
{
|
||
return chan->setPosition((unsigned int)(difftime / OutputRate), FMOD_TIMEUNIT_MS) == FMOD_OK;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// chan->setDelay(FMOD_DELAYTYPE_DSPCLOCK_START, DSPClock.Hi, DSPClock.Lo);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: SetChanHeadSettings
|
||
//
|
||
// If this sound is played at the same coordinates as the listener, make
|
||
// it head relative. Also, area sounds should use no 3D panning if close
|
||
// enough to the listener.
|
||
//
|
||
//==========================================================================
|
||
|
||
FMOD_MODE FMODSoundRenderer::SetChanHeadSettings(SoundListener *listener, FMOD::Channel *chan,
|
||
const FVector3 &pos, bool areasound,
|
||
FMOD_MODE oldmode) const
|
||
{
|
||
if (!listener->valid)
|
||
{
|
||
return oldmode;
|
||
}
|
||
FVector3 cpos, mpos;
|
||
|
||
cpos = listener->position;
|
||
|
||
if (areasound)
|
||
{
|
||
float level, old_level;
|
||
|
||
// How far are we from the perceived sound origin? Within a certain
|
||
// short distance, we interpolate between 2D panning and full 3D panning.
|
||
const double interp_range = 32.0;
|
||
double dist_sqr = (cpos - pos).LengthSquared();
|
||
|
||
if (dist_sqr == 0)
|
||
{
|
||
level = 0;
|
||
}
|
||
else if (dist_sqr <= interp_range * interp_range)
|
||
{ // Within interp_range: Interpolate between none and full 3D panning.
|
||
level = float(1 - (interp_range - sqrt(dist_sqr)) / interp_range);
|
||
}
|
||
else
|
||
{ // Beyond interp_range: Normal 3D panning.
|
||
level = 1;
|
||
}
|
||
if (chan->get3DPanLevel(&old_level) == FMOD_OK && old_level != level)
|
||
{ // Only set it if it's different.
|
||
chan->set3DPanLevel(level);
|
||
if (level < 1)
|
||
{ // Let the noise come from all speakers, not just the front ones.
|
||
// A centered 3D sound does not play at full volume, so neither should the 2D-panned one.
|
||
// This is sqrt(0.5), which is the result for a centered equal power panning.
|
||
chan->setSpeakerMix(0.70711f,0.70711f,0.70711f,0.70711f,0.70711f,0.70711f,0.70711f,0.70711f);
|
||
}
|
||
}
|
||
return oldmode;
|
||
}
|
||
else if (cpos == pos)
|
||
{ // Head relative
|
||
return (oldmode & ~FMOD_3D) | FMOD_2D;
|
||
}
|
||
// World relative
|
||
return (oldmode & ~FMOD_2D) | FMOD_3D;
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: CommonChannelSetup
|
||
//
|
||
// Assign an end callback to the channel and allocates a game channel for
|
||
// it.
|
||
//
|
||
//==========================================================================
|
||
|
||
FISoundChannel *FMODSoundRenderer::CommonChannelSetup(FMOD::Channel *chan, FISoundChannel *reuse_chan) const
|
||
{
|
||
FISoundChannel *schan;
|
||
|
||
if (reuse_chan != NULL)
|
||
{
|
||
schan = reuse_chan;
|
||
schan->SysChannel = chan;
|
||
}
|
||
else
|
||
{
|
||
schan = S_GetChannel(chan);
|
||
chan->getDelay(FMOD_DELAYTYPE_DSPCLOCK_START, &schan->StartTime.Hi, &schan->StartTime.Lo);
|
||
}
|
||
chan->setUserData(schan);
|
||
chan->setCallback(ChannelCallback);
|
||
GRolloff = NULL;
|
||
return schan;
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: StopSound
|
||
//
|
||
//==========================================================================
|
||
|
||
void FMODSoundRenderer::StopChannel(FISoundChannel *chan)
|
||
{
|
||
if (chan != NULL && chan->SysChannel != NULL)
|
||
{
|
||
((FMOD::Channel *)chan->SysChannel)->stop();
|
||
}
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: GetPosition
|
||
//
|
||
// Returns position of sound on this channel, in samples.
|
||
//
|
||
//==========================================================================
|
||
|
||
unsigned int FMODSoundRenderer::GetPosition(FISoundChannel *chan)
|
||
{
|
||
unsigned int pos;
|
||
|
||
if (chan == NULL || chan->SysChannel == NULL)
|
||
{
|
||
return 0;
|
||
}
|
||
((FMOD::Channel *)chan->SysChannel)->getPosition(&pos, FMOD_TIMEUNIT_PCM);
|
||
return pos;
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: SetSfxPaused
|
||
//
|
||
//==========================================================================
|
||
|
||
void FMODSoundRenderer::SetSfxPaused(bool paused, int slot)
|
||
{
|
||
int oldslots = SFXPaused;
|
||
|
||
if (paused)
|
||
{
|
||
SFXPaused |= 1 << slot;
|
||
}
|
||
else
|
||
{
|
||
SFXPaused &= ~(1 << slot);
|
||
}
|
||
//Printf("%d\n", SFXPaused);
|
||
if (oldslots != 0 && SFXPaused == 0)
|
||
{
|
||
PausableSfx->setPaused(false);
|
||
}
|
||
else if (oldslots == 0 && SFXPaused != 0)
|
||
{
|
||
PausableSfx->setPaused(true);
|
||
}
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: SetInactive
|
||
//
|
||
// This is similar to SetSfxPaused but will *pause* everything, including
|
||
// the global reverb effect. This is meant to be used only when the
|
||
// game is deactivated, not for general sound pausing.
|
||
//
|
||
//==========================================================================
|
||
|
||
void FMODSoundRenderer::SetInactive(bool inactive)
|
||
{
|
||
if (ChannelGroupTargetUnit != NULL)
|
||
{
|
||
ChannelGroupTargetUnit->setActive(!inactive);
|
||
}
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: UpdateSoundParams3D
|
||
//
|
||
//==========================================================================
|
||
|
||
void FMODSoundRenderer::UpdateSoundParams3D(SoundListener *listener, FISoundChannel *chan, bool areasound, const FVector3 &pos, const FVector3 &vel)
|
||
{
|
||
if (chan == NULL || chan->SysChannel == NULL)
|
||
return;
|
||
|
||
FMOD::Channel *fchan = (FMOD::Channel *)chan->SysChannel;
|
||
FMOD_MODE oldmode, mode;
|
||
|
||
if (fchan->getMode(&oldmode) != FMOD_OK)
|
||
{
|
||
oldmode = FMOD_3D | FMOD_SOFTWARE;
|
||
}
|
||
mode = SetChanHeadSettings(listener, fchan, pos, areasound, oldmode);
|
||
if (mode != oldmode)
|
||
{ // Only set the mode if it changed.
|
||
fchan->setMode(mode);
|
||
}
|
||
fchan->set3DAttributes((FMOD_VECTOR *)&pos[0], (FMOD_VECTOR *)&vel[0]);
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: UpdateListener
|
||
//
|
||
//==========================================================================
|
||
|
||
void FMODSoundRenderer::UpdateListener(SoundListener *listener)
|
||
{
|
||
FMOD_VECTOR pos, vel;
|
||
FMOD_VECTOR forward;
|
||
FMOD_VECTOR up;
|
||
|
||
if (!listener->valid)
|
||
{
|
||
return;
|
||
}
|
||
|
||
// Set velocity to 0 to prevent crazy doppler shifts just from running.
|
||
|
||
vel.x = listener->velocity.X;
|
||
vel.z = listener->velocity.Y;
|
||
vel.y = listener->velocity.Z;
|
||
pos.x = listener->position.X;
|
||
pos.z = listener->position.Y;
|
||
pos.y = listener->position.Z;
|
||
|
||
float angle = listener->angle;
|
||
forward.x = cos(angle);
|
||
forward.y = 0;
|
||
forward.z = sin(angle);
|
||
|
||
up.x = 0;
|
||
up.y = 1;
|
||
up.z = 0;
|
||
|
||
Sys->set3DListenerAttributes(0, &pos, &vel, &forward, &up);
|
||
|
||
bool underwater = false;
|
||
const ReverbContainer *env;
|
||
|
||
underwater = (listener->underwater && snd_waterlp);
|
||
if (ForcedEnvironment)
|
||
{
|
||
env = ForcedEnvironment;
|
||
}
|
||
else
|
||
{
|
||
env = listener->Environment;
|
||
if (env == NULL)
|
||
{
|
||
env = DefaultEnvironments[0];
|
||
}
|
||
}
|
||
if (env != PrevEnvironment || env->Modified)
|
||
{
|
||
DPrintf ("Reverb Environment %s\n", env->Name);
|
||
const_cast<ReverbContainer*>(env)->Modified = false;
|
||
Sys->setReverbProperties((FMOD_REVERB_PROPERTIES *)(&env->Properties));
|
||
PrevEnvironment = env;
|
||
|
||
if (!SfxReverbHooked)
|
||
{
|
||
SfxReverbHooked = ReconnectSFXReverbUnit();
|
||
}
|
||
}
|
||
|
||
if (underwater || env->SoftwareWater)
|
||
{
|
||
//PausableSfx->setPitch(0.64171f); // This appears to be what Duke 3D uses
|
||
PausableSfx->setPitch(0.7937005f); // Approx. 4 semitones lower; what Nash suggested
|
||
if (WaterLP != NULL)
|
||
{
|
||
if (LastWaterLP != snd_waterlp)
|
||
{
|
||
LastWaterLP = snd_waterlp;
|
||
WaterLP->setParameter(FMOD_DSP_LOWPASS_CUTOFF, snd_waterlp);
|
||
}
|
||
WaterLP->setActive(true);
|
||
if (WaterReverb != NULL && snd_waterreverb)
|
||
{
|
||
WaterReverb->setActive(true);
|
||
WaterReverb->setBypass(false);
|
||
SfxConnection->setMix(0);
|
||
}
|
||
else
|
||
{
|
||
// Let some of the original mix through so that high frequencies are
|
||
// not completely lost. The reverb unit has its own connection and
|
||
// preserves dry sounds itself if used.
|
||
SfxConnection->setMix(0.1f);
|
||
if (WaterReverb != NULL)
|
||
{
|
||
WaterReverb->setActive(true);
|
||
WaterReverb->setBypass(true);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
PausableSfx->setPitch(1);
|
||
if (WaterLP != NULL)
|
||
{
|
||
SfxConnection->setMix(1);
|
||
WaterLP->setActive(false);
|
||
if (WaterReverb != NULL)
|
||
{
|
||
WaterReverb->setActive(false);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: ReconnectSFXReverbUnit
|
||
//
|
||
// Locates the DSP unit responsible for software 3D reverb. There is only
|
||
// one, and it by default is connected directly to the ChannelGroup Target
|
||
// Unit. Older versions of FMOD created this at startup; newer versions
|
||
// delay creating it until the first call to setReverbProperties, at which
|
||
// point it persists until the system is closed.
|
||
//
|
||
// Upon locating the proper DSP unit, reconnects it to serve as an input to
|
||
// our water DSP chain after the Pausable SFX ChannelGroup.
|
||
//
|
||
//==========================================================================
|
||
|
||
bool FMODSoundRenderer::ReconnectSFXReverbUnit()
|
||
{
|
||
FMOD::DSP *unit;
|
||
FMOD_DSP_TYPE type;
|
||
int numinputs, i;
|
||
|
||
if (ChannelGroupTargetUnit == NULL || SfxReverbPlaceholder == NULL)
|
||
{
|
||
return false;
|
||
}
|
||
// Look for SFX Reverb unit
|
||
if (FMOD_OK != ChannelGroupTargetUnit->getNumInputs(&numinputs))
|
||
{
|
||
return false;
|
||
}
|
||
for (i = numinputs - 1; i >= 0; --i)
|
||
{
|
||
if (FMOD_OK == ChannelGroupTargetUnit->getInput(i, &unit, NULL) &&
|
||
FMOD_OK == unit->getType(&type))
|
||
{
|
||
if (type == FMOD_DSP_TYPE_SFXREVERB)
|
||
{
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
if (i < 0)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
// Found it! Now move it in the DSP graph to be done before the water
|
||
// effect.
|
||
if (FMOD_OK != ChannelGroupTargetUnit->disconnectFrom(unit))
|
||
{
|
||
return false;
|
||
}
|
||
if (FMOD_OK != SfxReverbPlaceholder->addInput(unit, NULL))
|
||
{
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: Sync
|
||
//
|
||
// Used by the save/load code to restart sounds at the same position they
|
||
// were in at the time of saving. Must not be nested.
|
||
//
|
||
//==========================================================================
|
||
|
||
void FMODSoundRenderer::Sync(bool sync)
|
||
{
|
||
DSPLocked = sync;
|
||
if (sync)
|
||
{
|
||
Sys->lockDSP();
|
||
Sys->getDSPClock(&DSPClock.Hi, &DSPClock.Lo);
|
||
}
|
||
else
|
||
{
|
||
Sys->unlockDSP();
|
||
}
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: UpdateSounds
|
||
//
|
||
//==========================================================================
|
||
|
||
void FMODSoundRenderer::UpdateSounds()
|
||
{
|
||
// Any sounds played between now and the next call to this function
|
||
// will start exactly one tic from now.
|
||
Sys->getDSPClock(&DSPClock.Hi, &DSPClock.Lo);
|
||
DSPClock.AsOne += OutputRate / TICRATE;
|
||
Sys->update();
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: LoadSoundRaw
|
||
//
|
||
//==========================================================================
|
||
|
||
SoundHandle FMODSoundRenderer::LoadSoundRaw(BYTE *sfxdata, int length, int frequency, int channels, int bits)
|
||
{
|
||
FMOD_CREATESOUNDEXINFO exinfo;
|
||
SoundHandle retval = { NULL };
|
||
|
||
if (length == 0) return retval;
|
||
|
||
InitCreateSoundExInfo(&exinfo);
|
||
exinfo.length = length;
|
||
exinfo.numchannels = channels;
|
||
exinfo.defaultfrequency = frequency;
|
||
switch (bits)
|
||
{
|
||
case 8:
|
||
// Need to convert sample data from unsigned to signed.
|
||
for (int i = 0; i < length; ++i)
|
||
{
|
||
sfxdata[i] = sfxdata[i] - 128;
|
||
}
|
||
|
||
case -8:
|
||
exinfo.format = FMOD_SOUND_FORMAT_PCM8;
|
||
break;
|
||
|
||
case 16:
|
||
exinfo.format = FMOD_SOUND_FORMAT_PCM16;
|
||
break;
|
||
|
||
case 32:
|
||
exinfo.format = FMOD_SOUND_FORMAT_PCM32;
|
||
break;
|
||
|
||
default:
|
||
return retval;
|
||
}
|
||
|
||
const FMOD_MODE samplemode = FMOD_3D | FMOD_OPENMEMORY | FMOD_SOFTWARE | FMOD_OPENRAW;
|
||
FMOD::Sound *sample;
|
||
FMOD_RESULT result;
|
||
|
||
result = Sys->createSound((char *)sfxdata, samplemode, &exinfo, &sample);
|
||
if (result != FMOD_OK)
|
||
{
|
||
DPrintf("Failed to allocate sample: Error %d\n", result);
|
||
return retval;
|
||
}
|
||
retval.data = sample;
|
||
return retval;
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: LoadSound
|
||
//
|
||
//==========================================================================
|
||
|
||
SoundHandle FMODSoundRenderer::LoadSound(BYTE *sfxdata, int length)
|
||
{
|
||
FMOD_CREATESOUNDEXINFO exinfo;
|
||
SoundHandle retval = { NULL };
|
||
|
||
if (length == 0) return retval;
|
||
|
||
InitCreateSoundExInfo(&exinfo);
|
||
exinfo.length = length;
|
||
|
||
const FMOD_MODE samplemode = FMOD_3D | FMOD_OPENMEMORY | FMOD_SOFTWARE;
|
||
FMOD::Sound *sample;
|
||
FMOD_RESULT result;
|
||
|
||
result = Sys->createSound((char *)sfxdata, samplemode, &exinfo, &sample);
|
||
if (result != FMOD_OK)
|
||
{
|
||
DPrintf("Failed to allocate sample: Error %d\n", result);
|
||
return retval;
|
||
}
|
||
retval.data = sample;
|
||
return retval;
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: UnloadSound
|
||
//
|
||
//==========================================================================
|
||
|
||
void FMODSoundRenderer::UnloadSound(SoundHandle sfx)
|
||
{
|
||
if (sfx.data != NULL)
|
||
{
|
||
((FMOD::Sound *)sfx.data)->release();
|
||
}
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: GetMSLength
|
||
//
|
||
//==========================================================================
|
||
|
||
unsigned int FMODSoundRenderer::GetMSLength(SoundHandle sfx)
|
||
{
|
||
if (sfx.data != NULL)
|
||
{
|
||
unsigned int length;
|
||
|
||
if (((FMOD::Sound *)sfx.data)->getLength(&length, FMOD_TIMEUNIT_MS) == FMOD_OK)
|
||
{
|
||
return length;
|
||
}
|
||
}
|
||
return 0; // Don't know.
|
||
}
|
||
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: GetMSLength
|
||
//
|
||
//==========================================================================
|
||
|
||
unsigned int FMODSoundRenderer::GetSampleLength(SoundHandle sfx)
|
||
{
|
||
if (sfx.data != NULL)
|
||
{
|
||
unsigned int length;
|
||
|
||
if (((FMOD::Sound *)sfx.data)->getLength(&length, FMOD_TIMEUNIT_PCM) == FMOD_OK)
|
||
{
|
||
return length;
|
||
}
|
||
}
|
||
return 0; // Don't know.
|
||
}
|
||
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: ChannelCallback static
|
||
//
|
||
// Handles when a channel finishes playing. This is only called when
|
||
// System::update is called and is therefore asynchronous with the actual
|
||
// end of the channel.
|
||
//
|
||
//==========================================================================
|
||
|
||
FMOD_RESULT F_CALLBACK FMODSoundRenderer::ChannelCallback
|
||
(FMOD_CHANNEL *channel, FMOD_CHANNEL_CALLBACKTYPE type, void *data1, void *data2)
|
||
{
|
||
if (type != FMOD_CHANNEL_CALLBACKTYPE_END)
|
||
{
|
||
return FMOD_OK;
|
||
}
|
||
FMOD::Channel *chan = (FMOD::Channel *)channel;
|
||
FISoundChannel *schan;
|
||
|
||
if (chan->getUserData((void **)&schan) == FMOD_OK && schan != NULL)
|
||
{
|
||
S_ChannelEnded(schan);
|
||
}
|
||
return FMOD_OK;
|
||
}
|
||
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: RolloffCallback static
|
||
//
|
||
// Calculates a volume for the sound based on distance.
|
||
//
|
||
//==========================================================================
|
||
|
||
float F_CALLBACK FMODSoundRenderer::RolloffCallback(FMOD_CHANNEL *channel, float distance)
|
||
{
|
||
FMOD::Channel *chan = (FMOD::Channel *)channel;
|
||
FISoundChannel *schan;
|
||
|
||
if (GRolloff != NULL)
|
||
{
|
||
return S_GetRolloff(GRolloff, distance * GDistScale, true);
|
||
}
|
||
else if (chan->getUserData((void **)&schan) == FMOD_OK && schan != NULL)
|
||
{
|
||
return S_GetRolloff(&schan->Rolloff, distance * schan->DistanceScale, true);
|
||
}
|
||
else
|
||
{
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: DrawWaveDebug
|
||
//
|
||
// Bit 0: ( 1) Show oscilloscope for sfx.
|
||
// Bit 1: ( 2) Show spectrum for sfx.
|
||
// Bit 2: ( 4) Show oscilloscope for music.
|
||
// Bit 3: ( 8) Show spectrum for music.
|
||
// Bit 4: (16) Show oscilloscope for all sounds.
|
||
// Bit 5: (32) Show spectrum for all sounds.
|
||
//
|
||
//==========================================================================
|
||
|
||
void FMODSoundRenderer::DrawWaveDebug(int mode)
|
||
{
|
||
const int window_height = 100;
|
||
int window_size;
|
||
int numoutchans;
|
||
int y;
|
||
|
||
if (FMOD_OK != Sys->getSoftwareFormat(NULL, NULL, &numoutchans, NULL, NULL, NULL))
|
||
{
|
||
return;
|
||
}
|
||
|
||
// Scale all the channel windows so one group fits completely on one row, with
|
||
// 16 pixels of padding between each window.
|
||
window_size = (screen->GetWidth() - 16) / numoutchans - 16;
|
||
|
||
float *wavearray = (float*)alloca(window_size*sizeof(float));
|
||
|
||
y = 16;
|
||
y = DrawChannelGroupOutput(SfxGroup, wavearray, window_size, window_height, y, mode);
|
||
y = DrawChannelGroupOutput(MusicGroup, wavearray, window_size, window_height, y, mode >> 2);
|
||
y = DrawSystemOutput(wavearray, window_size, window_height, y, mode >> 4);
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: DrawChannelGroupOutput
|
||
//
|
||
// Draws an oscilloscope and/or a spectrum for a channel group.
|
||
//
|
||
//==========================================================================
|
||
|
||
int FMODSoundRenderer::DrawChannelGroupOutput(FMOD::ChannelGroup *group, float *wavearray, int width, int height, int y, int mode)
|
||
{
|
||
int y1, y2;
|
||
|
||
switch (mode & 0x03)
|
||
{
|
||
case 0x01: // Oscilloscope only
|
||
return DrawChannelGroupWaveData(group, wavearray, width, height, y, false);
|
||
|
||
case 0x02: // Spectrum only
|
||
return DrawChannelGroupSpectrum(group, wavearray, width, height, y, false);
|
||
|
||
case 0x03: // Oscilloscope + Spectrum
|
||
width = (width + 16) / 2 - 16;
|
||
y1 = DrawChannelGroupSpectrum(group, wavearray, width, height, y, true);
|
||
y2 = DrawChannelGroupWaveData(group, wavearray, width, height, y, true);
|
||
return MAX(y1, y2);
|
||
}
|
||
return y;
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: DrawSystemOutput
|
||
//
|
||
// Like DrawChannelGroupOutput(), but uses the system object.
|
||
//
|
||
//==========================================================================
|
||
|
||
int FMODSoundRenderer::DrawSystemOutput(float *wavearray, int width, int height, int y, int mode)
|
||
{
|
||
int y1, y2;
|
||
|
||
switch (mode & 0x03)
|
||
{
|
||
case 0x01: // Oscilloscope only
|
||
return DrawSystemWaveData(wavearray, width, height, y, false);
|
||
|
||
case 0x02: // Spectrum only
|
||
return DrawSystemSpectrum(wavearray, width, height, y, false);
|
||
|
||
case 0x03: // Oscilloscope + Spectrum
|
||
width = (width + 16) / 2 - 16;
|
||
y1 = DrawSystemSpectrum(wavearray, width, height, y, true);
|
||
y2 = DrawSystemWaveData(wavearray, width, height, y, true);
|
||
return MAX(y1, y2);
|
||
}
|
||
return y;
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: DrawChannelGroupWaveData
|
||
//
|
||
// Draws all the output channels for a specified channel group.
|
||
// Setting skip to true causes it to skip every other window.
|
||
//
|
||
//==========================================================================
|
||
|
||
int FMODSoundRenderer::DrawChannelGroupWaveData(FMOD::ChannelGroup *group, float *wavearray, int width, int height, int y, bool skip)
|
||
{
|
||
int drawn = 0;
|
||
int x = 16;
|
||
|
||
while (FMOD_OK == group->getWaveData(wavearray, width, drawn))
|
||
{
|
||
drawn++;
|
||
DrawWave(wavearray, x, y, width, height);
|
||
x += (width + 16) << int(skip);
|
||
}
|
||
if (drawn)
|
||
{
|
||
y += height + 16;
|
||
}
|
||
return y;
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer::DrawSystemWaveData
|
||
//
|
||
// Like DrawChannelGroupWaveData, but it uses the system object to get the
|
||
// complete output.
|
||
//
|
||
//==========================================================================
|
||
|
||
int FMODSoundRenderer::DrawSystemWaveData(float *wavearray, int width, int height, int y, bool skip)
|
||
{
|
||
int drawn = 0;
|
||
int x = 16;
|
||
|
||
while (FMOD_OK == Sys->getWaveData(wavearray, width, drawn))
|
||
{
|
||
drawn++;
|
||
DrawWave(wavearray, x, y, width, height);
|
||
x += (width + 16) << int(skip);
|
||
}
|
||
if (drawn)
|
||
{
|
||
y += height + 16;
|
||
}
|
||
return y;
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: DrawWave
|
||
//
|
||
// Draws an oscilloscope at the specified coordinates on the screen. Each
|
||
// entry in the wavearray buffer has its own column. IOW, there are <width>
|
||
// entries in wavearray.
|
||
//
|
||
//==========================================================================
|
||
|
||
void FMODSoundRenderer::DrawWave(float *wavearray, int x, int y, int width, int height)
|
||
{
|
||
float scale = height / 2.f;
|
||
float mid = y + scale;
|
||
int i;
|
||
|
||
// Draw a box around the oscilloscope.
|
||
screen->DrawLine(x - 1, y - 1, x + width, y - 1, -1, MAKEARGB(160, 0, 40, 200));
|
||
screen->DrawLine(x + width + 1, y - 1, x + width, y + height, -1, MAKEARGB(160, 0, 40, 200));
|
||
screen->DrawLine(x + width, y + height, x - 1, y + height, -1, MAKEARGB(160, 0, 40, 200));
|
||
screen->DrawLine(x - 1, y + height, x - 1, y - 1, -1, MAKEARGB(160, 0, 40, 200));
|
||
|
||
// Draw the actual oscilloscope.
|
||
if (screen->Accel2D)
|
||
{ // Drawing this with lines is super-slow without hardware acceleration, at least with
|
||
// the debug build.
|
||
float lasty = mid - wavearray[0] * scale;
|
||
float newy;
|
||
for (i = 1; i < width; ++i)
|
||
{
|
||
newy = mid - wavearray[i] * scale;
|
||
screen->DrawLine(x + i - 1, int(lasty), x + i, int(newy), -1, MAKEARGB(255,255,248,248));
|
||
lasty = newy;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
for (i = 0; i < width; ++i)
|
||
{
|
||
float y = wavearray[i] * scale + mid;
|
||
screen->DrawPixel(x + i, int(y), -1, MAKEARGB(255,255,255,255));
|
||
}
|
||
}
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: DrawChannelGroupSpectrum
|
||
//
|
||
// Draws all the spectrum for a specified channel group.
|
||
// Setting skip to true causes it to skip every other window, starting at
|
||
// the second one.
|
||
//
|
||
//==========================================================================
|
||
|
||
int FMODSoundRenderer::DrawChannelGroupSpectrum(FMOD::ChannelGroup *group, float *spectrumarray, int width, int height, int y, bool skip)
|
||
{
|
||
int drawn = 0;
|
||
int x = 16;
|
||
|
||
if (skip)
|
||
{
|
||
x += width + 16;
|
||
}
|
||
while (FMOD_OK == group->getSpectrum(spectrumarray, SPECTRUM_SIZE, drawn, FMOD_DSP_FFT_WINDOW_TRIANGLE))
|
||
{
|
||
drawn++;
|
||
DrawSpectrum(spectrumarray, x, y, width, height);
|
||
x += (width + 16) << int(skip);
|
||
}
|
||
if (drawn)
|
||
{
|
||
y += height + 16;
|
||
}
|
||
return y;
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer::DrawSystemSpectrum
|
||
//
|
||
// Like DrawChannelGroupSpectrum, but it uses the system object to get the
|
||
// complete output.
|
||
//
|
||
//==========================================================================
|
||
|
||
int FMODSoundRenderer::DrawSystemSpectrum(float *spectrumarray, int width, int height, int y, bool skip)
|
||
{
|
||
int drawn = 0;
|
||
int x = 16;
|
||
|
||
if (skip)
|
||
{
|
||
x += width + 16;
|
||
}
|
||
while (FMOD_OK == Sys->getSpectrum(spectrumarray, SPECTRUM_SIZE, drawn, FMOD_DSP_FFT_WINDOW_TRIANGLE))
|
||
{
|
||
drawn++;
|
||
DrawSpectrum(spectrumarray, x, y, width, height);
|
||
x += (width + 16) << int(skip);
|
||
}
|
||
if (drawn)
|
||
{
|
||
y += height + 16;
|
||
}
|
||
return y;
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: DrawSpectrum
|
||
//
|
||
// Draws a spectrum at the specified coordinates on the screen.
|
||
//
|
||
//==========================================================================
|
||
|
||
void FMODSoundRenderer::DrawSpectrum(float *spectrumarray, int x, int y, int width, int height)
|
||
{
|
||
float scale = height / 2.f;
|
||
float mid = y + scale;
|
||
float db;
|
||
int top;
|
||
|
||
// Draw a border and dark background for the spectrum.
|
||
screen->DrawLine(x - 1, y - 1, x + width, y - 1, -1, MAKEARGB(160, 0, 40, 200));
|
||
screen->DrawLine(x + width + 1, y - 1, x + width, y + height, -1, MAKEARGB(160, 0, 40, 200));
|
||
screen->DrawLine(x + width, y + height, x - 1, y + height, -1, MAKEARGB(160, 0, 40, 200));
|
||
screen->DrawLine(x - 1, y + height, x - 1, y - 1, -1, MAKEARGB(160, 0, 40, 200));
|
||
screen->Dim(MAKERGB(0,0,0), 0.3f, x, y, width, height);
|
||
|
||
// Draw the actual spectrum.
|
||
for (int i = 0; i < width; ++i)
|
||
{
|
||
db = spectrumarray[i * (SPECTRUM_SIZE - 2) / width + 1];
|
||
db = MAX(-150.f, 10 * log10f(db) * 2); // Convert to decibels and clamp
|
||
db = 1.f - (db / -150.f);
|
||
db *= height;
|
||
top = (int)db;
|
||
if (top >= height)
|
||
{
|
||
top = height - 1;
|
||
}
|
||
// screen->Clear(x + i, int(y + height - db), x + i + 1, y + height, -1, MAKEARGB(255, 255, 255, 40));
|
||
screen->Dim(MAKERGB(255,255,40), 0.65f, x + i, y + height - top, 1, top);
|
||
}
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: DecodeSample
|
||
//
|
||
// Uses FMOD to decode a compressed sample to a 16-bit buffer. This is used
|
||
// by the DUMB XM reader to handle FMOD's OggMods.
|
||
//
|
||
//==========================================================================
|
||
|
||
short *FMODSoundRenderer::DecodeSample(int outlen, const void *coded, int sizebytes, ECodecType type)
|
||
{
|
||
FMOD_CREATESOUNDEXINFO exinfo;
|
||
FMOD::Sound *sound;
|
||
FMOD_SOUND_FORMAT format;
|
||
int channels;
|
||
unsigned int len, amt_read;
|
||
FMOD_RESULT result;
|
||
short *outbuf;
|
||
|
||
InitCreateSoundExInfo(&exinfo);
|
||
if (type == CODEC_Vorbis)
|
||
{
|
||
exinfo.suggestedsoundtype = FMOD_SOUND_TYPE_OGGVORBIS;
|
||
}
|
||
exinfo.length = sizebytes;
|
||
result = Sys->createSound((const char *)coded,
|
||
FMOD_2D | FMOD_SOFTWARE | FMOD_CREATESTREAM |
|
||
FMOD_OPENMEMORY_POINT | FMOD_OPENONLY | FMOD_LOWMEM,
|
||
&exinfo, &sound);
|
||
if (result != FMOD_OK)
|
||
{
|
||
return NULL;
|
||
}
|
||
result = sound->getFormat(NULL, &format, &channels, NULL);
|
||
// TODO: Handle more formats if it proves necessary.
|
||
if (result != FMOD_OK || format != FMOD_SOUND_FORMAT_PCM16 || channels != 1)
|
||
{
|
||
sound->release();
|
||
return NULL;
|
||
}
|
||
len = outlen;
|
||
// Must be malloc'ed for DUMB, which is C.
|
||
outbuf = (short *)malloc(len);
|
||
result = sound->readData(outbuf, len, &amt_read);
|
||
sound->release();
|
||
if (result == FMOD_ERR_FILE_EOF)
|
||
{
|
||
memset((BYTE *)outbuf + amt_read, 0, len - amt_read);
|
||
}
|
||
else if (result != FMOD_OK || amt_read != len)
|
||
{
|
||
free(outbuf);
|
||
return NULL;
|
||
}
|
||
return outbuf;
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: InitCreateSoundExInfo
|
||
//
|
||
// Allow for compiling with 4.26 APIs while still running with older DLLs.
|
||
//
|
||
//==========================================================================
|
||
|
||
void FMODSoundRenderer::InitCreateSoundExInfo(FMOD_CREATESOUNDEXINFO *exinfo) const
|
||
{
|
||
#if FMOD_VERSION >= 0x42600
|
||
if (ActiveFMODVersion < 0x42600)
|
||
{
|
||
// This parameter was added for 4.26.00, and trying to pass it to older
|
||
// DLLs will fail.
|
||
exinfo->cbsize = myoffsetof(FMOD_CREATESOUNDEXINFO, ignoresetfilesystem);
|
||
}
|
||
else
|
||
#endif
|
||
{
|
||
exinfo->cbsize = sizeof(*exinfo);
|
||
}
|
||
memset((BYTE *)exinfo + sizeof(exinfo->cbsize), 0, exinfo->cbsize - sizeof(exinfo->cbsize));
|
||
}
|