TombEngine/TR5Main/Sound/sound.cpp

831 lines
26 KiB
C++
Raw Normal View History

#include "framework.h"
2021-09-15 17:49:01 +03:00
#include <filesystem>
2021-09-25 16:04:16 +03:00
#include "winmain.h"
2021-09-15 17:49:01 +03:00
#include "Sound/sound.h"
2020-04-23 19:22:01 +02:00
#include "lara.h"
#include "camera.h"
2021-09-19 23:41:26 +03:00
#include "room.h"
2021-09-25 16:04:16 +03:00
#include "setup.h"
#include "configuration.h"
#include "level.h"
2021-08-26 19:47:59 +03:00
2018-08-21 21:32:56 +03:00
HSTREAM BASS_3D_Mixdown;
2021-10-29 02:22:26 +03:00
HFX BASS_FXHandler[(int)SOUND_FILTER::Count];
SoundTrackSlot BASS_Soundtrack[(int)SOUNDTRACK_PLAYTYPE::Count];
2020-04-24 19:15:05 +02:00
HSAMPLE SamplePointer[SOUND_MAX_SAMPLES];
SoundEffectSlot SoundSlot[SOUND_MAX_CHANNELS];
2018-08-19 09:46:58 +02:00
2021-10-29 02:22:26 +03:00
const BASS_BFX_FREEVERB BASS_ReverbTypes[(int)REVERB_TYPE::Count] = // Reverb presets
2018-08-19 09:46:58 +02:00
{ // Dry Mix | Wet Mix | Size | Damp | Width | Mode | Channel
{ 1.0f, 0.20f, 0.05f, 0.90f, 0.7f, 0, -1 }, // 0 = Outside
{ 1.0f, 0.20f, 0.35f, 0.15f, 0.8f, 0, -1 }, // 1 = Small room
{ 1.0f, 0.25f, 0.55f, 0.20f, 1.0f, 0, -1 }, // 2 = Medium room
{ 1.0f, 0.25f, 0.80f, 0.50f, 1.0f, 0, -1 }, // 3 = Large room
{ 1.0f, 0.25f, 0.90f, 1.00f, 1.0f, 0, -1 } // 4 = Pipe
2021-08-26 19:47:59 +03:00
};
2018-08-19 09:46:58 +02:00
2021-10-29 02:22:26 +03:00
std::map<std::string, int> SoundTrackMap;
std::vector<SoundTrackInfo> SoundTracks;
2021-08-26 19:47:59 +03:00
static int GlobalMusicVolume;
static int GlobalFXVolume;
void SetVolumeMusic(int vol)
{
GlobalMusicVolume = vol;
2021-08-26 19:47:59 +03:00
float fVol = static_cast<float>(vol) / 100.0f;
2021-10-29 02:22:26 +03:00
if (BASS_ChannelIsActive(BASS_Soundtrack[(int)SOUNDTRACK_PLAYTYPE::BGM].Channel))
{
2021-10-29 02:22:26 +03:00
BASS_ChannelSetAttribute(BASS_Soundtrack[(int)SOUNDTRACK_PLAYTYPE::BGM].Channel, BASS_ATTRIB_VOL, fVol);
2021-08-26 19:47:59 +03:00
}
2021-10-29 02:22:26 +03:00
if (BASS_ChannelIsActive(BASS_Soundtrack[(int)SOUNDTRACK_PLAYTYPE::OneShot].Channel))
2021-08-26 19:47:59 +03:00
{
2021-10-29 02:22:26 +03:00
BASS_ChannelSetAttribute(BASS_Soundtrack[(int)SOUNDTRACK_PLAYTYPE::OneShot].Channel, BASS_ATTRIB_VOL, fVol);
}
}
void SetVolumeFX(int vol)
{
GlobalFXVolume = vol;
}
2021-10-29 02:22:26 +03:00
bool LoadSample(char *pointer, int compSize, int uncompSize, int index)
{
2018-08-21 21:32:56 +03:00
if (index >= SOUND_MAX_SAMPLES)
{
2021-10-29 12:48:08 +03:00
TENLog("Sample index " + std::to_string(index) + " is larger than max. amount of samples", LogLevel::Warning);
2018-08-21 21:32:56 +03:00
return 0;
}
2018-08-19 09:46:58 +02:00
2018-08-21 21:32:56 +03:00
if (pointer == NULL || compSize <= 0)
{
2021-10-29 12:48:08 +03:00
TENLog("Sample size or memory address is incorrect for index " + std::to_string(index), LogLevel::Warning);
2018-08-21 21:32:56 +03:00
return 0;
}
// Load and uncompress sample to 32-bit float format
HSAMPLE sample = BASS_SampleLoad(true, pointer, 0, compSize, 1, SOUND_SAMPLE_FLAGS);
if (!sample)
{
2021-10-29 12:48:08 +03:00
TENLog("Error loading sample " + std::to_string(index), LogLevel::Error);
2018-08-21 21:32:56 +03:00
return false;
}
// Paranoid (c) TeslaRus
// Try to free sample before allocating new one
Sound_FreeSample(index);
BASS_SAMPLE info;
BASS_SampleGetInfo(sample, &info);
int finalLength = info.length + 44; // uncompSize is invalid after 16->32 bit conversion
if (info.freq != 22050 || info.chans != 1)
{
2021-10-29 12:48:08 +03:00
TENLog("Wrong sample parameters, must be 22050 Hz Mono", LogLevel::Error);
2018-08-21 21:32:56 +03:00
return false;
}
// Generate RIFF/WAV header to simplify loading sample data to stream. In case if RIFF/WAV header
// exists, stream could be completely created just by calling BASS_StreamCreateFile().
char* uncompBuffer = new char[finalLength];
ZeroMemory(uncompBuffer, finalLength);
memcpy(uncompBuffer, "RIFF\0\0\0\0WAVEfmt \20\0\0\0", 20);
memcpy(uncompBuffer + 36, "data\0\0\0\0", 8);
WAVEFORMATEX *wf = (WAVEFORMATEX*)(uncompBuffer + 20);
wf->wFormatTag = 3;
wf->nChannels = info.chans;
wf->wBitsPerSample = 32;
wf->nSamplesPerSec = info.freq;
wf->nBlockAlign = wf->nChannels * wf->wBitsPerSample / 8;
wf->nAvgBytesPerSec = wf->nSamplesPerSec * wf->nBlockAlign;
// Copy raw PCM data from temporary sample buffer to actual buffer which will be used by engine.
BASS_SampleGetData(sample, uncompBuffer + 44);
BASS_SampleFree(sample);
2018-08-19 09:46:58 +02:00
2018-08-21 21:32:56 +03:00
// Cut off trailing silence from samples to prevent gaps in looped playback
int cleanLength = info.length;
for (int i = 4; i < info.length; i += 4)
2018-08-19 09:46:58 +02:00
{
2018-08-21 21:32:56 +03:00
float *currentSample = reinterpret_cast<float*>(uncompBuffer + finalLength - i);
if (*currentSample > SOUND_32BIT_SILENCE_LEVEL || *currentSample < -SOUND_32BIT_SILENCE_LEVEL)
{
int alignment = i % wf->nBlockAlign;
cleanLength -= (i - alignment);
break;
}
2018-08-19 09:46:58 +02:00
}
2018-08-21 21:32:56 +03:00
// Put data size to header
*(DWORD*)(uncompBuffer + 4) = cleanLength + 44 - 8;
*(DWORD*)(uncompBuffer + 40) = cleanLength;
// Create actual sample
SamplePointer[index] = BASS_SampleLoad(true, uncompBuffer, 0, cleanLength + 44, 65535, SOUND_SAMPLE_FLAGS | BASS_SAMPLE_3D);
return true;
2018-08-19 09:46:58 +02:00
}
long SoundEffect(int effectID, PHD_3DPOS* position, int env_flags, float pitchMultiplier, float gainMultiplier)
2018-08-19 09:46:58 +02:00
{
if (effectID >= g_Level.SoundMap.size())
2018-08-19 09:46:58 +02:00
return 0;
if (BASS_GetDevice() == -1)
return 0;
if (!(env_flags & SFX_ALWAYS))
2018-08-19 09:46:58 +02:00
{
// Don't play effect if effect's environment isn't the same as camera position's environment
if ((env_flags & ENV_FLAG_WATER) != (g_Level.Rooms[Camera.pos.roomNumber].flags & ENV_FLAG_WATER))
2018-08-21 21:32:56 +03:00
return 0;
2018-08-19 09:46:58 +02:00
}
// Get actual sample index from SoundMap
int sampleIndex = g_Level.SoundMap[effectID];
2018-08-19 09:46:58 +02:00
// -1 means no such effect exists in level file.
// We set it to -2 afterwards to prevent further debug message firings.
if (sampleIndex == -1)
{
2021-10-29 12:48:08 +03:00
TENLog("Non present effect: " + std::to_string(effectID), LogLevel::Error);
g_Level.SoundMap[effectID] = -2;
2018-08-19 09:46:58 +02:00
return 0;
}
else if (sampleIndex == -2)
return 0;
2021-10-29 02:22:26 +03:00
SampleInfo* sampleInfo = &g_Level.SoundDetails[sampleIndex];
2018-08-19 09:46:58 +02:00
2021-10-29 02:22:26 +03:00
if (sampleInfo->Number < 0)
2018-08-19 09:46:58 +02:00
{
2021-10-29 12:48:08 +03:00
TENLog("No valid samples count for effect " + std::to_string(sampleIndex), LogLevel::Warning);
2018-08-19 09:46:58 +02:00
return 0;
}
2018-08-21 21:32:56 +03:00
// Assign common sample flags.
DWORD sampleFlags = SOUND_SAMPLE_FLAGS;
2018-08-19 09:46:58 +02:00
// Effect's chance to play.
2021-10-29 02:22:26 +03:00
if ((sampleInfo->Randomness) && ((GetRandomControl() & 127) > sampleInfo->Randomness))
2018-08-19 09:46:58 +02:00
return 0;
2018-08-21 21:32:56 +03:00
// Apply 3D attrib only to sound with position property
if (position)
sampleFlags |= BASS_SAMPLE_3D;
2018-08-19 09:46:58 +02:00
// Set & randomize volume (if needed)
2021-10-29 02:22:26 +03:00
float gain = (static_cast<float>(sampleInfo->Volume) / 255.0f) * gainMultiplier;
if ((sampleInfo->Flags & SOUND_FLAG_RND_GAIN))
gain -= (static_cast<float>(GetRandomControl()) / static_cast<float>(RAND_MAX))* SOUND_MAX_GAIN_CHANGE;
// Set and randomize pitch and additionally multiply by provided value (for vehicles etc)
2021-10-29 02:22:26 +03:00
float pitch = (1.0f + static_cast<float>(sampleInfo->Pitch) / 127.0f) * pitchMultiplier;
// Randomize pitch (if needed)
2021-10-29 02:22:26 +03:00
if ((sampleInfo->Flags & SOUND_FLAG_RND_PITCH))
pitch += ((static_cast<float>(GetRandomControl()) / static_cast<float>(RAND_MAX)) - 0.5f)* SOUND_MAX_PITCH_CHANGE * 2.0f;
// Calculate sound radius and distance to sound
2021-10-29 02:22:26 +03:00
float radius = (float)(sampleInfo->Radius) * 1024.0f;
float distance = Sound_DistanceToListener(position);
// Don't play sound if it's too far from listener's position.
if (distance > radius)
return 0;
// Get final volume of a sound.
float volume = Sound_Attenuate(gain, distance, radius);
// Get existing index, if any, of sound which is playing.
int existingChannel = Sound_EffectIsPlaying(effectID, position);
2018-08-19 09:46:58 +02:00
// Select behaviour based on effect playback type (bytes 0-1 of flags field)
2021-10-29 02:22:26 +03:00
auto playType = (SOUND_PLAYTYPE)(sampleInfo->Flags & 3);
2018-08-21 21:32:56 +03:00
switch (playType)
2018-08-19 09:46:58 +02:00
{
2021-10-29 02:22:26 +03:00
case SOUND_PLAYTYPE::Normal:
2018-08-19 09:46:58 +02:00
break;
2021-10-29 02:22:26 +03:00
case SOUND_PLAYTYPE::Wait:
if (existingChannel != -1) // Don't play until stopped
return 0;
2018-08-19 09:46:58 +02:00
break;
2021-10-29 02:22:26 +03:00
case SOUND_PLAYTYPE::Restart:
if (existingChannel != -1) // Stop existing and continue
Sound_FreeSlot(existingChannel, SOUND_XFADETIME_CUTSOUND);
2018-08-19 09:46:58 +02:00
break;
2021-10-29 02:22:26 +03:00
case SOUND_PLAYTYPE::Looped:
if (existingChannel != -1) // Just update parameters and return, if already playing
{
Sound_UpdateEffectPosition(existingChannel, position);
Sound_UpdateEffectAttributes(existingChannel, pitch, volume);
2018-08-19 09:46:58 +02:00
return 0;
}
2018-08-21 21:32:56 +03:00
sampleFlags |= BASS_SAMPLE_LOOP;
2018-08-19 09:46:58 +02:00
break;
}
// Randomly select arbitrary sample from the list, if more than one is present
int sampleToPlay = 0;
2021-10-29 02:22:26 +03:00
int numSamples = (sampleInfo->Flags >> 2) & 15;
2018-08-19 09:46:58 +02:00
if (numSamples == 1)
2021-10-29 02:22:26 +03:00
sampleToPlay = sampleInfo->Number;
2018-08-19 09:46:58 +02:00
else
2021-10-29 02:22:26 +03:00
sampleToPlay = sampleInfo->Number + (int)((GetRandomControl() * numSamples) >> 15);
2018-08-19 09:46:58 +02:00
// Get free channel to play sample
int freeSlot = Sound_GetFreeSlot();
if (freeSlot == -1)
{
2021-10-29 12:48:08 +03:00
TENLog("No free channel slot available!", LogLevel::Warning);
2018-08-19 09:46:58 +02:00
return 0;
}
// Create sample's stream and reset buffer back to normal value.
HSTREAM channel = BASS_SampleGetChannel(SamplePointer[sampleToPlay], true);
2018-08-19 09:46:58 +02:00
if (Sound_CheckBASSError("Trying to create channel for sample %d", false, sampleToPlay))
2018-08-19 09:46:58 +02:00
return 0;
2018-08-21 21:32:56 +03:00
// Finally ready to play sound, assign it to sound slot.
2021-10-29 02:22:26 +03:00
SoundSlot[freeSlot].State = SOUND_STATE::Idle;
SoundSlot[freeSlot].EffectID = effectID;
SoundSlot[freeSlot].Channel = channel;
SoundSlot[freeSlot].Gain = gain;
SoundSlot[freeSlot].Origin = position ? Vector3(position->xPos, position->yPos, position->zPos) : SOUND_OMNIPRESENT_ORIGIN;
2018-08-19 09:46:58 +02:00
if (Sound_CheckBASSError("Applying pitch/gain attribs on channel %x, sample %d", false, channel, sampleToPlay))
return 0;
2018-08-21 21:32:56 +03:00
// Set looped flag, if necessary
2021-10-29 02:22:26 +03:00
if (playType == SOUND_PLAYTYPE::Looped)
2018-08-21 21:32:56 +03:00
BASS_ChannelFlags(channel, BASS_SAMPLE_LOOP, BASS_SAMPLE_LOOP);
2018-08-19 09:46:58 +02:00
2018-08-21 21:32:56 +03:00
// Play channel
BASS_ChannelPlay(channel, false);
2018-08-19 09:46:58 +02:00
if (Sound_CheckBASSError("Queuing channel %x on sample mixer", false, freeSlot))
2018-08-19 09:46:58 +02:00
return 0;
// Set attributes
BASS_ChannelSet3DAttributes(channel, position ? BASS_3DMODE_NORMAL : BASS_3DMODE_OFF, SOUND_MAXVOL_RADIUS, radius, 360, 360, 0.0f);
Sound_UpdateEffectPosition(freeSlot, position, true);
Sound_UpdateEffectAttributes(freeSlot, pitch, volume);
if (Sound_CheckBASSError("Applying 3D attribs on channel %x, sound %d", false, channel, effectID))
return 0;
2018-08-19 09:46:58 +02:00
return 1;
}
void StopSoundEffect(short effectID)
2018-08-19 09:46:58 +02:00
{
for (int i = 0; i < SOUND_MAX_CHANNELS; i++)
2021-10-29 02:22:26 +03:00
if (SoundSlot[i].EffectID == effectID && SoundSlot[i].Channel != NULL && BASS_ChannelIsActive(SoundSlot[i].Channel) == BASS_ACTIVE_PLAYING)
Sound_FreeSlot(i, SOUND_XFADETIME_CUTSOUND);
2018-08-19 09:46:58 +02:00
}
2021-10-29 02:22:26 +03:00
void StopAllSounds()
2018-08-19 09:46:58 +02:00
{
for (int i = 0; i < SOUND_MAX_CHANNELS; i++)
2021-10-29 02:22:26 +03:00
if (SoundSlot[i].Channel != NULL && BASS_ChannelIsActive(SoundSlot[i].Channel))
BASS_ChannelStop(SoundSlot[i].Channel);
2018-08-19 09:46:58 +02:00
ZeroMemory(SoundSlot, (sizeof(SoundEffectSlot) * SOUND_MAX_CHANNELS));
}
2021-10-29 02:22:26 +03:00
void FreeSamples()
2018-08-19 09:46:58 +02:00
{
2021-10-29 02:22:26 +03:00
StopAllSounds();
2018-08-19 09:46:58 +02:00
for (int i = 0; i < SOUND_MAX_SAMPLES; i++)
Sound_FreeSample(i);
}
void PlaySoundTrack(std::string track, SOUNDTRACK_PLAYTYPE mode, QWORD position)
2018-08-19 09:46:58 +02:00
{
bool crossfade = false;
2021-10-29 02:22:26 +03:00
DWORD crossfadeTime = 0;
2018-08-21 21:32:56 +03:00
DWORD flags = BASS_STREAM_AUTOFREE | BASS_SAMPLE_FLOAT | BASS_ASYNCFILE;
2021-10-29 02:22:26 +03:00
bool channelActive = BASS_ChannelIsActive(BASS_Soundtrack[(int)mode].Channel);
if (channelActive && BASS_Soundtrack[(int)mode].Track.compare(track) == 0)
return;
2018-08-19 09:46:58 +02:00
2018-08-21 21:32:56 +03:00
switch (mode)
2018-08-19 09:46:58 +02:00
{
2021-10-29 02:22:26 +03:00
case SOUNDTRACK_PLAYTYPE::OneShot:
crossfadeTime = SOUND_XFADETIME_ONESHOT;
break;
2018-08-21 21:32:56 +03:00
2021-10-29 02:22:26 +03:00
case SOUNDTRACK_PLAYTYPE::BGM:
crossfade = true;
crossfadeTime = channelActive ? SOUND_XFADETIME_BGM : SOUND_XFADETIME_BGM_START;
flags |= BASS_SAMPLE_LOOP;
break;
2018-08-19 09:46:58 +02:00
}
2021-10-29 02:22:26 +03:00
if (channelActive)
BASS_ChannelSlideAttribute(BASS_Soundtrack[(int)mode].Channel, BASS_ATTRIB_VOL, -1.0f, crossfadeTime);
2018-08-21 21:32:56 +03:00
static char fullTrackName[1024];
char const* name = track.c_str();
2018-08-19 09:46:58 +02:00
snprintf(fullTrackName, sizeof(fullTrackName), TRACKS_PREFIX, name, "ogg");
if (!std::filesystem::exists(fullTrackName))
{
snprintf(fullTrackName, sizeof(fullTrackName), TRACKS_PREFIX, name, "mp3");
if (!std::filesystem::exists(fullTrackName))
{
snprintf(fullTrackName, sizeof(fullTrackName), TRACKS_PREFIX, name, "wav");
if (!std::filesystem::exists(fullTrackName))
{
2021-10-29 12:52:32 +03:00
TENLog("No any soundtrack files with name '" + track + "' were found", LogLevel::Error);
return;
}
}
}
2018-08-19 09:46:58 +02:00
2018-08-21 21:32:56 +03:00
auto stream = BASS_StreamCreateFile(false, fullTrackName, 0, 0, flags);
2018-08-19 09:46:58 +02:00
2021-10-29 12:52:32 +03:00
if (Sound_CheckBASSError("Opening soundtrack '%s'", false, fullTrackName))
return;
float masterVolume = (float)GlobalMusicVolume / 100.0f;
2018-08-19 09:46:58 +02:00
// Damp BGM track in case one-shot track is about to play.
2021-10-29 02:22:26 +03:00
if (mode == SOUNDTRACK_PLAYTYPE::OneShot)
{
2021-10-29 02:22:26 +03:00
if (BASS_ChannelIsActive(BASS_Soundtrack[(int)SOUNDTRACK_PLAYTYPE::BGM].Channel))
BASS_ChannelSlideAttribute(BASS_Soundtrack[(int)SOUNDTRACK_PLAYTYPE::BGM].Channel, BASS_ATTRIB_VOL, masterVolume * SOUND_BGM_DAMP_COEFFICIENT, SOUND_XFADETIME_BGM_START);
BASS_ChannelSetSync(stream, BASS_SYNC_FREE | BASS_SYNC_ONETIME | BASS_SYNC_MIXTIME, 0, Sound_FinishOneshotTrack, NULL);
}
2018-08-21 21:32:56 +03:00
// BGM tracks are crossfaded, and additionally shuffled a bit to make things more natural.
// Think everybody are fed up with same start-up sounds of Caves ambience...
2018-08-19 09:46:58 +02:00
if (crossfade)
2018-08-21 21:32:56 +03:00
{
// Crossfade...
BASS_ChannelSetAttribute(stream, BASS_ATTRIB_VOL, 0.0f);
BASS_ChannelSlideAttribute(stream, BASS_ATTRIB_VOL, masterVolume, crossfadeTime);
2018-08-19 09:46:58 +02:00
2018-08-21 21:32:56 +03:00
// Shuffle...
// Only activates if no custom position is passed as argument.
if (!position)
{
QWORD newPos = BASS_ChannelGetLength(stream, BASS_POS_BYTE) * (static_cast<float>(GetRandomControl()) / static_cast<float>(RAND_MAX));
BASS_ChannelSetPosition(stream, newPos, BASS_POS_BYTE);
}
2018-08-21 21:32:56 +03:00
}
else
BASS_ChannelSetAttribute(stream, BASS_ATTRIB_VOL, masterVolume);
BASS_ChannelPlay(stream, false);
// Try to restore position, if specified.
if (position && (BASS_ChannelGetLength(stream, BASS_POS_BYTE) > position))
BASS_ChannelSetPosition(stream, position, BASS_POS_BYTE);
2021-10-29 12:52:32 +03:00
if (Sound_CheckBASSError("Playing soundtrack '%s'", true, fullTrackName))
return;
2018-08-19 09:46:58 +02:00
2021-10-29 02:22:26 +03:00
BASS_Soundtrack[(int)mode].Channel = stream;
BASS_Soundtrack[(int)mode].Track = track;
}
void PlaySoundTrack(std::string track, short mask)
{
2021-10-29 02:30:36 +03:00
// If track name was included in script, play it as registered track and take mask into account.
// Otherwise, play it once without registering anywhere.
if (SoundTrackMap.count(track))
PlaySoundTrack(SoundTrackMap[track], mask);
else
PlaySoundTrack(track, SOUNDTRACK_PLAYTYPE::OneShot);
2018-08-21 21:32:56 +03:00
}
2018-08-19 09:46:58 +02:00
2021-10-29 02:22:26 +03:00
void PlaySoundTrack(int index, short mask)
2018-08-21 21:32:56 +03:00
{
2021-10-29 12:15:44 +03:00
if (SoundTracks.size() <= index)
{
2021-10-29 12:48:08 +03:00
TENLog("No track registered with index " + std::to_string(index), LogLevel::Error);
2021-10-29 12:15:44 +03:00
return;
}
2018-08-21 21:32:56 +03:00
// Check and modify soundtrack map mask, if needed.
// If existing mask is unmodified (same activation mask setup), track won't play.
2021-10-29 02:22:26 +03:00
2021-10-29 12:26:26 +03:00
if (mask && !(SoundTracks[index].Mode == SOUNDTRACK_PLAYTYPE::BGM))
2018-08-21 21:32:56 +03:00
{
2021-10-29 02:22:26 +03:00
short filteredMask = (mask >> 8) & 0x3F;
if ((SoundTracks[index].Mask & filteredMask) == filteredMask)
2018-08-21 21:32:56 +03:00
return; // Mask is the same, don't play it.
2021-10-29 02:22:26 +03:00
SoundTracks[index].Mask |= filteredMask;
2018-08-19 09:46:58 +02:00
}
2021-10-29 02:22:26 +03:00
PlaySoundTrack(SoundTracks[index].Name, SoundTracks[index].Mode);
2018-08-21 21:32:56 +03:00
}
2018-08-19 09:46:58 +02:00
2021-09-16 01:12:19 +03:00
void StopSoundTracks()
2018-08-21 21:32:56 +03:00
{
// Do quick fadeouts.
2021-10-29 02:22:26 +03:00
BASS_ChannelSlideAttribute(BASS_Soundtrack[(int)SOUNDTRACK_PLAYTYPE::OneShot].Channel, BASS_ATTRIB_VOL | BASS_SLIDE_LOG, -1.0f, SOUND_XFADETIME_ONESHOT);
BASS_ChannelSlideAttribute(BASS_Soundtrack[(int)SOUNDTRACK_PLAYTYPE::BGM].Channel, BASS_ATTRIB_VOL | BASS_SLIDE_LOG, -1.0f, SOUND_XFADETIME_ONESHOT);
2021-10-29 02:22:26 +03:00
BASS_Soundtrack[(int)SOUNDTRACK_PLAYTYPE::OneShot].Track = "";
BASS_Soundtrack[(int)SOUNDTRACK_PLAYTYPE::OneShot].Channel = NULL;
BASS_Soundtrack[(int)SOUNDTRACK_PLAYTYPE::BGM].Track = "";
BASS_Soundtrack[(int)SOUNDTRACK_PLAYTYPE::BGM].Channel = NULL;
}
void ClearSoundTrackMasks()
{
for (auto& track : SoundTracks) { track.Mask = 0; }
}
2018-08-21 21:32:56 +03:00
2021-10-29 03:54:30 +03:00
// Returns specified soundtrack type's stem name and playhead position.
// To be used with savegames. To restore soundtrack, use PlaySoundtrack function with playhead position passed as 3rd argument.
std::pair<std::string, QWORD> GetSoundTrackNameAndPosition(SOUNDTRACK_PLAYTYPE type)
{
auto track = BASS_Soundtrack[(int)type];
if (track.Track.empty() || !BASS_ChannelIsActive(track.Channel))
return std::pair<std::string, QWORD>();
std::filesystem::path path = track.Track;
return std::pair<std::string, QWORD>(path.stem().string(), BASS_ChannelGetPosition(track.Channel, BASS_POS_BYTE));
}
static void CALLBACK Sound_FinishOneshotTrack(HSYNC handle, DWORD channel, DWORD data, void* userData)
{
2021-10-29 02:22:26 +03:00
if (BASS_ChannelIsActive(BASS_Soundtrack[(int)SOUNDTRACK_PLAYTYPE::BGM].Channel))
BASS_ChannelSlideAttribute(BASS_Soundtrack[(int)SOUNDTRACK_PLAYTYPE::BGM].Channel, BASS_ATTRIB_VOL, (float)GlobalMusicVolume / 100.0f, SOUND_XFADETIME_BGM_START);
2021-10-29 02:22:26 +03:00
BASS_Soundtrack[(int)SOUNDTRACK_PLAYTYPE::OneShot].Track = "";
BASS_Soundtrack[(int)SOUNDTRACK_PLAYTYPE::OneShot].Channel = NULL;
2018-08-19 09:46:58 +02:00
}
void Sound_FreeSample(int index)
2018-08-19 09:46:58 +02:00
{
if (SamplePointer[index] != NULL)
{
2018-08-21 21:32:56 +03:00
BASS_SampleFree(SamplePointer[index]);
2018-08-19 09:46:58 +02:00
SamplePointer[index] = NULL;
}
}
// Get first free (non-playing) sound slot.
// If no free slots found, now try to hijack slot which is as far from listener as possible
int Sound_GetFreeSlot()
{
for (int i = 0; i < SOUND_MAX_CHANNELS; i++)
{
2021-10-29 02:22:26 +03:00
if (SoundSlot[i].Channel == NULL || !BASS_ChannelIsActive(SoundSlot[i].Channel))
2018-08-19 09:46:58 +02:00
return i;
}
// No free slots, hijack now.
float minDistance = 0;
int farSlot = -1;
for (int i = 0; i < SOUND_MAX_CHANNELS; i++)
{
2021-10-29 02:22:26 +03:00
float distance = Vector3(SoundSlot[i].Origin - Vector3(Camera.mikePos.x, Camera.mikePos.y, Camera.mikePos.z)).Length();
2018-08-19 09:46:58 +02:00
if (distance > minDistance)
{
minDistance = distance;
farSlot = i;
}
}
2021-10-29 12:48:08 +03:00
TENLog("Hijacking sound effect slot " + std::to_string(farSlot), LogLevel::Info);
Sound_FreeSlot(farSlot, SOUND_XFADETIME_HIJACKSOUND);
2018-08-19 09:46:58 +02:00
return farSlot;
}
// Returns slot ID in which effect is playing, if found. If not found, returns -1.
// We use origin position as a reference, because in original TRs it's not possible to clearly
// identify what's the source of the producing effect.
2018-08-21 21:32:56 +03:00
int Sound_EffectIsPlaying(int effectID, PHD_3DPOS *position)
2018-08-19 09:46:58 +02:00
{
for (int i = 0; i < SOUND_MAX_CHANNELS; i++)
{
2021-10-29 02:22:26 +03:00
if (SoundSlot[i].EffectID == effectID)
2018-08-19 09:46:58 +02:00
{
2021-10-29 02:22:26 +03:00
if (SoundSlot[i].Channel == NULL) // Free channel
2018-08-19 09:46:58 +02:00
continue;
2021-10-29 02:22:26 +03:00
if (BASS_ChannelIsActive(SoundSlot[i].Channel))
2018-08-19 09:46:58 +02:00
{
2018-08-21 21:32:56 +03:00
// Only check position on 3D samples. 2D samples stop immediately.
BASS_CHANNELINFO info;
2021-10-29 02:22:26 +03:00
BASS_ChannelGetInfo(SoundSlot[i].Channel, &info);
2018-08-21 21:32:56 +03:00
if (!(info.flags & BASS_SAMPLE_3D) || !position)
return i;
2018-08-19 09:46:58 +02:00
// Check if effect origin is equal OR in nearest possible hearing range.
2019-01-13 21:57:16 +01:00
Vector3 origin = Vector3(position->xPos, position->yPos, position->zPos);
2021-10-29 02:22:26 +03:00
if (Vector3::Distance(origin, SoundSlot[i].Origin) < SOUND_MAXVOL_RADIUS)
2018-08-19 09:46:58 +02:00
return i;
}
else
2021-10-29 02:22:26 +03:00
SoundSlot[i].Channel = NULL; // WTF, let's clean this up
2018-08-19 09:46:58 +02:00
}
}
return -1;
}
2018-08-21 21:32:56 +03:00
// Gets the distance to the source.
float Sound_DistanceToListener(PHD_3DPOS *position)
{
if (!position) return 0.0f; // Assume sound is 2D menu sound
2019-01-13 21:57:16 +01:00
return Sound_DistanceToListener(Vector3(position->xPos, position->yPos, position->zPos));
2018-08-21 21:32:56 +03:00
}
2019-01-13 21:57:16 +01:00
float Sound_DistanceToListener(Vector3 position)
2018-08-21 21:32:56 +03:00
{
2019-01-13 21:57:16 +01:00
return Vector3(Vector3(Camera.mikePos.x, Camera.mikePos.y, Camera.mikePos.z) - position).Length();
2018-08-21 21:32:56 +03:00
}
// Calculate attenuated volume.
2018-08-19 09:46:58 +02:00
2018-08-21 21:32:56 +03:00
float Sound_Attenuate(float gain, float distance, float radius)
2018-08-19 09:46:58 +02:00
{
2018-08-21 21:32:56 +03:00
float result = gain * (1.0f - (distance / radius));
result = result < 0 ? 0.0f : (result > 1.0f ? 1.0f : result);
return result * ((float)GlobalFXVolume / 100.0f);
2018-08-19 09:46:58 +02:00
}
// Stop and free desired sound slot.
void Sound_FreeSlot(int index, unsigned int fadeout)
2018-08-19 09:46:58 +02:00
{
if (index > SOUND_MAX_CHANNELS || index < 0)
return;
2018-08-21 21:32:56 +03:00
if (fadeout > 0)
2021-10-29 02:22:26 +03:00
BASS_ChannelSlideAttribute(SoundSlot[index].Channel, BASS_ATTRIB_VOL, -1.0f, fadeout);
2018-08-21 21:32:56 +03:00
else
2021-10-29 02:22:26 +03:00
BASS_ChannelStop(SoundSlot[index].Channel);
2018-08-21 21:32:56 +03:00
2021-10-29 02:22:26 +03:00
SoundSlot[index].Channel = NULL;
SoundSlot[index].State = SOUND_STATE::Idle;
SoundSlot[index].EffectID = -1;
2018-08-19 09:46:58 +02:00
}
// Update sound position in a level.
bool Sound_UpdateEffectPosition(int index, PHD_3DPOS *position, bool force)
2018-08-19 09:46:58 +02:00
{
if (index > SOUND_MAX_CHANNELS || index < 0)
return false;
2018-08-21 21:32:56 +03:00
if (position)
{
BASS_CHANNELINFO info;
2021-10-29 02:22:26 +03:00
BASS_ChannelGetInfo(SoundSlot[index].Channel, &info);
2021-09-25 14:47:14 +03:00
if (info.flags & BASS_SAMPLE_3D)
2018-08-21 21:32:56 +03:00
{
2021-10-29 02:22:26 +03:00
SoundSlot[index].Origin.x = position->xPos;
SoundSlot[index].Origin.y = position->yPos;
SoundSlot[index].Origin.z = position->zPos;
2021-09-25 14:47:14 +03:00
auto pos = BASS_3DVECTOR(position->xPos, position->yPos, position->zPos);
auto rot = BASS_3DVECTOR(position->xRot, position->yRot, position->zRot);
2021-10-29 02:22:26 +03:00
BASS_ChannelSet3DPosition(SoundSlot[index].Channel, &pos, &rot, NULL);
2018-08-21 21:32:56 +03:00
BASS_Apply3D();
}
}
// Reset activity flag, important for looped samples
2021-10-29 02:22:26 +03:00
if (BASS_ChannelIsActive(SoundSlot[index].Channel))
SoundSlot[index].State = SOUND_STATE::Idle;
2018-08-19 09:46:58 +02:00
return true;
}
// Update gain and pitch.
bool Sound_UpdateEffectAttributes(int index, float pitch, float gain)
{
if (index > SOUND_MAX_CHANNELS || index < 0)
return false;
2021-10-29 02:22:26 +03:00
BASS_ChannelSetAttribute(SoundSlot[index].Channel, BASS_ATTRIB_FREQ, 22050.0f * pitch);
BASS_ChannelSetAttribute(SoundSlot[index].Channel, BASS_ATTRIB_VOL, gain);
return true;
}
2018-08-19 09:46:58 +02:00
// Update whole sound scene in a level.
// Must be called every frame to update camera position and 3D parameters.
void Sound_UpdateScene()
{
2018-08-21 21:32:56 +03:00
// Apply environmental effects
2018-08-19 09:46:58 +02:00
static int currentReverb = -1;
if (currentReverb == -1 || g_Level.Rooms[Camera.pos.roomNumber].reverbType != currentReverb)
2018-08-19 09:46:58 +02:00
{
currentReverb = g_Level.Rooms[Camera.pos.roomNumber].reverbType;
2021-10-29 02:22:26 +03:00
if (currentReverb < (int)REVERB_TYPE::Count)
BASS_FXSetParameters(BASS_FXHandler[(int)SOUND_FILTER::Reverb], &BASS_ReverbTypes[currentReverb]);
2018-08-21 21:32:56 +03:00
}
for (int i = 0; i < SOUND_MAX_CHANNELS; i++)
{
2021-10-29 02:22:26 +03:00
if ((SoundSlot[i].Channel != NULL) && (BASS_ChannelIsActive(SoundSlot[i].Channel) == BASS_ACTIVE_PLAYING))
2018-08-21 21:32:56 +03:00
{
2021-10-29 02:22:26 +03:00
SampleInfo* sampleInfo = &g_Level.SoundDetails[g_Level.SoundMap[SoundSlot[i].EffectID]];
// Stop and clean up sounds which were in ending state in previous frame.
// In case sound is looping, make it ending unless they are re-fired in next frame.
2018-08-21 21:32:56 +03:00
2021-10-29 02:22:26 +03:00
if (SoundSlot[i].State == SOUND_STATE::Ending)
2018-08-21 21:32:56 +03:00
{
2021-10-29 02:22:26 +03:00
SoundSlot[i].State = SOUND_STATE::Ended;
Sound_FreeSlot(i, SOUND_XFADETIME_CUTSOUND);
2018-08-21 21:32:56 +03:00
continue;
}
2021-10-29 02:22:26 +03:00
else if ((SOUND_PLAYTYPE)(sampleInfo->Flags & 3) == SOUND_PLAYTYPE::Looped)
SoundSlot[i].State = SOUND_STATE::Ending;
2018-08-21 21:32:56 +03:00
// Calculate attenuation and clean up sounds which are out of listener range (only for 3D sounds).
2018-08-21 21:32:56 +03:00
2021-10-29 02:22:26 +03:00
if (SoundSlot[i].Origin != SOUND_OMNIPRESENT_ORIGIN)
2018-08-21 21:32:56 +03:00
{
2021-10-29 02:22:26 +03:00
float radius = (float)(sampleInfo->Radius) * 1024.0f;
float distance = Sound_DistanceToListener(SoundSlot[i].Origin);
if (distance > radius)
{
Sound_FreeSlot(i);
continue;
}
else
2021-10-29 02:22:26 +03:00
BASS_ChannelSetAttribute(SoundSlot[i].Channel, BASS_ATTRIB_VOL, Sound_Attenuate(SoundSlot[i].Gain, distance, radius));
2018-08-21 21:32:56 +03:00
}
}
2018-08-19 09:46:58 +02:00
}
// Apply current listener position.
2018-08-21 21:32:56 +03:00
2019-01-13 21:57:16 +01:00
Vector3 at = Vector3(Camera.target.x, Camera.target.y, Camera.target.z) -
Vector3(Camera.mikePos.x, Camera.mikePos.y, Camera.mikePos.z);
at.Normalize();
auto mikePos = BASS_3DVECTOR( Camera.mikePos.x, // Pos
Camera.mikePos.y,
Camera.mikePos.z);
auto laraVel = BASS_3DVECTOR(Lara.currentXvel, // Vel
Lara.currentYvel,
Lara.currentZvel);
auto atVec = BASS_3DVECTOR(at.x, at.y, at.z); // At
auto upVec = BASS_3DVECTOR(0.0f, 1.0f, 0.0f); // Up
BASS_Set3DPosition(&mikePos,
&laraVel,
&atVec,
&upVec);
2018-08-19 09:46:58 +02:00
BASS_Apply3D();
}
// Initialize BASS engine and also prepare all sound data.
// Called once on engine start-up.
void Sound_Init()
{
BASS_Init(g_Configuration.SoundDevice, 44100, BASS_DEVICE_3D, WindowsHandle, NULL);
if (Sound_CheckBASSError("Initializing BASS sound device", true))
return;
2018-08-19 09:46:58 +02:00
// Initialize BASS_FX plugin
BASS_FX_GetVersion();
if (Sound_CheckBASSError("Initializing FX plugin", true))
2018-08-19 09:46:58 +02:00
return;
2018-08-21 21:32:56 +03:00
// Set 3D world parameters.
// Rolloff is lessened since we have own attenuation implementation.
BASS_Set3DFactors(SOUND_BASS_UNITS, 1.5f, 0.5f);
BASS_SetConfig(BASS_CONFIG_3DALGORITHM, BASS_3DALG_FULL);
2018-08-19 09:46:58 +02:00
// Set minimum latency and 2 threads for updating.
// Most of modern PCs already have multi-core CPUs, so why not parallelize updating?
BASS_SetConfig(BASS_CONFIG_UPDATETHREADS, 2);
2018-08-21 21:32:56 +03:00
BASS_SetConfig(BASS_CONFIG_UPDATEPERIOD, 10);
2018-08-19 09:46:58 +02:00
2018-08-21 21:32:56 +03:00
// Create 3D mixdown channel and make it play forever.
2018-08-19 09:46:58 +02:00
// For realtime mixer channels, we need minimum buffer latency. It shouldn't affect reliability.
BASS_SetConfig(BASS_CONFIG_BUFFER, 40);
2018-08-21 21:32:56 +03:00
BASS_3D_Mixdown = BASS_StreamCreate(44100, 2, BASS_SAMPLE_FLOAT, STREAMPROC_DEVICE_3D, NULL);
BASS_ChannelPlay(BASS_3D_Mixdown, false);
2018-08-19 09:46:58 +02:00
// Reset buffer back to normal value.
BASS_SetConfig(BASS_CONFIG_BUFFER, 300);
if (Sound_CheckBASSError("Starting 3D mixdown", true))
2018-08-19 09:46:58 +02:00
return;
2018-08-21 21:32:56 +03:00
// Initialize channels and tracks array
2021-10-29 02:22:26 +03:00
ZeroMemory(BASS_Soundtrack, (sizeof(HSTREAM) * (int)SOUNDTRACK_PLAYTYPE::Count));
2018-08-21 21:32:56 +03:00
ZeroMemory(SoundSlot, (sizeof(SoundEffectSlot) * SOUND_MAX_CHANNELS));
2018-08-19 09:46:58 +02:00
2018-08-21 21:32:56 +03:00
// Attach reverb effect to 3D channel
2021-10-29 02:22:26 +03:00
BASS_FXHandler[(int)SOUND_FILTER::Reverb] = BASS_ChannelSetFX(BASS_3D_Mixdown, BASS_FX_BFX_FREEVERB, 0);
BASS_FXSetParameters(BASS_FXHandler[(int)SOUND_FILTER::Reverb], &BASS_ReverbTypes[(int)REVERB_TYPE::Outside]);
2018-08-21 21:32:56 +03:00
if (Sound_CheckBASSError("Attaching environmental FX", true))
return;
2018-08-21 21:32:56 +03:00
// Apply slight compression to 3D channel
2021-10-29 02:22:26 +03:00
BASS_FXHandler[(int)SOUND_FILTER::Compressor] = BASS_ChannelSetFX(BASS_3D_Mixdown, BASS_FX_BFX_COMPRESSOR2, 1);
auto comp = BASS_BFX_COMPRESSOR2{ 4.0f, -18.0f, 1.5f, 10.0f, 100.0f, -1 };
2021-10-29 02:22:26 +03:00
BASS_FXSetParameters(BASS_FXHandler[(int)SOUND_FILTER::Compressor], &comp);
2018-08-21 21:32:56 +03:00
if (Sound_CheckBASSError("Attaching compressor", true))
return;
2018-08-21 21:32:56 +03:00
return;
2018-08-19 09:46:58 +02:00
}
// Stop all sounds and streams, if any, unplug all channels from the mixer and unload BASS engine.
// Must be called on engine quit.
void Sound_DeInit()
{
BASS_Free();
}
bool Sound_CheckBASSError(const char* message, bool verbose, ...)
2018-08-19 09:46:58 +02:00
{
va_list argptr;
static char data[4096];
int bassError = BASS_ErrorGetCode();
if (verbose || bassError)
2018-08-19 09:46:58 +02:00
{
va_start(argptr, verbose);
int32_t written = vsprintf(data, (char*)message, argptr); // @TODO: replace with debug/console/message output later...
2018-08-19 09:46:58 +02:00
va_end(argptr);
snprintf(data + written, sizeof(data) - written, bassError ? ": error #%d" : ": success", bassError);
TENLog(data, bassError ? LogLevel::Error : LogLevel::Info);
2018-08-19 09:46:58 +02:00
}
return bassError != 0;
}
2020-04-24 19:15:05 +02:00
void SayNo()
2018-08-19 09:46:58 +02:00
{
SoundEffect(SFX_TR4_LARA_NO, NULL, SFX_ALWAYS);
2021-08-26 19:47:59 +03:00
}
2021-10-29 02:22:26 +03:00
void PlaySecretTrack()
{
2021-10-29 06:34:40 +03:00
// Secret soundtrack should be always last one on a list.
2021-10-29 06:42:17 +03:00
PlaySoundTrack(SoundTracks.back().Name, SOUNDTRACK_PLAYTYPE::OneShot);
2021-10-29 02:22:26 +03:00
}
2021-08-26 19:47:59 +03:00
int GetShatterSound(int shatterID)
{
auto fxID = StaticObjects[shatterID].shatterSound;
2021-09-10 13:55:40 +03:00
if (fxID != -1 && fxID < NUM_SFX)
return fxID;
2021-08-26 19:47:59 +03:00
if (shatterID < 3)
return SFX_TR5_SMASH_WOOD;
else
return SFX_TR5_SMASH_GLASS;
2021-09-15 17:49:01 +03:00
}
void PlaySoundSources()
{
for (size_t i = 0; i < g_Level.SoundSources.size(); i++)
{
SOUND_SOURCE_INFO* sound = &g_Level.SoundSources[i];
short t = sound->flags & 31;
short group = t & 1;
group += t & 2;
group += ((t >> 2) & 1) * 3;
group += ((t >> 3) & 1) * 4;
group += ((t >> 4) & 1) * 5;
if (!FlipStats[group] && (sound->flags & 128) == 0)
continue;
else if (FlipStats[group] && (sound->flags & 128) == 0)
continue;
SoundEffect(sound->soundId, (PHD_3DPOS*)&sound->x, 0);
}
2018-08-19 09:46:58 +02:00
}