2014-04-02 17:46:58 +02:00
|
|
|
/*****************************************************************************
|
2024-01-01 12:52:28 +01:00
|
|
|
* Copyright (c) 2014-2024 OpenRCT2 developers
|
2014-04-02 17:46:58 +02:00
|
|
|
*
|
2018-06-15 14:07:34 +02:00
|
|
|
* For a complete list of all authors, please refer to contributors.md
|
|
|
|
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
|
2014-04-02 17:46:58 +02:00
|
|
|
*
|
2018-06-15 14:07:34 +02:00
|
|
|
* OpenRCT2 is licensed under the GNU General Public License version 3.
|
2014-04-02 17:46:58 +02:00
|
|
|
*****************************************************************************/
|
|
|
|
|
2018-06-22 22:57:16 +02:00
|
|
|
#include "audio.h"
|
|
|
|
|
2017-03-26 22:42:07 +02:00
|
|
|
#include "../Context.h"
|
2018-06-22 22:57:16 +02:00
|
|
|
#include "../OpenRCT2.h"
|
2022-08-29 11:50:42 +02:00
|
|
|
#include "../PlatformEnvironment.h"
|
2018-06-22 22:57:16 +02:00
|
|
|
#include "../config/Config.h"
|
2017-02-12 14:32:39 +01:00
|
|
|
#include "../core/File.h"
|
2020-11-07 13:42:04 +01:00
|
|
|
#include "../core/FileStream.h"
|
2017-02-11 13:45:53 +01:00
|
|
|
#include "../core/Memory.hpp"
|
2017-03-29 19:44:19 +02:00
|
|
|
#include "../core/String.hpp"
|
2021-11-25 22:47:24 +01:00
|
|
|
#include "../entity/Peep.h"
|
2018-01-06 00:45:53 +01:00
|
|
|
#include "../interface/Viewport.h"
|
2018-01-06 18:32:25 +01:00
|
|
|
#include "../localisation/Language.h"
|
2018-06-22 22:57:16 +02:00
|
|
|
#include "../localisation/StringIds.h"
|
2022-05-09 19:34:38 +02:00
|
|
|
#include "../object/AudioObject.h"
|
|
|
|
#include "../object/ObjectManager.h"
|
2017-12-31 13:21:34 +01:00
|
|
|
#include "../ride/Ride.h"
|
2021-01-09 21:54:02 +01:00
|
|
|
#include "../ride/RideAudio.h"
|
2024-04-26 20:35:16 +02:00
|
|
|
#include "../scenes/intro/IntroScene.h"
|
2018-06-22 22:57:16 +02:00
|
|
|
#include "../ui/UiContext.h"
|
2017-12-13 13:02:24 +01:00
|
|
|
#include "../util/Util.h"
|
2022-06-27 18:41:53 +02:00
|
|
|
#include "../world/Climate.h"
|
|
|
|
#include "AudioChannel.h"
|
2018-06-22 22:57:16 +02:00
|
|
|
#include "AudioContext.h"
|
|
|
|
#include "AudioMixer.h"
|
2017-02-11 13:45:53 +01:00
|
|
|
|
2018-08-12 13:50:40 +02:00
|
|
|
#include <algorithm>
|
2022-06-27 18:41:53 +02:00
|
|
|
#include <cmath>
|
2022-07-29 18:45:10 +02:00
|
|
|
#include <memory>
|
2020-10-03 01:16:08 +02:00
|
|
|
#include <vector>
|
2018-08-12 13:50:40 +02:00
|
|
|
|
2020-10-06 23:34:42 +02:00
|
|
|
namespace OpenRCT2::Audio
|
2017-03-29 19:44:19 +02:00
|
|
|
{
|
2020-10-06 23:34:42 +02:00
|
|
|
struct AudioParams
|
|
|
|
{
|
|
|
|
bool in_range;
|
|
|
|
int32_t volume;
|
|
|
|
int32_t pan;
|
|
|
|
};
|
2014-04-02 17:46:58 +02:00
|
|
|
|
2020-10-06 23:34:42 +02:00
|
|
|
static std::vector<std::string> _audioDevices;
|
|
|
|
static int32_t _currentAudioDevice = -1;
|
2022-05-09 19:34:38 +02:00
|
|
|
static ObjectEntryIndex _soundsAudioObjectEntryIndex = OBJECT_ENTRY_INDEX_NULL;
|
2022-10-12 21:41:32 +02:00
|
|
|
static ObjectEntryIndex _soundsAdditionalAudioObjectEntryIndex = OBJECT_ENTRY_INDEX_NULL;
|
2022-05-09 19:34:38 +02:00
|
|
|
static ObjectEntryIndex _titleAudioObjectEntryIndex = OBJECT_ENTRY_INDEX_NULL;
|
2015-11-17 01:42:23 +01:00
|
|
|
|
2020-10-06 23:34:42 +02:00
|
|
|
bool gGameSoundsOff = false;
|
|
|
|
int32_t gVolumeAdjustZoom = 0;
|
2014-04-03 18:52:25 +02:00
|
|
|
|
2022-06-27 18:41:53 +02:00
|
|
|
static std::shared_ptr<IAudioChannel> _titleMusicChannel = nullptr;
|
2014-09-01 08:12:46 +02:00
|
|
|
|
2024-04-21 00:12:13 +02:00
|
|
|
VehicleSound gVehicleSoundList[kMaxVehicleSounds];
|
2017-03-29 19:44:19 +02:00
|
|
|
|
2020-10-06 23:34:42 +02:00
|
|
|
bool IsAvailable()
|
2017-03-29 19:44:19 +02:00
|
|
|
{
|
2020-10-06 23:34:42 +02:00
|
|
|
if (_currentAudioDevice == -1)
|
|
|
|
return false;
|
|
|
|
if (gGameSoundsOff)
|
|
|
|
return false;
|
2022-10-16 17:50:42 +02:00
|
|
|
if (!gConfigSound.SoundEnabled)
|
2020-10-06 23:34:42 +02:00
|
|
|
return false;
|
|
|
|
if (gOpenRCT2Headless)
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
2017-03-29 19:44:19 +02:00
|
|
|
|
2020-10-06 23:34:42 +02:00
|
|
|
void Init()
|
|
|
|
{
|
2022-06-27 18:41:53 +02:00
|
|
|
auto audioContext = GetContext()->GetAudioContext();
|
2022-10-16 17:50:42 +02:00
|
|
|
if (gConfigSound.Device.empty())
|
2017-03-29 19:44:19 +02:00
|
|
|
{
|
2022-06-27 18:41:53 +02:00
|
|
|
audioContext->SetOutputDevice("");
|
2020-10-06 23:34:42 +02:00
|
|
|
_currentAudioDevice = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-10-16 17:50:42 +02:00
|
|
|
audioContext->SetOutputDevice(gConfigSound.Device);
|
2020-10-06 23:34:42 +02:00
|
|
|
|
|
|
|
PopulateDevices();
|
|
|
|
for (int32_t i = 0; i < GetDeviceCount(); i++)
|
2017-03-29 19:44:19 +02:00
|
|
|
{
|
2022-10-16 17:50:42 +02:00
|
|
|
if (_audioDevices[i] == gConfigSound.Device)
|
2020-10-06 23:34:42 +02:00
|
|
|
{
|
|
|
|
_currentAudioDevice = i;
|
|
|
|
}
|
2017-03-29 19:44:19 +02:00
|
|
|
}
|
|
|
|
}
|
2022-05-30 23:34:29 +02:00
|
|
|
LoadAudioObjects();
|
|
|
|
}
|
2022-05-09 19:34:38 +02:00
|
|
|
|
2022-05-30 23:34:29 +02:00
|
|
|
void LoadAudioObjects()
|
|
|
|
{
|
2022-05-09 19:34:38 +02:00
|
|
|
auto& objManager = GetContext()->GetObjectManager();
|
2022-08-29 11:50:42 +02:00
|
|
|
|
2024-04-21 00:12:13 +02:00
|
|
|
Object* baseAudio = objManager.LoadObject(AudioObjectIdentifiers::kRCT2);
|
2024-04-01 00:28:17 +02:00
|
|
|
if (baseAudio != nullptr)
|
2022-05-09 19:34:38 +02:00
|
|
|
{
|
2024-04-01 00:28:17 +02:00
|
|
|
_soundsAudioObjectEntryIndex = objManager.GetLoadedObjectEntryIndex(baseAudio);
|
2022-05-09 19:34:38 +02:00
|
|
|
}
|
2022-08-29 11:50:42 +02:00
|
|
|
|
2024-04-21 00:12:13 +02:00
|
|
|
objManager.LoadObject(AudioObjectIdentifiers::kOpenRCT2Additional);
|
2022-10-12 21:41:32 +02:00
|
|
|
_soundsAdditionalAudioObjectEntryIndex = objManager.GetLoadedObjectEntryIndex(
|
2024-04-21 00:12:13 +02:00
|
|
|
AudioObjectIdentifiers::kOpenRCT2Additional);
|
|
|
|
objManager.LoadObject(AudioObjectIdentifiers::kRCT2Circus);
|
2017-03-29 19:44:19 +02:00
|
|
|
}
|
|
|
|
|
2020-10-06 23:34:42 +02:00
|
|
|
void PopulateDevices()
|
2017-03-29 19:44:19 +02:00
|
|
|
{
|
2020-10-06 23:34:42 +02:00
|
|
|
auto audioContext = OpenRCT2::GetContext()->GetAudioContext();
|
|
|
|
std::vector<std::string> devices = audioContext->GetOutputDevices();
|
|
|
|
|
|
|
|
// Replace blanks with localised unknown string
|
|
|
|
for (auto& device : devices)
|
2017-03-29 19:44:19 +02:00
|
|
|
{
|
2020-10-06 23:34:42 +02:00
|
|
|
if (device.empty())
|
|
|
|
{
|
2023-01-17 00:12:35 +01:00
|
|
|
device = LanguageGetString(STR_OPTIONS_SOUND_VALUE_DEFAULT);
|
2020-10-06 23:34:42 +02:00
|
|
|
}
|
2017-03-29 19:44:19 +02:00
|
|
|
}
|
2015-11-18 00:05:24 +01:00
|
|
|
|
2017-06-12 19:00:15 +02:00
|
|
|
#ifndef __linux__
|
2020-10-06 23:34:42 +02:00
|
|
|
// The first device is always system default on Windows and macOS
|
2023-01-17 00:12:35 +01:00
|
|
|
std::string defaultDevice = LanguageGetString(STR_OPTIONS_SOUND_VALUE_DEFAULT);
|
2020-10-06 23:34:42 +02:00
|
|
|
devices.insert(devices.begin(), defaultDevice);
|
2017-03-29 19:44:19 +02:00
|
|
|
#endif
|
|
|
|
|
2020-10-06 23:34:42 +02:00
|
|
|
_audioDevices = devices;
|
2017-03-29 19:44:19 +02:00
|
|
|
}
|
2020-10-06 23:34:42 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the audio parameters to use when playing the specified sound at a virtual location.
|
|
|
|
* @param soundId The sound effect to be played.
|
|
|
|
* @param location The location at which the sound effect is to be played.
|
|
|
|
* @return The audio parameters to be used when playing this sound effect.
|
|
|
|
*/
|
2022-05-09 19:34:38 +02:00
|
|
|
static AudioParams GetParametersFromLocation(AudioObject* obj, uint32_t sampleIndex, const CoordsXYZ& location)
|
2017-12-20 02:14:29 +01:00
|
|
|
{
|
2020-10-06 23:34:42 +02:00
|
|
|
int32_t volumeDown = 0;
|
|
|
|
AudioParams params;
|
|
|
|
params.in_range = true;
|
|
|
|
params.volume = 0;
|
|
|
|
params.pan = 0;
|
|
|
|
|
2022-10-11 20:39:24 +02:00
|
|
|
auto element = MapGetSurfaceElementAt(location);
|
2021-09-24 20:05:50 +02:00
|
|
|
if (element != nullptr && (element->GetBaseZ()) - 5 > location.z)
|
2017-03-29 19:44:19 +02:00
|
|
|
{
|
2020-10-06 23:34:42 +02:00
|
|
|
volumeDown = 10;
|
|
|
|
}
|
|
|
|
|
2023-01-16 21:14:50 +01:00
|
|
|
uint8_t rotation = GetCurrentRotation();
|
2022-10-12 14:14:45 +02:00
|
|
|
auto pos2 = Translate3DTo2DWithZ(rotation, location);
|
2017-03-29 19:44:19 +02:00
|
|
|
|
2023-01-21 20:57:17 +01:00
|
|
|
Viewport* viewport = nullptr;
|
2023-01-16 21:13:42 +01:00
|
|
|
while ((viewport = WindowGetPreviousViewport(viewport)) != nullptr)
|
2020-10-06 23:34:42 +02:00
|
|
|
{
|
|
|
|
if (viewport->flags & VIEWPORT_FLAG_SOUND_ON)
|
2017-03-29 19:44:19 +02:00
|
|
|
{
|
2020-10-06 23:34:42 +02:00
|
|
|
int16_t vx = pos2.x - viewport->viewPos.x;
|
2022-02-26 23:14:59 +01:00
|
|
|
params.pan = viewport->pos.x + viewport->zoom.ApplyInversedTo(vx);
|
2022-05-09 19:34:38 +02:00
|
|
|
|
|
|
|
auto sampleModifier = obj->GetSampleModifier(sampleIndex);
|
|
|
|
auto viewModifier = ((viewport->zoom.ApplyTo(-1024) - 1) * (1 << volumeDown)) + 1;
|
|
|
|
params.volume = sampleModifier + viewModifier;
|
2020-10-06 23:34:42 +02:00
|
|
|
|
2020-10-13 03:25:11 +02:00
|
|
|
if (!viewport->Contains(pos2) || params.volume < -10000)
|
2020-10-06 23:34:42 +02:00
|
|
|
{
|
|
|
|
params.in_range = false;
|
|
|
|
return params;
|
|
|
|
}
|
2017-03-29 19:44:19 +02:00
|
|
|
}
|
|
|
|
}
|
2015-11-18 00:05:24 +01:00
|
|
|
|
2020-10-06 23:34:42 +02:00
|
|
|
return params;
|
|
|
|
}
|
2017-03-29 19:44:19 +02:00
|
|
|
|
2022-10-12 21:41:32 +02:00
|
|
|
static std::tuple<AudioObject*, uint32_t> GetAudioObjectAndSampleIndex(SoundId id)
|
2017-03-29 19:44:19 +02:00
|
|
|
{
|
2022-05-09 19:34:38 +02:00
|
|
|
auto& objManager = GetContext()->GetObjectManager();
|
2022-10-12 21:41:32 +02:00
|
|
|
AudioObject* audioObject{};
|
|
|
|
uint32_t sampleIndex = EnumValue(id);
|
|
|
|
if (id >= SoundId::LiftRMC)
|
|
|
|
{
|
|
|
|
audioObject = static_cast<AudioObject*>(
|
|
|
|
objManager.GetLoadedObject(ObjectType::Audio, _soundsAdditionalAudioObjectEntryIndex));
|
|
|
|
sampleIndex -= EnumValue(SoundId::LiftRMC);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
audioObject = static_cast<AudioObject*>(
|
|
|
|
objManager.GetLoadedObject(ObjectType::Audio, _soundsAudioObjectEntryIndex));
|
|
|
|
}
|
|
|
|
return std::make_tuple(audioObject, sampleIndex);
|
2022-05-09 19:34:38 +02:00
|
|
|
}
|
2017-03-29 19:44:19 +02:00
|
|
|
|
2022-05-09 19:34:38 +02:00
|
|
|
static void Play(IAudioSource* audioSource, int32_t volume, int32_t pan)
|
|
|
|
{
|
2020-10-06 23:34:42 +02:00
|
|
|
int32_t mixerPan = 0;
|
|
|
|
if (pan != AUDIO_PLAY_AT_CENTRE)
|
|
|
|
{
|
|
|
|
int32_t x2 = pan << 16;
|
|
|
|
uint16_t screenWidth = std::max<int32_t>(64, OpenRCT2::GetContext()->GetUiContext()->GetWidth());
|
|
|
|
mixerPan = ((x2 / screenWidth) - 0x8000) >> 4;
|
|
|
|
}
|
2014-05-06 23:04:09 +02:00
|
|
|
|
2022-06-27 18:41:53 +02:00
|
|
|
CreateAudioChannel(audioSource, MixerGroup::Sound, false, DStoMixerVolume(volume), DStoMixerPan(mixerPan), 1, true);
|
2022-05-09 19:34:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void Play3D(SoundId soundId, const CoordsXYZ& loc)
|
|
|
|
{
|
|
|
|
if (!IsAvailable())
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Get sound from base object
|
2022-10-12 21:41:32 +02:00
|
|
|
auto [baseAudioObject, sampleIndex] = GetAudioObjectAndSampleIndex(soundId);
|
2022-05-09 19:34:38 +02:00
|
|
|
if (baseAudioObject != nullptr)
|
|
|
|
{
|
2022-10-12 21:41:32 +02:00
|
|
|
auto params = GetParametersFromLocation(baseAudioObject, sampleIndex, loc);
|
2022-05-09 19:34:38 +02:00
|
|
|
if (params.in_range)
|
|
|
|
{
|
2022-10-12 21:41:32 +02:00
|
|
|
auto source = baseAudioObject->GetSample(sampleIndex);
|
2022-05-09 19:34:38 +02:00
|
|
|
if (source != nullptr)
|
|
|
|
{
|
|
|
|
Play(source, params.volume, params.pan);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Play(SoundId soundId, int32_t volume, int32_t pan)
|
|
|
|
{
|
|
|
|
if (!IsAvailable())
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Get sound from base object
|
2022-10-12 21:41:32 +02:00
|
|
|
auto [baseAudioObject, sampleIndex] = GetAudioObjectAndSampleIndex(soundId);
|
2022-05-09 19:34:38 +02:00
|
|
|
if (baseAudioObject != nullptr)
|
|
|
|
{
|
2022-10-12 21:41:32 +02:00
|
|
|
auto source = baseAudioObject->GetSample(sampleIndex);
|
2022-05-09 19:34:38 +02:00
|
|
|
if (source != nullptr)
|
|
|
|
{
|
|
|
|
Play(source, volume, pan);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-21 15:53:29 +02:00
|
|
|
static bool IsRCT1TitleMusicAvailable()
|
2022-05-09 19:34:38 +02:00
|
|
|
{
|
2023-05-21 15:53:29 +02:00
|
|
|
auto env = GetContext()->GetPlatformEnvironment();
|
|
|
|
auto rct1path = env->GetDirectoryPath(DIRBASE::RCT1);
|
|
|
|
return !rct1path.empty();
|
|
|
|
}
|
|
|
|
|
|
|
|
static std::map<TitleMusicKind, std::string_view> GetAvailableMusicMap()
|
|
|
|
{
|
|
|
|
auto musicMap = std::map<TitleMusicKind, std::string_view>{
|
2024-04-21 00:12:13 +02:00
|
|
|
{ TitleMusicKind::OpenRCT2, AudioObjectIdentifiers::kOpenRCT2Title },
|
|
|
|
{ TitleMusicKind::RCT2, AudioObjectIdentifiers::kRCT2Title },
|
2023-05-15 00:42:47 +02:00
|
|
|
};
|
2023-05-21 15:53:29 +02:00
|
|
|
|
|
|
|
if (IsRCT1TitleMusicAvailable())
|
|
|
|
{
|
2024-04-21 00:12:13 +02:00
|
|
|
musicMap.emplace(TitleMusicKind::RCT1, AudioObjectIdentifiers::kRCT1Title);
|
2023-05-21 15:53:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return musicMap;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ObjectEntryDescriptor GetTitleMusicDescriptor(TitleMusicKind musicKind)
|
|
|
|
{
|
|
|
|
auto musicMap = GetAvailableMusicMap();
|
|
|
|
auto it = musicMap.find(musicKind);
|
|
|
|
if (musicKind == TitleMusicKind::Random)
|
|
|
|
{
|
|
|
|
it = std::next(musicMap.begin(), UtilRand() % musicMap.size());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (it != musicMap.end())
|
2022-05-09 19:34:38 +02:00
|
|
|
{
|
2023-05-21 15:53:29 +02:00
|
|
|
return ObjectEntryDescriptor(ObjectType::Audio, it->second);
|
2022-05-09 19:34:38 +02:00
|
|
|
}
|
2017-03-29 19:44:19 +02:00
|
|
|
|
2023-05-21 15:53:29 +02:00
|
|
|
// No music descriptor for the current setting, intentional for TitleMusicKind::None
|
|
|
|
return {};
|
2023-05-05 15:38:03 +02:00
|
|
|
}
|
|
|
|
|
2020-10-06 23:34:42 +02:00
|
|
|
void PlayTitleMusic()
|
2017-03-29 19:44:19 +02:00
|
|
|
{
|
2024-04-26 20:35:16 +02:00
|
|
|
if (gGameSoundsOff || !(gScreenFlags & SCREEN_FLAGS_TITLE_DEMO) || IntroIsPlaying())
|
2020-10-06 23:34:42 +02:00
|
|
|
{
|
|
|
|
StopTitleMusic();
|
|
|
|
return;
|
|
|
|
}
|
2017-03-29 19:44:19 +02:00
|
|
|
|
2022-06-27 18:41:53 +02:00
|
|
|
if (_titleMusicChannel != nullptr && !_titleMusicChannel->IsDone())
|
2020-10-06 23:34:42 +02:00
|
|
|
{
|
2018-06-22 22:57:16 +02:00
|
|
|
return;
|
2020-10-06 23:34:42 +02:00
|
|
|
}
|
2017-03-29 19:44:19 +02:00
|
|
|
|
2022-05-09 19:34:38 +02:00
|
|
|
// Load title sequence audio object
|
2023-05-21 15:53:29 +02:00
|
|
|
auto descriptor = GetTitleMusicDescriptor(gConfigSound.TitleMusic);
|
2022-05-09 19:34:38 +02:00
|
|
|
auto& objManager = GetContext()->GetObjectManager();
|
2022-05-17 00:02:03 +02:00
|
|
|
auto* audioObject = static_cast<AudioObject*>(objManager.LoadObject(descriptor));
|
2022-05-09 19:34:38 +02:00
|
|
|
if (audioObject != nullptr)
|
2020-10-06 23:34:42 +02:00
|
|
|
{
|
2022-05-09 19:34:38 +02:00
|
|
|
_titleAudioObjectEntryIndex = objManager.GetLoadedObjectEntryIndex(audioObject);
|
2020-10-06 23:34:42 +02:00
|
|
|
|
2022-05-09 19:34:38 +02:00
|
|
|
// Play first sample from object
|
|
|
|
auto source = audioObject->GetSample(0);
|
|
|
|
if (source != nullptr)
|
|
|
|
{
|
2022-06-27 18:41:53 +02:00
|
|
|
_titleMusicChannel = CreateAudioChannel(source, MixerGroup::TitleMusic, true);
|
2022-05-09 19:34:38 +02:00
|
|
|
}
|
2020-10-06 23:34:42 +02:00
|
|
|
}
|
2017-03-29 19:44:19 +02:00
|
|
|
}
|
2014-05-06 23:04:09 +02:00
|
|
|
|
2020-10-06 23:34:42 +02:00
|
|
|
void StopAll()
|
|
|
|
{
|
|
|
|
StopTitleMusic();
|
|
|
|
StopVehicleSounds();
|
2021-01-20 22:10:17 +01:00
|
|
|
RideAudio::StopAllChannels();
|
2023-01-17 22:16:36 +01:00
|
|
|
PeepStopCrowdNoise();
|
2022-06-27 18:41:53 +02:00
|
|
|
ClimateStopWeatherSound();
|
2020-10-06 23:34:42 +02:00
|
|
|
}
|
2020-10-03 01:16:08 +02:00
|
|
|
|
2020-10-06 23:34:42 +02:00
|
|
|
int32_t GetDeviceCount()
|
|
|
|
{
|
|
|
|
return static_cast<int32_t>(_audioDevices.size());
|
|
|
|
}
|
2020-10-03 01:16:08 +02:00
|
|
|
|
2020-10-06 23:34:42 +02:00
|
|
|
const std::string& GetDeviceName(int32_t index)
|
|
|
|
{
|
2020-10-07 00:04:47 +02:00
|
|
|
if (index < 0 || index >= GetDeviceCount())
|
|
|
|
{
|
|
|
|
static std::string InvalidDevice = "Invalid Device";
|
|
|
|
return InvalidDevice;
|
|
|
|
}
|
2020-10-06 23:34:42 +02:00
|
|
|
return _audioDevices[index];
|
|
|
|
}
|
2020-10-03 01:16:08 +02:00
|
|
|
|
2020-10-06 23:34:42 +02:00
|
|
|
int32_t GetCurrentDeviceIndex()
|
2017-03-29 19:44:19 +02:00
|
|
|
{
|
2020-10-06 23:34:42 +02:00
|
|
|
return _currentAudioDevice;
|
2017-03-29 19:44:19 +02:00
|
|
|
}
|
2014-08-27 00:34:34 +02:00
|
|
|
|
2020-10-06 23:34:42 +02:00
|
|
|
void StopTitleMusic()
|
2017-03-29 19:44:19 +02:00
|
|
|
{
|
2022-06-27 18:41:53 +02:00
|
|
|
if (_titleMusicChannel != nullptr)
|
2020-10-06 23:34:42 +02:00
|
|
|
{
|
2022-06-27 18:41:53 +02:00
|
|
|
_titleMusicChannel->Stop();
|
|
|
|
_titleMusicChannel = nullptr;
|
2020-10-06 23:34:42 +02:00
|
|
|
}
|
2022-05-09 19:34:38 +02:00
|
|
|
|
|
|
|
// Unload the audio object
|
|
|
|
if (_titleAudioObjectEntryIndex != OBJECT_ENTRY_INDEX_NULL)
|
|
|
|
{
|
|
|
|
auto& objManager = GetContext()->GetObjectManager();
|
2022-05-17 00:02:03 +02:00
|
|
|
auto* obj = objManager.GetLoadedObject(ObjectType::Audio, _titleAudioObjectEntryIndex);
|
2022-05-09 19:34:38 +02:00
|
|
|
if (obj != nullptr)
|
|
|
|
{
|
|
|
|
objManager.UnloadObjects({ obj->GetDescriptor() });
|
|
|
|
}
|
|
|
|
_titleAudioObjectEntryIndex = OBJECT_ENTRY_INDEX_NULL;
|
|
|
|
}
|
2017-03-29 19:44:19 +02:00
|
|
|
}
|
2015-10-17 19:37:08 +02:00
|
|
|
|
2020-10-06 23:34:42 +02:00
|
|
|
void InitRideSoundsAndInfo()
|
2017-03-29 19:44:19 +02:00
|
|
|
{
|
2021-01-09 21:54:02 +01:00
|
|
|
InitRideSounds(0);
|
2017-03-29 19:44:19 +02:00
|
|
|
}
|
2014-10-13 02:29:43 +02:00
|
|
|
|
2020-10-06 23:34:42 +02:00
|
|
|
void InitRideSounds(int32_t device)
|
2017-03-29 19:44:19 +02:00
|
|
|
{
|
2020-10-06 23:34:42 +02:00
|
|
|
Close();
|
|
|
|
for (auto& vehicleSound : gVehicleSoundList)
|
|
|
|
{
|
2024-04-21 00:12:13 +02:00
|
|
|
vehicleSound.id = kSoundIdNull;
|
2020-10-06 23:34:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
_currentAudioDevice = device;
|
2022-10-16 18:00:03 +02:00
|
|
|
ConfigSaveDefault();
|
2017-03-29 19:44:19 +02:00
|
|
|
}
|
|
|
|
|
2020-10-06 23:34:42 +02:00
|
|
|
void Close()
|
2017-03-29 19:44:19 +02:00
|
|
|
{
|
2023-01-17 22:16:36 +01:00
|
|
|
PeepStopCrowdNoise();
|
2020-10-06 23:34:42 +02:00
|
|
|
StopTitleMusic();
|
2021-01-20 22:10:17 +01:00
|
|
|
RideAudio::StopAllChannels();
|
2022-06-27 18:41:53 +02:00
|
|
|
ClimateStopWeatherSound();
|
2020-10-06 23:34:42 +02:00
|
|
|
_currentAudioDevice = -1;
|
2017-03-29 19:44:19 +02:00
|
|
|
}
|
2014-08-24 22:30:48 +02:00
|
|
|
|
2020-10-06 23:34:42 +02:00
|
|
|
void ToggleAllSounds()
|
2017-03-29 19:44:19 +02:00
|
|
|
{
|
2022-10-16 17:50:42 +02:00
|
|
|
gConfigSound.MasterSoundEnabled = !gConfigSound.MasterSoundEnabled;
|
|
|
|
if (gConfigSound.MasterSoundEnabled)
|
2020-10-06 23:34:42 +02:00
|
|
|
{
|
|
|
|
Resume();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Pause();
|
|
|
|
}
|
|
|
|
|
2023-01-16 21:13:42 +01:00
|
|
|
WindowInvalidateByClass(WindowClass::Options);
|
2017-03-29 19:44:19 +02:00
|
|
|
}
|
2020-10-06 23:34:42 +02:00
|
|
|
|
|
|
|
void Pause()
|
2017-03-29 19:44:19 +02:00
|
|
|
{
|
2020-10-06 23:34:42 +02:00
|
|
|
gGameSoundsOff = true;
|
|
|
|
StopVehicleSounds();
|
2021-01-20 22:10:17 +01:00
|
|
|
RideAudio::StopAllChannels();
|
2023-01-17 22:16:36 +01:00
|
|
|
PeepStopCrowdNoise();
|
2022-06-27 18:41:53 +02:00
|
|
|
ClimateStopWeatherSound();
|
2023-03-11 17:30:40 +01:00
|
|
|
StopTitleMusic();
|
2017-03-29 19:44:19 +02:00
|
|
|
}
|
2018-08-17 21:32:48 +02:00
|
|
|
|
2020-10-06 23:34:42 +02:00
|
|
|
void Resume()
|
|
|
|
{
|
|
|
|
gGameSoundsOff = false;
|
2023-03-11 17:30:40 +01:00
|
|
|
PlayTitleMusic();
|
2020-10-06 23:34:42 +02:00
|
|
|
}
|
2017-03-29 19:44:19 +02:00
|
|
|
|
2020-10-06 23:34:42 +02:00
|
|
|
void StopVehicleSounds()
|
2017-03-29 19:44:19 +02:00
|
|
|
{
|
2020-10-06 23:34:42 +02:00
|
|
|
if (!IsAvailable())
|
|
|
|
return;
|
|
|
|
|
|
|
|
for (auto& vehicleSound : gVehicleSoundList)
|
2017-03-29 19:44:19 +02:00
|
|
|
{
|
2024-04-21 00:12:13 +02:00
|
|
|
if (vehicleSound.id != kSoundIdNull)
|
2017-03-29 19:44:19 +02:00
|
|
|
{
|
2024-04-21 00:12:13 +02:00
|
|
|
vehicleSound.id = kSoundIdNull;
|
2020-10-06 23:34:42 +02:00
|
|
|
if (vehicleSound.TrackSound.Id != SoundId::Null)
|
|
|
|
{
|
2022-06-27 18:41:53 +02:00
|
|
|
vehicleSound.TrackSound.Channel->Stop();
|
2020-10-06 23:34:42 +02:00
|
|
|
}
|
|
|
|
if (vehicleSound.OtherSound.Id != SoundId::Null)
|
|
|
|
{
|
2022-06-27 18:41:53 +02:00
|
|
|
vehicleSound.OtherSound.Channel->Stop();
|
2020-10-06 23:34:42 +02:00
|
|
|
}
|
2017-03-29 19:44:19 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-10-06 23:34:42 +02:00
|
|
|
|
2022-06-27 18:41:53 +02:00
|
|
|
static IAudioMixer* GetMixer()
|
|
|
|
{
|
|
|
|
auto audioContext = GetContext()->GetAudioContext();
|
|
|
|
return audioContext->GetMixer();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::shared_ptr<IAudioChannel> CreateAudioChannel(
|
|
|
|
SoundId id, bool loop, int32_t volume, float pan, double rate, bool forget)
|
|
|
|
{
|
|
|
|
// Get sound from base object
|
2022-10-12 21:41:32 +02:00
|
|
|
auto [baseAudioObject, sampleIndex] = GetAudioObjectAndSampleIndex(id);
|
2022-06-27 18:41:53 +02:00
|
|
|
if (baseAudioObject != nullptr)
|
|
|
|
{
|
2022-10-12 21:41:32 +02:00
|
|
|
auto source = baseAudioObject->GetSample(sampleIndex);
|
2022-06-27 18:41:53 +02:00
|
|
|
if (source != nullptr)
|
|
|
|
{
|
|
|
|
return CreateAudioChannel(source, MixerGroup::Sound, loop, volume, pan, rate, forget);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::shared_ptr<IAudioChannel> CreateAudioChannel(
|
|
|
|
IAudioSource* source, MixerGroup group, bool loop, int32_t volume, float pan, double rate, bool forget)
|
|
|
|
{
|
|
|
|
auto* mixer = GetMixer();
|
|
|
|
if (mixer == nullptr)
|
|
|
|
{
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
mixer->Lock();
|
2024-03-01 21:23:29 +01:00
|
|
|
auto channel = mixer->Play(source, loop ? kMixerLoopInfinite : kMixerLoopNone, forget);
|
2022-06-27 18:41:53 +02:00
|
|
|
if (channel != nullptr)
|
|
|
|
{
|
|
|
|
channel->SetGroup(group);
|
|
|
|
channel->SetVolume(volume);
|
|
|
|
channel->SetPan(pan);
|
|
|
|
channel->SetRate(rate);
|
|
|
|
channel->UpdateOldVolume();
|
|
|
|
}
|
|
|
|
mixer->Unlock();
|
|
|
|
return channel;
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t DStoMixerVolume(int32_t volume)
|
|
|
|
{
|
2024-03-01 21:23:29 +01:00
|
|
|
return static_cast<int32_t>(kMixerVolumeMax * (std::pow(10.0f, static_cast<float>(volume) / 2000)));
|
2022-06-27 18:41:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
float DStoMixerPan(int32_t pan)
|
|
|
|
{
|
|
|
|
constexpr int32_t DSBPAN_LEFT = -10000;
|
|
|
|
constexpr int32_t DSBPAN_RIGHT = 10000;
|
|
|
|
return ((static_cast<float>(pan) + -DSBPAN_LEFT) / DSBPAN_RIGHT) / 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
double DStoMixerRate(int32_t frequency)
|
|
|
|
{
|
|
|
|
return static_cast<double>(frequency) / 22050;
|
|
|
|
}
|
|
|
|
|
2020-10-06 23:34:42 +02:00
|
|
|
} // namespace OpenRCT2::Audio
|