bool FMODSoundRenderer::Init()

in doom_py/src/vizdoom/src/sound/fmodsound.cpp [641:1176]


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;
	ChannelGroupTargetUnitOutput = NULL;
	SfxReverbHooked = false;
	SfxReverbPlaceholder = NULL;
	OutputPlugin = 0;

	Printf("I_InitSound: Initializing FMOD\n");

	// This is just for safety. Normally this should never be called if FMod Ex cannot be found.
	if (!IsFModExPresent())
	{
		Sys = NULL;
		Printf(TEXTCOLOR_ORANGE"Failed to load fmodex"
#ifdef _WIN64
			"64"
#endif
			".dll\n");
		return false;
	}

	// 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 FMOD_VERSION >= 0x43600
	if (version < 0x43600)
#else
	if (version < 0x42000)
#endif
	{
		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)
	{
		// '\xa9' is the copyright symbol in the Windows-1252 code page.
		Printf("FMOD Sound System, copyright \xa9 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

#if !defined _WIN32 && !defined __APPLE__
	// 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);
#if FMOD_VERSION >= 0x43600
	// We were built with an FMOD that only returns the control panel frequency
	result = Sys->getDriverCaps(driver, &Driver_Caps, &Driver_MinFrequency, &speakermode);
	Driver_MaxFrequency = Driver_MinFrequency;
#else
	// We were built with an FMOD that returns a frequency range
	result = Sys->getDriverCaps(driver, &Driver_Caps, &Driver_MinFrequency, &Driver_MaxFrequency, &speakermode);
#endif
	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;
	if (format == FMOD_SOUND_FORMAT_PCM8)
	{
		// PCM-8 sounds like garbage with anything but DirectSound.
		FMOD_OUTPUTTYPE output;
		if (FMOD_OK != Sys->getOutput(&output) || output != FMOD_OUTPUTTYPE_DSOUND)
		{
			format = FMOD_SOUND_FORMAT_PCM16;
		}
	}
	eval = Enum_NumForName(ResamplerNames, snd_resampler);
	resampler = eval >= 0 ? FMOD_DSP_RESAMPLER(eval) : FMOD_DSP_RESAMPLER_LINEAR;
	// These represented the frequency limits for hardware channels, which we never used anyway.
//	samplerate = clamp<int>(snd_samplerate, Driver_MinFrequency, Driver_MaxFrequency);
	samplerate = snd_samplerate;
	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)
	{
		// These flags are the same thing, just with different names.
#ifdef FMOD_INIT_SOFTWARE_HRTF
		initflags |= FMOD_INIT_SOFTWARE_HRTF;
#else
		initflags |= FMOD_INIT_HRTF_LOWPASS;
#endif
	}
	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;
			}
		}
		else if (result == FMOD_ERR_NET_SOCKET_ERROR && (initflags & FMOD_INIT_ENABLE_PROFILE))
		{
			Printf(TEXTCOLOR_RED"  Could not create socket. Retrying without profiling.\n");
			initflags &= ~FMOD_INIT_ENABLE_PROFILE;
			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_SFXREVERB, &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)
				{
					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);

							// These parameters are entirely empirical and can probably
							// stand some improvement, but it sounds remarkably close
							// to the old reverb unit's output.
							WaterReverb->setParameter(FMOD_DSP_SFXREVERB_LFREFERENCE, 150);
							WaterReverb->setParameter(FMOD_DSP_SFXREVERB_HFREFERENCE, 10000);
							WaterReverb->setParameter(FMOD_DSP_SFXREVERB_ROOM, 0);
							WaterReverb->setParameter(FMOD_DSP_SFXREVERB_ROOMHF, -5000);
							WaterReverb->setParameter(FMOD_DSP_SFXREVERB_DRYLEVEL, 0);
							WaterReverb->setParameter(FMOD_DSP_SFXREVERB_DECAYHFRATIO, 1);
							WaterReverb->setParameter(FMOD_DSP_SFXREVERB_DECAYTIME, 0.25f);
							WaterReverb->setParameter(FMOD_DSP_SFXREVERB_DENSITY, 100);
							WaterReverb->setParameter(FMOD_DSP_SFXREVERB_DIFFUSION, 100);
							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;
			}
			else
			{
				FMOD::DSP *dontcare;
				result = ChannelGroupTargetUnit->getOutput(0, &dontcare, &ChannelGroupTargetUnitOutput);
				if (result != FMOD_OK)
				{
					ChannelGroupTargetUnitOutput = 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);
	// The default is 16k, which periodically starves later FMOD versions
	// when streaming FLAC files.
	Sys->setStreamBufferSize(64*1024, FMOD_TIMEUNIT_RAWBYTES);
	snd_sfxvolume.Callback ();
	return true;
}