From a2e6691ac286ebbdaedffcd5b0fd72e95d609017 Mon Sep 17 00:00:00 2001 From: Ted John Date: Mon, 9 May 2022 18:34:38 +0100 Subject: [PATCH] Add new audio objects for loading sounds --- data/language/en-GB.txt | 4 +- src/openrct2-ui/audio/AudioChannel.cpp | 10 - src/openrct2-ui/audio/AudioContext.cpp | 80 +- src/openrct2-ui/audio/AudioContext.h | 18 +- src/openrct2-ui/audio/AudioMixer.cpp | 909 +++++++++----------- src/openrct2-ui/audio/AudioMixer.h | 84 ++ src/openrct2-ui/audio/FileAudioSource.cpp | 59 +- src/openrct2-ui/audio/MemoryAudioSource.cpp | 161 ++-- src/openrct2-ui/libopenrct2ui.vcxproj | 3 +- src/openrct2-ui/windows/Options.cpp | 68 +- src/openrct2/Context.cpp | 52 -- src/openrct2/Context.h | 65 -- src/openrct2/Editor.cpp | 2 +- src/openrct2/audio/Audio.cpp | 217 ++--- src/openrct2/audio/AudioChannel.h | 2 - src/openrct2/audio/AudioContext.h | 2 + src/openrct2/audio/AudioMixer.cpp | 90 +- src/openrct2/audio/AudioMixer.h | 13 +- src/openrct2/audio/AudioSource.h | 2 +- src/openrct2/audio/DummyAudioContext.cpp | 10 + src/openrct2/audio/NullAudioSource.cpp | 4 + src/openrct2/audio/audio.h | 12 + src/openrct2/config/Config.cpp | 4 +- src/openrct2/config/Config.h | 11 +- src/openrct2/core/Path.cpp | 6 + src/openrct2/core/Path.hpp | 1 + src/openrct2/core/Zip.cpp | 5 + src/openrct2/entity/Peep.cpp | 6 +- src/openrct2/libopenrct2.vcxproj | 4 + src/openrct2/localisation/StringIds.h | 2 - src/openrct2/network/NetworkBase.cpp | 22 - src/openrct2/object/AudioObject.cpp | 46 + src/openrct2/object/AudioObject.h | 31 + src/openrct2/object/AudioSampleTable.cpp | 220 +++++ src/openrct2/object/AudioSampleTable.h | 54 ++ src/openrct2/object/Object.cpp | 15 + src/openrct2/object/Object.h | 37 + src/openrct2/object/ObjectFactory.cpp | 17 +- src/openrct2/object/ObjectLimits.h | 22 +- src/openrct2/object/ObjectList.cpp | 1 + src/openrct2/object/ObjectManager.cpp | 290 ++++--- src/openrct2/object/ObjectManager.h | 2 +- src/openrct2/park/ParkFile.cpp | 4 +- src/openrct2/ride/RideAudio.cpp | 25 +- src/openrct2/ride/TrackDesign.cpp | 2 +- src/openrct2/ride/Vehicle.cpp | 41 +- 46 files changed, 1583 insertions(+), 1152 deletions(-) create mode 100644 src/openrct2-ui/audio/AudioMixer.h create mode 100644 src/openrct2/object/AudioObject.cpp create mode 100644 src/openrct2/object/AudioObject.h create mode 100644 src/openrct2/object/AudioSampleTable.cpp create mode 100644 src/openrct2/object/AudioSampleTable.h diff --git a/data/language/en-GB.txt b/data/language/en-GB.txt index dd92f8606a..675e09bead 100644 --- a/data/language/en-GB.txt +++ b/data/language/en-GB.txt @@ -1952,8 +1952,6 @@ STR_2738 :Title screen music: STR_2739 :None STR_2740 :RollerCoaster Tycoon 1 STR_2741 :RollerCoaster Tycoon 2 -STR_2742 :css50.dat not found -STR_2743 :Copy ‘data/css17.dat’ from your RCT1 installation to ‘data/css50.dat’ in your RCT2 installation, or make sure the path to RCT1 in the Advanced tab is correct. STR_2749 :My new scenario # New strings used in the cheats window previously these were ??? STR_2750 :Move all items to top @@ -2994,7 +2992,7 @@ STR_5832 :Show height as generic units instead of measurement format set unde STR_5833 :Changes what date format is used STR_5834 :Select which audio device OpenRCT2 will use STR_5835 :Mute the game if the window loses focus -STR_5836 :Select music to use on the main menu.{NEWLINE}Selecting RCT1 theme requires that you copy ‘data/css17.dat’ from your RCT1 game folder to ‘data/css50.dat’ in your RCT2 folder, or set the path to RCT1 in the Miscellaneous tab. +STR_5836 :Select music to use on the main menu. STR_5837 :Create and manage custom UI themes STR_5838 :Show a separate button for the finance window in the toolbar STR_5839 :Show a separate button for the research and development window in the toolbar diff --git a/src/openrct2-ui/audio/AudioChannel.cpp b/src/openrct2-ui/audio/AudioChannel.cpp index ad4875f4c2..920e36c234 100644 --- a/src/openrct2-ui/audio/AudioChannel.cpp +++ b/src/openrct2-ui/audio/AudioChannel.cpp @@ -42,7 +42,6 @@ namespace OpenRCT2::Audio bool _stopping = false; bool _done = true; bool _deleteondone = false; - bool _deletesourceondone = false; public: AudioChannelImpl() @@ -59,10 +58,6 @@ namespace OpenRCT2::Audio speex_resampler_destroy(_resampler); _resampler = nullptr; } - if (_deletesourceondone) - { - delete _source; - } } [[nodiscard]] IAudioSource* GetSource() const override @@ -214,11 +209,6 @@ namespace OpenRCT2::Audio _deleteondone = value; } - void SetDeleteSourceOnDone(bool value) override - { - _deletesourceondone = value; - } - [[nodiscard]] bool IsPlaying() const override { return !_done; diff --git a/src/openrct2-ui/audio/AudioContext.cpp b/src/openrct2-ui/audio/AudioContext.cpp index 6384c5ac46..c803692ffd 100644 --- a/src/openrct2-ui/audio/AudioContext.cpp +++ b/src/openrct2-ui/audio/AudioContext.cpp @@ -10,9 +10,12 @@ #include "AudioContext.h" #include "../SDLException.h" +#include "AudioMixer.h" #include +#include #include +#include #include #include @@ -21,7 +24,7 @@ namespace OpenRCT2::Audio class AudioContext final : public IAudioContext { private: - IAudioMixer* _audioMixer = nullptr; + std::unique_ptr _audioMixer; public: AudioContext() @@ -30,18 +33,17 @@ namespace OpenRCT2::Audio { SDLException::Throw("SDL_Init(SDL_INIT_AUDIO)"); } - _audioMixer = AudioMixer::Create(); + _audioMixer = std::make_unique(); } ~AudioContext() override { - delete _audioMixer; SDL_QuitSubSystem(SDL_INIT_AUDIO); } IAudioMixer* GetMixer() override { - return _audioMixer; + return _audioMixer.get(); } std::vector GetOutputDevices() override @@ -65,14 +67,42 @@ namespace OpenRCT2::Audio _audioMixer->Init(szDeviceName); } + IAudioSource* CreateStreamFromCSS(const std::string& path, uint32_t index) override + { + auto& format = _audioMixer->GetFormat(); + return AddSource(AudioSource::CreateMemoryFromCSS1(path, index, &format)); + } + IAudioSource* CreateStreamFromWAV(const std::string& path) override { - return AudioSource::CreateStreamFromWAV(path); + return AddSource(AudioSource::CreateStreamFromWAV(path)); + } + + IAudioSource* CreateStreamFromCSS(std::unique_ptr stream, uint32_t index) override + { + auto rw = StreamToSDL2(std::move(stream)); + if (rw == nullptr) + { + return nullptr; + } + + auto& format = _audioMixer->GetFormat(); + return AddSource(AudioSource::CreateMemoryFromCSS1(rw, index, &format)); } IAudioSource* CreateStreamFromWAV(std::unique_ptr stream) override { - return AudioSource::CreateStreamFromWAV(std::move(stream)); + constexpr size_t STREAM_MIN_SIZE = 2 * 1024 * 1024; // 2 MiB + auto loadIntoRAM = stream->GetLength() < STREAM_MIN_SIZE; + auto rw = StreamToSDL2(std::move(stream)); + if (rw == nullptr) + { + return nullptr; + } + + return AddSource( + loadIntoRAM ? AudioSource::CreateMemoryFromWAV(rw, &_audioMixer->GetFormat()) + : AudioSource::CreateStreamFromWAV(rw)); } void StartTitleMusic() override @@ -120,6 +150,44 @@ namespace OpenRCT2::Audio void StopVehicleSounds() override { } + + private: + IAudioSource* AddSource(std::unique_ptr source) + { + return _audioMixer->AddSource(std::move(source)); + } + + static SDL_RWops* StreamToSDL2(std::unique_ptr stream) + { + auto rw = SDL_AllocRW(); + if (rw == nullptr) + return nullptr; + *rw = {}; + + rw->type = SDL_RWOPS_UNKNOWN; + rw->hidden.unknown.data1 = stream.release(); + rw->seek = [](SDL_RWops* ctx, Sint64 offset, int whence) { + auto ptr = static_cast(ctx->hidden.unknown.data1); + ptr->Seek(offset, whence); + return static_cast(ptr->GetPosition()); + }; + rw->read = [](SDL_RWops* ctx, void* buf, size_t size, size_t maxnum) { + auto ptr = static_cast(ctx->hidden.unknown.data1); + return static_cast(ptr->TryRead(buf, size * maxnum) / size); + }; + rw->size = [](SDL_RWops* ctx) { + auto ptr = static_cast(ctx->hidden.unknown.data1); + return static_cast(ptr->GetLength()); + }; + rw->close = [](SDL_RWops* ctx) { + auto ptr = static_cast(ctx->hidden.unknown.data1); + delete ptr; + ctx->hidden.unknown.data1 = nullptr; + delete ctx; + return 0; + }; + return rw; + } }; std::unique_ptr CreateAudioContext() diff --git a/src/openrct2-ui/audio/AudioContext.h b/src/openrct2-ui/audio/AudioContext.h index dd175aa7ea..d2e4ef32e1 100644 --- a/src/openrct2-ui/audio/AudioContext.h +++ b/src/openrct2-ui/audio/AudioContext.h @@ -50,6 +50,7 @@ namespace OpenRCT2::Audio struct ISDLAudioSource : public IAudioSource { + [[nodiscard]] virtual bool IsReleased() const abstract; [[nodiscard]] virtual AudioFormat GetFormat() const abstract; }; @@ -62,11 +63,13 @@ namespace OpenRCT2::Audio namespace AudioSource { - IAudioSource* CreateMemoryFromCSS1(const std::string& path, size_t index, const AudioFormat* targetFormat = nullptr); - IAudioSource* CreateMemoryFromWAV(const std::string& path, const AudioFormat* targetFormat = nullptr); - IAudioSource* CreateStreamFromWAV(const std::string& path); - IAudioSource* CreateStreamFromWAV(SDL_RWops* rw); - IAudioSource* CreateStreamFromWAV(std::unique_ptr stream); + std::unique_ptr CreateMemoryFromCSS1( + const std::string& path, size_t index, const AudioFormat* targetFormat = nullptr); + std::unique_ptr CreateMemoryFromCSS1( + SDL_RWops* rw, size_t index, const AudioFormat* targetFormat = nullptr); + std::unique_ptr CreateMemoryFromWAV(SDL_RWops* rw, const AudioFormat* targetFormat = nullptr); + std::unique_ptr CreateStreamFromWAV(const std::string& path); + std::unique_ptr CreateStreamFromWAV(SDL_RWops* rw); } // namespace AudioSource namespace AudioChannel @@ -74,11 +77,6 @@ namespace OpenRCT2::Audio ISDLAudioChannel* Create(); } - namespace AudioMixer - { - IAudioMixer* Create(); - } - [[nodiscard]] std::unique_ptr CreateAudioContext(); } // namespace OpenRCT2::Audio diff --git a/src/openrct2-ui/audio/AudioMixer.cpp b/src/openrct2-ui/audio/AudioMixer.cpp index 3af92b91f7..8fea1a5642 100644 --- a/src/openrct2-ui/audio/AudioMixer.cpp +++ b/src/openrct2-ui/audio/AudioMixer.cpp @@ -1,5 +1,5 @@ /***************************************************************************** - * Copyright (c) 2014-2020 OpenRCT2 developers + * Copyright (c) 2014-2022 OpenRCT2 developers * * For a complete list of all authors, please refer to contributors.md * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 @@ -7,510 +7,433 @@ * OpenRCT2 is licensed under the GNU General Public License version 3. *****************************************************************************/ -#include "AudioContext.h" -#include "AudioFormat.h" +#include "AudioMixer.h" -#include #include #include -#include -#include #include -#include -#include -#include -#include -#include #include #include -#include -namespace OpenRCT2::Audio +using namespace OpenRCT2::Audio; + +AudioMixer::AudioMixer() { - class AudioMixerImpl final : public IAudioMixer - { - private: - IAudioSource* _nullSource = nullptr; + _nullSource = AudioSource::CreateNull(); +} - SDL_AudioDeviceID _deviceId = 0; - AudioFormat _format = {}; - std::list _channels; - float _volume = 1.0f; - float _adjustSoundVolume = 0.0f; - float _adjustMusicVolume = 0.0f; - uint8_t _settingSoundVolume = 0xFF; - uint8_t _settingMusicVolume = 0xFF; +AudioMixer::~AudioMixer() +{ + Close(); + delete _nullSource; +} - IAudioSource* _css1Sources[RCT2SoundCount] = { nullptr }; - IAudioSource* _musicSources[PATH_ID_END] = { nullptr }; +void AudioMixer::Init(const char* device) +{ + Close(); - std::vector _channelBuffer; - std::vector _convertBuffer; - std::vector _effectBuffer; - - public: - AudioMixerImpl() - { - _nullSource = AudioSource::CreateNull(); - } - - ~AudioMixerImpl() override - { - AudioMixerImpl::Close(); - delete _nullSource; - } - - void Init(const char* device) override - { - Close(); - - SDL_AudioSpec want = {}; - want.freq = 22050; - want.format = AUDIO_S16SYS; - want.channels = 2; - want.samples = 2048; - want.callback = [](void* arg, uint8_t* dst, int32_t length) -> void { - auto mixer = static_cast(arg); - mixer->GetNextAudioChunk(dst, static_cast(length)); - }; - want.userdata = this; - - SDL_AudioSpec have; - _deviceId = SDL_OpenAudioDevice(device, 0, &want, &have, 0); - _format.format = have.format; - _format.channels = have.channels; - _format.freq = have.freq; - - LoadAllSounds(); - - SDL_PauseAudioDevice(_deviceId, 0); - } - - void Close() override - { - // Free channels - Lock(); - for (IAudioChannel* channel : _channels) - { - delete channel; - } - _channels.clear(); - Unlock(); - - SDL_CloseAudioDevice(_deviceId); - - // Free sources - for (size_t i = 0; i < std::size(_css1Sources); i++) - { - if (_css1Sources[i] != _nullSource) - { - SafeDelete(_css1Sources[i]); - } - } - for (size_t i = 0; i < std::size(_musicSources); i++) - { - if (_musicSources[i] != _nullSource) - { - SafeDelete(_musicSources[i]); - } - } - - // Free buffers - _channelBuffer.clear(); - _channelBuffer.shrink_to_fit(); - _convertBuffer.clear(); - _convertBuffer.shrink_to_fit(); - _effectBuffer.clear(); - _effectBuffer.shrink_to_fit(); - } - - void Lock() override - { - SDL_LockAudioDevice(_deviceId); - } - - void Unlock() override - { - SDL_UnlockAudioDevice(_deviceId); - } - - IAudioChannel* Play(IAudioSource* source, int32_t loop, bool deleteondone, bool deletesourceondone) override - { - Lock(); - ISDLAudioChannel* channel = AudioChannel::Create(); - if (channel != nullptr) - { - channel->Play(source, loop); - channel->SetDeleteOnDone(deleteondone); - channel->SetDeleteSourceOnDone(deletesourceondone); - _channels.push_back(channel); - } - Unlock(); - return channel; - } - - void Stop(IAudioChannel* channel) override - { - Lock(); - channel->SetStopping(true); - Unlock(); - } - - bool LoadMusic(size_t pathId) override - { - bool result = false; - if (pathId < std::size(_musicSources)) - { - IAudioSource* source = _musicSources[pathId]; - if (source == nullptr) - { - const utf8* path = context_get_path_legacy(static_cast(pathId)); - source = AudioSource::CreateMemoryFromWAV(path, &_format); - if (source == nullptr) - { - source = _nullSource; - } - _musicSources[pathId] = source; - } - result = source != _nullSource; - } - return result; - } - - void SetVolume(float volume) override - { - _volume = volume; - } - - IAudioSource* GetSoundSource(SoundId id) override - { - return _css1Sources[static_cast(id)]; - } - - IAudioSource* GetMusicSource(int32_t id) override - { - return _musicSources[id]; - } - - private: - void LoadAllSounds() - { - const utf8* css1Path = context_get_path_legacy(PATH_ID_CSS1); - for (size_t i = 0; i < std::size(_css1Sources); i++) - { - auto source = AudioSource::CreateMemoryFromCSS1(css1Path, i, &_format); - if (source == nullptr) - { - source = _nullSource; - } - _css1Sources[i] = source; - } - } - - void GetNextAudioChunk(uint8_t* dst, size_t length) - { - UpdateAdjustedSound(); - - // Zero the output buffer - std::fill_n(dst, length, 0); - - // Mix channels onto output buffer - auto it = _channels.begin(); - while (it != _channels.end()) - { - auto channel = *it; - MixerGroup group = channel->GetGroup(); - if ((group != MixerGroup::Sound || gConfigSound.sound_enabled) && gConfigSound.master_sound_enabled - && gConfigSound.master_volume != 0) - { - MixChannel(channel, dst, length); - } - if ((channel->IsDone() && channel->DeleteOnDone()) || channel->IsStopping()) - { - delete channel; - it = _channels.erase(it); - } - else - { - it++; - } - } - } - - void UpdateAdjustedSound() - { - // Did the volume level get changed? Recalculate level in this case. - if (_settingSoundVolume != gConfigSound.sound_volume) - { - _settingSoundVolume = gConfigSound.sound_volume; - _adjustSoundVolume = powf(static_cast(_settingSoundVolume) / 100.f, 10.f / 6.f); - } - if (_settingMusicVolume != gConfigSound.ride_music_volume) - { - _settingMusicVolume = gConfigSound.ride_music_volume; - _adjustMusicVolume = powf(static_cast(_settingMusicVolume) / 100.f, 10.f / 6.f); - } - } - - void MixChannel(ISDLAudioChannel* channel, uint8_t* data, size_t length) - { - int32_t byteRate = _format.GetByteRate(); - auto numSamples = static_cast(length / byteRate); - double rate = 1; - if (_format.format == AUDIO_S16SYS) - { - rate = channel->GetRate(); - } - - bool mustConvert = false; - SDL_AudioCVT cvt; - cvt.len_ratio = 1; - AudioFormat streamformat = channel->GetFormat(); - if (streamformat != _format) - { - if (SDL_BuildAudioCVT( - &cvt, streamformat.format, streamformat.channels, streamformat.freq, _format.format, _format.channels, - _format.freq) - == -1) - { - // Unable to convert channel data - return; - } - mustConvert = true; - } - - // Read raw PCM from channel - int32_t readSamples = numSamples * rate; - auto readLength = static_cast(readSamples / cvt.len_ratio) * byteRate; - _channelBuffer.resize(readLength); - size_t bytesRead = channel->Read(_channelBuffer.data(), readLength); - - // Convert data to required format if necessary - void* buffer = nullptr; - size_t bufferLen = 0; - if (mustConvert) - { - if (Convert(&cvt, _channelBuffer.data(), bytesRead)) - { - buffer = cvt.buf; - bufferLen = cvt.len_cvt; - } - else - { - return; - } - } - else - { - buffer = _channelBuffer.data(); - bufferLen = bytesRead; - } - - // Apply effects - if (rate != 1) - { - auto inRate = static_cast(bufferLen / byteRate); - int32_t outRate = numSamples; - if (bytesRead != readLength) - { - inRate = _format.freq; - outRate = _format.freq * (1 / rate); - } - _effectBuffer.resize(length); - bufferLen = ApplyResample( - channel, buffer, static_cast(bufferLen / byteRate), numSamples, inRate, outRate); - buffer = _effectBuffer.data(); - } - - // Apply panning and volume - ApplyPan(channel, buffer, bufferLen, byteRate); - int32_t mixVolume = ApplyVolume(channel, buffer, bufferLen); - - // Finally mix on to destination buffer - size_t dstLength = std::min(length, bufferLen); - SDL_MixAudioFormat( - data, static_cast(buffer), _format.format, static_cast(dstLength), mixVolume); - - channel->UpdateOldVolume(); - } - - /** - * Resample the given buffer into _effectBuffer. - * Assumes that srcBuffer is the same format as _format. - */ - size_t ApplyResample( - ISDLAudioChannel* channel, const void* srcBuffer, int32_t srcSamples, int32_t dstSamples, int32_t inRate, - int32_t outRate) - { - int32_t byteRate = _format.GetByteRate(); - - // Create resampler - SpeexResamplerState* resampler = channel->GetResampler(); - if (resampler == nullptr) - { - resampler = speex_resampler_init(_format.channels, _format.freq, _format.freq, 0, nullptr); - channel->SetResampler(resampler); - } - speex_resampler_set_rate(resampler, inRate, outRate); - - uint32_t inLen = srcSamples; - uint32_t outLen = dstSamples; - speex_resampler_process_interleaved_int( - resampler, static_cast(srcBuffer), &inLen, - reinterpret_cast(_effectBuffer.data()), &outLen); - - return outLen * byteRate; - } - - void ApplyPan(const IAudioChannel* channel, void* buffer, size_t len, size_t sampleSize) - { - if (channel->GetPan() != 0.5f && _format.channels == 2) - { - switch (_format.format) - { - case AUDIO_S16SYS: - EffectPanS16(channel, static_cast(buffer), static_cast(len / sampleSize)); - break; - case AUDIO_U8: - EffectPanU8(channel, static_cast(buffer), static_cast(len / sampleSize)); - break; - } - } - } - - int32_t ApplyVolume(const IAudioChannel* channel, void* buffer, size_t len) - { - float volumeAdjust = _volume; - volumeAdjust *= gConfigSound.master_sound_enabled ? (static_cast(gConfigSound.master_volume) / 100.0f) - : 0.0f; - - switch (channel->GetGroup()) - { - case MixerGroup::Sound: - volumeAdjust *= _adjustSoundVolume; - - // Cap sound volume on title screen so music is more audible - if (gScreenFlags & SCREEN_FLAGS_TITLE_DEMO) - { - volumeAdjust = std::min(volumeAdjust, 0.75f); - } - break; - case MixerGroup::RideMusic: - case MixerGroup::TitleMusic: - volumeAdjust *= _adjustMusicVolume; - break; - } - - int32_t startVolume = channel->GetOldVolume() * volumeAdjust; - int32_t endVolume = channel->GetVolume() * volumeAdjust; - if (channel->IsStopping()) - { - endVolume = 0; - } - - int32_t mixVolume = channel->GetVolume() * volumeAdjust; - if (startVolume != endVolume) - { - // Set to max since we are adjusting the volume ourselves - mixVolume = MIXER_VOLUME_MAX; - - // Fade between volume levels to smooth out sound and minimize clicks from sudden volume changes - int32_t fadeLength = static_cast(len) / _format.BytesPerSample(); - switch (_format.format) - { - case AUDIO_S16SYS: - EffectFadeS16(static_cast(buffer), fadeLength, startVolume, endVolume); - break; - case AUDIO_U8: - EffectFadeU8(static_cast(buffer), fadeLength, startVolume, endVolume); - break; - } - } - return mixVolume; - } - - static void EffectPanS16(const IAudioChannel* channel, int16_t* data, int32_t length) - { - const float dt = 1.0f / static_cast(length * 2.0f); - float volumeL = channel->GetOldVolumeL(); - float volumeR = channel->GetOldVolumeR(); - const float d_left = dt * (channel->GetVolumeL() - channel->GetOldVolumeL()); - const float d_right = dt * (channel->GetVolumeR() - channel->GetOldVolumeR()); - - for (int32_t i = 0; i < length * 2; i += 2) - { - data[i + 0] = static_cast(volumeL * static_cast(data[i + 0])); - data[i + 1] = static_cast(volumeR * static_cast(data[i + 1])); - volumeL += d_left; - volumeR += d_right; - } - } - - static void EffectPanU8(const IAudioChannel* channel, uint8_t* data, int32_t length) - { - float volumeL = channel->GetVolumeL(); - float volumeR = channel->GetVolumeR(); - float oldVolumeL = channel->GetOldVolumeL(); - float oldVolumeR = channel->GetOldVolumeR(); - - for (int32_t i = 0; i < length * 2; i += 2) - { - float t = static_cast(i) / static_cast(length * 2.0f); - data[i] = static_cast(data[i] * ((1.0 - t) * oldVolumeL + t * volumeL)); - data[i + 1] = static_cast(data[i + 1] * ((1.0 - t) * oldVolumeR + t * volumeR)); - } - } - - static void EffectFadeS16(int16_t* data, int32_t length, int32_t startvolume, int32_t endvolume) - { - static_assert(SDL_MIX_MAXVOLUME == MIXER_VOLUME_MAX, "Max volume differs between OpenRCT2 and SDL2"); - - float startvolume_f = static_cast(startvolume) / SDL_MIX_MAXVOLUME; - float endvolume_f = static_cast(endvolume) / SDL_MIX_MAXVOLUME; - for (int32_t i = 0; i < length; i++) - { - float t = static_cast(i) / length; - data[i] = static_cast(data[i] * ((1.0f - t) * startvolume_f + t * endvolume_f)); - } - } - - static void EffectFadeU8(uint8_t* data, int32_t length, int32_t startvolume, int32_t endvolume) - { - static_assert(SDL_MIX_MAXVOLUME == MIXER_VOLUME_MAX, "Max volume differs between OpenRCT2 and SDL2"); - - float startvolume_f = static_cast(startvolume) / SDL_MIX_MAXVOLUME; - float endvolume_f = static_cast(endvolume) / SDL_MIX_MAXVOLUME; - for (int32_t i = 0; i < length; i++) - { - float t = static_cast(i) / length; - data[i] = static_cast(data[i] * ((1.0f - t) * startvolume_f + t * endvolume_f)); - } - } - - bool Convert(SDL_AudioCVT* cvt, const void* src, size_t len) - { - // tofix: there seems to be an issue with converting audio using SDL_ConvertAudio in the callback vs preconverted, - // can cause pops and static depending on sample rate and channels - bool result = false; - if (len != 0 && cvt->len_mult != 0) - { - size_t reqConvertBufferCapacity = len * cvt->len_mult; - _convertBuffer.resize(reqConvertBufferCapacity); - std::copy_n(static_cast(src), len, _convertBuffer.data()); - - cvt->len = static_cast(len); - cvt->buf = static_cast(_convertBuffer.data()); - if (SDL_ConvertAudio(cvt) >= 0) - { - result = true; - } - } - return result; - } + SDL_AudioSpec want = {}; + want.freq = 22050; + want.format = AUDIO_S16SYS; + want.channels = 2; + want.samples = 2048; + want.callback = [](void* arg, uint8_t* dst, int32_t length) -> void { + auto mixer = static_cast(arg); + mixer->GetNextAudioChunk(dst, static_cast(length)); + mixer->RemoveReleasedSources(); }; + want.userdata = this; - IAudioMixer* AudioMixer::Create() + SDL_AudioSpec have; + _deviceId = SDL_OpenAudioDevice(device, 0, &want, &have, 0); + _format.format = have.format; + _format.channels = have.channels; + _format.freq = have.freq; + + SDL_PauseAudioDevice(_deviceId, 0); +} + +void AudioMixer::Close() +{ + // Free channels + Lock(); + for (IAudioChannel* channel : _channels) { - return new AudioMixerImpl(); + delete channel; } -} // namespace OpenRCT2::Audio + _channels.clear(); + Unlock(); + + SDL_CloseAudioDevice(_deviceId); + + // Free buffers + _channelBuffer.clear(); + _channelBuffer.shrink_to_fit(); + _convertBuffer.clear(); + _convertBuffer.shrink_to_fit(); + _effectBuffer.clear(); + _effectBuffer.shrink_to_fit(); +} + +void AudioMixer::Lock() +{ + SDL_LockAudioDevice(_deviceId); +} + +void AudioMixer::Unlock() +{ + SDL_UnlockAudioDevice(_deviceId); +} + +IAudioChannel* AudioMixer::Play(IAudioSource* source, int32_t loop, bool deleteondone) +{ + Lock(); + ISDLAudioChannel* channel = AudioChannel::Create(); + if (channel != nullptr) + { + channel->Play(source, loop); + channel->SetDeleteOnDone(deleteondone); + _channels.push_back(channel); + } + Unlock(); + return channel; +} + +void AudioMixer::Stop(IAudioChannel* channel) +{ + Lock(); + channel->SetStopping(true); + Unlock(); +} + +void AudioMixer::SetVolume(float volume) +{ + _volume = volume; +} + +ISDLAudioSource* AudioMixer::AddSource(std::unique_ptr source) +{ + std::lock_guard guard(_mutex); + if (source != nullptr) + { + _sources.push_back(std::move(source)); + return _sources.back().get(); + } + return nullptr; +} + +void AudioMixer::RemoveReleasedSources() +{ + std::lock_guard guard(_mutex); + _sources.erase( + std::remove_if( + _sources.begin(), _sources.end(), + [](std::unique_ptr& source) { + { + return source->IsReleased(); + } + }), + _sources.end()); +} + +const AudioFormat& AudioMixer::GetFormat() const +{ + return _format; +} + +void AudioMixer::GetNextAudioChunk(uint8_t* dst, size_t length) +{ + UpdateAdjustedSound(); + + // Zero the output buffer + std::fill_n(dst, length, 0); + + // Mix channels onto output buffer + auto it = _channels.begin(); + while (it != _channels.end()) + { + auto channel = *it; + if ((channel->IsDone() && channel->DeleteOnDone()) || channel->IsStopping()) + { + delete channel; + it = _channels.erase(it); + } + else + { + auto group = channel->GetGroup(); + if ((group != MixerGroup::Sound || gConfigSound.sound_enabled) && gConfigSound.master_sound_enabled + && gConfigSound.master_volume != 0) + { + MixChannel(channel, dst, length); + } + it++; + } + } +} + +void AudioMixer::UpdateAdjustedSound() +{ + // Did the volume level get changed? Recalculate level in this case. + if (_settingSoundVolume != gConfigSound.sound_volume) + { + _settingSoundVolume = gConfigSound.sound_volume; + _adjustSoundVolume = powf(static_cast(_settingSoundVolume) / 100.f, 10.f / 6.f); + } + if (_settingMusicVolume != gConfigSound.ride_music_volume) + { + _settingMusicVolume = gConfigSound.ride_music_volume; + _adjustMusicVolume = powf(static_cast(_settingMusicVolume) / 100.f, 10.f / 6.f); + } +} + +void AudioMixer::MixChannel(ISDLAudioChannel* channel, uint8_t* data, size_t length) +{ + int32_t byteRate = _format.GetByteRate(); + auto numSamples = static_cast(length / byteRate); + double rate = 1; + if (_format.format == AUDIO_S16SYS) + { + rate = channel->GetRate(); + } + + bool mustConvert = false; + SDL_AudioCVT cvt; + cvt.len_ratio = 1; + AudioFormat streamformat = channel->GetFormat(); + if (streamformat != _format) + { + if (SDL_BuildAudioCVT( + &cvt, streamformat.format, streamformat.channels, streamformat.freq, _format.format, _format.channels, + _format.freq) + == -1) + { + // Unable to convert channel data + return; + } + mustConvert = true; + } + + // Read raw PCM from channel + int32_t readSamples = numSamples * rate; + auto readLength = static_cast(readSamples / cvt.len_ratio) * byteRate; + _channelBuffer.resize(readLength); + size_t bytesRead = channel->Read(_channelBuffer.data(), readLength); + + // Convert data to required format if necessary + void* buffer = nullptr; + size_t bufferLen = 0; + if (mustConvert) + { + if (Convert(&cvt, _channelBuffer.data(), bytesRead)) + { + buffer = cvt.buf; + bufferLen = cvt.len_cvt; + } + else + { + return; + } + } + else + { + buffer = _channelBuffer.data(); + bufferLen = bytesRead; + } + + // Apply effects + if (rate != 1) + { + auto inRate = static_cast(bufferLen / byteRate); + int32_t outRate = numSamples; + if (bytesRead != readLength) + { + inRate = _format.freq; + outRate = _format.freq * (1 / rate); + } + _effectBuffer.resize(length); + bufferLen = ApplyResample(channel, buffer, static_cast(bufferLen / byteRate), numSamples, inRate, outRate); + buffer = _effectBuffer.data(); + } + + // Apply panning and volume + ApplyPan(channel, buffer, bufferLen, byteRate); + int32_t mixVolume = ApplyVolume(channel, buffer, bufferLen); + + // Finally mix on to destination buffer + size_t dstLength = std::min(length, bufferLen); + SDL_MixAudioFormat(data, static_cast(buffer), _format.format, static_cast(dstLength), mixVolume); + + channel->UpdateOldVolume(); +} + +/** + * Resample the given buffer into _effectBuffer. + * Assumes that srcBuffer is the same format as _format. + */ +size_t AudioMixer::ApplyResample( + ISDLAudioChannel* channel, const void* srcBuffer, int32_t srcSamples, int32_t dstSamples, int32_t inRate, int32_t outRate) +{ + int32_t byteRate = _format.GetByteRate(); + + // Create resampler + SpeexResamplerState* resampler = channel->GetResampler(); + if (resampler == nullptr) + { + resampler = speex_resampler_init(_format.channels, _format.freq, _format.freq, 0, nullptr); + channel->SetResampler(resampler); + } + speex_resampler_set_rate(resampler, inRate, outRate); + + uint32_t inLen = srcSamples; + uint32_t outLen = dstSamples; + speex_resampler_process_interleaved_int( + resampler, static_cast(srcBuffer), &inLen, reinterpret_cast(_effectBuffer.data()), + &outLen); + + return outLen * byteRate; +} + +void AudioMixer::ApplyPan(const IAudioChannel* channel, void* buffer, size_t len, size_t sampleSize) +{ + if (channel->GetPan() != 0.5f && _format.channels == 2) + { + switch (_format.format) + { + case AUDIO_S16SYS: + EffectPanS16(channel, static_cast(buffer), static_cast(len / sampleSize)); + break; + case AUDIO_U8: + EffectPanU8(channel, static_cast(buffer), static_cast(len / sampleSize)); + break; + } + } +} + +int32_t AudioMixer::ApplyVolume(const IAudioChannel* channel, void* buffer, size_t len) +{ + float volumeAdjust = _volume; + volumeAdjust *= gConfigSound.master_sound_enabled ? (static_cast(gConfigSound.master_volume) / 100.0f) : 0.0f; + + switch (channel->GetGroup()) + { + case MixerGroup::Sound: + volumeAdjust *= _adjustSoundVolume; + + // Cap sound volume on title screen so music is more audible + if (gScreenFlags & SCREEN_FLAGS_TITLE_DEMO) + { + volumeAdjust = std::min(volumeAdjust, 0.75f); + } + break; + case MixerGroup::RideMusic: + case MixerGroup::TitleMusic: + volumeAdjust *= _adjustMusicVolume; + break; + } + + int32_t startVolume = channel->GetOldVolume() * volumeAdjust; + int32_t endVolume = channel->GetVolume() * volumeAdjust; + if (channel->IsStopping()) + { + endVolume = 0; + } + + int32_t mixVolume = channel->GetVolume() * volumeAdjust; + if (startVolume != endVolume) + { + // Set to max since we are adjusting the volume ourselves + mixVolume = MIXER_VOLUME_MAX; + + // Fade between volume levels to smooth out sound and minimize clicks from sudden volume changes + int32_t fadeLength = static_cast(len) / _format.BytesPerSample(); + switch (_format.format) + { + case AUDIO_S16SYS: + EffectFadeS16(static_cast(buffer), fadeLength, startVolume, endVolume); + break; + case AUDIO_U8: + EffectFadeU8(static_cast(buffer), fadeLength, startVolume, endVolume); + break; + } + } + return mixVolume; +} + +void AudioMixer::EffectPanS16(const IAudioChannel* channel, int16_t* data, int32_t length) +{ + const float dt = 1.0f / static_cast(length * 2.0f); + float volumeL = channel->GetOldVolumeL(); + float volumeR = channel->GetOldVolumeR(); + const float d_left = dt * (channel->GetVolumeL() - channel->GetOldVolumeL()); + const float d_right = dt * (channel->GetVolumeR() - channel->GetOldVolumeR()); + + for (int32_t i = 0; i < length * 2; i += 2) + { + data[i + 0] = static_cast(volumeL * static_cast(data[i + 0])); + data[i + 1] = static_cast(volumeR * static_cast(data[i + 1])); + volumeL += d_left; + volumeR += d_right; + } +} + +void AudioMixer::EffectPanU8(const IAudioChannel* channel, uint8_t* data, int32_t length) +{ + float volumeL = channel->GetVolumeL(); + float volumeR = channel->GetVolumeR(); + float oldVolumeL = channel->GetOldVolumeL(); + float oldVolumeR = channel->GetOldVolumeR(); + + for (int32_t i = 0; i < length * 2; i += 2) + { + float t = static_cast(i) / static_cast(length * 2.0f); + data[i] = static_cast(data[i] * ((1.0 - t) * oldVolumeL + t * volumeL)); + data[i + 1] = static_cast(data[i + 1] * ((1.0 - t) * oldVolumeR + t * volumeR)); + } +} + +void AudioMixer::EffectFadeS16(int16_t* data, int32_t length, int32_t startvolume, int32_t endvolume) +{ + static_assert(SDL_MIX_MAXVOLUME == MIXER_VOLUME_MAX, "Max volume differs between OpenRCT2 and SDL2"); + + float startvolume_f = static_cast(startvolume) / SDL_MIX_MAXVOLUME; + float endvolume_f = static_cast(endvolume) / SDL_MIX_MAXVOLUME; + for (int32_t i = 0; i < length; i++) + { + float t = static_cast(i) / length; + data[i] = static_cast(data[i] * ((1.0f - t) * startvolume_f + t * endvolume_f)); + } +} + +void AudioMixer::EffectFadeU8(uint8_t* data, int32_t length, int32_t startvolume, int32_t endvolume) +{ + static_assert(SDL_MIX_MAXVOLUME == MIXER_VOLUME_MAX, "Max volume differs between OpenRCT2 and SDL2"); + + float startvolume_f = static_cast(startvolume) / SDL_MIX_MAXVOLUME; + float endvolume_f = static_cast(endvolume) / SDL_MIX_MAXVOLUME; + for (int32_t i = 0; i < length; i++) + { + float t = static_cast(i) / length; + data[i] = static_cast(data[i] * ((1.0f - t) * startvolume_f + t * endvolume_f)); + } +} + +bool AudioMixer::Convert(SDL_AudioCVT* cvt, const void* src, size_t len) +{ + // tofix: there seems to be an issue with converting audio using SDL_ConvertAudio in the callback vs preconverted, + // can cause pops and static depending on sample rate and channels + bool result = false; + if (len != 0 && cvt->len_mult != 0) + { + size_t reqConvertBufferCapacity = len * cvt->len_mult; + _convertBuffer.resize(reqConvertBufferCapacity); + std::copy_n(static_cast(src), len, _convertBuffer.data()); + + cvt->len = static_cast(len); + cvt->buf = static_cast(_convertBuffer.data()); + if (SDL_ConvertAudio(cvt) >= 0) + { + result = true; + } + } + return result; +} diff --git a/src/openrct2-ui/audio/AudioMixer.h b/src/openrct2-ui/audio/AudioMixer.h new file mode 100644 index 0000000000..d8499b3089 --- /dev/null +++ b/src/openrct2-ui/audio/AudioMixer.h @@ -0,0 +1,84 @@ +/***************************************************************************** + * Copyright (c) 2014-2022 OpenRCT2 developers + * + * For a complete list of all authors, please refer to contributors.md + * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is licensed under the GNU General Public License version 3. + *****************************************************************************/ + +#pragma once + +#include "AudioContext.h" +#include "AudioFormat.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace OpenRCT2::Audio +{ + class AudioMixer final : public IAudioMixer + { + private: + std::vector> _sources; + IAudioSource* _nullSource = nullptr; + + SDL_AudioDeviceID _deviceId = 0; + AudioFormat _format = {}; + std::list _channels; + float _volume = 1.0f; + float _adjustSoundVolume = 0.0f; + float _adjustMusicVolume = 0.0f; + uint8_t _settingSoundVolume = 0xFF; + uint8_t _settingMusicVolume = 0xFF; + + std::vector _channelBuffer; + std::vector _convertBuffer; + std::vector _effectBuffer; + + std::mutex _mutex; + + public: + AudioMixer(); + ~AudioMixer() override; + void Init(const char* device) override; + void Close() override; + void Lock() override; + void Unlock() override; + IAudioChannel* Play(IAudioSource* source, int32_t loop, bool deleteondone) override; + void Stop(IAudioChannel* channel) override; + void SetVolume(float volume) override; + ISDLAudioSource* AddSource(std::unique_ptr source); + + const AudioFormat& GetFormat() const; + + private: + void GetNextAudioChunk(uint8_t* dst, size_t length); + void UpdateAdjustedSound(); + void MixChannel(ISDLAudioChannel* channel, uint8_t* data, size_t length); + void RemoveReleasedSources(); + + /** + * Resample the given buffer into _effectBuffer. + * Assumes that srcBuffer is the same format as _format. + */ + size_t ApplyResample( + ISDLAudioChannel* channel, const void* srcBuffer, int32_t srcSamples, int32_t dstSamples, int32_t inRate, + int32_t outRate); + void ApplyPan(const IAudioChannel* channel, void* buffer, size_t len, size_t sampleSize); + int32_t ApplyVolume(const IAudioChannel* channel, void* buffer, size_t len); + static void EffectPanS16(const IAudioChannel* channel, int16_t* data, int32_t length); + static void EffectPanU8(const IAudioChannel* channel, uint8_t* data, int32_t length); + static void EffectFadeS16(int16_t* data, int32_t length, int32_t startvolume, int32_t endvolume); + static void EffectFadeU8(uint8_t* data, int32_t length, int32_t startvolume, int32_t endvolume); + bool Convert(SDL_AudioCVT* cvt, const void* src, size_t len); + }; +} // namespace OpenRCT2::Audio diff --git a/src/openrct2-ui/audio/FileAudioSource.cpp b/src/openrct2-ui/audio/FileAudioSource.cpp index b26257fc64..685efb7ec7 100644 --- a/src/openrct2-ui/audio/FileAudioSource.cpp +++ b/src/openrct2-ui/audio/FileAudioSource.cpp @@ -28,11 +28,26 @@ namespace OpenRCT2::Audio SDL_RWops* _rw = nullptr; uint64_t _dataBegin = 0; uint64_t _dataLength = 0; + bool _released{}; public: ~FileAudioSource() override { - Unload(); + Release(); + } + + bool IsReleased() const override + { + return _released; + } + + void Release() override + { + if (!_released) + { + Unload(); + _released = true; + } } [[nodiscard]] uint64_t GetLength() const override @@ -181,55 +196,23 @@ namespace OpenRCT2::Audio } }; - IAudioSource* AudioSource::CreateStreamFromWAV(const std::string& path) + std::unique_ptr AudioSource::CreateStreamFromWAV(const std::string& path) { - IAudioSource* source = nullptr; - SDL_RWops* rw = SDL_RWFromFile(path.c_str(), "rb"); + auto* rw = SDL_RWFromFile(path.c_str(), "rb"); if (rw != nullptr) { return AudioSource::CreateStreamFromWAV(rw); } - return source; + return nullptr; } - IAudioSource* AudioSource::CreateStreamFromWAV(SDL_RWops* rw) + std::unique_ptr AudioSource::CreateStreamFromWAV(SDL_RWops* rw) { - auto source = new FileAudioSource(); + auto source = std::make_unique(); if (!source->LoadWAV(rw)) { - delete source; source = nullptr; } return source; } - - IAudioSource* AudioSource::CreateStreamFromWAV(std::unique_ptr stream) - { - auto rw = new SDL_RWops(); - *rw = {}; - rw->type = SDL_RWOPS_UNKNOWN; - rw->hidden.unknown.data1 = stream.release(); - rw->seek = [](SDL_RWops* ctx, Sint64 offset, int whence) { - auto ptr = static_cast(ctx->hidden.unknown.data1); - ptr->Seek(offset, whence); - return static_cast(ptr->GetPosition()); - }; - rw->read = [](SDL_RWops* ctx, void* buf, size_t size, size_t maxnum) { - auto ptr = static_cast(ctx->hidden.unknown.data1); - return static_cast(ptr->TryRead(buf, size * maxnum) / size); - }; - rw->size = [](SDL_RWops* ctx) { - auto ptr = static_cast(ctx->hidden.unknown.data1); - return static_cast(ptr->GetLength()); - }; - rw->close = [](SDL_RWops* ctx) { - auto ptr = static_cast(ctx->hidden.unknown.data1); - delete ptr; - ctx->hidden.unknown.data1 = nullptr; - delete ctx; - return 0; - }; - return CreateStreamFromWAV(rw); - } - } // namespace OpenRCT2::Audio diff --git a/src/openrct2-ui/audio/MemoryAudioSource.cpp b/src/openrct2-ui/audio/MemoryAudioSource.cpp index 4553fe8957..aee8322266 100644 --- a/src/openrct2-ui/audio/MemoryAudioSource.cpp +++ b/src/openrct2-ui/audio/MemoryAudioSource.cpp @@ -27,8 +27,9 @@ namespace OpenRCT2::Audio private: AudioFormat _format = {}; std::vector _data; - uint8_t* _dataSDL = nullptr; - size_t _length = 0; + uint8_t* _dataSDL{}; + size_t _length{}; + bool _released{}; const uint8_t* GetData() { @@ -38,7 +39,21 @@ namespace OpenRCT2::Audio public: ~MemoryAudioSource() override { - Unload(); + Release(); + } + + bool IsReleased() const override + { + return _released; + } + + void Release() override + { + if (!_released) + { + Unload(); + _released = true; + } } [[nodiscard]] uint64_t GetLength() const override @@ -67,87 +82,63 @@ namespace OpenRCT2::Audio return bytesToRead; } - bool LoadWAV(const utf8* path) + bool LoadWAV(SDL_RWops* rw) { - log_verbose("MemoryAudioSource::LoadWAV(%s)", path); - Unload(); bool result = false; - SDL_RWops* rw = SDL_RWFromFile(path, "rb"); - if (rw != nullptr) + SDL_AudioSpec audiospec = {}; + uint32_t audioLen; + SDL_AudioSpec* spec = SDL_LoadWAV_RW(rw, false, &audiospec, &_dataSDL, &audioLen); + if (spec != nullptr) { - SDL_AudioSpec audiospec = {}; - uint32_t audioLen; - SDL_AudioSpec* spec = SDL_LoadWAV_RW(rw, false, &audiospec, &_dataSDL, &audioLen); - if (spec != nullptr) - { - _format.freq = spec->freq; - _format.format = spec->format; - _format.channels = spec->channels; - _length = audioLen; - result = true; - } - else - { - log_verbose("Error loading %s, unsupported WAV format", path); - } - SDL_RWclose(rw); - } - else - { - log_verbose("Error loading %s", path); + _format.freq = spec->freq; + _format.format = spec->format; + _format.channels = spec->channels; + _length = audioLen; + result = true; } + SDL_RWclose(rw); return result; } - bool LoadCSS1(const utf8* path, size_t index) + bool LoadCSS1(SDL_RWops* rw, size_t index) { - log_verbose("MemoryAudioSource::LoadCSS1(%s, %d)", path, index); - Unload(); - bool result = false; - SDL_RWops* rw = SDL_RWFromFile(path, "rb"); - if (rw != nullptr) + bool result{}; + uint32_t numSounds{}; + SDL_RWread(rw, &numSounds, sizeof(numSounds), 1); + if (index < numSounds) { - uint32_t numSounds{}; - SDL_RWread(rw, &numSounds, sizeof(numSounds), 1); - if (index < numSounds) + SDL_RWseek(rw, index * 4, RW_SEEK_CUR); + + uint32_t pcmOffset{}; + SDL_RWread(rw, &pcmOffset, sizeof(pcmOffset), 1); + SDL_RWseek(rw, pcmOffset, RW_SEEK_SET); + + uint32_t pcmSize{}; + SDL_RWread(rw, &pcmSize, sizeof(pcmSize), 1); + _length = pcmSize; + + WaveFormatEx waveFormat{}; + SDL_RWread(rw, &waveFormat, sizeof(waveFormat), 1); + _format.freq = waveFormat.frequency; + _format.format = AUDIO_S16LSB; + _format.channels = waveFormat.channels; + + try { - SDL_RWseek(rw, index * 4, RW_SEEK_CUR); - - uint32_t pcmOffset{}; - SDL_RWread(rw, &pcmOffset, sizeof(pcmOffset), 1); - SDL_RWseek(rw, pcmOffset, RW_SEEK_SET); - - uint32_t pcmSize{}; - SDL_RWread(rw, &pcmSize, sizeof(pcmSize), 1); - _length = pcmSize; - - WaveFormatEx waveFormat{}; - SDL_RWread(rw, &waveFormat, sizeof(waveFormat), 1); - _format.freq = waveFormat.frequency; - _format.format = AUDIO_S16LSB; - _format.channels = waveFormat.channels; - - try - { - _data.resize(_length); - SDL_RWread(rw, _data.data(), _length, 1); - result = true; - } - catch (const std::bad_alloc&) - { - log_verbose("Unable to allocate data"); - } + _data.resize(_length); + SDL_RWread(rw, _data.data(), _length, 1); + result = true; + } + catch (const std::bad_alloc&) + { + log_verbose("Unable to allocate data"); } - SDL_RWclose(rw); - } - else - { - log_verbose("Unable to load %s", path); } + SDL_RWclose(rw); return result; } @@ -195,44 +186,58 @@ namespace OpenRCT2::Audio } }; - IAudioSource* AudioSource::CreateMemoryFromCSS1(const std::string& path, size_t index, const AudioFormat* targetFormat) + std::unique_ptr AudioSource::CreateMemoryFromCSS1( + const std::string& path, size_t index, const AudioFormat* targetFormat) { - auto source = new MemoryAudioSource(); - if (source->LoadCSS1(path.c_str(), index)) + log_verbose("AudioSource::CreateMemoryFromCSS1(%s, %d)", path.c_str(), index); + SDL_RWops* rw = SDL_RWFromFile(path.c_str(), "rb"); + if (rw != nullptr) + { + return CreateMemoryFromCSS1(rw, index, targetFormat); + } + else + { + log_verbose("Unable to load %s", path.c_str()); + } + return nullptr; + } + + std::unique_ptr AudioSource::CreateMemoryFromCSS1( + SDL_RWops* rw, size_t index, const AudioFormat* targetFormat) + { + auto source = std::make_unique(); + if (source->LoadCSS1(rw, index)) { if (targetFormat != nullptr && source->GetFormat() != *targetFormat) { if (!source->Convert(targetFormat)) { - delete source; source = nullptr; } } } else { - delete source; source = nullptr; } return source; } - IAudioSource* AudioSource::CreateMemoryFromWAV(const std::string& path, const AudioFormat* targetFormat) + std::unique_ptr AudioSource::CreateMemoryFromWAV(SDL_RWops* rw, const AudioFormat* targetFormat) { - auto source = new MemoryAudioSource(); - if (source->LoadWAV(path.c_str())) + auto source = std::make_unique(); + if (source->LoadWAV(rw)) { if (targetFormat != nullptr && source->GetFormat() != *targetFormat) { if (!source->Convert(targetFormat)) { - SafeDelete(source); + source = nullptr; } } } else { - delete source; source = nullptr; } return source; diff --git a/src/openrct2-ui/libopenrct2ui.vcxproj b/src/openrct2-ui/libopenrct2ui.vcxproj index 6a82fbeea3..585572a032 100644 --- a/src/openrct2-ui/libopenrct2ui.vcxproj +++ b/src/openrct2-ui/libopenrct2ui.vcxproj @@ -32,6 +32,7 @@ + @@ -119,7 +120,7 @@ - + diff --git a/src/openrct2-ui/windows/Options.cpp b/src/openrct2-ui/windows/Options.cpp index ef341af83d..643a65f813 100644 --- a/src/openrct2-ui/windows/Options.cpp +++ b/src/openrct2-ui/windows/Options.cpp @@ -1343,18 +1343,35 @@ private: Dropdown::SetChecked(OpenRCT2::Audio::GetCurrentDeviceIndex(), true); break; case WIDX_TITLE_MUSIC_DROPDOWN: - uint32_t numItems = 4; - - for (size_t i = 0; i < numItems; i++) + { + if (gConfigGeneral.rct1_path.empty()) { - gDropdownItems[i].Format = STR_DROPDOWN_MENU_LABEL; - gDropdownItems[i].Args = TitleMusicNames[i]; + // Only show None and RCT2 + int32_t numItems{}; + gDropdownItems[numItems].Format = STR_DROPDOWN_MENU_LABEL; + gDropdownItems[numItems++].Args = TitleMusicNames[0]; + gDropdownItems[numItems].Format = STR_DROPDOWN_MENU_LABEL; + gDropdownItems[numItems++].Args = TitleMusicNames[2]; + ShowDropdown(widget, numItems); + if (gConfigSound.title_music == TitleMusicKind::None) + Dropdown::SetChecked(0, true); + else if (gConfigSound.title_music == TitleMusicKind::Rct2) + Dropdown::SetChecked(1, true); + } + else + { + // Show None, RCT1, RCT2 and random + int32_t numItems{}; + for (auto musicName : TitleMusicNames) + { + gDropdownItems[numItems].Format = STR_DROPDOWN_MENU_LABEL; + gDropdownItems[numItems++].Args = musicName; + } + ShowDropdown(widget, numItems); + Dropdown::SetChecked(EnumValue(gConfigSound.title_music), true); } - - ShowDropdown(widget, numItems); - - Dropdown::SetChecked(gConfigSound.title_music, true); break; + } } } @@ -1384,21 +1401,24 @@ private: Invalidate(); break; case WIDX_TITLE_MUSIC_DROPDOWN: - if ((dropdownIndex == 1 || dropdownIndex == 3) && !File::Exists(context_get_path_legacy(PATH_ID_CSS50))) + { + auto titleMusic = static_cast(dropdownIndex); + if (gConfigGeneral.rct1_path.empty() && dropdownIndex != 0) { - context_show_error(STR_OPTIONS_MUSIC_ERR_CSS50_NOT_FOUND, STR_OPTIONS_MUSIC_ERR_CSS50_NOT_FOUND_HINT, {}); - } - else - { - gConfigSound.title_music = static_cast(dropdownIndex); - config_save_default(); - Invalidate(); + titleMusic = TitleMusicKind::Rct2; } + gConfigSound.title_music = titleMusic; + config_save_default(); + Invalidate(); + OpenRCT2::Audio::StopTitleMusic(); - if (dropdownIndex != 0) + if (titleMusic != TitleMusicKind::None) + { OpenRCT2::Audio::PlayTitleMusic(); + } break; + } } } @@ -1440,6 +1460,16 @@ private: return { 500, 0 }; } + rct_string_id GetTitleMusicName() + { + auto index = EnumValue(gConfigSound.title_music); + if (index < 0 || static_cast(index) >= std::size(TitleMusicNames)) + { + index = EnumValue(TitleMusicKind::None); + } + return TitleMusicNames[index]; + } + void AudioPrepareDraw() { // Sound device @@ -1469,7 +1499,7 @@ private: auto ft = Formatter::Common(); ft.Add(audioDeviceName); - widgets[WIDX_TITLE_MUSIC].text = TitleMusicNames[gConfigSound.title_music]; + widgets[WIDX_TITLE_MUSIC].text = GetTitleMusicName(); SetCheckboxValue(WIDX_SOUND_CHECKBOX, gConfigSound.sound_enabled); SetCheckboxValue(WIDX_MASTER_SOUND_CHECKBOX, gConfigSound.master_sound_enabled); diff --git a/src/openrct2/Context.cpp b/src/openrct2/Context.cpp index 39a84157db..3ecd91da5a 100644 --- a/src/openrct2/Context.cpp +++ b/src/openrct2/Context.cpp @@ -320,50 +320,6 @@ namespace OpenRCT2 context_open_window(WC_SAVE_PROMPT); } - std::string GetPathLegacy(int32_t pathId) override - { - static constexpr const char* const LegacyFileNames[PATH_ID_END] = { - nullptr, nullptr, "css1.dat", "css2.dat", "css4.dat", "css5.dat", "css6.dat", "css7.dat", - "css8.dat", "css9.dat", "css11.dat", "css12.dat", "css13.dat", "css14.dat", "css15.dat", "css3.dat", - "css17.dat", "css18.dat", "css19.dat", "css20.dat", "css21.dat", "css22.dat", nullptr, "css23.dat", - "css24.dat", "css25.dat", "css26.dat", "css27.dat", "css28.dat", "css29.dat", "css30.dat", "css31.dat", - "css32.dat", "css33.dat", "css34.dat", "css35.dat", "css36.dat", "css37.dat", "css38.dat", "CUSTOM1.WAV", - "CUSTOM2.WAV", "css39.dat", "css40.dat", "css41.dat", nullptr, "css42.dat", "css43.dat", "css44.dat", - "css45.dat", "css46.dat", "css50.dat", - }; - - std::string result; - if (pathId == PATH_ID_CSS50) - { - if (!(_env->GetDirectoryPath(DIRBASE::RCT1).empty())) - { - auto dataPath = _env->GetDirectoryPath(DIRBASE::RCT1, DIRID::DATA); - result = Path::ResolveCasing(Path::Combine(dataPath, u8"css17.dat")); - - if (!File::Exists(result)) - { - auto rct1Path = _env->GetDirectoryPath(DIRBASE::RCT1); - result = Path::ResolveCasing(Path::Combine(rct1Path, u8"RCTdeluxe_install", u8"Data", u8"css17.dat")); - } - } - else - { - auto dataPath = _env->GetDirectoryPath(DIRBASE::RCT2, DIRID::DATA); - result = Path::ResolveCasing(Path::Combine(dataPath, u8"css50.dat")); - } - } - else if (pathId >= 0 && pathId < PATH_ID_END) - { - auto fileName = LegacyFileNames[pathId]; - if (fileName != nullptr) - { - auto dataPath = _env->GetDirectoryPath(DIRBASE::RCT2, DIRID::DATA); - result = Path::Combine(dataPath, fileName); - } - } - return result; - } - bool Initialise() final override { if (_initialised) @@ -1553,14 +1509,6 @@ void context_quit() GetContext()->Quit(); } -const utf8* context_get_path_legacy(int32_t pathId) -{ - static utf8 result[MAX_PATH]; - auto path = GetContext()->GetPathLegacy(pathId); - String::Set(result, sizeof(result), path.c_str()); - return result; -} - bool ContextOpenCommonFileDialog(utf8* outFilename, OpenRCT2::Ui::FileDialogDesc& desc, size_t outSize) { try diff --git a/src/openrct2/Context.h b/src/openrct2/Context.h index 0d4dd72414..de3e2a6493 100644 --- a/src/openrct2/Context.h +++ b/src/openrct2/Context.h @@ -160,10 +160,6 @@ namespace OpenRCT2 virtual bool HasNewVersionInfo() const abstract; virtual const NewVersionInfo* GetNewVersionInfo() const abstract; - /** - * This is deprecated, use IPlatformEnvironment. - */ - virtual std::string GetPathLegacy(int32_t pathId) abstract; virtual void SetTimeScale(float newScale) abstract; virtual float GetTimeScale() const abstract; @@ -191,66 +187,6 @@ namespace constexpr float GAME_MIN_TIME_SCALE = 0.1f; constexpr float GAME_MAX_TIME_SCALE = 5.0f; -/** - * Legacy get_file_path IDs. - * Remove when context_get_path_legacy is removed. - */ -enum -{ - PATH_ID_G1, - PATH_ID_PLUGIN, - PATH_ID_CSS1, - PATH_ID_CSS2, - PATH_ID_CSS4, - PATH_ID_CSS5, - PATH_ID_CSS6, - PATH_ID_CSS7, - PATH_ID_CSS8, - PATH_ID_CSS9, - PATH_ID_CSS11, - PATH_ID_CSS12, - PATH_ID_CSS13, - PATH_ID_CSS14, - PATH_ID_CSS15, - PATH_ID_CSS3, - PATH_ID_CSS17, - PATH_ID_CSS18, - PATH_ID_CSS19, - PATH_ID_CSS20, - PATH_ID_CSS21, - PATH_ID_CSS22, - PATH_ID_SCORES, - PATH_ID_CSS23, - PATH_ID_CSS24, - PATH_ID_CSS25, - PATH_ID_CSS26, - PATH_ID_CSS27, - PATH_ID_CSS28, - PATH_ID_CSS29, - PATH_ID_CSS30, - PATH_ID_CSS31, - PATH_ID_CSS32, - PATH_ID_CSS33, - PATH_ID_CSS34, - PATH_ID_CSS35, - PATH_ID_CSS36, - PATH_ID_CSS37, - PATH_ID_CSS38, - PATH_ID_CUSTOM1, - PATH_ID_CUSTOM2, - PATH_ID_CSS39, - PATH_ID_CSS40, - PATH_ID_CSS41, - PATH_ID_SIXFLAGS_MAGICMOUNTAIN, - PATH_ID_CSS42, - PATH_ID_CSS43, - PATH_ID_CSS44, - PATH_ID_CSS45, - PATH_ID_CSS46, - PATH_ID_CSS50, - PATH_ID_END, -}; - void context_init(); void context_setcurrentcursor(CursorID cursor); void context_update_cursor_scale(); @@ -283,7 +219,6 @@ void context_update_map_tooltip(); void context_handle_input(); void context_input_handle_keyboard(bool isTitle); void context_quit(); -const utf8* context_get_path_legacy(int32_t pathId); bool context_load_park_from_file(const utf8* path); bool context_load_park_from_stream(void* stream); bool ContextOpenCommonFileDialog(utf8* outFilename, OpenRCT2::Ui::FileDialogDesc& desc, size_t outSize); diff --git a/src/openrct2/Editor.cpp b/src/openrct2/Editor.cpp index f05c845a4c..2d62c7055c 100644 --- a/src/openrct2/Editor.cpp +++ b/src/openrct2/Editor.cpp @@ -72,7 +72,7 @@ namespace Editor // Unload objects first, the repository is re-populated which owns the objects. auto& objectManager = context->GetObjectManager(); - objectManager.UnloadAll(); + objectManager.UnloadAllTransient(); // Scan objects if necessary const auto& localisationService = context->GetLocalisationService(); diff --git a/src/openrct2/audio/Audio.cpp b/src/openrct2/audio/Audio.cpp index adeb309689..afcf2a94ec 100644 --- a/src/openrct2/audio/Audio.cpp +++ b/src/openrct2/audio/Audio.cpp @@ -21,6 +21,8 @@ #include "../interface/Viewport.h" #include "../localisation/Language.h" #include "../localisation/StringIds.h" +#include "../object/AudioObject.h" +#include "../object/ObjectManager.h" #include "../ride/Ride.h" #include "../ride/RideAudio.h" #include "../ui/UiContext.h" @@ -42,6 +44,8 @@ namespace OpenRCT2::Audio static std::vector _audioDevices; static int32_t _currentAudioDevice = -1; + static ObjectEntryIndex _soundsAudioObjectEntryIndex = OBJECT_ENTRY_INDEX_NULL; + static ObjectEntryIndex _titleAudioObjectEntryIndex = OBJECT_ENTRY_INDEX_NULL; bool gGameSoundsOff = false; int32_t gVolumeAdjustZoom = 0; @@ -51,77 +55,6 @@ namespace OpenRCT2::Audio VehicleSound gVehicleSoundList[MaxVehicleSounds]; - // clang-format off - static int32_t SoundVolumeAdjust[RCT2SoundCount] = - { - 0, // LiftClassic - 0, // TrackFrictionClassicWood - 0, // FrictionClassic - 0, // Scream1 - 0, // Click1 - 0, // Click2 - 0, // PlaceItem - 0, // Scream2 - 0, // Scream3 - 0, // Scream4 - 0, // Scream5 - 0, // Scream6 - 0, // LiftFrictionWheels - -400, // Purchase - 0, // Crash - 0, // LayingOutWater - 0, // Water1 - 0, // Water2 - 0, // TrainWhistle - 0, // TrainDeparting - -1000, // WaterSplash - 0, // GoKartEngine - -800, // RideLaunch1 - -1700, // RideLaunch2 - -700, // Cough1 - -700, // Cough2 - -700, // Cough3 - -700, // Cough4 - 0, // Rain - 0, // Thunder1 - 0, // Thunder2 - 0, // TrackFrictionTrain - 0, // TrackFrictionWater - 0, // BalloonPop - -700, // MechanicFix - 0, // Scream7 - -2500, // ToiletFlush original value: -1000 - 0, // Click3 - 0, // Quack - 0, // NewsItem - 0, // WindowOpen - -900, // Laugh1 - -900, // Laugh2 - -900, // Laugh3 - 0, // Applause - -600, // HauntedHouseScare - -700, // HauntedHouseScream1 - -700, // HauntedHouseScream2 - -2550, // BlockBrakeClose - -2900, // BlockBrakeRelease - 0, // Error - -3400, // BrakeRelease - 0, // LiftArrow - 0, // LiftWood - 0, // TrackFrictionWood - 0, // LiftWildMouse - 0, // LiftBM - 0, // TrackFrictionBM - 0, // Scream8 - 0, // Tram - -2000, // DoorOpen - -2700, // DoorClose - -700 // Portcullis - }; - // clang-format on - - static AudioParams GetParametersFromLocation(SoundId soundId, const CoordsXYZ& location); - bool IsAvailable() { if (_currentAudioDevice == -1) @@ -155,6 +88,14 @@ namespace OpenRCT2::Audio } } } + + auto& objManager = GetContext()->GetObjectManager(); + auto baseAudio = objManager.LoadObject(AudioObjectIdentifiers::Rct2Base); + if (baseAudio != nullptr) + { + _soundsAudioObjectEntryIndex = objManager.GetLoadedObjectEntryIndex(baseAudio); + } + objManager.LoadObject(AudioObjectIdentifiers::Rct2Circus); } void PopulateDevices() @@ -180,25 +121,13 @@ namespace OpenRCT2::Audio _audioDevices = devices; } - void Play3D(SoundId soundId, const CoordsXYZ& loc) - { - if (!IsAvailable()) - return; - - AudioParams params = GetParametersFromLocation(soundId, loc); - if (params.in_range) - { - Play(soundId, params.volume, params.pan); - } - } - /** * 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. */ - static AudioParams GetParametersFromLocation(SoundId soundId, const CoordsXYZ& location) + static AudioParams GetParametersFromLocation(AudioObject* obj, uint32_t sampleIndex, const CoordsXYZ& location) { int32_t volumeDown = 0; AudioParams params; @@ -222,8 +151,10 @@ namespace OpenRCT2::Audio { int16_t vx = pos2.x - viewport->viewPos.x; params.pan = viewport->pos.x + viewport->zoom.ApplyInversedTo(vx); - params.volume = SoundVolumeAdjust[static_cast(soundId)] - + ((viewport->zoom.ApplyTo(-1024) - 1) * (1 << volumeDown)) + 1; + + auto sampleModifier = obj->GetSampleModifier(sampleIndex); + auto viewModifier = ((viewport->zoom.ApplyTo(-1024) - 1) * (1 << volumeDown)) + 1; + params.volume = sampleModifier + viewModifier; if (!viewport->Contains(pos2) || params.volume < -10000) { @@ -236,11 +167,16 @@ namespace OpenRCT2::Audio return params; } - void Play(SoundId soundId, int32_t volume, int32_t pan) + AudioObject* GetBaseAudioObject() { - if (gGameSoundsOff) - return; + auto& objManager = GetContext()->GetObjectManager(); + auto baseAudioObject = static_cast( + objManager.GetLoadedObject(ObjectType::Audio, _soundsAudioObjectEntryIndex)); + return baseAudioObject; + } + static void Play(IAudioSource* audioSource, int32_t volume, int32_t pan) + { int32_t mixerPan = 0; if (pan != AUDIO_PLAY_AT_CENTRE) { @@ -249,7 +185,62 @@ namespace OpenRCT2::Audio mixerPan = ((x2 / screenWidth) - 0x8000) >> 4; } - Mixer_Play_Effect(soundId, MIXER_LOOP_NONE, DStoMixerVolume(volume), DStoMixerPan(mixerPan), 1, 1); + Mixer_Play_Effect(audioSource, MIXER_LOOP_NONE, DStoMixerVolume(volume), DStoMixerPan(mixerPan), 1, 1); + } + + void Play3D(SoundId soundId, const CoordsXYZ& loc) + { + if (!IsAvailable()) + return; + + // Get sound from base object + auto baseAudioObject = GetBaseAudioObject(); + if (baseAudioObject != nullptr) + { + auto params = GetParametersFromLocation(baseAudioObject, EnumValue(soundId), loc); + if (params.in_range) + { + auto source = baseAudioObject->GetSample(EnumValue(soundId)); + 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 + auto baseAudioObject = GetBaseAudioObject(); + if (baseAudioObject != nullptr) + { + auto source = baseAudioObject->GetSample(EnumValue(soundId)); + if (source != nullptr) + { + Play(source, volume, pan); + } + } + } + + static ObjectEntryDescriptor GetTitleMusicDescriptor() + { + switch (gConfigSound.title_music) + { + default: + return {}; + case TitleMusicKind::Rct1: + return ObjectEntryDescriptor(ObjectType::Audio, AudioObjectIdentifiers::Rct1Title); + case TitleMusicKind::Rct2: + return ObjectEntryDescriptor(ObjectType::Audio, AudioObjectIdentifiers::Rct2Title); + case TitleMusicKind::Random: + return ObjectEntryDescriptor( + ObjectType::Audio, + (util_rand() & 1) ? AudioObjectIdentifiers::Rct1Title : AudioObjectIdentifiers::Rct2Title); + } } void PlayTitleMusic() @@ -265,26 +256,24 @@ namespace OpenRCT2::Audio return; } - int32_t pathId; - switch (gConfigSound.title_music) + // Load title sequence audio object + auto descriptor = GetTitleMusicDescriptor(); + auto& objManager = GetContext()->GetObjectManager(); + auto audioObject = static_cast(objManager.LoadObject(descriptor)); + if (audioObject != nullptr) { - case 1: - pathId = PATH_ID_CSS50; - break; - case 2: - pathId = PATH_ID_CSS17; - break; - case 3: - pathId = (util_rand() & 1) ? PATH_ID_CSS50 : PATH_ID_CSS17; - break; - default: - return; - } + _titleAudioObjectEntryIndex = objManager.GetLoadedObjectEntryIndex(audioObject); - gTitleMusicChannel = Mixer_Play_Music(pathId, MIXER_LOOP_INFINITE, true); - if (gTitleMusicChannel != nullptr) - { - Mixer_Channel_SetGroup(gTitleMusicChannel, OpenRCT2::Audio::MixerGroup::TitleMusic); + // Play first sample from object + auto source = audioObject->GetSample(0); + if (source != nullptr) + { + gTitleMusicChannel = Mixer_Play_Music(source, MIXER_LOOP_INFINITE, true); + if (gTitleMusicChannel != nullptr) + { + Mixer_Channel_SetGroup(gTitleMusicChannel, MixerGroup::TitleMusic); + } + } } } @@ -324,6 +313,18 @@ namespace OpenRCT2::Audio Mixer_Stop_Channel(gTitleMusicChannel); gTitleMusicChannel = nullptr; } + + // Unload the audio object + if (_titleAudioObjectEntryIndex != OBJECT_ENTRY_INDEX_NULL) + { + auto& objManager = GetContext()->GetObjectManager(); + auto obj = objManager.GetLoadedObject(ObjectType::Audio, _titleAudioObjectEntryIndex); + if (obj != nullptr) + { + objManager.UnloadObjects({ obj->GetDescriptor() }); + } + _titleAudioObjectEntryIndex = OBJECT_ENTRY_INDEX_NULL; + } } void StopWeatherSound() diff --git a/src/openrct2/audio/AudioChannel.h b/src/openrct2/audio/AudioChannel.h index ec8619f622..4ba828dca6 100644 --- a/src/openrct2/audio/AudioChannel.h +++ b/src/openrct2/audio/AudioChannel.h @@ -58,8 +58,6 @@ namespace OpenRCT2::Audio virtual bool DeleteOnDone() const abstract; virtual void SetDeleteOnDone(bool value) abstract; - virtual void SetDeleteSourceOnDone(bool value) abstract; - virtual bool IsPlaying() const abstract; virtual void Play(IAudioSource* source, int32_t loop = 0) abstract; diff --git a/src/openrct2/audio/AudioContext.h b/src/openrct2/audio/AudioContext.h index 6b9ee914d2..5cd63b40b6 100644 --- a/src/openrct2/audio/AudioContext.h +++ b/src/openrct2/audio/AudioContext.h @@ -34,6 +34,8 @@ namespace OpenRCT2::Audio virtual std::vector GetOutputDevices() abstract; virtual void SetOutputDevice(const std::string& deviceName) abstract; + virtual IAudioSource* CreateStreamFromCSS(const std::string& path, uint32_t index) abstract; + virtual IAudioSource* CreateStreamFromCSS(std::unique_ptr stream, uint32_t index) abstract; virtual IAudioSource* CreateStreamFromWAV(const std::string& path) abstract; virtual IAudioSource* CreateStreamFromWAV(std::unique_ptr stream) abstract; diff --git a/src/openrct2/audio/AudioMixer.cpp b/src/openrct2/audio/AudioMixer.cpp index 5a1df13051..0fdde2667f 100644 --- a/src/openrct2/audio/AudioMixer.cpp +++ b/src/openrct2/audio/AudioMixer.cpp @@ -11,6 +11,8 @@ #include "../Context.h" #include "../config/Config.h" +#include "../object/AudioObject.h" +#include "../object/ObjectManager.h" #include "AudioChannel.h" #include "AudioContext.h" #include "AudioSource.h" @@ -37,31 +39,43 @@ void Mixer_Init(const char* device) audioContext->SetOutputDevice(std::string(device)); } -void* Mixer_Play_Effect(SoundId id, int32_t loop, int32_t volume, float pan, double rate, int32_t deleteondone) +IAudioChannel* Mixer_Play_Effect(SoundId id, int32_t loop, int32_t volume, float pan, double rate, int32_t deleteondone) +{ + if (gConfigSound.sound_enabled) + { + // Get sound from base object + auto baseAudioObject = GetBaseAudioObject(); + if (baseAudioObject != nullptr) + { + auto source = baseAudioObject->GetSample(EnumValue(id)); + if (source != nullptr) + { + return Mixer_Play_Effect(source, loop, volume, pan, rate, deleteondone); + } + } + } + return nullptr; +} + +IAudioChannel* Mixer_Play_Effect( + IAudioSource* source, int32_t loop, int32_t volume, float pan, double rate, int32_t deleteondone) { IAudioChannel* channel = nullptr; if (gConfigSound.sound_enabled) { - if (static_cast(id) >= RCT2SoundCount) + IAudioMixer* mixer = GetMixer(); + if (mixer != nullptr) { - log_error("Tried to play an invalid sound id. %i", id); - } - else - { - IAudioMixer* mixer = GetMixer(); - if (mixer != nullptr) + mixer->Lock(); + channel = mixer->Play(source, loop, deleteondone != 0); + if (channel != nullptr) { - mixer->Lock(); - IAudioSource* source = mixer->GetSoundSource(id); - channel = mixer->Play(source, loop, deleteondone != 0, false); - if (channel != nullptr) - { - channel->SetVolume(volume); - channel->SetPan(pan); - channel->SetRate(rate); - } - mixer->Unlock(); + channel->SetVolume(volume); + channel->SetPan(pan); + channel->SetRate(rate); + channel->UpdateOldVolume(); } + mixer->Unlock(); } } return channel; @@ -140,7 +154,7 @@ template static void* PlayMusic(T&& src, int32_t loop) if (stream == nullptr) return nullptr; - auto* channel = mixer->Play(stream, loop, false, true); + auto* channel = mixer->Play(stream, loop, false); if (channel == nullptr) { delete stream; @@ -151,36 +165,15 @@ template static void* PlayMusic(T&& src, int32_t loop) return channel; } -void* Mixer_Play_Music(int32_t pathId, int32_t loop, int32_t streaming) +IAudioChannel* Mixer_Play_Music(IAudioSource* source, int32_t loop, int32_t streaming) { - IAudioChannel* channel = nullptr; - IAudioMixer* mixer = GetMixer(); - if (mixer != nullptr) + auto* mixer = GetMixer(); + if (mixer == nullptr) { - if (streaming) - { - const utf8* path = context_get_path_legacy(pathId); - - auto audioContext = GetContext()->GetAudioContext(); - IAudioSource* source = audioContext->CreateStreamFromWAV(path); - if (source != nullptr) - { - channel = mixer->Play(source, loop, false, true); - if (channel == nullptr) - { - delete source; - } - } - } - else - { - if (mixer->LoadMusic(pathId)) - { - IAudioSource* source = mixer->GetMusicSource(pathId); - channel = mixer->Play(source, MIXER_LOOP_INFINITE, false, false); - } - } + return nullptr; } + + auto* channel = mixer->Play(source, loop, false); if (channel != nullptr) { channel->SetGroup(MixerGroup::RideMusic); @@ -188,11 +181,6 @@ void* Mixer_Play_Music(int32_t pathId, int32_t loop, int32_t streaming) return channel; } -void* Mixer_Play_Music(const char* path, int32_t loop) -{ - return PlayMusic(path, loop); -} - void* Mixer_Play_Music(std::unique_ptr stream, int32_t loop) { return PlayMusic(std::move(stream), loop); diff --git a/src/openrct2/audio/AudioMixer.h b/src/openrct2/audio/AudioMixer.h index fffb0bd447..f1729317a4 100644 --- a/src/openrct2/audio/AudioMixer.h +++ b/src/openrct2/audio/AudioMixer.h @@ -43,13 +43,9 @@ namespace OpenRCT2::Audio virtual void Close() abstract; virtual void Lock() abstract; virtual void Unlock() abstract; - virtual IAudioChannel* Play(IAudioSource* source, int32_t loop, bool deleteondone, bool deletesourceondone) abstract; + virtual IAudioChannel* Play(IAudioSource* source, int32_t loop, bool deleteondone) abstract; virtual void Stop(IAudioChannel* channel) abstract; - virtual bool LoadMusic(size_t pathid) abstract; virtual void SetVolume(float volume) abstract; - - virtual IAudioSource* GetSoundSource(SoundId id) abstract; - virtual IAudioSource* GetMusicSource(int32_t id) abstract; }; } // namespace OpenRCT2::Audio @@ -61,8 +57,10 @@ namespace OpenRCT2::Audio #endif void Mixer_Init(const char* device); -void* Mixer_Play_Effect( +OpenRCT2::Audio::IAudioChannel* Mixer_Play_Effect( OpenRCT2::Audio::SoundId id, int32_t loop, int32_t volume, float pan, double rate, int32_t deleteondone); +OpenRCT2::Audio::IAudioChannel* Mixer_Play_Effect( + OpenRCT2::Audio::IAudioSource* source, int32_t loop, int32_t volume, float pan, double rate, int32_t deleteondone); void Mixer_Stop_Channel(void* channel); void Mixer_Channel_Volume(void* channel, int32_t volume); void Mixer_Channel_Pan(void* channel, float pan); @@ -71,8 +69,7 @@ int32_t Mixer_Channel_IsPlaying(void* channel); uint64_t Mixer_Channel_GetOffset(void* channel); int32_t Mixer_Channel_SetOffset(void* channel, uint64_t offset); void Mixer_Channel_SetGroup(void* channel, OpenRCT2::Audio::MixerGroup group); -void* Mixer_Play_Music(int32_t pathId, int32_t loop, int32_t streaming); -void* Mixer_Play_Music(const char* path, int32_t loop); +OpenRCT2::Audio::IAudioChannel* Mixer_Play_Music(OpenRCT2::Audio::IAudioSource* source, int32_t loop, int32_t streaming); void* Mixer_Play_Music(std::unique_ptr stream, int32_t loop); void Mixer_SetVolume(float volume); diff --git a/src/openrct2/audio/AudioSource.h b/src/openrct2/audio/AudioSource.h index 98a5350e44..414690a09c 100644 --- a/src/openrct2/audio/AudioSource.h +++ b/src/openrct2/audio/AudioSource.h @@ -21,8 +21,8 @@ namespace OpenRCT2::Audio { virtual ~IAudioSource() = default; + virtual void Release() abstract; virtual uint64_t GetLength() const abstract; - // virtual AudioFormat GetFormat() abstract; virtual size_t Read(void* dst, uint64_t offset, size_t len) abstract; }; diff --git a/src/openrct2/audio/DummyAudioContext.cpp b/src/openrct2/audio/DummyAudioContext.cpp index 09d05904fd..90795ba341 100644 --- a/src/openrct2/audio/DummyAudioContext.cpp +++ b/src/openrct2/audio/DummyAudioContext.cpp @@ -26,6 +26,16 @@ namespace OpenRCT2::Audio { } + IAudioSource* CreateStreamFromCSS(const std::string& /* path */, uint32_t /* index */) override + { + return nullptr; + } + + IAudioSource* CreateStreamFromCSS(std::unique_ptr /* stream */, uint32_t /* index */) override + { + return nullptr; + } + IAudioSource* CreateStreamFromWAV(const std::string& /*path*/) override { return nullptr; diff --git a/src/openrct2/audio/NullAudioSource.cpp b/src/openrct2/audio/NullAudioSource.cpp index 9b8b07af91..6090680df9 100644 --- a/src/openrct2/audio/NullAudioSource.cpp +++ b/src/openrct2/audio/NullAudioSource.cpp @@ -18,6 +18,10 @@ namespace OpenRCT2::Audio class NullAudioSource : public IAudioSource { public: + void Release() override + { + } + uint64_t GetLength() const override { return 0; diff --git a/src/openrct2/audio/audio.h b/src/openrct2/audio/audio.h index 7480b0a2a8..f79f062fa1 100644 --- a/src/openrct2/audio/audio.h +++ b/src/openrct2/audio/audio.h @@ -15,6 +15,7 @@ #include +class AudioObject; struct CoordsXYZ; namespace OpenRCT2::Audio @@ -120,12 +121,21 @@ namespace OpenRCT2::Audio DoorOpen, DoorClose, Portcullis, + CrowdAmbience, NoScream = 254, Null = 255 }; constexpr uint8_t RCT2SoundCount = static_cast(SoundId::Portcullis) + 1; + namespace AudioObjectIdentifiers + { + constexpr std::string_view Rct1Title = "rct1.audio.title"; + constexpr std::string_view Rct2Base = "rct2.audio.base"; + constexpr std::string_view Rct2Title = "rct2.audio.title"; + constexpr std::string_view Rct2Circus = "rct2.audio.circus"; + } // namespace AudioObjectIdentifiers + extern bool gGameSoundsOff; extern int32_t gVolumeAdjustZoom; @@ -243,4 +253,6 @@ namespace OpenRCT2::Audio void StopAll(); + AudioObject* GetBaseAudioObject(); + } // namespace OpenRCT2::Audio diff --git a/src/openrct2/config/Config.cpp b/src/openrct2/config/Config.cpp index a103ae24ec..fc59f098f4 100644 --- a/src/openrct2/config/Config.cpp +++ b/src/openrct2/config/Config.cpp @@ -360,7 +360,7 @@ namespace Config model->device = reader->GetCString("audio_device", nullptr); model->master_sound_enabled = reader->GetBoolean("master_sound", true); model->master_volume = reader->GetInt32("master_volume", 100); - model->title_music = reader->GetInt32("title_music", 2); + model->title_music = static_cast(reader->GetInt32("title_music", EnumValue(TitleMusicKind::Rct2))); model->sound_enabled = reader->GetBoolean("sound", true); model->sound_volume = reader->GetInt32("sound_volume", 100); model->ride_music_enabled = reader->GetBoolean("ride_music", true); @@ -376,7 +376,7 @@ namespace Config writer->WriteString("audio_device", model->device); writer->WriteBoolean("master_sound", model->master_sound_enabled); writer->WriteInt32("master_volume", model->master_volume); - writer->WriteInt32("title_music", model->title_music); + writer->WriteInt32("title_music", EnumValue(model->title_music)); writer->WriteBoolean("sound", model->sound_enabled); writer->WriteInt32("sound_volume", model->sound_volume); writer->WriteBoolean("ride_music", model->ride_music_enabled); diff --git a/src/openrct2/config/Config.h b/src/openrct2/config/Config.h index dd5a74056b..1599129f8d 100644 --- a/src/openrct2/config/Config.h +++ b/src/openrct2/config/Config.h @@ -22,6 +22,7 @@ enum class ScaleQuality : int32_t; enum class Sort : int32_t; enum class VirtualFloorStyles : int32_t; enum class DrawingEngine : int32_t; +enum class TitleMusicKind : int32_t; struct GeneralConfiguration { @@ -140,7 +141,7 @@ struct SoundConfiguration utf8* device; bool master_sound_enabled; uint8_t master_volume; - uint8_t title_music; + TitleMusicKind title_music; bool sound_enabled; uint8_t sound_volume; bool ride_music_enabled; @@ -246,6 +247,14 @@ enum class MeasurementFormat : int32_t SI }; +enum class TitleMusicKind : int32_t +{ + None, + Rct1, + Rct2, + Random +}; + extern GeneralConfiguration gConfigGeneral; extern InterfaceConfiguration gConfigInterface; extern SoundConfiguration gConfigSound; diff --git a/src/openrct2/core/Path.cpp b/src/openrct2/core/Path.cpp index 4954c42537..e6e51ca211 100644 --- a/src/openrct2/core/Path.cpp +++ b/src/openrct2/core/Path.cpp @@ -85,6 +85,12 @@ namespace Path return fs::u8path(path).replace_extension(fs::u8path(newExtension)).u8string(); } + bool IsAbsolute(u8string_view path) + { + auto p = fs::u8path(path); + return p.is_absolute(); + } + u8string GetAbsolute(u8string_view relative) { std::error_code ec; diff --git a/src/openrct2/core/Path.hpp b/src/openrct2/core/Path.hpp index b32ab85d27..8326929d2b 100644 --- a/src/openrct2/core/Path.hpp +++ b/src/openrct2/core/Path.hpp @@ -31,6 +31,7 @@ namespace Path u8string GetFileNameWithoutExtension(u8string_view path); u8string GetExtension(u8string_view path); u8string WithExtension(u8string_view path, u8string_view newExtension); + bool IsAbsolute(u8string_view path); u8string GetAbsolute(u8string_view relative); bool Equals(u8string_view a, u8string_view b); diff --git a/src/openrct2/core/Zip.cpp b/src/openrct2/core/Zip.cpp index d87644dc4b..6e5c170c56 100644 --- a/src/openrct2/core/Zip.cpp +++ b/src/openrct2/core/Zip.cpp @@ -220,6 +220,11 @@ private: : _zip(zip) , _index(index) { + zip_stat_t zipFileStat{}; + if (zip_stat_index(_zip, _index, 0, &zipFileStat) == ZIP_ER_OK) + { + _len = zipFileStat.size; + } } ~ZipItemStream() override diff --git a/src/openrct2/entity/Peep.cpp b/src/openrct2/entity/Peep.cpp index 3e2e6b28a7..55a9db480f 100644 --- a/src/openrct2/entity/Peep.cpp +++ b/src/openrct2/entity/Peep.cpp @@ -1259,8 +1259,7 @@ void peep_update_crowd_noise() // Mute crowd noise if (_crowdSoundChannel != nullptr) { - Mixer_Stop_Channel(_crowdSoundChannel); - _crowdSoundChannel = nullptr; + Mixer_Channel_Volume(_crowdSoundChannel, 0); } } else @@ -1276,7 +1275,8 @@ void peep_update_crowd_noise() // Load and play crowd noise if needed and set volume if (_crowdSoundChannel == nullptr) { - _crowdSoundChannel = Mixer_Play_Music(PATH_ID_CSS2, MIXER_LOOP_INFINITE, false); + _crowdSoundChannel = Mixer_Play_Effect( + OpenRCT2::Audio::SoundId::CrowdAmbience, MIXER_LOOP_INFINITE, 0, 0.5f, 1, false); if (_crowdSoundChannel != nullptr) { Mixer_Channel_SetGroup(_crowdSoundChannel, OpenRCT2::Audio::MixerGroup::Sound); diff --git a/src/openrct2/libopenrct2.vcxproj b/src/openrct2/libopenrct2.vcxproj index 28cc879860..355d35f6e8 100644 --- a/src/openrct2/libopenrct2.vcxproj +++ b/src/openrct2/libopenrct2.vcxproj @@ -284,6 +284,8 @@ + + @@ -753,6 +755,8 @@ + + diff --git a/src/openrct2/localisation/StringIds.h b/src/openrct2/localisation/StringIds.h index 12d06b8638..713016e5f1 100644 --- a/src/openrct2/localisation/StringIds.h +++ b/src/openrct2/localisation/StringIds.h @@ -2060,8 +2060,6 @@ enum : uint16_t STR_OPTIONS_MUSIC_VALUE_NONE = 2739, STR_ROLLERCOASTER_TYCOON_1_DROPDOWN = 2740, STR_ROLLERCOASTER_TYCOON_2_DROPDOWN = 2741, - STR_OPTIONS_MUSIC_ERR_CSS50_NOT_FOUND = 2742, - STR_OPTIONS_MUSIC_ERR_CSS50_NOT_FOUND_HINT = 2743, // 2744--2748 unused diff --git a/src/openrct2/network/NetworkBase.cpp b/src/openrct2/network/NetworkBase.cpp index 07db282fbd..7c0a2f750f 100644 --- a/src/openrct2/network/NetworkBase.cpp +++ b/src/openrct2/network/NetworkBase.cpp @@ -2297,14 +2297,6 @@ void NetworkBase::Client_Handle_OBJECTS_LIST(NetworkConnection& connection, Netw _missingObjects.clear(); } - if (totalObjects > OBJECT_ENTRY_COUNT) - { - connection.SetLastDisconnectReason(STR_MULTIPLAYER_SERVER_INVALID_REQUEST); - connection.Disconnect(); - log_warning("Server sent invalid amount of objects"); - return; - } - if (totalObjects > 0) { char objectListMsg[256]; @@ -2464,20 +2456,6 @@ void NetworkBase::Server_Handle_MAPREQUEST(NetworkConnection& connection, Networ { uint32_t size; packet >> size; - if (size > OBJECT_ENTRY_COUNT) - { - connection.SetLastDisconnectReason(STR_MULTIPLAYER_CLIENT_INVALID_REQUEST); - connection.Disconnect(); - std::string playerName = "(unknown)"; - if (connection.Player != nullptr) - { - playerName = connection.Player->Name; - } - std::string text = std::string("Player ") + playerName + std::string(" requested invalid amount of objects"); - AppendServerLog(text); - log_warning(text.c_str()); - return; - } log_verbose("Client requested %u objects", size); auto& repo = GetContext().GetObjectRepository(); for (uint32_t i = 0; i < size; i++) diff --git a/src/openrct2/object/AudioObject.cpp b/src/openrct2/object/AudioObject.cpp new file mode 100644 index 0000000000..6f3d807076 --- /dev/null +++ b/src/openrct2/object/AudioObject.cpp @@ -0,0 +1,46 @@ +/***************************************************************************** + * Copyright (c) 2014-2022 OpenRCT2 developers + * + * For a complete list of all authors, please refer to contributors.md + * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is licensed under the GNU General Public License version 3. + *****************************************************************************/ + +#include "AudioObject.h" + +#include "../Context.h" +#include "../PlatformEnvironment.h" +#include "../audio/AudioContext.h" +#include "../core/Json.hpp" +#include "../core/Path.hpp" + +using namespace OpenRCT2; +using namespace OpenRCT2::Audio; + +void AudioObject::Load() +{ + _sampleTable.Load(); +} + +void AudioObject::Unload() +{ + _sampleTable.Unload(); +} + +void AudioObject::ReadJson(IReadObjectContext* context, json_t& root) +{ + Guard::Assert(root.is_object(), "BannerObject::ReadJson expects parameter root to be object"); + _sampleTable.ReadFromJson(context, root); + PopulateTablesFromJson(context, root); +} + +IAudioSource* AudioObject::GetSample(uint32_t index) const +{ + return _sampleTable.GetSample(index); +} + +int32_t AudioObject::GetSampleModifier(uint32_t index) const +{ + return _sampleTable.GetSampleModifier(index); +} diff --git a/src/openrct2/object/AudioObject.h b/src/openrct2/object/AudioObject.h new file mode 100644 index 0000000000..4b6e2caa0f --- /dev/null +++ b/src/openrct2/object/AudioObject.h @@ -0,0 +1,31 @@ +/***************************************************************************** + * Copyright (c) 2014-2022 OpenRCT2 developers + * + * For a complete list of all authors, please refer to contributors.md + * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is licensed under the GNU General Public License version 3. + *****************************************************************************/ + +#pragma once + +#include "../audio/AudioSource.h" +#include "../core/IStream.hpp" +#include "AudioSampleTable.h" +#include "Object.h" + +#include + +class AudioObject final : public Object +{ +private: + AudioSampleTable _sampleTable; + +public: + void ReadJson(IReadObjectContext* context, json_t& root) override; + void Load() override; + void Unload() override; + + OpenRCT2::Audio::IAudioSource* GetSample(uint32_t index) const; + int32_t GetSampleModifier(uint32_t index) const; +}; diff --git a/src/openrct2/object/AudioSampleTable.cpp b/src/openrct2/object/AudioSampleTable.cpp new file mode 100644 index 0000000000..00f90fd28e --- /dev/null +++ b/src/openrct2/object/AudioSampleTable.cpp @@ -0,0 +1,220 @@ +/***************************************************************************** + * Copyright (c) 2014-2022 OpenRCT2 developers + * + * For a complete list of all authors, please refer to contributors.md + * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is licensed under the GNU General Public License version 3. + *****************************************************************************/ + +#include "AudioSampleTable.h" + +#include "../Context.h" +#include "../PlatformEnvironment.h" +#include "../audio/AudioContext.h" +#include "../core/Json.hpp" +#include "../core/Path.hpp" + +using namespace OpenRCT2; +using namespace OpenRCT2::Audio; + +struct SourceInfo +{ + std::string Path; + std::vector Range{}; +}; + +static std::vector ParseRange(std::string_view s) +{ + // Currently only supports [###] or [###..###] + std::vector result = {}; + if (s.length() >= 3 && s[0] == '[' && s[s.length() - 1] == ']') + { + s = s.substr(1, s.length() - 2); + auto parts = String::Split(s, ".."); + if (parts.size() == 1) + { + result.push_back(std::stoi(parts[0])); + } + else + { + auto left = std::stoi(parts[0]); + auto right = std::stoi(parts[1]); + if (left <= right) + { + for (auto i = left; i <= right; i++) + { + result.push_back(i); + } + } + else + { + for (auto i = right; i >= left; i--) + { + result.push_back(i); + } + } + } + } + return result; +} + +static SourceInfo ParseSource(std::string_view source) +{ + SourceInfo info; + if (String::StartsWith(source, "$RCT1:DATA/")) + { + auto name = source.substr(11); + auto rangeStart = name.find('['); + if (rangeStart != std::string::npos) + { + info.Range = ParseRange(name.substr(rangeStart)); + name = name.substr(0, rangeStart); + } + + auto env = GetContext()->GetPlatformEnvironment(); + auto dataPath = env->GetDirectoryPath(DIRBASE::RCT1, DIRID::DATA); + info.Path = Path::Combine(dataPath, name); + } + else if (String::StartsWith(source, "$RCT2:DATA/")) + { + auto name = source.substr(11); + auto rangeStart = name.find('['); + if (rangeStart != std::string::npos) + { + info.Range = ParseRange(name.substr(rangeStart)); + name = name.substr(0, rangeStart); + } + + auto env = GetContext()->GetPlatformEnvironment(); + auto dataPath = env->GetDirectoryPath(DIRBASE::RCT2, DIRID::DATA); + info.Path = Path::Combine(dataPath, name); + } + else if (String::StartsWith(source, "$[")) + { + info.Range = ParseRange(source.substr(1)); + } + else + { + info.Path = source; + } + return info; +} + +void AudioSampleTable::ReadFromJson(IReadObjectContext* context, const json_t& root) +{ + json_t jSamples = root["samples"]; + if (jSamples.is_array()) + { + for (auto& jSample : jSamples) + { + SourceInfo sourceInfo; + int32_t modifier{}; + if (jSample.is_string()) + { + sourceInfo = ParseSource(jSample.get()); + } + else if (jSample.is_object()) + { + sourceInfo = ParseSource(jSample.at("source").get()); + if (jSample.contains("modifier")) + { + modifier = jSample.at("modifier").get(); + } + } + + auto asset = context->GetAsset(sourceInfo.Path); + if (sourceInfo.Range.empty()) + { + auto& entry = _entries.emplace_back(); + entry.Asset = asset; + entry.Modifier = modifier; + } + else + { + for (auto index : sourceInfo.Range) + { + auto& entry = _entries.emplace_back(); + entry.Asset = asset; + entry.PathIndex = index; + entry.Modifier = modifier; + } + } + } + } +} + +void AudioSampleTable::LoadFrom(const AudioSampleTable& table, size_t index, size_t length) +{ + auto audioContext = GetContext()->GetAudioContext(); + auto numEntries = std::min(_entries.size(), length); + for (size_t i = 0; i < numEntries; i++) + { + auto& entry = _entries[i]; + if (entry.Source != nullptr) + { + continue; + } + + auto sourceIndex = index + i; + if (sourceIndex >= table._entries.size()) + { + continue; + } + + const auto& sourceEntry = table._entries[sourceIndex]; + if (sourceEntry.Asset) + { + auto stream = sourceEntry.Asset->GetStream(); + if (stream != nullptr) + { + if (sourceEntry.PathIndex) + { + entry.Source = audioContext->CreateStreamFromCSS(std::move(stream), *sourceEntry.PathIndex); + } + else + { + entry.Source = audioContext->CreateStreamFromWAV(std::move(stream)); + } + entry.Modifier = sourceEntry.Modifier; + } + } + } +} + +void AudioSampleTable::Load() +{ + LoadFrom(*this, 0, _entries.size()); +} + +void AudioSampleTable::Unload() +{ + for (auto& entry : _entries) + { + entry.Source->Release(); + entry.Source = nullptr; + } +} + +size_t AudioSampleTable::GetCount() const +{ + return _entries.size(); +} + +IAudioSource* AudioSampleTable::GetSample(uint32_t index) const +{ + if (index < _entries.size()) + { + return _entries[index].Source; + } + return nullptr; +} + +int32_t AudioSampleTable::GetSampleModifier(uint32_t index) const +{ + if (index < _entries.size()) + { + return _entries[index].Modifier; + } + return 0; +} diff --git a/src/openrct2/object/AudioSampleTable.h b/src/openrct2/object/AudioSampleTable.h new file mode 100644 index 0000000000..0f340e137a --- /dev/null +++ b/src/openrct2/object/AudioSampleTable.h @@ -0,0 +1,54 @@ +/***************************************************************************** + * Copyright (c) 2014-2022 OpenRCT2 developers + * + * For a complete list of all authors, please refer to contributors.md + * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is licensed under the GNU General Public License version 3. + *****************************************************************************/ + +#pragma once + +#include "../audio/AudioSource.h" +#include "../core/JsonFwd.hpp" + +struct IReadObjectContext; + +class AudioSampleTable +{ +private: + struct Entry + { + OpenRCT2::Audio::IAudioSource* Source{}; + std::optional Asset; + std::optional PathIndex; + int32_t Modifier{}; + }; + + std::vector _entries; + +public: + /** + * Read the entries from the given JSON into the table, but do not load anything. + */ + void ReadFromJson(IReadObjectContext* context, const json_t& root); + + /** + * Load all available entries from the given table. + */ + void LoadFrom(const AudioSampleTable& table, size_t index, size_t length); + + /** + * Load all available entries. + */ + void Load(); + + /** + * Unloads all entries that are currently loaded. + */ + void Unload(); + + size_t GetCount() const; + OpenRCT2::Audio::IAudioSource* GetSample(uint32_t index) const; + int32_t GetSampleModifier(uint32_t index) const; +}; diff --git a/src/openrct2/object/Object.cpp b/src/openrct2/object/Object.cpp index be7a43e268..451fa42a55 100644 --- a/src/openrct2/object/Object.cpp +++ b/src/openrct2/object/Object.cpp @@ -303,6 +303,21 @@ uint64_t ObjectAsset::GetSize() const return 0; } +std::vector ObjectAsset::GetData() const +{ + if (_zipPath.empty()) + { + return File::ReadAllBytes(_path); + } + + auto zipArchive = Zip::TryOpen(_zipPath, ZIP_ACCESS::READ); + if (zipArchive != nullptr) + { + return zipArchive->GetFileData(_path); + } + return {}; +} + std::unique_ptr ObjectAsset::GetStream() const { if (_zipPath.empty()) diff --git a/src/openrct2/object/Object.h b/src/openrct2/object/Object.h index e69ab06195..9d6821c516 100644 --- a/src/openrct2/object/Object.h +++ b/src/openrct2/object/Object.h @@ -16,6 +16,7 @@ #include "ImageTable.h" #include "StringTable.h" +#include #include #include #include @@ -46,6 +47,7 @@ enum class ObjectType : uint8_t Music, FootpathSurface, FootpathRailings, + Audio, Count, None = 255 @@ -53,6 +55,35 @@ enum class ObjectType : uint8_t ObjectType& operator++(ObjectType& d, int); +constexpr std::array ObjectTypes = { + ObjectType::Ride, + ObjectType::SmallScenery, + ObjectType::LargeScenery, + ObjectType::Walls, + ObjectType::Banners, + ObjectType::Paths, + ObjectType::PathBits, + ObjectType::SceneryGroup, + ObjectType::ParkEntrance, + ObjectType::Water, + ObjectType::ScenarioText, + ObjectType::TerrainSurface, + ObjectType::TerrainEdge, + ObjectType::Station, + ObjectType::Music, + ObjectType::FootpathSurface, + ObjectType::FootpathRailings, + ObjectType::Audio, +}; + +// Object types that can be saved in a park file. +constexpr std::array TransientObjectTypes = { + ObjectType::Ride, ObjectType::SmallScenery, ObjectType::LargeScenery, ObjectType::Walls, + ObjectType::Banners, ObjectType::Paths, ObjectType::PathBits, ObjectType::SceneryGroup, + ObjectType::ParkEntrance, ObjectType::Water, ObjectType::TerrainSurface, ObjectType::TerrainEdge, + ObjectType::Station, ObjectType::Music, ObjectType::FootpathSurface, ObjectType::FootpathRailings, +}; + namespace ObjectSelectionFlags { constexpr uint8_t Selected = (1 << 0); @@ -224,6 +255,7 @@ public: [[nodiscard]] bool IsAvailable() const; [[nodiscard]] uint64_t GetSize() const; + [[nodiscard]] std::vector GetData() const; [[nodiscard]] std::unique_ptr GetStream() const; }; @@ -397,3 +429,8 @@ void object_entry_get_name_fixed(utf8* buffer, size_t bufferSize, const rct_obje void* object_entry_get_chunk(ObjectType objectType, ObjectEntryIndex index); const Object* object_entry_get_object(ObjectType objectType, ObjectEntryIndex index); + +constexpr bool IsIntransientObjectType(ObjectType type) +{ + return type == ObjectType::Audio; +} diff --git a/src/openrct2/object/ObjectFactory.cpp b/src/openrct2/object/ObjectFactory.cpp index e2d0ba61e1..a1049915d4 100644 --- a/src/openrct2/object/ObjectFactory.cpp +++ b/src/openrct2/object/ObjectFactory.cpp @@ -20,6 +20,7 @@ #include "../core/String.hpp" #include "../core/Zip.h" #include "../rct12/SawyerChunkReader.h" +#include "AudioObject.h" #include "BannerObject.h" #include "EntranceObject.h" #include "FootpathItemObject.h" @@ -69,8 +70,15 @@ public: ObjectAsset GetAsset(std::string_view path) const override { - auto absolutePath = Path::Combine(_basePath, path); - return ObjectAsset(absolutePath); + if (Path::IsAbsolute(path)) + { + return ObjectAsset(path); + } + else + { + auto absolutePath = Path::Combine(_basePath, path); + return ObjectAsset(absolutePath); + } } }; @@ -367,6 +375,9 @@ namespace ObjectFactory case ObjectType::FootpathRailings: result = std::make_unique(); break; + case ObjectType::Audio: + result = std::make_unique(); + break; default: throw std::runtime_error("Invalid object type"); } @@ -405,6 +416,8 @@ namespace ObjectFactory return ObjectType::FootpathSurface; if (s == "footpath_railings") return ObjectType::FootpathRailings; + if (s == "audio") + return ObjectType::Audio; return ObjectType::None; } diff --git a/src/openrct2/object/ObjectLimits.h b/src/openrct2/object/ObjectLimits.h index 9f8094c6d0..1e5e70f241 100644 --- a/src/openrct2/object/ObjectLimits.h +++ b/src/openrct2/object/ObjectLimits.h @@ -30,26 +30,6 @@ constexpr const uint16_t MAX_STATION_OBJECTS = 255; constexpr const uint16_t MAX_MUSIC_OBJECTS = 255; constexpr const uint16_t MAX_FOOTPATH_SURFACE_OBJECTS = 255; constexpr const uint16_t MAX_FOOTPATH_RAILINGS_OBJECTS = 255; - -// clang-format off -constexpr const uint16_t OBJECT_ENTRY_COUNT = - MAX_RIDE_OBJECTS + - MAX_SMALL_SCENERY_OBJECTS + - MAX_LARGE_SCENERY_OBJECTS + - MAX_WALL_SCENERY_OBJECTS + - MAX_BANNER_OBJECTS + - MAX_PATH_OBJECTS + - MAX_PATH_ADDITION_OBJECTS + - MAX_SCENERY_GROUP_OBJECTS + - MAX_PARK_ENTRANCE_OBJECTS + - MAX_WATER_OBJECTS + - MAX_SCENARIO_TEXT_OBJECTS + - MAX_TERRAIN_SURFACE_OBJECTS + - MAX_TERRAIN_EDGE_OBJECTS + - MAX_STATION_OBJECTS + - MAX_MUSIC_OBJECTS + - MAX_FOOTPATH_SURFACE_OBJECTS + - MAX_FOOTPATH_RAILINGS_OBJECTS; -// clang-format on +constexpr const uint16_t MAX_AUDIO_OBJECTS = 255; constexpr const uint8_t DAT_NAME_LENGTH = 8; diff --git a/src/openrct2/object/ObjectList.cpp b/src/openrct2/object/ObjectList.cpp index 75b1ae5dc4..e353621a4b 100644 --- a/src/openrct2/object/ObjectList.cpp +++ b/src/openrct2/object/ObjectList.cpp @@ -40,6 +40,7 @@ int32_t object_entry_group_counts[] = { MAX_MUSIC_OBJECTS, MAX_FOOTPATH_SURFACE_OBJECTS, MAX_FOOTPATH_RAILINGS_OBJECTS, + MAX_AUDIO_OBJECTS, }; static_assert(std::size(object_entry_group_counts) == EnumValue(ObjectType::Count)); diff --git a/src/openrct2/object/ObjectManager.cpp b/src/openrct2/object/ObjectManager.cpp index 662c008197..f4f8b9fd1f 100644 --- a/src/openrct2/object/ObjectManager.cpp +++ b/src/openrct2/object/ObjectManager.cpp @@ -33,12 +33,23 @@ #include #include +/** + * Represents an object that is to be loaded or is loaded and ready + * to be placed in an object list. + */ +struct ObjectToLoad +{ + const ObjectRepositoryItem* RepositoryItem{}; + Object* LoadedObject{}; + ObjectEntryIndex Index{}; +}; + class ObjectManager final : public IObjectManager { private: IObjectRepository& _objectRepository; - std::vector _loadedObjects; + std::array, EnumValue(ObjectType::Count)> _loadedObjects; std::array, RIDE_TYPE_COUNT> _rideTypeToObjectMap; // Used to return a safe empty vector back from GetAllRideEntries, can be removed when std::span is available @@ -48,8 +59,6 @@ public: explicit ObjectManager(IObjectRepository& objectRepository) : _objectRepository(objectRepository) { - _loadedObjects.resize(OBJECT_ENTRY_COUNT); - UpdateSceneryGroupIndexes(); ResetTypeToRideEntryIndexMap(); } @@ -59,15 +68,6 @@ public: UnloadAll(); } - Object* GetLoadedObject(size_t index) override - { - if (index >= _loadedObjects.size()) - { - return nullptr; - } - return _loadedObjects[index]; - } - Object* GetLoadedObject(ObjectType objectType, size_t index) override { // This is sometimes done deliberately (to avoid boilerplate), so no need to log_warn for this. @@ -87,8 +87,13 @@ public: return nullptr; } - auto objectIndex = GetIndexFromTypeEntry(objectType, index); - return GetLoadedObject(objectIndex); + const auto& list = GetObjectList(objectType); + if (index >= list.size()) + { + return nullptr; + } + + return list[index]; } Object* GetLoadedObject(const ObjectEntryDescriptor& entry) override @@ -208,24 +213,27 @@ public: } } + void UnloadAllTransient() override + { + UnloadAll(true); + } + void UnloadAll() override { - for (auto* object : _loadedObjects) - { - UnloadObject(object); - } - UpdateSceneryGroupIndexes(); - ResetTypeToRideEntryIndexMap(); + UnloadAll(false); } void ResetObjects() override { - for (auto& loadedObject : _loadedObjects) + for (auto& list : _loadedObjects) { - if (loadedObject != nullptr) + for (auto* loadedObject : list) { - loadedObject->Unload(); - loadedObject->Load(); + if (loadedObject != nullptr) + { + loadedObject->Unload(); + loadedObject->Load(); + } } } UpdateSceneryGroupIndexes(); @@ -281,13 +289,37 @@ public: } private: - Object* LoadObject(int32_t slot, std::string_view identifier) + std::vector& GetObjectList(ObjectType type) + { + auto typeIndex = EnumValue(type); + return _loadedObjects[typeIndex]; + } + + void UnloadAll(bool onlyTransient) + { + for (auto type : ObjectTypes) + { + if (!onlyTransient || !IsIntransientObjectType(type)) + { + auto& list = GetObjectList(type); + for (auto* loadedObject : list) + { + UnloadObject(loadedObject); + } + list.clear(); + } + } + UpdateSceneryGroupIndexes(); + ResetTypeToRideEntryIndexMap(); + } + + Object* LoadObject(ObjectEntryIndex slot, std::string_view identifier) { const ObjectRepositoryItem* ori = _objectRepository.FindObject(identifier); return RepositoryItemToObject(ori, slot); } - Object* RepositoryItemToObject(const ObjectRepositoryItem* ori, std::optional slot = {}) + Object* RepositoryItemToObject(const ObjectRepositoryItem* ori, std::optional slot = {}) { if (ori == nullptr) return nullptr; @@ -299,7 +331,8 @@ private: ObjectType objectType = ori->Type; if (slot) { - if (_loadedObjects.size() > static_cast(*slot) && _loadedObjects[*slot] != nullptr) + auto& list = GetObjectList(objectType); + if (list.size() > *slot && list[*slot] != nullptr) { // Slot already taken return nullptr; @@ -309,17 +342,18 @@ private: { slot = FindSpareSlot(objectType); } - if (slot.has_value()) + if (slot) { auto* object = GetOrLoadObject(ori); if (object != nullptr) { - if (_loadedObjects.size() <= static_cast(*slot)) + auto& list = GetObjectList(objectType); + if (list.size() <= *slot) { - _loadedObjects.resize(slot.value() + 1); + list.resize(*slot + 1); } loadedObject = object; - _loadedObjects[slot.value()] = object; + list[*slot] = object; UpdateSceneryGroupIndexes(); ResetTypeToRideEntryIndexMap(); } @@ -327,21 +361,20 @@ private: return loadedObject; } - std::optional FindSpareSlot(ObjectType objectType) + std::optional FindSpareSlot(ObjectType objectType) { - size_t firstIndex = GetIndexFromTypeEntry(objectType, 0); - size_t endIndex = firstIndex + object_entry_group_counts[EnumValue(objectType)]; - for (size_t i = firstIndex; i < endIndex; i++) + auto& list = GetObjectList(objectType); + auto it = std::find(list.begin(), list.end(), nullptr); + if (it != list.end()) { - if (_loadedObjects.size() <= i) - { - _loadedObjects.resize(i + 1); - return static_cast(i); - } - if (_loadedObjects[i] == nullptr) - { - return static_cast(i); - } + return static_cast(std::distance(list.begin(), it)); + } + + auto maxSize = object_entry_group_counts[EnumValue(objectType)]; + if (list.size() < static_cast(maxSize)) + { + list.emplace_back(); + return static_cast(list.size() - 1); } return std::nullopt; } @@ -351,10 +384,11 @@ private: Guard::ArgumentNotNull(object, GUARD_LINE); auto result = std::numeric_limits().max(); - auto it = std::find(_loadedObjects.begin(), _loadedObjects.end(), object); - if (it != _loadedObjects.end()) + auto& list = GetObjectList(object->GetObjectType()); + auto it = std::find(list.begin(), list.end(), object); + if (it != list.end()) { - result = std::distance(_loadedObjects.begin(), it); + result = std::distance(list.begin(), it); } return result; } @@ -364,6 +398,11 @@ private: if (object == nullptr) return; + // Because it's possible to have the same loaded object for multiple + // slots, we have to make sure find and set all of them to nullptr + auto& list = GetObjectList(object->GetObjectType()); + std::replace(list.begin(), list.end(), object, static_cast(nullptr)); + object->Unload(); // TODO try to prevent doing a repository search @@ -372,10 +411,6 @@ private: { _objectRepository.UnregisterLoadedObject(ori, object); } - - // Because it's possible to have the same loaded object for multiple - // slots, we have to make sure find and set all of them to nullptr - std::replace(_loadedObjects.begin(), _loadedObjects.end(), object, static_cast(nullptr)); } void UnloadObjectsExcept(const std::vector& newLoadedObjects) @@ -393,62 +428,58 @@ private: // Unload objects that are not in the hash set size_t totalObjectsLoaded = 0; size_t numObjectsUnloaded = 0; - for (auto* object : _loadedObjects) + for (auto type : ObjectTypes) { - if (object == nullptr) - continue; - - totalObjectsLoaded++; - if (exceptSet.find(object) == exceptSet.end()) + if (!IsIntransientObjectType(type)) { - UnloadObject(object); - numObjectsUnloaded++; + auto& list = GetObjectList(type); + for (auto& object : list) + { + if (object == nullptr) + continue; + + totalObjectsLoaded++; + if (exceptSet.find(object) == exceptSet.end()) + { + UnloadObject(object); + object = nullptr; + numObjectsUnloaded++; + } + } } } log_verbose("%u / %u objects unloaded", numObjectsUnloaded, totalObjectsLoaded); } - template void UpdateSceneryGroupIndexes(Object* object) + template void UpdateSceneryGroupIndexes(ObjectType type) { - auto* sceneryEntry = static_cast(object->GetLegacyData()); - sceneryEntry->scenery_tab_id = GetPrimarySceneryGroupEntryIndex(object); + auto& list = GetObjectList(type); + for (auto* loadedObject : list) + { + if (loadedObject != nullptr) + { + auto* sceneryEntry = static_cast(loadedObject->GetLegacyData()); + sceneryEntry->scenery_tab_id = GetPrimarySceneryGroupEntryIndex(loadedObject); + } + } } void UpdateSceneryGroupIndexes() { - for (auto* loadedObject : _loadedObjects) - { - // The list can contain unused slots, skip them. - if (loadedObject == nullptr) - continue; + UpdateSceneryGroupIndexes(ObjectType::SmallScenery); + UpdateSceneryGroupIndexes(ObjectType::LargeScenery); + UpdateSceneryGroupIndexes(ObjectType::Walls); + UpdateSceneryGroupIndexes(ObjectType::Banners); + UpdateSceneryGroupIndexes(ObjectType::PathBits); - switch (loadedObject->GetObjectType()) + auto& list = GetObjectList(ObjectType::SceneryGroup); + for (auto* loadedObject : list) + { + auto sgObject = static_cast(loadedObject); + if (sgObject != nullptr) { - case ObjectType::SmallScenery: - UpdateSceneryGroupIndexes(loadedObject); - break; - case ObjectType::LargeScenery: - UpdateSceneryGroupIndexes(loadedObject); - break; - case ObjectType::Walls: - UpdateSceneryGroupIndexes(loadedObject); - break; - case ObjectType::Banners: - UpdateSceneryGroupIndexes(loadedObject); - break; - case ObjectType::PathBits: - UpdateSceneryGroupIndexes(loadedObject); - break; - case ObjectType::SceneryGroup: - { - auto sgObject = dynamic_cast(loadedObject); - sgObject->UpdateEntryIndexes(); - break; - } - default: - // This switch only handles scenery ObjectTypes. - break; + sgObject->UpdateEntryIndexes(); } } @@ -471,28 +502,33 @@ private: return entryIndex; } - std::vector GetRequiredObjects(const ObjectList& objectList) + std::vector GetRequiredObjects(const ObjectList& objectList) { - std::vector requiredObjects; + std::vector requiredObjects; std::vector missingObjects; for (auto objectType = ObjectType::Ride; objectType < ObjectType::Count; objectType++) { - auto maxObjectsOfType = static_cast(object_entry_group_counts[EnumValue(objectType)]); - for (ObjectEntryIndex i = 0; i < maxObjectsOfType; i++) + auto& descriptors = objectList.GetList(objectType); + auto maxSize = static_cast(object_entry_group_counts[EnumValue(objectType)]); + auto listSize = static_cast(std::min(descriptors.size(), maxSize)); + for (ObjectEntryIndex i = 0; i < listSize; i++) { - const ObjectRepositoryItem* ori = nullptr; const auto& entry = objectList.GetObject(objectType, i); if (entry.HasValue()) { - ori = _objectRepository.FindObject(entry); + const auto* ori = _objectRepository.FindObject(entry); if (ori == nullptr && entry.GetType() != ObjectType::ScenarioText) { missingObjects.push_back(entry); ReportMissingObject(entry); } + + ObjectToLoad otl; + otl.RepositoryItem = ori; + otl.Index = i; + requiredObjects.push_back(otl); } - requiredObjects.push_back(ori); } } @@ -528,19 +564,17 @@ private: } } - void LoadObjects(std::vector& requiredObjects) + void LoadObjects(std::vector& requiredObjects) { std::vector objects; std::vector newLoadedObjects; std::vector badObjects; - objects.resize(OBJECT_ENTRY_COUNT); - newLoadedObjects.reserve(OBJECT_ENTRY_COUNT); // Read objects std::mutex commonMutex; - ParallelFor(requiredObjects, [this, &commonMutex, requiredObjects, &objects, &badObjects, &newLoadedObjects](size_t i) { - auto* requiredObject = requiredObjects[i]; - Object* object = nullptr; + ParallelFor(requiredObjects, [&](size_t i) { + auto& otl = requiredObjects[i]; + auto* requiredObject = otl.RepositoryItem; if (requiredObject != nullptr) { auto* loadedObject = requiredObject->LoadedObject.get(); @@ -557,18 +591,21 @@ private: } else { - object = newObject.get(); - newLoadedObjects.push_back(object); + otl.LoadedObject = newObject.get(); + newLoadedObjects.push_back(otl.LoadedObject); // Connect the ori to the registered object _objectRepository.RegisterLoadedObject(requiredObject, std::move(newObject)); } } else { - object = loadedObject; + otl.LoadedObject = loadedObject; + } + { + std::lock_guard guard(commonMutex); + objects.push_back(loadedObject); } } - objects[i] = object; }); // Load objects @@ -590,14 +627,32 @@ private: // Unload objects which are not in the required list. if (objects.empty()) { - UnloadAll(); + UnloadAllTransient(); } else { UnloadObjectsExcept(objects); } - _loadedObjects = std::move(objects); + // Set the new object lists + for (auto type : ObjectTypes) + { + if (!IsIntransientObjectType(type)) + { + auto& list = GetObjectList(type); + list.clear(); + } + } + for (auto& otl : requiredObjects) + { + auto objectType = otl.LoadedObject->GetObjectType(); + auto& list = GetObjectList(objectType); + if (list.size() <= otl.Index) + { + list.resize(otl.Index + 1); + } + list[otl.Index] = otl.LoadedObject; + } log_verbose("%u / %u new objects loaded", newLoadedObjects.size(), requiredObjects.size()); } @@ -666,17 +721,6 @@ private: std::copy_n(entry->name, DAT_NAME_LENGTH, objName); Console::Error::WriteLine("[%s] Object could not be loaded.", objName); } - - static int32_t GetIndexFromTypeEntry(ObjectType objectType, size_t entryIndex) - { - int32_t result = 0; - for (int32_t i = 0; i < EnumValue(objectType); i++) - { - result += object_entry_group_counts[i]; - } - result += static_cast(entryIndex); - return result; - } }; std::unique_ptr CreateObjectManager(IObjectRepository& objectRepository) @@ -719,7 +763,7 @@ void object_manager_unload_objects(const std::vector& ent void object_manager_unload_all_objects() { auto& objectManager = OpenRCT2::GetContext()->GetObjectManager(); - objectManager.UnloadAll(); + objectManager.UnloadAllTransient(); } rct_string_id object_manager_get_source_game_string(const ObjectSourceGame sourceGame) diff --git a/src/openrct2/object/ObjectManager.h b/src/openrct2/object/ObjectManager.h index 7fe2d69a89..decdf52f88 100644 --- a/src/openrct2/object/ObjectManager.h +++ b/src/openrct2/object/ObjectManager.h @@ -25,7 +25,6 @@ struct IObjectManager { } - virtual Object* GetLoadedObject(size_t index) abstract; virtual Object* GetLoadedObject(ObjectType objectType, size_t index) abstract; virtual Object* GetLoadedObject(const ObjectEntryDescriptor& entry) abstract; virtual ObjectEntryIndex GetLoadedObjectEntryIndex(std::string_view identifier) abstract; @@ -38,6 +37,7 @@ struct IObjectManager virtual Object* LoadObject(const ObjectEntryDescriptor& descriptor) abstract; virtual void LoadObjects(const ObjectList& entries) abstract; virtual void UnloadObjects(const std::vector& entries) abstract; + virtual void UnloadAllTransient() abstract; virtual void UnloadAll() abstract; virtual void ResetObjects() abstract; diff --git a/src/openrct2/park/ParkFile.cpp b/src/openrct2/park/ParkFile.cpp index 70fdff0604..595c21b4af 100644 --- a/src/openrct2/park/ParkFile.cpp +++ b/src/openrct2/park/ParkFile.cpp @@ -361,8 +361,8 @@ namespace OpenRCT2 auto objectList = objManager.GetLoadedObjects(); // Write number of object sub lists - cs.Write(static_cast(ObjectType::Count)); - for (auto objectType = ObjectType::Ride; objectType < ObjectType::Count; objectType++) + cs.Write(static_cast(TransientObjectTypes.size())); + for (auto objectType : TransientObjectTypes) { // Write sub list const auto& list = objectList.GetList(objectType); diff --git a/src/openrct2/ride/RideAudio.cpp b/src/openrct2/ride/RideAudio.cpp index 0bf6731faf..651247ab83 100644 --- a/src/openrct2/ride/RideAudio.cpp +++ b/src/openrct2/ride/RideAudio.cpp @@ -11,9 +11,11 @@ #include "../Context.h" #include "../OpenRCT2.h" +#include "../audio/AudioChannel.h" #include "../audio/AudioMixer.h" #include "../audio/audio.h" #include "../config/Config.h" +#include "../object/AudioObject.h" #include "../object/MusicObject.h" #include "../object/ObjectManager.h" #include "Ride.h" @@ -175,22 +177,31 @@ namespace OpenRCT2::RideAudio static void StartRideMusicChannel(const ViewportRideMusicInstance& instance) { + auto& objManager = GetContext()->GetObjectManager(); + // Create new music channel auto ride = get_ride(instance.RideId); if (ride->type == RIDE_TYPE_CIRCUS) { - auto channel = Mixer_Play_Music(PATH_ID_CSS24, MIXER_LOOP_NONE, true); - if (channel != nullptr) + ObjectEntryDescriptor desc(ObjectType::Audio, AudioObjectIdentifiers::Rct2Circus); + auto audioObj = static_cast(objManager.GetLoadedObject(desc)); + if (audioObj != nullptr) { - // Move circus music to the sound mixer group - Mixer_Channel_SetGroup(channel, Audio::MixerGroup::Sound); - - _musicChannels.emplace_back(instance, channel); + auto source = audioObj->GetSample(0); + if (source != nullptr) + { + auto channel = Mixer_Play_Music(source, MIXER_LOOP_NONE, true); + if (channel != nullptr) + { + // Move circus music to the sound mixer group + channel->SetGroup(Audio::MixerGroup::Sound); + _musicChannels.emplace_back(instance, channel); + } + } } } else { - auto& objManager = GetContext()->GetObjectManager(); auto musicObj = static_cast(objManager.GetLoadedObject(ObjectType::Music, ride->music)); if (musicObj != nullptr) { diff --git a/src/openrct2/ride/TrackDesign.cpp b/src/openrct2/ride/TrackDesign.cpp index 22b4d1a214..641f821e87 100644 --- a/src/openrct2/ride/TrackDesign.cpp +++ b/src/openrct2/ride/TrackDesign.cpp @@ -620,7 +620,7 @@ std::unique_ptr TrackDesignImport(const utf8* path) static void TrackDesignLoadSceneryObjects(TrackDesign* td6) { auto& objectManager = OpenRCT2::GetContext()->GetObjectManager(); - objectManager.UnloadAll(); + objectManager.UnloadAllTransient(); // Load ride object if (td6->vehicle_object.HasValue()) diff --git a/src/openrct2/ride/Vehicle.cpp b/src/openrct2/ride/Vehicle.cpp index 7152e51f86..7981a22d8a 100644 --- a/src/openrct2/ride/Vehicle.cpp +++ b/src/openrct2/ride/Vehicle.cpp @@ -1085,33 +1085,36 @@ static void UpdateSound( volume = volume / 8; volume = std::max(volume - 0x1FFF, -10000); + if (id != sound.Id && sound.Id != OpenRCT2::Audio::SoundId::Null) + { + sound.Id = OpenRCT2::Audio::SoundId::Null; + Mixer_Stop_Channel(sound.Channel); + } if (id == OpenRCT2::Audio::SoundId::Null) { - if (sound.Id != OpenRCT2::Audio::SoundId::Null) - { - sound.Id = OpenRCT2::Audio::SoundId::Null; - Mixer_Stop_Channel(sound.Channel); - } return; } - if (sound.Id != OpenRCT2::Audio::SoundId::Null && id != sound.Id) + if (sound.Id == OpenRCT2::Audio::SoundId::Null) { - Mixer_Stop_Channel(sound.Channel); - } - - if ((sound.Id == OpenRCT2::Audio::SoundId::Null) || (id != sound.Id)) - { - sound.Id = id; - sound.Pan = sound_params->pan_x; - sound.Volume = volume; - sound.Frequency = sound_params->frequency; - uint16_t frequency = SoundFrequency(id, sound_params->frequency); - uint8_t looping = _soundParams[static_cast(id)][0]; - int32_t pan = sound_params->pan_x; - sound.Channel = Mixer_Play_Effect( + auto frequency = SoundFrequency(id, sound_params->frequency); + auto looping = _soundParams[static_cast(id)][0]; + auto pan = sound_params->pan_x; + auto channel = Mixer_Play_Effect( id, looping ? MIXER_LOOP_INFINITE : MIXER_LOOP_NONE, DStoMixerVolume(volume), DStoMixerPan(pan), DStoMixerRate(frequency), 0); + if (channel != nullptr) + { + sound.Id = id; + sound.Pan = sound_params->pan_x; + sound.Volume = volume; + sound.Frequency = sound_params->frequency; + sound.Channel = channel; + } + else + { + sound.Id = OpenRCT2::Audio::SoundId::Null; + } return; } if (volume != sound.Volume)