From 2f39442d25dc0da37850efc39db5fe1dca85e1c6 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sat, 9 Jan 2021 20:54:02 +0000 Subject: [PATCH] Implement ride music objects and refactor --- src/openrct2-ui/audio/AudioContext.cpp | 5 + src/openrct2-ui/audio/AudioContext.h | 2 + src/openrct2-ui/audio/FileAudioSource.cpp | 43 ++ src/openrct2-ui/windows/Options.cpp | 3 +- src/openrct2-ui/windows/Ride.cpp | 127 ++++-- src/openrct2/Context.cpp | 1 + src/openrct2/actions/RideSetSettingAction.cpp | 8 +- src/openrct2/audio/Audio.cpp | 57 +-- src/openrct2/audio/AudioContext.h | 2 + src/openrct2/audio/AudioMixer.cpp | 48 ++ src/openrct2/audio/AudioMixer.h | 5 + src/openrct2/audio/DummyAudioContext.cpp | 5 + src/openrct2/audio/audio.h | 41 +- src/openrct2/core/Path.cpp | 43 +- src/openrct2/core/Path.hpp | 4 +- src/openrct2/core/Zip.cpp | 274 ++++++++++-- src/openrct2/core/Zip.h | 6 + src/openrct2/core/ZipAndroid.cpp | 34 ++ src/openrct2/interface/Window.cpp | 3 +- src/openrct2/libopenrct2.vcxproj | 6 +- src/openrct2/object/MusicObject.cpp | 169 +++++++ src/openrct2/object/MusicObject.h | 64 +++ src/openrct2/object/Object.cpp | 80 ++++ src/openrct2/object/Object.h | 27 ++ src/openrct2/object/ObjectFactory.cpp | 37 +- src/openrct2/object/ObjectLimits.h | 2 +- src/openrct2/object/ObjectManager.cpp | 34 ++ src/openrct2/object/ObjectRepository.cpp | 2 +- src/openrct2/object/RideObject.h | 3 +- src/openrct2/ride/MusicList.cpp | 115 ----- src/openrct2/ride/MusicList.h | 71 --- src/openrct2/ride/Ride.cpp | 307 +------------ src/openrct2/ride/Ride.h | 3 - src/openrct2/ride/RideAudio.cpp | 422 ++++++++++++++++++ src/openrct2/ride/RideAudio.h | 22 + 35 files changed, 1401 insertions(+), 674 deletions(-) create mode 100644 src/openrct2/object/MusicObject.cpp create mode 100644 src/openrct2/object/MusicObject.h delete mode 100644 src/openrct2/ride/MusicList.cpp delete mode 100644 src/openrct2/ride/MusicList.h create mode 100644 src/openrct2/ride/RideAudio.cpp create mode 100644 src/openrct2/ride/RideAudio.h diff --git a/src/openrct2-ui/audio/AudioContext.cpp b/src/openrct2-ui/audio/AudioContext.cpp index a6b907ae63..35d2a254f6 100644 --- a/src/openrct2-ui/audio/AudioContext.cpp +++ b/src/openrct2-ui/audio/AudioContext.cpp @@ -70,6 +70,11 @@ namespace OpenRCT2::Audio return AudioSource::CreateStreamFromWAV(path); } + IAudioSource* CreateStreamFromWAV(std::unique_ptr stream) override + { + return AudioSource::CreateStreamFromWAV(std::move(stream)); + } + void StartTitleMusic() override { } diff --git a/src/openrct2-ui/audio/AudioContext.h b/src/openrct2-ui/audio/AudioContext.h index 0fe4a17def..9babdb4635 100644 --- a/src/openrct2-ui/audio/AudioContext.h +++ b/src/openrct2-ui/audio/AudioContext.h @@ -9,6 +9,7 @@ #pragma once +#include #include #include #include @@ -66,6 +67,7 @@ namespace OpenRCT2::Audio 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); } // namespace AudioSource namespace AudioChannel diff --git a/src/openrct2-ui/audio/FileAudioSource.cpp b/src/openrct2-ui/audio/FileAudioSource.cpp index f5cffb6f2a..e9a504a845 100644 --- a/src/openrct2-ui/audio/FileAudioSource.cpp +++ b/src/openrct2-ui/audio/FileAudioSource.cpp @@ -202,4 +202,47 @@ namespace OpenRCT2::Audio } return source; } + + IAudioSource* AudioSource::CreateStreamFromWAV(std::unique_ptr stream) + { + using streamptr = std::unique_ptr*; + auto data = new std::unique_ptr(std::move(stream)); + + auto rw = new SDL_RWops(); + *rw = {}; + rw->type = SDL_RWOPS_UNKNOWN; + rw->hidden.unknown.data1 = data; + rw->seek = [](SDL_RWops* ctx, Sint64 offset, int whence) { + auto ptr = static_cast(ctx->hidden.unknown.data1); + auto dir = std::ios_base::beg; + if (whence == RW_SEEK_CUR) + { + dir = std::ios_base::cur; + } + else if (whence == RW_SEEK_END) + { + dir = std::ios_base::end; + } + (*ptr)->seekg(offset, dir); + return (*ptr)->fail() ? -1 : static_cast((*ptr)->tellg()); + }; + rw->read = [](SDL_RWops* ctx, void* buf, size_t size, size_t maxnum) { + auto ptr = static_cast(ctx->hidden.unknown.data1); + (*ptr)->read(static_cast(buf), size * maxnum); + return static_cast((*ptr)->gcount() / size); + }; + rw->size = [](SDL_RWops* ctx) { + auto ptr = static_cast(ctx->hidden.unknown.data1); + return static_cast((*ptr)->tellg()); + }; + 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/windows/Options.cpp b/src/openrct2-ui/windows/Options.cpp index 4ba57e6b59..b4edb0b41e 100644 --- a/src/openrct2-ui/windows/Options.cpp +++ b/src/openrct2-ui/windows/Options.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -1374,7 +1375,7 @@ static void window_options_audio_mouseup(rct_window* w, rct_widgetindex widgetIn gConfigSound.ride_music_enabled = !gConfigSound.ride_music_enabled; if (!gConfigSound.ride_music_enabled) { - OpenRCT2::Audio::StopRideMusic(); + RideAudioStopAllChannels(); } config_save_default(); w->Invalidate(); diff --git a/src/openrct2-ui/windows/Ride.cpp b/src/openrct2-ui/windows/Ride.cpp index ee5aa50d4d..b2160a1fe3 100644 --- a/src/openrct2-ui/windows/Ride.cpp +++ b/src/openrct2-ui/windows/Ride.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -52,6 +53,8 @@ #include #include #include +#include + using namespace OpenRCT2; static constexpr const rct_string_id WINDOW_TITLE = STR_RIDE_WINDOW_TITLE; @@ -4938,15 +4941,20 @@ static void window_ride_colour_scrollpaint(rct_window* w, rct_drawpixelinfo* dpi #pragma region Music static constexpr const uint8_t MusicStyleOrder[] = { - MUSIC_STYLE_GENTLE, MUSIC_STYLE_SUMMER, MUSIC_STYLE_WATER, MUSIC_STYLE_RAGTIME, MUSIC_STYLE_TECHNO, - MUSIC_STYLE_MECHANICAL, MUSIC_STYLE_MODERN, MUSIC_STYLE_WILD_WEST, MUSIC_STYLE_PIRATES, MUSIC_STYLE_ROCK, - MUSIC_STYLE_ROCK_STYLE_2, MUSIC_STYLE_ROCK_STYLE_3, MUSIC_STYLE_FANTASY, MUSIC_STYLE_HORROR, MUSIC_STYLE_TOYLAND, - MUSIC_STYLE_CANDY_STYLE, MUSIC_STYLE_ROMAN_FANFARE, MUSIC_STYLE_ORIENTAL, MUSIC_STYLE_MARTIAN, MUSIC_STYLE_SPACE, - MUSIC_STYLE_JUNGLE_DRUMS, MUSIC_STYLE_JURASSIC, MUSIC_STYLE_EGYPTIAN, MUSIC_STYLE_DODGEMS_BEAT, MUSIC_STYLE_SNOW, - MUSIC_STYLE_ICE, MUSIC_STYLE_MEDIEVAL, MUSIC_STYLE_URBAN, MUSIC_STYLE_ORGAN + MUSIC_STYLE_GENTLE, MUSIC_STYLE_SUMMER, MUSIC_STYLE_WATER, + MUSIC_STYLE_RAGTIME, MUSIC_STYLE_TECHNO, MUSIC_STYLE_MECHANICAL, + MUSIC_STYLE_MODERN, MUSIC_STYLE_WILD_WEST, MUSIC_STYLE_PIRATES, + MUSIC_STYLE_ROCK, MUSIC_STYLE_ROCK_STYLE_2, MUSIC_STYLE_ROCK_STYLE_3, + MUSIC_STYLE_FANTASY, MUSIC_STYLE_HORROR, MUSIC_STYLE_TOYLAND, + MUSIC_STYLE_CANDY_STYLE, MUSIC_STYLE_ROMAN_FANFARE, MUSIC_STYLE_ORIENTAL, + MUSIC_STYLE_MARTIAN, MUSIC_STYLE_SPACE, MUSIC_STYLE_JUNGLE_DRUMS, + MUSIC_STYLE_JURASSIC, MUSIC_STYLE_EGYPTIAN, MUSIC_STYLE_DODGEMS_BEAT, + MUSIC_STYLE_SNOW, MUSIC_STYLE_ICE, MUSIC_STYLE_MEDIEVAL, + MUSIC_STYLE_URBAN, MUSIC_STYLE_ORGAN, MUSIC_STYLE_CUSTOM_MUSIC_1, + MUSIC_STYLE_CUSTOM_MUSIC_2 }; -static uint8_t window_ride_current_music_style_order[42]; +static std::vector window_ride_current_music_style_order; /** * @@ -5001,6 +5009,25 @@ static void window_ride_music_resize(rct_window* w) window_set_resize(w, 316, 81, 316, 81); } +static size_t GetMusicStyleOrder(ObjectEntryIndex musicObjectIndex) +{ + auto& objManager = GetContext()->GetObjectManager(); + auto musicObj = static_cast(objManager.GetLoadedObject(ObjectType::Music, musicObjectIndex)); + + // Get the index in the order list + auto originalStyleId = musicObj->GetOriginalStyleId(); + if (originalStyleId) + { + auto it = std::find(std::begin(MusicStyleOrder), std::end(MusicStyleOrder), *originalStyleId); + if (it != std::end(MusicStyleOrder)) + { + return std::distance(std::begin(MusicStyleOrder), it); + } + } + + return std::numeric_limits::max(); +} + /** * * rct2: 0x006B1EFC @@ -5015,37 +5042,67 @@ static void window_ride_music_mousedown(rct_window* w, rct_widgetindex widgetInd if (ride == nullptr) return; - int32_t numItems = 0; - if (ride->type == RIDE_TYPE_MERRY_GO_ROUND) + // Construct list of available music + auto& musicOrder = window_ride_current_music_style_order; + musicOrder.clear(); + auto& objManager = GetContext()->GetObjectManager(); + for (ObjectEntryIndex i = 0; i < MAX_MUSIC_OBJECTS; i++) { - window_ride_current_music_style_order[numItems++] = MUSIC_STYLE_FAIRGROUND_ORGAN; - } - else - { - for (size_t n = 0; n < std::size(MusicStyleOrder); n++) - window_ride_current_music_style_order[numItems++] = MusicStyleOrder[n]; + auto musicObj = static_cast(objManager.GetLoadedObject(ObjectType::Music, i)); + if (musicObj != nullptr) + { + // Hide custom music if the WAV file does not exist + auto originalStyleId = musicObj->GetOriginalStyleId(); + if (originalStyleId == MUSIC_STYLE_CUSTOM_MUSIC_1 || originalStyleId == MUSIC_STYLE_CUSTOM_MUSIC_2) + { + auto numTracks = musicObj->GetTrackCount(); + if (numTracks > 0) + { + auto track0 = musicObj->GetTrack(0); + if (!track0->Asset.IsAvailable()) + { + continue; + } + } + else + { + continue; + } + } - if (OpenRCT2::Audio::gRideMusicInfoList[36].length != 0) - window_ride_current_music_style_order[numItems++] = MUSIC_STYLE_CUSTOM_MUSIC_1; - if (OpenRCT2::Audio::gRideMusicInfoList[37].length != 0) - window_ride_current_music_style_order[numItems++] = MUSIC_STYLE_CUSTOM_MUSIC_2; + if (musicObj->SupportsRideType(ride->type)) + { + musicOrder.push_back(i); + } + } } - for (auto i = 0; i < numItems; i++) + // Sort available music by the original RCT2 list order + std::stable_sort(musicOrder.begin(), musicOrder.end(), [](const ObjectEntryIndex& a, const ObjectEntryIndex& b) { + auto orderA = GetMusicStyleOrder(a); + auto orderB = GetMusicStyleOrder(b); + return orderA < orderB; + }); + + // Setup dropdown list + auto numItems = musicOrder.size(); + for (size_t i = 0; i < numItems; i++) { + auto musicObj = static_cast(objManager.GetLoadedObject(ObjectType::Music, musicOrder[i])); gDropdownItemsFormat[i] = STR_DROPDOWN_MENU_LABEL; - gDropdownItemsArgs[i] = MusicStyleNames[window_ride_current_music_style_order[i]]; + gDropdownItemsArgs[i] = musicObj->NameStringId; } WindowDropdownShowTextCustomWidth( { w->windowPos.x + dropdownWidget->left, w->windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1, w->colours[1], 0, Dropdown::Flag::StayOpen, numItems, widget->right - dropdownWidget->left); - for (auto i = 0; i < numItems; i++) + // Set currently checked item + for (size_t i = 0; i < numItems; i++) { - if (window_ride_current_music_style_order[i] == ride->music) + if (musicOrder[i] == ride->music) { - Dropdown::SetChecked(i, true); + Dropdown::SetChecked(static_cast(i), true); } } } @@ -5056,13 +5113,12 @@ static void window_ride_music_mousedown(rct_window* w, rct_widgetindex widgetInd */ static void window_ride_music_dropdown(rct_window* w, rct_widgetindex widgetIndex, int32_t dropdownIndex) { - uint8_t musicStyle; - - if (widgetIndex != WIDX_MUSIC_DROPDOWN || dropdownIndex == -1) - return; - - musicStyle = window_ride_current_music_style_order[dropdownIndex]; - set_operating_setting(w->number, RideSetSetting::MusicType, musicStyle); + if (widgetIndex == WIDX_MUSIC_DROPDOWN && dropdownIndex >= 0 + && static_cast(dropdownIndex) < window_ride_current_music_style_order.size()) + { + auto musicStyle = window_ride_current_music_style_order[dropdownIndex]; + set_operating_setting(w->number, RideSetSetting::MusicType, musicStyle); + } } /** @@ -5099,7 +5155,14 @@ static void window_ride_music_invalidate(rct_window* w) ride->FormatNameTo(ft); // Set selected music - window_ride_music_widgets[WIDX_MUSIC].text = MusicStyleNames[ride->music]; + rct_string_id musicName = STR_NONE; + auto& objManager = GetContext()->GetObjectManager(); + auto musicObj = static_cast(objManager.GetLoadedObject(ObjectType::Music, ride->music)); + if (musicObj != nullptr) + { + musicName = musicObj->NameStringId; + } + window_ride_music_widgets[WIDX_MUSIC].text = musicName; // Set music activated auto isMusicActivated = (ride->lifecycle_flags & RIDE_LIFECYCLE_MUSIC) != 0; diff --git a/src/openrct2/Context.cpp b/src/openrct2/Context.cpp index 543af3ae70..208311b36f 100644 --- a/src/openrct2/Context.cpp +++ b/src/openrct2/Context.cpp @@ -175,6 +175,7 @@ namespace OpenRCT2 gfx_object_check_all_images_freed(); gfx_unload_g2(); gfx_unload_g1(); + Audio::Close(); config_release(); Instance = nullptr; diff --git a/src/openrct2/actions/RideSetSettingAction.cpp b/src/openrct2/actions/RideSetSettingAction.cpp index b95454fde3..e6f163af87 100644 --- a/src/openrct2/actions/RideSetSettingAction.cpp +++ b/src/openrct2/actions/RideSetSettingAction.cpp @@ -9,6 +9,8 @@ #include "RideSetSettingAction.h" +#include "../Context.h" +#include "../object/ObjectManager.h" #include "../ride/Ride.h" #include "../ride/RideData.h" @@ -100,12 +102,16 @@ GameActions::Result::Ptr RideSetSettingAction::Query() const case RideSetSetting::Music: break; case RideSetSetting::MusicType: - if (_value >= MUSIC_STYLE_COUNT) + { + auto& objManager = OpenRCT2::GetContext()->GetObjectManager(); + auto musicObj = objManager.GetLoadedObject(ObjectType::Music, _value); + if (musicObj == nullptr) { log_warning("Invalid music style: %u", _value); return MakeResult(GameActions::Status::InvalidParameters, STR_CANT_CHANGE_OPERATING_MODE); } break; + } case RideSetSetting::LiftHillSpeed: if (!ride_is_valid_lift_hill_speed(ride)) { diff --git a/src/openrct2/audio/Audio.cpp b/src/openrct2/audio/Audio.cpp index 383cc3380e..4294a4577e 100644 --- a/src/openrct2/audio/Audio.cpp +++ b/src/openrct2/audio/Audio.cpp @@ -22,6 +22,7 @@ #include "../localisation/StringIds.h" #include "../peep/Peep.h" #include "../ride/Ride.h" +#include "../ride/RideAudio.h" #include "../ui/UiContext.h" #include "../util/Util.h" #include "AudioContext.h" @@ -48,9 +49,6 @@ namespace OpenRCT2::Audio void* gTitleMusicChannel = nullptr; void* gWeatherSoundChannel = nullptr; - RideMusic gRideMusicList[MaxRideMusic]; - RideMusicParams gRideMusicParamsList[MaxRideMusic]; - RideMusicParams* gRideMusicParamsListEnd; VehicleSound gVehicleSoundList[MaxVehicleSounds]; // clang-format off @@ -290,26 +288,11 @@ namespace OpenRCT2::Audio } } - void StopRideMusic() - { - for (auto& rideMusic : gRideMusicList) - { - if (rideMusic.ride_id != RIDE_ID_NULL) - { - rideMusic.ride_id = RIDE_ID_NULL; - if (rideMusic.sound_channel != nullptr) - { - Mixer_Stop_Channel(rideMusic.sound_channel); - } - } - } - } - void StopAll() { StopTitleMusic(); StopVehicleSounds(); - StopRideMusic(); + RideAudioStopAllChannels(); peep_stop_crowd_noise(); StopWeatherSound(); } @@ -355,33 +338,7 @@ namespace OpenRCT2::Audio void InitRideSoundsAndInfo() { - int32_t deviceNum = 0; - InitRideSounds(deviceNum); - - for (auto& rideMusicInfo : gRideMusicInfoList) - { - const utf8* path = context_get_path_legacy(rideMusicInfo.path_id); - if (File::Exists(path)) - { - try - { - auto fs = OpenRCT2::FileStream(path, OpenRCT2::FILE_MODE_OPEN); - uint32_t head = fs.ReadValue(); - if (head == 0x78787878) - { - rideMusicInfo.length = 0; - } - // The length used to be hardcoded, but we stopped doing that to allow replacement. - if (rideMusicInfo.length == 0) - { - rideMusicInfo.length = fs.GetLength(); - } - } - catch (const std::exception&) - { - } - } - } + InitRideSounds(0); } void InitRideSounds(int32_t device) @@ -394,17 +351,13 @@ namespace OpenRCT2::Audio _currentAudioDevice = device; config_save_default(); - for (auto& rideMusic : gRideMusicList) - { - rideMusic.ride_id = RIDE_ID_NULL; - } } void Close() { peep_stop_crowd_noise(); StopTitleMusic(); - StopRideMusic(); + RideAudioStopAllChannels(); StopWeatherSound(); _currentAudioDevice = -1; } @@ -429,7 +382,7 @@ namespace OpenRCT2::Audio { gGameSoundsOff = true; StopVehicleSounds(); - StopRideMusic(); + RideAudioStopAllChannels(); peep_stop_crowd_noise(); StopWeatherSound(); } diff --git a/src/openrct2/audio/AudioContext.h b/src/openrct2/audio/AudioContext.h index 35633445ea..b4495cfd33 100644 --- a/src/openrct2/audio/AudioContext.h +++ b/src/openrct2/audio/AudioContext.h @@ -11,6 +11,7 @@ #include "../common.h" +#include #include #include #include @@ -34,6 +35,7 @@ namespace OpenRCT2::Audio virtual void SetOutputDevice(const std::string& deviceName) abstract; virtual IAudioSource* CreateStreamFromWAV(const std::string& path) abstract; + virtual IAudioSource* CreateStreamFromWAV(std::unique_ptr stream) abstract; virtual void StartTitleMusic() abstract; diff --git a/src/openrct2/audio/AudioMixer.cpp b/src/openrct2/audio/AudioMixer.cpp index 0e96410ad8..67b96d06ed 100644 --- a/src/openrct2/audio/AudioMixer.cpp +++ b/src/openrct2/audio/AudioMixer.cpp @@ -166,6 +166,54 @@ 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) +{ + IAudioChannel* channel = nullptr; + IAudioMixer* mixer = GetMixer(); + if (mixer != nullptr) + { + auto audioContext = GetContext()->GetAudioContext(); + auto source = audioContext->CreateStreamFromWAV(path); + if (source != nullptr) + { + channel = mixer->Play(source, loop, false, true); + if (channel == nullptr) + { + delete source; + } + } + } + if (channel != nullptr) + { + channel->SetGroup(MixerGroup::RideMusic); + } + return channel; +} + +void* Mixer_Play_Music(std::unique_ptr stream, int32_t loop) +{ + IAudioChannel* channel = nullptr; + IAudioMixer* mixer = GetMixer(); + if (mixer != nullptr) + { + auto audioContext = GetContext()->GetAudioContext(); + auto source = audioContext->CreateStreamFromWAV(std::move(stream)); + if (source != nullptr) + { + channel = mixer->Play(source, loop, false, true); + if (channel == nullptr) + { + delete source; + } + } + } + if (channel != nullptr) + { + channel->SetGroup(MixerGroup::RideMusic); + } + return channel; +} + void Mixer_SetVolume(float volume) { GetMixer()->SetVolume(volume); diff --git a/src/openrct2/audio/AudioMixer.h b/src/openrct2/audio/AudioMixer.h index cfba2b95a9..e844713e8e 100644 --- a/src/openrct2/audio/AudioMixer.h +++ b/src/openrct2/audio/AudioMixer.h @@ -11,6 +11,9 @@ #include "../common.h" +#include +#include + #define MIXER_VOLUME_MAX 128 #define MIXER_LOOP_NONE 0 #define MIXER_LOOP_INFINITE (-1) @@ -69,6 +72,8 @@ 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); +void* Mixer_Play_Music(std::unique_ptr stream, int32_t loop); void Mixer_SetVolume(float volume); int32_t DStoMixerVolume(int32_t volume); diff --git a/src/openrct2/audio/DummyAudioContext.cpp b/src/openrct2/audio/DummyAudioContext.cpp index 46f51ac74c..abc1d79aac 100644 --- a/src/openrct2/audio/DummyAudioContext.cpp +++ b/src/openrct2/audio/DummyAudioContext.cpp @@ -31,6 +31,11 @@ namespace OpenRCT2::Audio return nullptr; } + IAudioSource* CreateStreamFromWAV(std::unique_ptr) override + { + return nullptr; + } + void StartTitleMusic() override { } diff --git a/src/openrct2/audio/audio.h b/src/openrct2/audio/audio.h index c3d7123162..8c05248d81 100644 --- a/src/openrct2/audio/audio.h +++ b/src/openrct2/audio/audio.h @@ -12,12 +12,13 @@ #include "../common.h" #include "../ride/RideTypes.h" +#include + struct CoordsXYZ; namespace OpenRCT2::Audio { constexpr size_t MaxDeviceNameSize = 256; - constexpr size_t MaxRideMusic = 32; constexpr size_t MaxVehicleSounds = 14; constexpr size_t MaxDefaultMusic = 46; constexpr uint16_t SoundIdNull = 0xFFFF; @@ -26,33 +27,6 @@ namespace OpenRCT2::Audio enum class SoundId : uint8_t; - struct RideMusic - { - ride_id_t ride_id; - uint8_t tune_id; - int16_t volume; - int16_t pan; - uint16_t frequency; - void* sound_channel; - }; - - struct RideMusicInfo - { - uint8_t path_id; - uint32_t offset; - uint32_t length; - }; - - struct RideMusicParams - { - ride_id_t ride_id; - uint8_t tune_id; - int32_t offset; - int16_t volume; - int16_t pan; - uint16_t frequency; - }; - struct Sound { SoundId Id; @@ -157,11 +131,6 @@ namespace OpenRCT2::Audio extern void* gTitleMusicChannel; extern void* gWeatherSoundChannel; - extern RideMusic gRideMusicList[MaxRideMusic]; - extern RideMusicInfo gRideMusicInfoList[MaxDefaultMusic]; - extern RideMusicParams gRideMusicParamsList[MaxRideMusic]; - extern RideMusicParams* gRideMusicParamsListEnd; - extern VehicleSound gVehicleSoundList[MaxVehicleSounds]; /** @@ -247,12 +216,6 @@ namespace OpenRCT2::Audio */ void StopWeatherSound(); - /** - * Stops ride music from playing. - * rct2: 0x006BCA9F - */ - void StopRideMusic(); - /** * Stops the title music from playing. * rct2: 0x006BD0BD diff --git a/src/openrct2/core/Path.cpp b/src/openrct2/core/Path.cpp index 072694340f..25da5eb41c 100644 --- a/src/openrct2/core/Path.cpp +++ b/src/openrct2/core/Path.cpp @@ -30,12 +30,45 @@ namespace Path return safe_strcat_path(buffer, src, bufferSize); } - std::string Combine(const std::string& a, const std::string& b) + static constexpr bool IsPathSeparator(char c) { - utf8 buffer[MAX_PATH]; - String::Set(buffer, sizeof(buffer), a.c_str()); - Path::Append(buffer, sizeof(buffer), b.c_str()); - return std::string(buffer); +#ifdef _WIN32 + if (c == '\\') + return true; +#endif + return c == '/'; + } + + std::string Combine(std::string_view a, std::string_view b) + { + if (a.empty()) + return std::string(b); + if (b.empty()) + return std::string(a); + auto aEnd = a[a.size() - 1]; + auto bBegin = b[0]; + if (IsPathSeparator(aEnd)) + { + if (IsPathSeparator(bBegin)) + { + return std::string(a) + std::string(b.substr(1)); + } + else + { + return std::string(a) + std::string(b); + } + } + else + { + if (IsPathSeparator(bBegin)) + { + return std::string(a) + std::string(b); + } + else + { + return std::string(a) + PATH_SEPARATOR + std::string(b); + } + } } std::string GetDirectory(const std::string& path) diff --git a/src/openrct2/core/Path.hpp b/src/openrct2/core/Path.hpp index 26bb76d762..bcda50099c 100644 --- a/src/openrct2/core/Path.hpp +++ b/src/openrct2/core/Path.hpp @@ -16,9 +16,9 @@ namespace Path { utf8* Append(utf8* buffer, size_t bufferSize, const utf8* src); - std::string Combine(const std::string& a, const std::string& b); + std::string Combine(std::string_view a, std::string_view b); - template static std::string Combine(const std::string& a, const std::string& b, Args... args) + template static std::string Combine(std::string_view a, std::string_view b, Args... args) { return Combine(a, Combine(b, args...)); } diff --git a/src/openrct2/core/Zip.cpp b/src/openrct2/core/Zip.cpp index c991e90f1f..92ee14d472 100644 --- a/src/openrct2/core/Zip.cpp +++ b/src/openrct2/core/Zip.cpp @@ -7,12 +7,60 @@ * OpenRCT2 is licensed under the GNU General Public License version 3. *****************************************************************************/ +#include "Zip.h" + +#include "IStream.hpp" + +#include #ifndef __ANDROID__ -# include "Zip.h" - -# include "IStream.hpp" - # include +#endif + +static std::string NormalisePath(std::string_view path) +{ + std::string result; + if (!path.empty()) + { + // Convert back slashes to forward slashes + result = std::string(path); + for (auto ch = result.data(); *ch != '\0'; ch++) + { + if (*ch == '\\') + { + *ch = '/'; + } + } + } + return result; +} + +/** + * Normalises both the given path and the stored paths and finds the first match. + */ +std::optional IZipArchive::GetIndexFromPath(std::string_view path) const +{ + auto normalisedPath = NormalisePath(path); + if (!normalisedPath.empty()) + { + auto numFiles = GetNumFiles(); + for (size_t i = 0; i < numFiles; i++) + { + auto normalisedZipPath = NormalisePath(GetFileName(i)); + if (normalisedZipPath == normalisedPath) + { + return i; + } + } + } + return -1; +} + +bool IZipArchive::Exists(std::string_view path) const +{ + return GetIndexFromPath(path).has_value(); +} + +#ifndef __ANDROID__ class ZipArchive final : public IZipArchive { @@ -78,25 +126,38 @@ public: { std::vector result; auto index = GetIndexFromPath(path); - auto dataSize = GetFileSize(index); - if (dataSize > 0 && dataSize < SIZE_MAX) + if (index) { - auto zipFile = zip_fopen_index(_zip, index, 0); - if (zipFile != nullptr) + auto dataSize = GetFileSize(*index); + if (dataSize > 0 && dataSize < SIZE_MAX) { - result.resize(static_cast(dataSize)); - uint64_t readBytes = zip_fread(zipFile, result.data(), dataSize); - if (readBytes != dataSize) + auto zipFile = zip_fopen_index(_zip, *index, 0); + if (zipFile != nullptr) { - result.clear(); - result.shrink_to_fit(); + result.resize(static_cast(dataSize)); + uint64_t readBytes = zip_fread(zipFile, result.data(), dataSize); + if (readBytes != dataSize) + { + result.clear(); + result.shrink_to_fit(); + } + zip_fclose(zipFile); } - zip_fclose(zipFile); } } return result; } + std::unique_ptr GetFileStream(std::string_view path) const override + { + auto index = GetIndexFromPath(path); + if (index) + { + return std::make_unique(_zip, *index); + } + return {}; + } + void SetFileData(std::string_view path, std::vector&& data) override { // Push buffer to an internal list as libzip requires access to it until the zip @@ -106,66 +167,183 @@ public: auto source = zip_source_buffer(_zip, writeBuffer.data(), writeBuffer.size(), 0); auto index = GetIndexFromPath(path); - if (index == -1) + if (index) { - zip_add(_zip, path.data(), source); + zip_replace(_zip, *index, source); } else { - zip_replace(_zip, index, source); + zip_add(_zip, path.data(), source); } } void DeleteFile(std::string_view path) override { auto index = GetIndexFromPath(path); - zip_delete(_zip, index); + if (index) + { + zip_delete(_zip, *index); + } + else + { + throw std::runtime_error("File does not exist."); + } } void RenameFile(std::string_view path, std::string_view newPath) override { auto index = GetIndexFromPath(path); - zip_file_rename(_zip, index, newPath.data(), ZIP_FL_ENC_GUESS); + if (index) + { + zip_file_rename(_zip, *index, newPath.data(), ZIP_FL_ENC_GUESS); + } + else + { + throw std::runtime_error("File does not exist."); + } } private: - /** - * Normalises both the given path and the stored paths and finds the first match. - */ - zip_int64_t GetIndexFromPath(std::string_view path) const + class ifilestream final : public std::istream { - auto normalisedPath = NormalisePath(path); - if (!normalisedPath.empty()) + private: + class ifilestreambuf final : public std::streambuf { - auto numFiles = zip_get_num_entries(_zip, 0); - for (zip_int64_t i = 0; i < numFiles; i++) - { - auto normalisedZipPath = NormalisePath(zip_get_name(_zip, i, ZIP_FL_ENC_GUESS)); - if (normalisedZipPath == normalisedPath) - { - return i; - } - } - } - return -1; - } + private: + zip* _zip; + zip_int64_t _index; + zip_file_t* _zipFile{}; + zip_int64_t _pos{}; + zip_int64_t _maxLen{}; - static std::string NormalisePath(std::string_view path) - { - std::string result(path); - if (!path.empty()) - { - // Convert back slashes to forward slashes - for (auto ch = result.data(); *ch != '\0'; ch++) + public: + ifilestreambuf(zip* zip, zip_int64_t index) + : _zip(zip) + , _index(index) { - if (*ch == '\\') + } + + ifilestreambuf(const ifilestreambuf&) = delete; + + ~ifilestreambuf() override + { + close(); + } + + private: + void close() + { + if (_zipFile != nullptr) { - *ch = '/'; + zip_fclose(_zipFile); + _zipFile = nullptr; } } + + bool reset() + { + close(); + + _pos = 0; + _maxLen = 0; + _zipFile = zip_fopen_index(_zip, _index, 0); + if (_zipFile == nullptr) + { + return false; + } + + zip_stat_t zipFileStat{}; + if (zip_stat_index(_zip, _index, 0, &zipFileStat) != ZIP_ER_OK) + { + return false; + } + + _maxLen = zipFileStat.size; + return true; + } + + std::streamsize xsgetn(char_type* dst, std::streamsize len) override + { + if (_zipFile == nullptr && !reset()) + { + return 0; + } + + auto read = zip_fread(_zipFile, dst, len); + if (read <= 0) + { + return 0; + } + _pos += read; + return read; + } + + void skip(zip_int64_t len) + { + if (_zipFile != nullptr || reset()) + { + char buffer[2048]{}; + while (len > 0) + { + auto readLen = std::min(len, sizeof(buffer)); + auto read = zip_fread(_zipFile, buffer, readLen); + if (read <= 0) + { + break; + } + _pos += read; + len -= read; + } + } + } + + pos_type seekpos(pos_type pos, ios_base::openmode mode) override final + { + if (pos > _pos) + { + // Read to seek fowards + skip(pos - _pos); + } + else if (pos < _pos) + { + // Can not seek backwards, start from the beginning + reset(); + skip(pos); + } + return std::clamp(pos, 0, _maxLen); + } + + pos_type seekoff(off_type off, ios_base::seekdir dir, ios_base::openmode mode) override + { + if (dir == std::ios::beg) + { + return seekpos(off, std::ios::in); + } + else if (dir == std::ios::cur) + { + return seekpos(_pos + off, std::ios::in); + } + else if (dir == std::ios::end) + { + return seekpos(_maxLen - off, std::ios::in); + } + else + { + return std::streampos(-1); + } + } + }; + + private: + ifilestreambuf _streambuf; + + public: + ifilestream(zip* zip, zip_int64_t index) + : std::istream(&_streambuf) + , _streambuf(zip, index) + { } - return result; - } + }; }; namespace Zip diff --git a/src/openrct2/core/Zip.h b/src/openrct2/core/Zip.h index aefff22e7c..7582cffebf 100644 --- a/src/openrct2/core/Zip.h +++ b/src/openrct2/core/Zip.h @@ -11,7 +11,9 @@ #include "../common.h" +#include #include +#include #include #include @@ -28,6 +30,7 @@ struct IZipArchive virtual std::string GetFileName(size_t index) const abstract; virtual uint64_t GetFileSize(size_t index) const abstract; virtual std::vector GetFileData(std::string_view path) const abstract; + virtual std::unique_ptr GetFileStream(std::string_view path) const abstract; /** * Creates or overwrites a file within the zip archive to the given data buffer. @@ -38,6 +41,9 @@ struct IZipArchive virtual void DeleteFile(std::string_view path) abstract; virtual void RenameFile(std::string_view path, std::string_view newPath) abstract; + + std::optional GetIndexFromPath(std::string_view path) const; + bool Exists(std::string_view path) const; }; enum class ZIP_ACCESS diff --git a/src/openrct2/core/ZipAndroid.cpp b/src/openrct2/core/ZipAndroid.cpp index 240d3ac2a9..ff8b1e3ae1 100644 --- a/src/openrct2/core/ZipAndroid.cpp +++ b/src/openrct2/core/ZipAndroid.cpp @@ -113,6 +113,12 @@ public: return std::vector(dataPtr, dataPtr + dataSize); } + std::unique_ptr GetFileStream(std::string_view path) const override + { + auto data = GetFileData(path); + return std::make_unique(std::move(data)); + } + void SetFileData(std::string_view path, std::vector&& data) override { STUB(); @@ -127,6 +133,34 @@ public: { STUB(); } + +private: + class memstream final : public std::istream + { + private: + class vector_streambuf : public std::basic_streambuf> + { + public: + explicit vector_streambuf(const std::vector& vec) + { + this->setg( + reinterpret_cast(const_cast(vec.data())), + reinterpret_cast(const_cast(vec.data())), + reinterpret_cast(const_cast(vec.data() + vec.size()))); + } + }; + + std::vector _data; + vector_streambuf _streambuf; + + public: + memstream(std::vector&& data) + : std::istream(&_streambuf) + , _data(data) + , _streambuf(_data) + { + } + }; }; namespace Zip diff --git a/src/openrct2/interface/Window.cpp b/src/openrct2/interface/Window.cpp index 96465e6441..c05a43343a 100644 --- a/src/openrct2/interface/Window.cpp +++ b/src/openrct2/interface/Window.cpp @@ -22,6 +22,7 @@ #include "../localisation/Localisation.h" #include "../localisation/StringIds.h" #include "../platform/platform.h" +#include "../ride/RideAudio.h" #include "../scenario/Scenario.h" #include "../sprites.h" #include "../ui/UiContext.h" @@ -1784,7 +1785,7 @@ void window_close_construction_windows() */ void window_update_viewport_ride_music() { - OpenRCT2::Audio::gRideMusicParamsListEnd = &OpenRCT2::Audio::gRideMusicParamsList[0]; + RideAudioClearAllViewportInstances(); g_music_tracking_viewport = nullptr; for (auto it = g_window_list.rbegin(); it != g_window_list.rend(); it++) diff --git a/src/openrct2/libopenrct2.vcxproj b/src/openrct2/libopenrct2.vcxproj index f81520925b..a8fa569548 100644 --- a/src/openrct2/libopenrct2.vcxproj +++ b/src/openrct2/libopenrct2.vcxproj @@ -255,6 +255,7 @@ + @@ -352,8 +353,8 @@ - + @@ -671,6 +672,7 @@ + @@ -787,8 +789,8 @@ - + diff --git a/src/openrct2/object/MusicObject.cpp b/src/openrct2/object/MusicObject.cpp new file mode 100644 index 0000000000..4f21db7088 --- /dev/null +++ b/src/openrct2/object/MusicObject.cpp @@ -0,0 +1,169 @@ +/***************************************************************************** + * Copyright (c) 2014-2021 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 "MusicObject.h" + +#include "../Context.h" +#include "../OpenRCT2.h" +#include "../PlatformEnvironment.h" +#include "../core/IStream.hpp" +#include "../core/Json.hpp" +#include "../core/Path.hpp" +#include "../localisation/StringIds.h" +#include "../ride/Ride.h" +#include "RideObject.h" + +#include + +using namespace OpenRCT2; + +constexpr size_t DEFAULT_BYTES_PER_TICK = 1378; + +void MusicObject::Load() +{ + GetStringTable().Sort(); + NameStringId = language_allocate_object_string(GetName()); + + for (auto& track : _tracks) + { + track.BytesPerTick = DEFAULT_BYTES_PER_TICK; + track.Length = track.Asset.GetLength(); + } +} + +void MusicObject::Unload() +{ + language_free_object_string(NameStringId); + NameStringId = 0; +} + +void MusicObject::DrawPreview(rct_drawpixelinfo* dpi, int32_t width, int32_t height) const +{ + // Write (no image) + int32_t x = width / 2; + int32_t y = height / 2; + gfx_draw_string_centred(dpi, STR_WINDOW_NO_IMAGE, { x, y }, COLOUR_BLACK, nullptr); +} + +void MusicObject::ReadJson(IReadObjectContext* context, json_t& root) +{ + _originalStyleId = {}; + _rideTypes.clear(); + _tracks.clear(); + + auto& properties = root["properties"]; + if (properties != nullptr) + { + const auto& originalStyleId = properties["originalStyleId"]; + if (originalStyleId.is_number_integer()) + { + _originalStyleId = originalStyleId.get(); + } + + const auto& jRideTypes = properties["rideTypes"]; + if (jRideTypes.is_array()) + { + ParseRideTypes(jRideTypes); + } + + auto& jTracks = properties["tracks"]; + if (jTracks.is_array()) + { + ParseTracks(*context, jTracks); + } + } + + PopulateTablesFromJson(context, root); +} + +void MusicObject::ParseRideTypes(const json_t& jRideTypes) +{ + for (const auto& jRideType : jRideTypes) + { + auto szRideType = Json::GetString(jRideType); + if (!szRideType.empty()) + { + auto rideType = RideObject::ParseRideType(szRideType); + if (rideType != RIDE_TYPE_NULL) + { + _rideTypes.push_back(rideType); + } + } + } +} + +void MusicObject::ParseTracks(IReadObjectContext& context, json_t& jTracks) +{ + for (auto& jTrack : jTracks) + { + if (jTrack.is_object()) + { + MusicObjectTrack track; + track.Name = Json::GetString(jTrack["name"]); + auto source = Json::GetString(jTrack["source"]); + if (source.empty()) + { + context.LogError(ObjectError::InvalidProperty, "Invalid audio track definition."); + } + else + { + track.Asset = GetAsset(context, source); + _tracks.push_back(std::move(track)); + } + } + } +} + +std::optional MusicObject::GetOriginalStyleId() const +{ + return _originalStyleId; +} + +bool MusicObject::SupportsRideType(uint8_t rideType) +{ + if (_rideTypes.size() == 0) + { + // Default behaviour for music is to only exclude from merry-go-round + return rideType != RIDE_TYPE_MERRY_GO_ROUND; + } + else + { + auto it = std::find(_rideTypes.begin(), _rideTypes.end(), rideType); + return it != _rideTypes.end(); + } +} + +size_t MusicObject::GetTrackCount() const +{ + return _tracks.size(); +} + +const MusicObjectTrack* MusicObject::GetTrack(size_t trackIndex) const +{ + if (_tracks.size() > trackIndex) + { + return &_tracks[trackIndex]; + } + return {}; +} + +ObjectAsset MusicObject::GetAsset(IReadObjectContext& context, std::string_view path) +{ + if (path.find("$RCT2:DATA/") == 0) + { + auto platformEnvironment = GetContext()->GetPlatformEnvironment(); + auto dir = platformEnvironment->GetDirectoryPath(DIRBASE::RCT2, DIRID::DATA); + auto path2 = Path::Combine(dir, std::string(path.substr(11))); + return ObjectAsset(path2); + } + else + { + return context.GetAsset(path); + } +} diff --git a/src/openrct2/object/MusicObject.h b/src/openrct2/object/MusicObject.h new file mode 100644 index 0000000000..2e82bedda6 --- /dev/null +++ b/src/openrct2/object/MusicObject.h @@ -0,0 +1,64 @@ +/***************************************************************************** + * Copyright (c) 2014-2019 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 "Object.h" + +#include +#include + +class MusicObjectTrack +{ +public: + std::string Name; + ObjectAsset Asset; + + /** + * The number of PCM bytes to seek per game tick when the music is playing offscreen. + */ + size_t BytesPerTick; + + /** + * The length of the PCM track in bytes. + */ + size_t Length; +}; + +class MusicObject final : public Object +{ +private: + std::vector _rideTypes; + std::vector _tracks; + std::optional _originalStyleId; + +public: + rct_string_id NameStringId{}; + + explicit MusicObject(const rct_object_entry& entry) + : Object(entry) + { + } + + void ReadJson(IReadObjectContext* context, json_t& root) override; + void Load() override; + void Unload() override; + + void DrawPreview(rct_drawpixelinfo* dpi, int32_t width, int32_t height) const override; + + std::optional GetOriginalStyleId() const; + bool SupportsRideType(uint8_t rideType); + size_t GetTrackCount() const; + const MusicObjectTrack* GetTrack(size_t trackIndex) const; + +private: + void ParseRideTypes(const json_t& jRideTypes); + void ParseTracks(IReadObjectContext& context, json_t& jTracks); + static ObjectAsset GetAsset(IReadObjectContext& context, std::string_view path); +}; diff --git a/src/openrct2/object/Object.cpp b/src/openrct2/object/Object.cpp index a037985993..433d2aad14 100644 --- a/src/openrct2/object/Object.cpp +++ b/src/openrct2/object/Object.cpp @@ -10,8 +10,10 @@ #include "Object.h" #include "../Context.h" +#include "../core/File.h" #include "../core/Memory.hpp" #include "../core/String.hpp" +#include "../core/Zip.h" #include "../localisation/Language.h" #include "../localisation/LocalisationService.h" #include "../localisation/StringIds.h" @@ -165,6 +167,84 @@ std::optional rct_object_entry::GetSceneryType() const } } +class zipstreamwrapper final : public std::istream +{ +private: + std::unique_ptr _zipArchive; + std::unique_ptr _base; + +public: + zipstreamwrapper(std::unique_ptr zipArchive, std::unique_ptr base) + : std::istream(base->rdbuf()) + , _zipArchive(std::move(zipArchive)) + , _base(std::move(base)) + { + } +}; + +bool ObjectAsset::IsAvailable() const +{ + if (_zipPath.empty()) + { + return File::Exists(_path); + } + else + { + auto zipArchive = Zip::TryOpen(_zipPath, ZIP_ACCESS::READ); + return zipArchive != nullptr && zipArchive->Exists(_path); + } +} + +size_t ObjectAsset::GetLength() const +{ + if (_zipPath.empty()) + { + try + { + return File::ReadAllBytes(_path).size(); + } + catch (...) + { + return 0; + } + } + else + { + auto zipArchive = Zip::TryOpen(_zipPath, ZIP_ACCESS::READ); + if (zipArchive != nullptr) + { + auto index = zipArchive->GetIndexFromPath(_path); + if (index) + { + auto size = zipArchive->GetFileSize(*index); + return size; + } + } + } + return 0; +} + +std::unique_ptr ObjectAsset::GetStream() const +{ + if (_zipPath.empty()) + { + return std::make_unique(_path, std::ios::binary); + } + else + { + auto zipArchive = Zip::TryOpen(_zipPath, ZIP_ACCESS::READ); + if (zipArchive != nullptr) + { + auto stream = zipArchive->GetFileStream(_path); + if (stream != nullptr) + { + return std::make_unique(std::move(zipArchive), std::move(stream)); + } + } + } + return {}; +} + #ifdef __WARN_SUGGEST_FINAL_METHODS__ # pragma GCC diagnostic pop #endif diff --git a/src/openrct2/object/Object.h b/src/openrct2/object/Object.h index ae045a02bd..41a9cdb826 100644 --- a/src/openrct2/object/Object.h +++ b/src/openrct2/object/Object.h @@ -15,6 +15,9 @@ #include "ImageTable.h" #include "StringTable.h" +#include +#include +#include #include #include #include @@ -200,6 +203,29 @@ enum class ObjectError : uint32_t UnexpectedEOF, }; +class ObjectAsset +{ +private: + std::string _zipPath; + std::string _path; + +public: + ObjectAsset() = default; + ObjectAsset(std::string_view path) + : _path(path) + { + } + ObjectAsset(std::string_view zipPath, std::string_view path) + : _zipPath(zipPath) + , _path(path) + { + } + + bool IsAvailable() const; + size_t GetLength() const; + std::unique_ptr GetStream() const; +}; + struct IReadObjectContext { virtual ~IReadObjectContext() = default; @@ -208,6 +234,7 @@ struct IReadObjectContext virtual IObjectRepository& GetObjectRepository() abstract; virtual bool ShouldLoadImages() abstract; virtual std::vector GetData(std::string_view path) abstract; + virtual ObjectAsset GetAsset(std::string_view path) abstract; virtual void LogWarning(ObjectError code, const utf8* text) abstract; virtual void LogError(ObjectError code, const utf8* text) abstract; diff --git a/src/openrct2/object/ObjectFactory.cpp b/src/openrct2/object/ObjectFactory.cpp index 922458db36..98a045d31c 100644 --- a/src/openrct2/object/ObjectFactory.cpp +++ b/src/openrct2/object/ObjectFactory.cpp @@ -25,6 +25,7 @@ #include "FootpathItemObject.h" #include "FootpathObject.h" #include "LargeSceneryObject.h" +#include "MusicObject.h" #include "Object.h" #include "ObjectLimits.h" #include "ObjectList.h" @@ -44,6 +45,7 @@ struct IFileDataRetriever { virtual ~IFileDataRetriever() = default; virtual std::vector GetData(std::string_view path) const abstract; + virtual ObjectAsset GetAsset(std::string_view path) const abstract; }; class FileSystemDataRetriever : public IFileDataRetriever @@ -59,19 +61,27 @@ public: std::vector GetData(std::string_view path) const override { - auto absolutePath = Path::Combine(_basePath, std::string(path).c_str()); + auto absolutePath = Path::Combine(_basePath, path); return File::ReadAllBytes(absolutePath); } + + ObjectAsset GetAsset(std::string_view path) const override + { + auto absolutePath = Path::Combine(_basePath, path); + return ObjectAsset(absolutePath); + } }; class ZipDataRetriever : public IFileDataRetriever { private: + const std::string _path; const IZipArchive& _zipArchive; public: - ZipDataRetriever(const IZipArchive& zipArchive) - : _zipArchive(zipArchive) + ZipDataRetriever(std::string_view path, const IZipArchive& zipArchive) + : _path(path) + , _zipArchive(zipArchive) { } @@ -79,6 +89,11 @@ public: { return _zipArchive.GetFileData(path); } + + ObjectAsset GetAsset(std::string_view path) const override + { + return ObjectAsset(_path, path); + } }; class ReadObjectContext : public IReadObjectContext @@ -137,6 +152,15 @@ public: return {}; } + ObjectAsset GetAsset(std::string_view path) override + { + if (_fileDataRetriever != nullptr) + { + return _fileDataRetriever->GetAsset(path); + } + return {}; + } + void LogWarning(ObjectError code, const utf8* text) override { _wasWarning = true; @@ -314,6 +338,9 @@ namespace ObjectFactory case ObjectType::Station: result = std::make_unique(entry); break; + case ObjectType::Music: + result = std::make_unique(entry); + break; default: throw std::runtime_error("Invalid object type"); } @@ -348,6 +375,8 @@ namespace ObjectFactory return ObjectType::TerrainEdge; if (s == "station") return ObjectType::Station; + if (s == "music") + return ObjectType::Music; return ObjectType::None; } @@ -366,7 +395,7 @@ namespace ObjectFactory if (jRoot.is_object()) { - auto fileDataRetriever = ZipDataRetriever(*archive); + auto fileDataRetriever = ZipDataRetriever(path, *archive); return CreateObjectFromJson(objectRepository, jRoot, &fileDataRetriever); } } diff --git a/src/openrct2/object/ObjectLimits.h b/src/openrct2/object/ObjectLimits.h index 7c4c81c0b2..febbda2544 100644 --- a/src/openrct2/object/ObjectLimits.h +++ b/src/openrct2/object/ObjectLimits.h @@ -25,7 +25,7 @@ constexpr const uint16_t MAX_SCENARIO_TEXT_OBJECTS = 1; constexpr const uint16_t MAX_TERRAIN_SURFACE_OBJECTS = 18; constexpr const uint16_t MAX_TERRAIN_EDGE_OBJECTS = 255; constexpr const uint16_t MAX_STATION_OBJECTS = 255; -constexpr const uint16_t MAX_MUSIC_OBJECTS = 0; +constexpr const uint16_t MAX_MUSIC_OBJECTS = 255; // clang-format off constexpr const uint16_t OBJECT_ENTRY_COUNT = diff --git a/src/openrct2/object/ObjectManager.cpp b/src/openrct2/object/ObjectManager.cpp index fa10a29406..763a92c127 100644 --- a/src/openrct2/object/ObjectManager.cpp +++ b/src/openrct2/object/ObjectManager.cpp @@ -284,6 +284,40 @@ public: LoadObject("rct2.station.pagoda"); LoadObject("rct2.station.space"); LoadObject("openrct2.station.noentrance"); + + // Music + LoadObject("rct2.music.dodgems"); + LoadObject("rct2.music.fairground"); + LoadObject("rct2.music.roman"); + LoadObject("rct2.music.oriental"); + LoadObject("rct2.music.martian"); + LoadObject("rct2.music.jungle"); + LoadObject("rct2.music.egyptian"); + LoadObject("rct2.music.toyland"); + LoadObject("rct2.music.space"); + LoadObject("rct2.music.horror"); + LoadObject("rct2.music.techno"); + LoadObject("rct2.music.gentle"); + LoadObject("rct2.music.summer"); + LoadObject("rct2.music.water"); + LoadObject("rct2.music.wildwest"); + LoadObject("rct2.music.jurassic"); + LoadObject("rct2.music.rock1"); + LoadObject("rct2.music.ragtime"); + LoadObject("rct2.music.fantasy"); + LoadObject("rct2.music.rock2"); + LoadObject("rct2.music.ice"); + LoadObject("rct2.music.snow"); + LoadObject("rct2.music.custom1"); + LoadObject("rct2.music.custom2"); + LoadObject("rct2.music.medieval"); + LoadObject("rct2.music.urban"); + LoadObject("rct2.music.organ"); + LoadObject("rct2.music.mechanical"); + LoadObject("rct2.music.modern"); + LoadObject("rct2.music.pirate"); + LoadObject("rct2.music.rock3"); + LoadObject("rct2.music.candy"); } static rct_string_id GetObjectSourceGameString(const ObjectSourceGame sourceGame) diff --git a/src/openrct2/object/ObjectRepository.cpp b/src/openrct2/object/ObjectRepository.cpp index 63bec03893..e436e36563 100644 --- a/src/openrct2/object/ObjectRepository.cpp +++ b/src/openrct2/object/ObjectRepository.cpp @@ -76,7 +76,7 @@ class ObjectFileIndex final : public FileIndex { private: static constexpr uint32_t MAGIC_NUMBER = 0x5844494F; // OIDX - static constexpr uint16_t VERSION = 26; + static constexpr uint16_t VERSION = 27; static constexpr auto PATTERN = "*.dat;*.pob;*.json;*.parkobj"; IObjectRepository& _objectRepository; diff --git a/src/openrct2/object/RideObject.h b/src/openrct2/object/RideObject.h index c85ebf7bba..c7c9202781 100644 --- a/src/openrct2/object/RideObject.h +++ b/src/openrct2/object/RideObject.h @@ -46,6 +46,8 @@ public: void SetRepositoryItem(ObjectRepositoryItem* item) const override; + static uint8_t ParseRideType(const std::string& s); + private: void ReadLegacyVehicle(IReadObjectContext* context, OpenRCT2::IStream* stream, rct_ride_entry_vehicle* vehicle); @@ -59,7 +61,6 @@ private: static uint8_t CalculateNumHorizontalFrames(const rct_ride_entry_vehicle* vehicleEntry); static bool IsRideTypeShopOrFacility(uint8_t rideType); - static uint8_t ParseRideType(const std::string& s); static uint8_t ParseRideCategory(const std::string& s); static ShopItem ParseShopItem(const std::string& s); static colour_t ParseColour(const std::string& s); diff --git a/src/openrct2/ride/MusicList.cpp b/src/openrct2/ride/MusicList.cpp deleted file mode 100644 index abb26327c8..0000000000 --- a/src/openrct2/ride/MusicList.cpp +++ /dev/null @@ -1,115 +0,0 @@ -/***************************************************************************** - * Copyright (c) 2014-2020 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 "MusicList.h" - -#include "../Context.h" -#include "../audio/audio.h" -#include "../common.h" - -namespace OpenRCT2::Audio -{ -#define MAKE_TUNEID_LIST(...) std::vector({ __VA_ARGS__ }) - - // 0x009AEF28 - std::vector gRideMusicStyleTuneIds[] = { - MAKE_TUNEID_LIST(TUNE_DODGEMS_BEAT), // MUSIC_STYLE_DODGEMS_BEAT - MAKE_TUNEID_LIST( // MUSIC_STYLE_FAIRGROUND_ORGAN - TUNE_CHILDREN_OF_THE_REGIMENT, TUNE_SERENADE_OP_21, TUNE_IN_CONTINENTAL_MOOD, TUNE_WEDDING_JOURNEY, - TUNE_TALES_FROM_THE_VIENNA_WOODS, TUNE_SLAVONIC_DANCE, TUNE_CSS_10, TUNE_DAS_ALPENHORN, TUNE_BELLA_BELLA_BIMBA, - TUNE_THE_BLOND_SAILOR, TUNE_POET_AND_PEASANT_OVERTURE, TUNE_WALTZ_MEDLEY, TUNE_CSS_16), - MAKE_TUNEID_LIST(TUNE_CAESARS_MARCH), // MUSIC_STYLE_ROMAN_FANFARE - MAKE_TUNEID_LIST(TUNE_NINJAS_NOODLES), // MUSIC_STYLE_ORIENTAL - MAKE_TUNEID_LIST(TUNE_INVADERS), // MUSIC_STYLE_MARTIAN - MAKE_TUNEID_LIST(TUNE_JUNGLE_JUICE), // MUSIC_STYLE_JUNGLE_DRUMS - MAKE_TUNEID_LIST(TUNE_PHARAOHS_TOMB), // MUSIC_STYLE_EGYPTIAN - MAKE_TUNEID_LIST(TUNE_ETERNAL_TOYBOX), // MUSIC_STYLE_TOYLAND - MAKE_TUNEID_LIST(TUNE_CIRCUS_SHOW), // MUSIC_STYLE_CIRCUS_SHOW - MAKE_TUNEID_LIST(TUNE_VOYAGE_TO_ANDROMEDA), // MUSIC_STYLE_SPACE - MAKE_TUNEID_LIST(TUNE_VAMPIRES_LAIR), // MUSIC_STYLE_HORROR - MAKE_TUNEID_LIST(TUNE_BRIMBLES_BEAT), // MUSIC_STYLE_TECHNO - MAKE_TUNEID_LIST(TUNE_DRIFTING_TO_HEAVEN), // MUSIC_STYLE_GENTLE - MAKE_TUNEID_LIST(TUNE_MID_SUMMERS_HEAT), // MUSIC_STYLE_SUMMER - MAKE_TUNEID_LIST(TUNE_ATLANTIS), // MUSIC_STYLE_WATER - MAKE_TUNEID_LIST(TUNE_WILD_WEST_KID), // MUSIC_STYLE_WILD_WEST - MAKE_TUNEID_LIST(TUNE_BLOCKBUSTER), // MUSIC_STYLE_JURASSIC - MAKE_TUNEID_LIST(TUNE_AIRTIME_ROCK), // MUSIC_STYLE_ROCK - MAKE_TUNEID_LIST(TUNE_SEARCHLIGHT_RAG), // MUSIC_STYLE_RAGTIME - MAKE_TUNEID_LIST(TUNE_FLIGHT_OF_FANTASY), // MUSIC_STYLE_FANTASY - MAKE_TUNEID_LIST(TUNE_BIG_ROCK), // MUSIC_STYLE_ROCK_STYLE_2 - MAKE_TUNEID_LIST(TUNE_HYPOTHERMIA), // MUSIC_STYLE_ICE - MAKE_TUNEID_LIST(TUNE_LAST_SLEIGH_RIDE), // MUSIC_STYLE_SNOW - MAKE_TUNEID_LIST(TUNE_CUSTOM_1), // MUSIC_STYLE_CUSTOM_MUSIC_1 - MAKE_TUNEID_LIST(TUNE_CUSTOM_2), // MUSIC_STYLE_CUSTOM_MUSIC_2 - MAKE_TUNEID_LIST(TUNE_PIPES_OF_GLENCAIRN), // MUSIC_STYLE_MEDIEVAL - MAKE_TUNEID_LIST(TUNE_TRAFFIC_JAM), // MUSIC_STYLE_URBAN - MAKE_TUNEID_LIST(TUNE_TOCCATA), // MUSIC_STYLE_ORGAN - MAKE_TUNEID_LIST(TUNE_MANIC_MECHANIC), // MUSIC_STYLE_MECHANICAL - MAKE_TUNEID_LIST(TUNE_TECHNO_TORTURE), // MUSIC_STYLE_MODERN - MAKE_TUNEID_LIST(TUNE_WHAT_SHALL_WE_DO_WITH_THE_DRUNKEN_SAILOR), // MUSIC_STYLE_PIRATES - MAKE_TUNEID_LIST(TUNE_SPACE_ROCK), // MUSIC_STYLE_ROCK_STYLE_3 - MAKE_TUNEID_LIST(TUNE_SWEAT_DREAMS), // MUSIC_STYLE_CANDY_STYLE - }; - -#define INIT_MUSIC_INFO(path_id, offset) \ - { \ - path_id, offset, 0 \ - } - - // 0x009AF1C8 - RideMusicInfo gRideMusicInfoList[MaxDefaultMusic] = { - INIT_MUSIC_INFO(PATH_ID_CSS4, 1378), - INIT_MUSIC_INFO(PATH_ID_CSS5, 1378), - INIT_MUSIC_INFO(PATH_ID_CSS6, 1378), - INIT_MUSIC_INFO(PATH_ID_CSS7, 1378), - INIT_MUSIC_INFO(PATH_ID_CSS8, 1378), - INIT_MUSIC_INFO(PATH_ID_CSS9, 1378), - INIT_MUSIC_INFO(0, 1378), // Referred to the nearly empty CSS10.DAT file - INIT_MUSIC_INFO(PATH_ID_CSS11, 1378), - INIT_MUSIC_INFO(PATH_ID_CSS12, 1378), - INIT_MUSIC_INFO(PATH_ID_CSS13, 1378), - INIT_MUSIC_INFO(PATH_ID_CSS14, 1378), - INIT_MUSIC_INFO(PATH_ID_CSS15, 1378), - INIT_MUSIC_INFO(0, 1378), // Referred to the nearly empty CSS16.DAT file - INIT_MUSIC_INFO(PATH_ID_CSS3, 689), - INIT_MUSIC_INFO(PATH_ID_CSS17, 2756), - INIT_MUSIC_INFO(PATH_ID_CSS18, 2756), - INIT_MUSIC_INFO(PATH_ID_CSS19, 2756), - INIT_MUSIC_INFO(PATH_ID_CSS20, 2756), - INIT_MUSIC_INFO(PATH_ID_CSS21, 2756), - INIT_MUSIC_INFO(PATH_ID_CSS22, 2756), - INIT_MUSIC_INFO(PATH_ID_CSS23, 2756), - INIT_MUSIC_INFO(PATH_ID_CSS24, 2756), - INIT_MUSIC_INFO(PATH_ID_CSS25, 2756), - INIT_MUSIC_INFO(PATH_ID_CSS26, 2756), - INIT_MUSIC_INFO(PATH_ID_CSS27, 2756), - INIT_MUSIC_INFO(PATH_ID_CSS28, 2756), - INIT_MUSIC_INFO(PATH_ID_CSS29, 2756), - INIT_MUSIC_INFO(PATH_ID_CSS30, 2756), - INIT_MUSIC_INFO(PATH_ID_CSS31, 2756), - INIT_MUSIC_INFO(PATH_ID_CSS32, 2756), - INIT_MUSIC_INFO(PATH_ID_CSS33, 2756), - INIT_MUSIC_INFO(PATH_ID_CSS34, 2756), - INIT_MUSIC_INFO(PATH_ID_CSS35, 2756), - INIT_MUSIC_INFO(PATH_ID_CSS36, 2756), - INIT_MUSIC_INFO(PATH_ID_CSS37, 2756), - INIT_MUSIC_INFO(PATH_ID_CSS38, 2756), - INIT_MUSIC_INFO(PATH_ID_CUSTOM1, 2756), - INIT_MUSIC_INFO(PATH_ID_CUSTOM2, 2756), - INIT_MUSIC_INFO(PATH_ID_CSS39, 2756), - INIT_MUSIC_INFO(PATH_ID_CSS40, 1378), - INIT_MUSIC_INFO(PATH_ID_CSS41, 2756), - INIT_MUSIC_INFO(PATH_ID_CSS42, 2756), - INIT_MUSIC_INFO(PATH_ID_CSS43, 2756), - INIT_MUSIC_INFO(PATH_ID_CSS44, 2756), - INIT_MUSIC_INFO(PATH_ID_CSS45, 2756), - INIT_MUSIC_INFO(PATH_ID_CSS46, 2756), - }; - -} // namespace OpenRCT2::Audio diff --git a/src/openrct2/ride/MusicList.h b/src/openrct2/ride/MusicList.h deleted file mode 100644 index 1c94081d8f..0000000000 --- a/src/openrct2/ride/MusicList.h +++ /dev/null @@ -1,71 +0,0 @@ -/***************************************************************************** - * Copyright (c) 2014-2020 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/audio.h" -#include "../common.h" - -#include - -namespace OpenRCT2::Audio -{ - enum - { - TUNE_CHILDREN_OF_THE_REGIMENT, // 00 - TUNE_SERENADE_OP_21, // 01 - TUNE_IN_CONTINENTAL_MOOD, // 02 - TUNE_WEDDING_JOURNEY, // 03 - TUNE_TALES_FROM_THE_VIENNA_WOODS, // 04 - TUNE_SLAVONIC_DANCE, // 05 - TUNE_CSS_10, // 06, empty - TUNE_DAS_ALPENHORN, // 07 - TUNE_BELLA_BELLA_BIMBA, // 08 - TUNE_THE_BLOND_SAILOR, // 09 - TUNE_POET_AND_PEASANT_OVERTURE, // 10 - TUNE_WALTZ_MEDLEY, // 11 - TUNE_CSS_16, // 12, empty - TUNE_DODGEMS_BEAT, // 13 - TUNE_RCT2_THEME_MUSIC, // 14 - TUNE_CAESARS_MARCH, // 15 - TUNE_NINJAS_NOODLES, // 16 - TUNE_INVADERS, // 17 - TUNE_JUNGLE_JUICE, // 18 - TUNE_PHARAOHS_TOMB, // 19 - TUNE_ETERNAL_TOYBOX, // 20 - TUNE_CIRCUS_SHOW, // 21 - TUNE_VOYAGE_TO_ANDROMEDA, // 22 - TUNE_VAMPIRES_LAIR, // 23 - TUNE_BRIMBLES_BEAT, // 24 - TUNE_DRIFTING_TO_HEAVEN, // 25 - TUNE_MID_SUMMERS_HEAT, // 26 - TUNE_ATLANTIS, // 27 - TUNE_WILD_WEST_KID, // 28 - TUNE_BLOCKBUSTER, // 29 - TUNE_AIRTIME_ROCK, // 30 - TUNE_SEARCHLIGHT_RAG, // 31 - TUNE_FLIGHT_OF_FANTASY, // 32 - TUNE_BIG_ROCK, // 33 - TUNE_HYPOTHERMIA, // 34 - TUNE_LAST_SLEIGH_RIDE, // 35 - TUNE_CUSTOM_1, // 36 - TUNE_CUSTOM_2, // 37 - TUNE_PIPES_OF_GLENCAIRN, // 38 - TUNE_TRAFFIC_JAM, // 39 - TUNE_TOCCATA, // 40 - TUNE_MANIC_MECHANIC, // 41 - TUNE_TECHNO_TORTURE, // 42 - TUNE_WHAT_SHALL_WE_DO_WITH_THE_DRUNKEN_SAILOR, // 43 - TUNE_SPACE_ROCK, // 44 - TUNE_SWEAT_DREAMS, // 45, (sic) - }; - - extern std::vector gRideMusicStyleTuneIds[]; - -} // namespace OpenRCT2::Audio diff --git a/src/openrct2/ride/Ride.cpp b/src/openrct2/ride/Ride.cpp index fa01f8fa23..ac26bb3e2d 100644 --- a/src/openrct2/ride/Ride.cpp +++ b/src/openrct2/ride/Ride.cpp @@ -33,6 +33,7 @@ #include "../management/Marketing.h" #include "../management/NewsItem.h" #include "../network/network.h" +#include "../object/MusicObject.h" #include "../object/ObjectList.h" #include "../object/ObjectManager.h" #include "../object/StationObject.h" @@ -55,7 +56,7 @@ #include "../world/Scenery.h" #include "../world/Sprite.h" #include "CableLift.h" -#include "MusicList.h" +#include "RideAudio.h" #include "RideData.h" #include "ShopItem.h" #include "Station.h" @@ -2037,7 +2038,7 @@ void Ride::UpdateAll() for (auto& ride : GetRideManager()) ride.Update(); - ride_music_update_final(); + RideUpdateMusicChannels(); } std::unique_ptr Ride::SaveToTrackDesign() const @@ -2898,10 +2899,14 @@ static void ride_music_update(Ride* ride) // Select random tune from available tunes for a music style (of course only merry-go-rounds have more than one tune) if (ride->music_tune_id == 255) { - const auto& musicStyleTunes = OpenRCT2::Audio::gRideMusicStyleTuneIds[ride->music]; - auto numTunes = musicStyleTunes.size(); - ride->music_tune_id = musicStyleTunes[util_rand() % numTunes]; - ride->music_position = 0; + auto& objManager = GetContext()->GetObjectManager(); + auto musicObj = static_cast(objManager.GetLoadedObject(ObjectType::Music, ride->music)); + if (musicObj != nullptr) + { + auto numTracks = musicObj->GetTrackCount(); + ride->music_tune_id = static_cast(util_rand() % numTracks); + ride->music_position = 0; + } return; } @@ -2918,7 +2923,7 @@ static void ride_music_update(Ride* ride) sampleRate += 22050; } - ride->music_position = ride_music_params_update(rideCoords, ride, sampleRate, ride->music_position, &ride->music_tune_id); + RideUpdateMusicInstance(*ride, rideCoords, sampleRate); } #pragma endregion @@ -3514,294 +3519,6 @@ void ride_set_map_tooltip(TileElement* tileElement) } } -static int32_t ride_music_params_update_label_51( - uint32_t a1, uint8_t* tuneId, Ride* ride, int32_t v32, int32_t pan_x, uint16_t sampleRate) -{ - if (a1 < OpenRCT2::Audio::gRideMusicInfoList[*tuneId].length) - { - OpenRCT2::Audio::RideMusicParams* ride_music_params = OpenRCT2::Audio::gRideMusicParamsListEnd; - if (ride_music_params < &OpenRCT2::Audio::gRideMusicParamsList[std::size(OpenRCT2::Audio::gRideMusicParamsList)]) - { - ride_music_params->ride_id = ride->id; - ride_music_params->tune_id = *tuneId; - ride_music_params->offset = a1; - ride_music_params->volume = v32; - ride_music_params->pan = pan_x; - ride_music_params->frequency = sampleRate; - OpenRCT2::Audio::gRideMusicParamsListEnd++; - } - - return a1; - } - else - { - *tuneId = 0xFF; - return 0; - } -} - -static int32_t ride_music_params_update_label_58(uint32_t position, uint8_t* tuneId) -{ - OpenRCT2::Audio::RideMusicInfo* ride_music_info = &OpenRCT2::Audio::gRideMusicInfoList[*tuneId]; - position += ride_music_info->offset; - if (position < ride_music_info->length) - { - return position; - } - else - { - *tuneId = 0xFF; - return 0; - } -} - -/** - * - * rct2: 0x006BC3AC - * Update ride music parameters - * @param x (ax) - * @param y (cx) - * @param z (dx) - * @param sampleRate (di) - * @param rideIndex (bl) - * @param position (ebp) - * @param tuneId (bh) - * @returns new position (ebp) - */ -int32_t ride_music_params_update( - const CoordsXYZ& rideCoords, Ride* ride, uint16_t sampleRate, uint32_t position, uint8_t* tuneId) -{ - if (!(gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) && !OpenRCT2::Audio::gGameSoundsOff - && g_music_tracking_viewport != nullptr) - { - const ScreenCoordsXY rotatedCoords = translate_3d_to_2d_with_z(get_current_rotation(), rideCoords); - rct_viewport* viewport = g_music_tracking_viewport; - int16_t view_width = viewport->view_width; - int16_t view_width2 = view_width * 2; - int16_t view_x = viewport->viewPos.x - view_width2; - int16_t view_y = viewport->viewPos.y - view_width; - int16_t view_x2 = view_width2 + view_width2 + viewport->view_width + view_x; - int16_t view_y2 = view_width + view_width + viewport->view_height + view_y; - - if (view_x >= rotatedCoords.x || view_y >= rotatedCoords.y || view_x2 < rotatedCoords.x || view_y2 < rotatedCoords.y) - { - return ride_music_params_update_label_58(position, tuneId); - } - - int32_t x2 = viewport->pos.x + ((rotatedCoords.x - viewport->viewPos.x) / viewport->zoom); - x2 *= 0x10000; - uint16_t screenwidth = context_get_width(); - if (screenwidth < 64) - { - screenwidth = 64; - } - int32_t pan_x = ((x2 / screenwidth) - 0x8000) >> 4; - - int32_t y2 = viewport->pos.y + ((rotatedCoords.y - viewport->viewPos.y) / viewport->zoom); - y2 *= 0x10000; - uint16_t screenheight = context_get_height(); - if (screenheight < 64) - { - screenheight = 64; - } - int32_t pan_y = ((y2 / screenheight) - 0x8000) >> 4; - - uint8_t vol1 = 255; - uint8_t vol2 = 255; - int32_t panx2 = pan_x; - int32_t pany2 = pan_y; - if (pany2 < 0) - { - pany2 = -pany2; - } - if (pany2 > 6143) - { - pany2 = 6143; - } - pany2 -= 2048; - if (pany2 > 0) - { - pany2 = -((pany2 / 4) - 1024) / 4; - vol1 = static_cast(pany2); - if (pany2 >= 256) - { - vol1 = 255; - } - } - - if (panx2 < 0) - { - panx2 = -panx2; - } - if (panx2 > 6143) - { - panx2 = 6143; - } - panx2 -= 2048; - if (panx2 > 0) - { - panx2 = -((panx2 / 4) - 1024) / 4; - vol2 = static_cast(panx2); - if (panx2 >= 256) - { - vol2 = 255; - } - } - if (vol1 >= vol2) - { - vol1 = vol2; - } - if (vol1 < OpenRCT2::Audio::gVolumeAdjustZoom * 3) - { - vol1 = 0; - } - else - { - vol1 = vol1 - (OpenRCT2::Audio::gVolumeAdjustZoom * 3); - } - int32_t v32 = -((static_cast(-vol1 - 1) * static_cast(-vol1 - 1)) / 16) - 700; - if (vol1 && v32 >= -4000) - { - if (pan_x > 10000) - { - pan_x = 10000; - } - if (pan_x < -10000) - { - pan_x = -10000; - } - OpenRCT2::Audio::RideMusic* ride_music = &OpenRCT2::Audio::gRideMusicList[0]; - int32_t channel = 0; - while (ride_music->ride_id != ride->id || ride_music->tune_id != *tuneId) - { - ride_music++; - channel++; - if (static_cast(channel) >= OpenRCT2::Audio::MaxRideMusic) - { - OpenRCT2::Audio::RideMusicInfo* ride_music_info = &OpenRCT2::Audio::gRideMusicInfoList[*tuneId]; - const uint32_t offset = position + ride_music_info->offset; - return ride_music_params_update_label_51(offset, tuneId, ride, v32, pan_x, sampleRate); - } - } - int32_t playing = Mixer_Channel_IsPlaying(OpenRCT2::Audio::gRideMusicList[channel].sound_channel); - if (!playing) - { - *tuneId = 0xFF; - return 0; - } - const uint32_t offset = static_cast( - Mixer_Channel_GetOffset(OpenRCT2::Audio::gRideMusicList[channel].sound_channel)); - return ride_music_params_update_label_51(offset, tuneId, ride, v32, pan_x, sampleRate); - } - else - { - return ride_music_params_update_label_58(position, tuneId); - } - } - return position; -} - -/** - * Play/update ride music based on structs updated in 0x006BC3AC - * rct2: 0x006BC6D8 - */ -void ride_music_update_final() -{ - if ((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) != 0 || (gScreenFlags & SCREEN_FLAGS_TITLE_DEMO) != 0) - return; - - // TODO Allow circus music (CSS24) to play if ride music is disabled (that should be sound) - if (OpenRCT2::Audio::gGameSoundsOff || !gConfigSound.ride_music_enabled) - return; - - // Stop currently playing music that is not in music params list or not playing? - for (auto& rideMusic : OpenRCT2::Audio::gRideMusicList) - { - if (rideMusic.ride_id != RIDE_ID_NULL) - { - OpenRCT2::Audio::RideMusicParams* rideMusicParams = &OpenRCT2::Audio::gRideMusicParamsList[0]; - int32_t isPlaying = 0; - while (rideMusicParams < OpenRCT2::Audio::gRideMusicParamsListEnd && !isPlaying) - { - if (rideMusicParams->ride_id == rideMusic.ride_id && rideMusicParams->tune_id == rideMusic.tune_id) - { - isPlaying = Mixer_Channel_IsPlaying(rideMusic.sound_channel); - break; - } - rideMusicParams++; - } - if (!isPlaying) - { - Mixer_Stop_Channel(rideMusic.sound_channel); - rideMusic.ride_id = RIDE_ID_NULL; - } - } - } - - int32_t freeChannelIndex = 0; - for (auto* rideMusicParams = &OpenRCT2::Audio::gRideMusicParamsList[0]; - rideMusicParams < OpenRCT2::Audio::gRideMusicParamsListEnd; rideMusicParams++) - { - if (rideMusicParams->ride_id != RIDE_ID_NULL) - { - auto* rideMusic = &OpenRCT2::Audio::gRideMusicList[0]; - int32_t channelIndex = 0; - // Look for existing entry, if not found start playing the sound, otherwise update parameters. - while (rideMusicParams->ride_id != rideMusic->ride_id || rideMusicParams->tune_id != rideMusic->tune_id) - { - if (rideMusic->ride_id == RIDE_ID_NULL) - { - freeChannelIndex = channelIndex; - } - rideMusic++; - channelIndex++; - if (static_cast(channelIndex) >= OpenRCT2::Audio::MaxRideMusic) - { - auto* ride_music_info = &OpenRCT2::Audio::gRideMusicInfoList[rideMusicParams->tune_id]; - auto* ride_music_3 = &OpenRCT2::Audio::gRideMusicList[freeChannelIndex]; - ride_music_3->sound_channel = Mixer_Play_Music(ride_music_info->path_id, MIXER_LOOP_NONE, true); - if (ride_music_3->sound_channel) - { - ride_music_3->volume = rideMusicParams->volume; - ride_music_3->pan = rideMusicParams->pan; - ride_music_3->frequency = rideMusicParams->frequency; - ride_music_3->ride_id = rideMusicParams->ride_id; - ride_music_3->tune_id = rideMusicParams->tune_id; - Mixer_Channel_Volume(ride_music_3->sound_channel, DStoMixerVolume(ride_music_3->volume)); - Mixer_Channel_Pan(ride_music_3->sound_channel, DStoMixerPan(ride_music_3->pan)); - Mixer_Channel_Rate(ride_music_3->sound_channel, DStoMixerRate(ride_music_3->frequency)); - int32_t offset = std::max(0, rideMusicParams->offset - 10000); - Mixer_Channel_SetOffset(ride_music_3->sound_channel, offset); - - // Move circus music to the sound mixer group - if (ride_music_info->path_id == PATH_ID_CSS24) - { - Mixer_Channel_SetGroup(ride_music_3->sound_channel, Audio::MixerGroup::Sound); - } - } - return; - } - } - - if (rideMusicParams->volume != rideMusic->volume) - { - rideMusic->volume = rideMusicParams->volume; - Mixer_Channel_Volume(rideMusic->sound_channel, DStoMixerVolume(rideMusic->volume)); - } - if (rideMusicParams->pan != rideMusic->pan) - { - rideMusic->pan = rideMusicParams->pan; - Mixer_Channel_Pan(rideMusic->sound_channel, DStoMixerPan(rideMusic->pan)); - } - if (rideMusicParams->frequency != rideMusic->frequency) - { - rideMusic->frequency = rideMusicParams->frequency; - Mixer_Channel_Rate(rideMusic->sound_channel, DStoMixerRate(rideMusic->frequency)); - } - } - } -} - #pragma endregion money32 set_operating_setting(ride_id_t rideId, RideSetSetting setting, uint8_t value) diff --git a/src/openrct2/ride/Ride.h b/src/openrct2/ride/Ride.h index 850e874fdc..b933f518a1 100644 --- a/src/openrct2/ride/Ride.h +++ b/src/openrct2/ride/Ride.h @@ -1137,9 +1137,6 @@ void ride_construction_invalidate_current_track(); std::optional sub_6C683D( const CoordsXYZD& location, track_type_t type, uint16_t extra_params, TileElement** output_element, uint16_t flags); void ride_set_map_tooltip(TileElement* tileElement); -int32_t ride_music_params_update( - const CoordsXYZ& rideCoords, Ride* ride, uint16_t sampleRate, uint32_t position, uint8_t* tuneId); -void ride_music_update_final(); void ride_prepare_breakdown(Ride* ride, int32_t breakdownReason); TileElement* ride_get_station_start_track_element(Ride* ride, StationIndex stationIndex); TileElement* ride_get_station_exit_element(const CoordsXYZ& elementPos); diff --git a/src/openrct2/ride/RideAudio.cpp b/src/openrct2/ride/RideAudio.cpp new file mode 100644 index 0000000000..653e4423d5 --- /dev/null +++ b/src/openrct2/ride/RideAudio.cpp @@ -0,0 +1,422 @@ +/***************************************************************************** + * Copyright (c) 2014-2020 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 "RideAudio.h" + +#include "../Context.h" +#include "../OpenRCT2.h" +#include "../audio/AudioMixer.h" +#include "../audio/audio.h" +#include "../config/Config.h" +#include "../object/MusicObject.h" +#include "../object/ObjectManager.h" +#include "Ride.h" + +#include +#include + +using namespace OpenRCT2; +using namespace OpenRCT2::Audio; + +constexpr size_t MAX_RIDE_MUSIC_CHANNELS = 32; + +/** + * Represents a particular instance of ride music that can be heard in a viewport. + * These are created each frame via enumerating each ride / viewport. + */ +struct ViewportRideMusicInstance +{ + ride_id_t RideId; + uint8_t TrackIndex{}; + + size_t Offset{}; + int16_t Volume{}; + int16_t Pan{}; + uint16_t Frequency{}; +}; + +/** + * Represents an audio channel to play a particular ride's music track. + */ +struct RideMusicChannel +{ + ride_id_t RideId{}; + uint8_t TrackIndex{}; + + size_t Offset{}; + int16_t Volume{}; + int16_t Pan{}; + uint16_t Frequency{}; + + void* Channel{}; + + RideMusicChannel(const ViewportRideMusicInstance& instance, void* channel) + { + RideId = instance.RideId; + TrackIndex = instance.TrackIndex; + + Offset = std::max(0, instance.Offset - 10000); + Volume = instance.Volume; + Pan = instance.Pan; + Frequency = instance.Frequency; + + Channel = channel; + + Mixer_Channel_SetOffset(channel, Offset); + Mixer_Channel_Volume(channel, DStoMixerVolume(Volume)); + Mixer_Channel_Pan(channel, DStoMixerPan(Pan)); + Mixer_Channel_Rate(channel, DStoMixerRate(Frequency)); + } + + RideMusicChannel(const RideMusicChannel&) = delete; + + RideMusicChannel(RideMusicChannel&& src) noexcept + { + *this = std::move(src); + } + + RideMusicChannel& operator=(RideMusicChannel&& src) noexcept + { + RideId = src.RideId; + TrackIndex = src.TrackIndex; + + Offset = src.Offset; + Volume = src.Volume; + Pan = src.Pan; + Frequency = src.Frequency; + + if (Channel != nullptr) + { + Mixer_Stop_Channel(Channel); + } + Channel = src.Channel; + src.Channel = nullptr; + + return *this; + } + + ~RideMusicChannel() + { + if (Channel != nullptr) + { + Mixer_Stop_Channel(Channel); + Channel = nullptr; + } + } + + bool IsPlaying() const + { + if (Channel != nullptr) + { + return Mixer_Channel_IsPlaying(Channel); + } + return false; + } + + size_t GetOffset() const + { + if (Channel != nullptr) + { + return Mixer_Channel_GetOffset(Channel); + } + return 0; + } + + void Update(const ViewportRideMusicInstance& instance) + { + if (Volume != instance.Volume) + { + Volume = instance.Volume; + if (Channel != nullptr) + { + Mixer_Channel_Volume(Channel, DStoMixerVolume(Volume)); + } + } + if (Pan != instance.Pan) + { + Pan = instance.Pan; + if (Channel != nullptr) + { + Mixer_Channel_Pan(Channel, DStoMixerPan(Pan)); + } + } + if (Frequency != instance.Frequency) + { + Frequency = instance.Frequency; + if (Channel != nullptr) + { + Mixer_Channel_Rate(Channel, DStoMixerRate(Frequency)); + } + } + } +}; + +static std::vector _musicInstances; +static std::vector _musicChannels; + +void RideAudioStopAllChannels() +{ + _musicChannels.clear(); +} + +void RideAudioClearAllViewportInstances() +{ + _musicInstances.clear(); +} + +static void StartRideMusicChannel(const ViewportRideMusicInstance& instance) +{ + // 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) + { + // Move circus music to the sound mixer group + Mixer_Channel_SetGroup(channel, 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) + { + auto track = musicObj->GetTrack(instance.TrackIndex); + if (track != nullptr) + { + auto stream = track->Asset.GetStream(); + auto channel = Mixer_Play_Music(std::move(stream), MIXER_LOOP_NONE); + if (channel != nullptr) + { + _musicChannels.emplace_back(instance, channel); + } + } + } + } +} + +static void StopInactiveRideMusicChannels() +{ + _musicChannels.erase( + std::remove_if( + _musicChannels.begin(), _musicChannels.end(), + [](const auto& channel) { + auto found = std::any_of(_musicInstances.begin(), _musicInstances.end(), [&channel](const auto& instance) { + return instance.RideId == channel.RideId && instance.TrackIndex == channel.TrackIndex; + }); + if (!found || !channel.IsPlaying()) + { + return true; + } + else + { + return false; + } + }), + _musicChannels.end()); +} + +static void UpdateRideMusicChannelForMusicParams(const ViewportRideMusicInstance& instance) +{ + // Find existing music channel + auto foundChannel = std::find_if( + _musicChannels.begin(), _musicChannels.end(), [&instance](const RideMusicChannel& channel) { + return channel.RideId == instance.RideId && channel.TrackIndex == instance.TrackIndex; + }); + + if (foundChannel != _musicChannels.end()) + { + foundChannel->Update(instance); + } + else if (_musicChannels.size() < MAX_RIDE_MUSIC_CHANNELS) + { + StartRideMusicChannel(instance); + } +} + +/** + * Start, update and stop audio channels for each ride music instance that can be heard across all viewports. + */ +void RideUpdateMusicChannels() +{ + if ((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) != 0 || (gScreenFlags & SCREEN_FLAGS_TITLE_DEMO) != 0) + return; + + // TODO Allow circus music (CSS24) to play if ride music is disabled (that should be sound) + if (gGameSoundsOff || !gConfigSound.ride_music_enabled) + return; + + StopInactiveRideMusicChannels(); + for (const auto& instance : _musicInstances) + { + UpdateRideMusicChannelForMusicParams(instance); + } +} + +static std::pair RideMusicGetTrackOffsetLength(const Ride& ride) +{ + if (ride.type == RIDE_TYPE_CIRCUS) + { + return { 1378, 12427456 }; + } + else + { + auto& objManager = GetContext()->GetObjectManager(); + auto musicObj = static_cast(objManager.GetLoadedObject(ObjectType::Music, ride.music)); + if (musicObj != nullptr) + { + auto numTracks = musicObj->GetTrackCount(); + if (ride.music_tune_id < numTracks) + { + auto track = musicObj->GetTrack(ride.music_tune_id); + return { track->BytesPerTick, track->Length }; + } + } + } + return { 0, 0 }; +} + +static void RideUpdateMusicPosition(Ride& ride) +{ + auto [trackOffset, trackLength] = RideMusicGetTrackOffsetLength(ride); + auto position = ride.music_position + trackOffset; + if (position < trackLength) + { + ride.music_position = position; + } + else + { + ride.music_tune_id = TUNE_ID_NULL; + ride.music_position = 0; + } +} + +static void RideUpdateMusicPosition(Ride& ride, size_t offset, size_t length, int16_t volume, int16_t pan, uint16_t sampleRate) +{ + if (offset < length) + { + if (_musicInstances.size() < MAX_RIDE_MUSIC_CHANNELS) + { + auto& instance = _musicInstances.emplace_back(); + instance.RideId = ride.id; + instance.TrackIndex = ride.music_tune_id; + instance.Offset = offset; + instance.Volume = volume; + instance.Pan = pan; + instance.Frequency = sampleRate; + } + ride.music_position = static_cast(offset); + } + else + { + ride.music_tune_id = TUNE_ID_NULL; + ride.music_position = 0; + } +} + +static void RideUpdateMusicPosition(Ride& ride, int16_t volume, int16_t pan, uint16_t sampleRate) +{ + auto foundChannel = std::find_if(_musicChannels.begin(), _musicChannels.end(), [&ride](const auto& channel) { + return channel.RideId == ride.id && channel.TrackIndex == ride.music_tune_id; + }); + + auto [trackOffset, trackLength] = RideMusicGetTrackOffsetLength(ride); + if (foundChannel != _musicChannels.end()) + { + if (foundChannel->IsPlaying()) + { + // Since we have a real music channel, use the offset from that + auto newOffset = foundChannel->GetOffset(); + RideUpdateMusicPosition(ride, newOffset, trackLength, volume, pan, sampleRate); + } + else + { + // We had a real music channel, but it isn't playing anymore, so stop the track + ride.music_position = 0; + ride.music_tune_id = TUNE_ID_NULL; + } + } + else + { + // We do not have a real music channel, so simulate the playing of the music track + auto newOffset = ride.music_position + trackOffset; + RideUpdateMusicPosition(ride, newOffset, trackLength, volume, pan, sampleRate); + } +} + +static uint8_t CalculateVolume(int32_t pan) +{ + uint8_t result = 255; + int32_t v = std::min(std::abs(pan), 6143) - 2048; + if (v > 0) + { + v = -((v / 4) - 1024) / 4; + result = static_cast(std::clamp(v, 0, 255)); + } + return result; +} + +/** + * Register an instance of audible ride music for this frame at the given coordinates. + */ +void RideUpdateMusicInstance(Ride& ride, const CoordsXYZ& rideCoords, uint16_t sampleRate) +{ + if (!(gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) && !gGameSoundsOff && g_music_tracking_viewport != nullptr) + { + auto rotatedCoords = translate_3d_to_2d_with_z(get_current_rotation(), rideCoords); + auto viewport = g_music_tracking_viewport; + auto viewWidth = viewport->view_width; + auto viewWidth2 = viewWidth * 2; + auto viewX = viewport->viewPos.x - viewWidth2; + auto viewY = viewport->viewPos.y - viewWidth; + auto viewX2 = viewWidth2 + viewWidth2 + viewport->view_width + viewX; + auto viewY2 = viewWidth + viewWidth + viewport->view_height + viewY; + if (viewX >= rotatedCoords.x || viewY >= rotatedCoords.y || viewX2 < rotatedCoords.x || viewY2 < rotatedCoords.y) + { + RideUpdateMusicPosition(ride); + } + else + { + auto x2 = (viewport->pos.x + ((rotatedCoords.x - viewport->viewPos.x) / viewport->zoom)) * 0x10000; + auto screenWidth = std::max(context_get_width(), 64); + auto panX = ((x2 / screenWidth) - 0x8000) >> 4; + + auto y2 = (viewport->pos.y + ((rotatedCoords.y - viewport->viewPos.y) / viewport->zoom)) * 0x10000; + auto screenHeight = std::max(context_get_height(), 64); + auto panY = ((y2 / screenHeight) - 0x8000) >> 4; + + auto volX = CalculateVolume(panX); + auto volY = CalculateVolume(panY); + auto volXY = std::min(volX, volY); + if (volXY < gVolumeAdjustZoom * 3) + { + volXY = 0; + } + else + { + volXY = volXY - (gVolumeAdjustZoom * 3); + } + + int16_t newVolume = -((static_cast(-volXY - 1) * static_cast(-volXY - 1)) / 16) - 700; + if (volXY != 0 && newVolume >= -4000) + { + auto newPan = std::clamp(panX, -10000, 10000); + RideUpdateMusicPosition(ride, newVolume, newPan, sampleRate); + } + else + { + RideUpdateMusicPosition(ride); + } + } + } +} diff --git a/src/openrct2/ride/RideAudio.h b/src/openrct2/ride/RideAudio.h new file mode 100644 index 0000000000..61f1e1a681 --- /dev/null +++ b/src/openrct2/ride/RideAudio.h @@ -0,0 +1,22 @@ +/***************************************************************************** + * Copyright (c) 2014-2021 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 + +struct CoordsXYZ; +struct Ride; + +constexpr uint8_t TUNE_ID_NULL = 0xFF; + +void RideAudioClearAllViewportInstances(); +void RideAudioStopAllChannels(); +void RideUpdateMusicChannels(); +void RideUpdateMusicInstance(Ride& ride, const CoordsXYZ& rideCoords, uint16_t sampleRate);