mirror of
https://github.com/ZDoom/qzdoom-gpl.git
synced 2024-12-12 13:12:14 +00:00
3b35ad2db2
to the rolloff information back to NULL when starting the sound fails. - Fixed: Rolloff information was taken from the sfxinfo that contained the actual sound data, not the one that was used for starting the sound. SVN r1206 (trunk)
2523 lines
71 KiB
C++
2523 lines
71 KiB
C++
/*
|
||
** i_sound.cpp
|
||
** System interface for sound; uses fmod.dll
|
||
**
|
||
**---------------------------------------------------------------------------
|
||
** Copyright 1998-2008 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
|
||
|
||
#include "templates.h"
|
||
#include "fmodsound.h"
|
||
#include "c_cvars.h"
|
||
#include "i_system.h"
|
||
#include "w_wad.h"
|
||
#include "i_music.h"
|
||
#include "v_text.h"
|
||
#include "v_palette.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 --------------------------------------------
|
||
|
||
FMOD_RESULT SPC_CreateCodec(FMOD::System *sys);
|
||
|
||
// 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 },
|
||
|
||
// 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;
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// 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;
|
||
|
||
Printf("I_InitSound: Initializing FMOD\n");
|
||
|
||
// Create a System object and initialize.
|
||
result = FMOD::System_Create(&Sys);
|
||
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 < FMOD_VERSION)
|
||
{
|
||
wrongver = "an old";
|
||
}
|
||
else if ((version & 0xFFFF00) > (FMOD_VERSION & 0xFFFF00))
|
||
{
|
||
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 requires 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;
|
||
}
|
||
|
||
if (!ShowedBanner)
|
||
{
|
||
Printf("FMOD Sound System, copyright <20> Firelight Technologies Pty, Ltd., 1994-2008.\n");
|
||
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
|
||
|
||
// Set the user specified output mode.
|
||
eval = Enum_NumForName(OutputNames, snd_output);
|
||
if (eval >= 0)
|
||
{
|
||
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);
|
||
Sys->setOutput(FMOD_OUTPUTTYPE_AUTODETECT);
|
||
}
|
||
}
|
||
|
||
result = Sys->getNumDrivers(&driver);
|
||
if (result == FMOD_OK)
|
||
{
|
||
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(snd_channels + NUM_EXTRA_SOFTWARE_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);
|
||
result = sfx_head->getInput(0, &pausable_head, &SfxConnection);
|
||
|
||
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);
|
||
result = dry->setMix(0.1f);
|
||
result = WaterReverb->addInput(WaterLP, NULL);
|
||
result = sfx_head->addInput(WaterReverb, NULL);
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
|
||
result = SPC_CreateCodec(Sys);
|
||
if (result != FMOD_OK)
|
||
{
|
||
Printf(TEXTCOLOR_BLUE" Could not register SPC codec. (Error %d)\n", result);
|
||
}
|
||
|
||
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)
|
||
{
|
||
unsigned int i;
|
||
|
||
//S_StopAllChannels();
|
||
|
||
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;
|
||
}
|
||
|
||
// Free all loaded samples
|
||
for (i = 0; i < S_sfx.Size(); i++)
|
||
{
|
||
if (S_sfx[i].data != NULL)
|
||
{
|
||
((FMOD::Sound *)S_sfx[i].data)->release();
|
||
S_sfx[i].data = NULL;
|
||
}
|
||
}
|
||
|
||
Sys->close();
|
||
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;
|
||
|
||
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, total;
|
||
FString out;
|
||
|
||
channels = 0;
|
||
total = update = stream = dsp = 0;
|
||
Sys->getChannelsPlaying(&channels);
|
||
Sys->getCPUUsage(&dsp, &stream, &update, &total);
|
||
|
||
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"%% "
|
||
"Update:"TEXTCOLOR_YELLOW"%2.2f"TEXTCOLOR_NORMAL"%%)",
|
||
channels, total, dsp, stream, 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 = { sizeof(exinfo), };
|
||
FMOD_MODE mode;
|
||
int sample_shift;
|
||
int channel_shift;
|
||
|
||
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 = { sizeof(exinfo), };
|
||
FMOD::Sound *stream;
|
||
FMOD_RESULT result;
|
||
bool url;
|
||
|
||
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
|
||
//
|
||
//==========================================================================
|
||
|
||
FSoundChan *FMODSoundRenderer::StartSound(sfxinfo_t *sfx, float vol, int pitch, int chanflags, FSoundChan *reuse_chan)
|
||
{
|
||
int id = int(sfx - &S_sfx[0]);
|
||
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 (chanflags & CHAN_LOOP)
|
||
{
|
||
mode = (mode & ~FMOD_LOOP_OFF) | FMOD_LOOP_NORMAL;
|
||
}
|
||
chan->setMode(mode);
|
||
chan->setChannelGroup((chanflags & (CHAN_UI | CHAN_NOPAUSE)) ? SfxGroup : PausableSfx);
|
||
if (freq != 0)
|
||
{
|
||
chan->setFrequency(freq);
|
||
}
|
||
chan->setVolume(vol);
|
||
HandleChannelDelay(chan, reuse_chan, freq);
|
||
chan->setPaused(false);
|
||
return CommonChannelSetup(chan, reuse_chan);
|
||
}
|
||
|
||
DPrintf ("Sound %s failed to play: %d\n", sfx->name.GetChars(), result);
|
||
return 0;
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: StartSound3D
|
||
//
|
||
//==========================================================================
|
||
|
||
CVAR(Float, snd_3dspread, 180, 0)
|
||
|
||
FSoundChan *FMODSoundRenderer::StartSound3D(sfxinfo_t *sfx, SoundListener *listener, float vol,
|
||
FRolloffInfo *rolloff, float distscale,
|
||
int pitch, int priority, const FVector3 &pos, const FVector3 &vel,
|
||
int channum, int chanflags, FSoundChan *reuse_chan)
|
||
{
|
||
int id = int(sfx - &S_sfx[0]);
|
||
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 paremeters 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 (chanflags & CHAN_LOOP)
|
||
{
|
||
mode = (mode & ~FMOD_LOOP_OFF) | FMOD_LOOP_NORMAL;
|
||
}
|
||
mode = SetChanHeadSettings(listener, chan, sfx, pos, channum, chanflags, mode);
|
||
chan->setMode(mode);
|
||
chan->setChannelGroup((chanflags & (CHAN_UI | CHAN_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);
|
||
}
|
||
HandleChannelDelay(chan, reuse_chan, freq);
|
||
chan->setPaused(false);
|
||
FSoundChan *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.
|
||
// Otherwise, record its starting time.
|
||
//
|
||
//==========================================================================
|
||
|
||
void FMODSoundRenderer::HandleChannelDelay(FMOD::Channel *chan, FSoundChan *reuse_chan, 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 CHAN_ABSTIME is set, the sound is being restored, and
|
||
// the channel's start time is actually its seek position.
|
||
if (reuse_chan->ChanFlags & CHAN_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);
|
||
reuse_chan->ChanFlags &= ~CHAN_ABSTIME;
|
||
}
|
||
else
|
||
{
|
||
QWORD difftime = nowtime.AsOne - reuse_chan->StartTime.AsOne;
|
||
if (difftime > 0)
|
||
{
|
||
chan->setPosition((unsigned int)(difftime / OutputRate), FMOD_TIMEUNIT_MS);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
chan->setDelay(FMOD_DELAYTYPE_DSPCLOCK_START, DSPClock.Hi, DSPClock.Lo);
|
||
}
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// 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, sfxinfo_t *sfx,
|
||
const FVector3 &pos, int channum, int chanflags,
|
||
FMOD_MODE oldmode) const
|
||
{
|
||
if (!listener->valid)
|
||
{
|
||
return oldmode;
|
||
}
|
||
FVector3 cpos, mpos;
|
||
|
||
cpos = listener->position;
|
||
|
||
if (chanflags & CHAN_AREA)
|
||
{
|
||
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.
|
||
//
|
||
//==========================================================================
|
||
|
||
FSoundChan *FMODSoundRenderer::CommonChannelSetup(FMOD::Channel *chan, FSoundChan *reuse_chan) const
|
||
{
|
||
FSoundChan *schan;
|
||
|
||
if (reuse_chan != NULL)
|
||
{
|
||
schan = reuse_chan;
|
||
schan->ChanFlags &= ~CHAN_EVICTED;
|
||
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(FMOD_CHANNEL_CALLBACKTYPE_END, ChannelEndCallback, 0);
|
||
GRolloff = NULL;
|
||
return schan;
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: StopSound
|
||
//
|
||
//==========================================================================
|
||
|
||
void FMODSoundRenderer::StopSound(FSoundChan *chan)
|
||
{
|
||
if (chan == NULL)
|
||
return;
|
||
|
||
if (chan->SysChannel != NULL)
|
||
{
|
||
// S_EvictAllChannels() will set the CHAN_EVICTED flag to indicate
|
||
// that it wants to keep all the channel information around.
|
||
if (!(chan->ChanFlags & CHAN_EVICTED))
|
||
{
|
||
chan->ChanFlags |= CHAN_FORGETTABLE;
|
||
}
|
||
((FMOD::Channel *)chan->SysChannel)->stop();
|
||
}
|
||
else
|
||
{
|
||
S_ReturnChannel(chan);
|
||
}
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: GetPosition
|
||
//
|
||
// Returns position of sound on this channel, in samples.
|
||
//
|
||
//==========================================================================
|
||
|
||
unsigned int FMODSoundRenderer::GetPosition(FSoundChan *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, FSoundChan *chan, 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, chan->SfxInfo, pos, chan->EntChannel, chan->ChanFlags, 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;
|
||
|
||
if (ForcedEnvironment)
|
||
{
|
||
env = ForcedEnvironment;
|
||
}
|
||
else
|
||
{
|
||
underwater = (listener->underwater && snd_waterlp);
|
||
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 (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 :: 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 :: LoadSound
|
||
//
|
||
//==========================================================================
|
||
|
||
bool FMODSoundRenderer::LoadSound(sfxinfo_t *sfx)
|
||
{
|
||
if (sfx->data == NULL)
|
||
{
|
||
void **slot = &sfx->data;
|
||
BYTE *sfxdata;
|
||
BYTE *sfxstart;
|
||
int size;
|
||
FMOD_RESULT result;
|
||
FMOD_MODE samplemode;
|
||
FMOD_CREATESOUNDEXINFO exinfo = { sizeof(exinfo), };
|
||
FMOD::Sound *sample;
|
||
|
||
samplemode = FMOD_3D | FMOD_OPENMEMORY | FMOD_SOFTWARE;
|
||
|
||
sfxdata = NULL;
|
||
sample = NULL;
|
||
|
||
size = Wads.LumpLength(sfx->lumpnum);
|
||
if (size <= 0) return false;
|
||
|
||
FWadLump wlump = Wads.OpenLumpNum(sfx->lumpnum);
|
||
sfxstart = sfxdata = new BYTE[size];
|
||
wlump.Read(sfxdata, size);
|
||
SDWORD len = ((SDWORD *)sfxdata)[1];
|
||
|
||
// If the sound is raw, just load it as such.
|
||
// Otherwise, try the sound as DMX format.
|
||
// If that fails, let FMOD try and figure it out.
|
||
if (sfx->bLoadRAW ||
|
||
(((BYTE *)sfxdata)[0] == 3 && ((BYTE *)sfxdata)[1] == 0 && len <= size - 8))
|
||
{
|
||
int frequency;
|
||
|
||
if (sfx->bLoadRAW)
|
||
{
|
||
len = Wads.LumpLength (sfx->lumpnum);
|
||
frequency = (sfx->bForce22050 ? 22050 : 11025);
|
||
}
|
||
else
|
||
{
|
||
frequency = ((WORD *)sfxdata)[1];
|
||
if (frequency == 0)
|
||
{
|
||
frequency = 11025;
|
||
}
|
||
sfxstart = sfxdata + 8;
|
||
}
|
||
|
||
exinfo.length = len;
|
||
exinfo.numchannels = 1;
|
||
exinfo.defaultfrequency = frequency;
|
||
exinfo.format = FMOD_SOUND_FORMAT_PCM8;
|
||
|
||
samplemode |= FMOD_OPENRAW;
|
||
|
||
// Need to convert sample data from unsigned to signed.
|
||
for (int i = 0; i < len; ++i)
|
||
{
|
||
sfxstart[i] = sfxstart[i] - 128;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
exinfo.length = size;
|
||
}
|
||
|
||
if (exinfo.length == 0)
|
||
{
|
||
DPrintf("Sample has a length of 0\n");
|
||
}
|
||
else
|
||
{
|
||
result = Sys->createSound((char *)sfxstart, samplemode, &exinfo, &sample);
|
||
if (result != FMOD_OK)
|
||
{
|
||
DPrintf("Failed to allocate sample: Error %d\n", result);
|
||
|
||
if (sfxdata != NULL)
|
||
{
|
||
delete[] sfxdata;
|
||
}
|
||
return false;
|
||
}
|
||
*slot = sample;
|
||
}
|
||
|
||
if (sfxdata != NULL)
|
||
{
|
||
delete[] sfxdata;
|
||
}
|
||
|
||
if (sample != NULL)
|
||
{
|
||
sample->setUserData(sfx);
|
||
}
|
||
}
|
||
return sfx->data != NULL;
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: UnloadSound
|
||
//
|
||
//==========================================================================
|
||
|
||
void FMODSoundRenderer::UnloadSound(sfxinfo_t *sfx)
|
||
{
|
||
if (sfx->data != NULL)
|
||
{
|
||
((FMOD::Sound *)sfx->data)->release();
|
||
sfx->data = NULL;
|
||
}
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// FMODSoundRenderer :: GetMSLength
|
||
//
|
||
//==========================================================================
|
||
|
||
unsigned int FMODSoundRenderer::GetMSLength(sfxinfo_t *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 :: ChannelEndCallback static
|
||
//
|
||
// Called when the channel finishes playing.
|
||
//
|
||
//==========================================================================
|
||
|
||
FMOD_RESULT F_CALLBACK FMODSoundRenderer::ChannelEndCallback
|
||
(FMOD_CHANNEL *channel, FMOD_CHANNEL_CALLBACKTYPE type,
|
||
int cmd, unsigned int data1, unsigned int data2)
|
||
{
|
||
assert(type == FMOD_CHANNEL_CALLBACKTYPE_END);
|
||
FMOD::Channel *chan = (FMOD::Channel *)channel;
|
||
FSoundChan *schan;
|
||
|
||
if (chan->getUserData((void **)&schan) == FMOD_OK && schan != NULL)
|
||
{
|
||
bool evicted;
|
||
|
||
// If the sound was stopped with GSnd->StopSound(), then we know
|
||
// it wasn't evicted. Otherwise, if it's looping, it must have
|
||
// been evicted. If it's not looping, then it was evicted if it
|
||
// didn't reach the end of its playback.
|
||
if (schan->ChanFlags & CHAN_FORGETTABLE)
|
||
{
|
||
evicted = false;
|
||
}
|
||
else if (schan->ChanFlags & (CHAN_LOOP | CHAN_EVICTED))
|
||
{
|
||
evicted = true;
|
||
}
|
||
else
|
||
{
|
||
FMOD::Sound *sound;
|
||
unsigned int len, pos;
|
||
|
||
evicted = false; // Assume not evicted
|
||
if (FMOD_OK == chan->getPosition(&pos, FMOD_TIMEUNIT_PCM))
|
||
{
|
||
// If position is 0, then this sound either didn't have
|
||
// a chance to play at all, or it stopped normally.
|
||
if (pos == 0)
|
||
{
|
||
if (schan->ChanFlags & CHAN_JUSTSTARTED)
|
||
{
|
||
evicted = true;
|
||
}
|
||
}
|
||
else if (FMOD_OK == chan->getCurrentSound(&sound))
|
||
{
|
||
if (FMOD_OK == sound->getLength(&len, FMOD_TIMEUNIT_PCM))
|
||
{
|
||
if (pos < len)
|
||
{
|
||
evicted = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (!evicted)
|
||
{
|
||
S_ReturnChannel(schan);
|
||
}
|
||
else
|
||
{
|
||
schan->ChanFlags |= CHAN_EVICTED;
|
||
schan->SysChannel = NULL;
|
||
}
|
||
}
|
||
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;
|
||
FSoundChan *schan;
|
||
FRolloffInfo *rolloff;
|
||
|
||
if (GRolloff != NULL)
|
||
{
|
||
rolloff = GRolloff;
|
||
distance *= GDistScale;
|
||
}
|
||
else if (chan->getUserData((void **)&schan) == FMOD_OK && schan != NULL)
|
||
{
|
||
rolloff = schan->Rolloff;
|
||
distance *= schan->DistanceScale;
|
||
}
|
||
else
|
||
{
|
||
return 0;
|
||
}
|
||
if (rolloff == NULL)
|
||
{
|
||
return 0;
|
||
}
|
||
|
||
if (distance <= rolloff->MinDistance)
|
||
{
|
||
return 1;
|
||
}
|
||
if (rolloff->RolloffType == ROLLOFF_Log)
|
||
{ // Logarithmic rolloff has no max distance where it goes silent.
|
||
return rolloff->MinDistance / (rolloff->MinDistance + rolloff->RolloffFactor * (distance - rolloff->MinDistance));
|
||
}
|
||
if (distance >= rolloff->MaxDistance)
|
||
{
|
||
return 0;
|
||
}
|
||
|
||
float volume = (rolloff->MaxDistance - distance) / (rolloff->MaxDistance - rolloff->MinDistance);
|
||
if (rolloff->RolloffType == ROLLOFF_Custom && S_SoundCurve != NULL)
|
||
{
|
||
volume = S_SoundCurve[int(S_SoundCurveSize * (1 - volume))] / 127.f;
|
||
}
|
||
if (rolloff->RolloffType == ROLLOFF_Linear)
|
||
{
|
||
return volume;
|
||
}
|
||
else
|
||
{
|
||
return (powf(10.f, volume) - 1.f) / 9.f;
|
||
}
|
||
}
|
||
|
||
//==========================================================================
|
||
//
|
||
// 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;
|
||
float wavearray[MAXWIDTH];
|
||
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;
|
||
|
||
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 = { sizeof(exinfo), };
|
||
FMOD::Sound *sound;
|
||
FMOD_SOUND_FORMAT format;
|
||
int channels;
|
||
unsigned int len, amt_read;
|
||
FMOD_RESULT result;
|
||
short *outbuf;
|
||
|
||
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.
|
||
// Does OggMod work with stereo samples?
|
||
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;
|
||
}
|