From 5a70fd97fbaf83fcb4d67881f4d44d469217fbcf Mon Sep 17 00:00:00 2001 From: Duncan Date: Wed, 20 Mar 2024 11:14:52 +0000 Subject: [PATCH] Move vehicle sounds update into Ui library (#21577) * Move vehicle sounds update into Ui library * Add missing statics * Apply review comments * Sprinkle some const * Clang format accumulate --- src/openrct2-ui/WindowManager.cpp | 5 + src/openrct2-ui/interface/Window.cpp | 28 ++ src/openrct2-ui/interface/Window.h | 3 + src/openrct2-ui/libopenrct2ui.vcxproj | 2 + src/openrct2-ui/ride/VehicleSounds.cpp | 605 +++++++++++++++++++++++++ src/openrct2-ui/ride/VehicleSounds.h | 6 + src/openrct2/interface/Window.cpp | 25 - src/openrct2/interface/Window.h | 3 - src/openrct2/ride/Vehicle.cpp | 592 +----------------------- src/openrct2/ride/Vehicle.h | 4 - src/openrct2/windows/Intent.h | 1 + 11 files changed, 654 insertions(+), 620 deletions(-) create mode 100644 src/openrct2-ui/ride/VehicleSounds.cpp create mode 100644 src/openrct2-ui/ride/VehicleSounds.h diff --git a/src/openrct2-ui/WindowManager.cpp b/src/openrct2-ui/WindowManager.cpp index 7c98b8b031..f9f37aa088 100644 --- a/src/openrct2-ui/WindowManager.cpp +++ b/src/openrct2-ui/WindowManager.cpp @@ -10,6 +10,7 @@ #include "WindowManager.h" #include "interface/Theme.h" +#include "ride/VehicleSounds.h" #include "windows/Window.h" #include @@ -510,6 +511,10 @@ public: WindowInvalidateByClass(WindowClass::Research); break; + case INTENT_ACTION_UPDATE_VEHICLE_SOUNDS: + OpenRCT2::Audio::UpdateVehicleSounds(); + break; + case INTENT_ACTION_TRACK_DESIGN_REMOVE_PROVISIONAL: TrackPlaceClearProvisionalTemporarily(); break; diff --git a/src/openrct2-ui/interface/Window.cpp b/src/openrct2-ui/interface/Window.cpp index b594080f9a..f8c2936f76 100644 --- a/src/openrct2-ui/interface/Window.cpp +++ b/src/openrct2-ui/interface/Window.cpp @@ -832,3 +832,31 @@ ScreenCoordsXY WindowGetViewportSoundIconPos(WindowBase& w) const uint8_t buttonOffset = (gConfigInterface.WindowButtonsOnTheLeft) ? CloseButtonWidth + 2 : 0; return w.windowPos + ScreenCoordsXY{ 2 + buttonOffset, 2 }; } + +namespace OpenRCT2::Ui::Windows +{ + WindowBase* WindowGetListening() + { + for (auto it = g_window_list.rbegin(); it != g_window_list.rend(); it++) + { + auto& w = **it; + if (w.flags & WF_DEAD) + continue; + + auto viewport = w.viewport; + if (viewport != nullptr) + { + if (viewport->flags & VIEWPORT_FLAG_SOUND_ON) + { + return &w; + } + } + } + return nullptr; + } + + WindowClass WindowGetClassification(const WindowBase& window) + { + return window.classification; + } +} // namespace OpenRCT2::Ui::Windows diff --git a/src/openrct2-ui/interface/Window.h b/src/openrct2-ui/interface/Window.h index 0356e779e7..4542f32638 100644 --- a/src/openrct2-ui/interface/Window.h +++ b/src/openrct2-ui/interface/Window.h @@ -70,4 +70,7 @@ namespace OpenRCT2::Ui::Windows void WindowTileInspectorKeyboardShortcutToggleInvisibility(); extern const StringId ColourSchemeNames[4]; + + WindowBase* WindowGetListening(); + WindowClass WindowGetClassification(const WindowBase& window); } // namespace OpenRCT2::Ui::Windows diff --git a/src/openrct2-ui/libopenrct2ui.vcxproj b/src/openrct2-ui/libopenrct2ui.vcxproj index 358c1577d7..0ab8e83b43 100644 --- a/src/openrct2-ui/libopenrct2ui.vcxproj +++ b/src/openrct2-ui/libopenrct2ui.vcxproj @@ -74,6 +74,7 @@ + @@ -133,6 +134,7 @@ + diff --git a/src/openrct2-ui/ride/VehicleSounds.cpp b/src/openrct2-ui/ride/VehicleSounds.cpp new file mode 100644 index 0000000000..e848ffd36d --- /dev/null +++ b/src/openrct2-ui/ride/VehicleSounds.cpp @@ -0,0 +1,605 @@ +#include "VehicleSounds.h" + +#include "../interface/Viewport.h" +#include "../interface/Window.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace OpenRCT2::Audio +{ + namespace + { + template class TrainIterator; + template class Train + { + public: + explicit Train(T* vehicle) + : FirstCar(vehicle) + { + assert(FirstCar->IsHead()); + } + int32_t GetMass() const; + + friend class TrainIterator; + using iterator = TrainIterator; + iterator begin() const + { + return iterator{ FirstCar }; + } + iterator end() const + { + return iterator{}; + } + + private: + T* FirstCar; + }; + template class TrainIterator + { + public: + using iterator = TrainIterator; + using iterator_category = std::forward_iterator_tag; + using value_type = T; + using pointer = T*; + using reference = T&; + + TrainIterator() = default; + explicit TrainIterator(T* vehicle) + : Current(vehicle) + { + } + reference operator*() const + { + return *Current; + } + iterator& operator++() + { + Current = GetEntity(NextVehicleId); + if (Current != nullptr) + { + NextVehicleId = Current->next_vehicle_on_train; + } + return *this; + } + iterator operator++(int) + { + iterator temp = *this; + ++*this; + return temp; + } + bool operator!=(const iterator& other) const + { + return Current != other.Current; + } + + private: + T* Current = nullptr; + EntityId NextVehicleId = EntityId::GetNull(); + }; + } // namespace + + template int32_t Train::GetMass() const + { + return std::accumulate( + begin(), end(), 0, [](int32_t totalMass, const Vehicle& vehicle) { return totalMass + vehicle.mass; }); + } + + static bool SoundCanPlay(const Vehicle& vehicle) + { + if (gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) + return false; + + if ((gScreenFlags & SCREEN_FLAGS_TRACK_DESIGNER) && GetGameState().EditorStep != EditorStep::RollercoasterDesigner) + return false; + + if (vehicle.sound1_id == SoundId::Null && vehicle.sound2_id == SoundId::Null) + return false; + + if (vehicle.x == LOCATION_NULL) + return false; + + if (g_music_tracking_viewport == nullptr) + return false; + + const auto quarter_w = g_music_tracking_viewport->view_width / 4; + const auto quarter_h = g_music_tracking_viewport->view_height / 4; + + auto left = g_music_tracking_viewport->viewPos.x; + auto bottom = g_music_tracking_viewport->viewPos.y; + + if (Ui::Windows::WindowGetClassification(*gWindowAudioExclusive) == WindowClass::MainWindow) + { + left -= quarter_w; + bottom -= quarter_h; + } + + if (left >= vehicle.SpriteData.SpriteRect.GetRight() || bottom >= vehicle.SpriteData.SpriteRect.GetBottom()) + return false; + + auto right = g_music_tracking_viewport->view_width + left; + auto top = g_music_tracking_viewport->view_height + bottom; + + if (Ui::Windows::WindowGetClassification(*gWindowAudioExclusive) == WindowClass::MainWindow) + { + right += quarter_w + quarter_w; + top += quarter_h + quarter_h; + } + + if (right < vehicle.SpriteData.SpriteRect.GetRight() || top < vehicle.SpriteData.SpriteRect.GetTop()) + return false; + + return true; + } + + /** + * + * rct2: 0x006BC2F3 + */ + static uint16_t GetSoundPriority(const Vehicle& vehicle) + { + int32_t result = Train(&vehicle).GetMass() + (std::abs(vehicle.velocity) >> 13); + + for (const auto& vehicleSound : gVehicleSoundList) + { + if (vehicleSound.id == vehicle.Id.ToUnderlying()) + { + // Vehicle sounds will get higher priority if they are already playing + return result + 300; + } + } + + return result; + } + + static VehicleSoundParams CreateSoundParam(const Vehicle& vehicle, uint16_t priority) + { + VehicleSoundParams param; + param.priority = priority; + int32_t panX = (vehicle.SpriteData.SpriteRect.GetLeft() / 2) + (vehicle.SpriteData.SpriteRect.GetRight() / 2) + - g_music_tracking_viewport->viewPos.x; + panX = g_music_tracking_viewport->zoom.ApplyInversedTo(panX); + panX += g_music_tracking_viewport->pos.x; + + uint16_t screenWidth = ContextGetWidth(); + if (screenWidth < 64) + { + screenWidth = 64; + } + param.pan_x = ((((panX * 65536) / screenWidth) - 0x8000) >> 4); + + int32_t panY = (vehicle.SpriteData.SpriteRect.GetTop() / 2) + (vehicle.SpriteData.SpriteRect.GetBottom() / 2) + - g_music_tracking_viewport->viewPos.y; + panY = g_music_tracking_viewport->zoom.ApplyInversedTo(panY); + panY += g_music_tracking_viewport->pos.y; + + uint16_t screenHeight = ContextGetHeight(); + if (screenHeight < 64) + { + screenHeight = 64; + } + param.pan_y = ((((panY * 65536) / screenHeight) - 0x8000) >> 4); + + int32_t frequency = std::abs(vehicle.velocity); + + const auto* rideType = vehicle.GetRideEntry(); + if (rideType != nullptr) + { + if (rideType->Cars[vehicle.vehicle_type].double_sound_frequency & 1) + { + frequency *= 2; + } + } + + // * 0.0105133... + frequency >>= 5; // /32 + frequency *= 5512; + frequency >>= 14; // /16384 + + frequency += 11025; + frequency += 16 * vehicle.sound_vector_factor; + param.frequency = static_cast(frequency); + param.id = vehicle.Id.ToUnderlying(); + param.volume = 0; + + if (vehicle.x != LOCATION_NULL) + { + auto surfaceElement = MapGetSurfaceElementAt(CoordsXY{ vehicle.x, vehicle.y }); + + // vehicle underground + if (surfaceElement != nullptr && surfaceElement->GetBaseZ() > vehicle.z) + { + param.volume = 0x30; + } + } + return param; + } + + /** + * + * rct2: 0x006BB9FF + */ + static void UpdateSoundParams( + const Vehicle& vehicle, FixedVector& vehicleSoundParamsList) + { + if (!SoundCanPlay(vehicle)) + return; + + uint16_t soundPriority = GetSoundPriority(vehicle); + // Find a sound param of lower priority to use + auto soundParamIter = std::find_if( + vehicleSoundParamsList.begin(), vehicleSoundParamsList.end(), + [soundPriority](const auto& param) { return soundPriority > param.priority; }); + + if (soundParamIter == std::end(vehicleSoundParamsList)) + { + if (vehicleSoundParamsList.size() < MaxVehicleSounds) + { + vehicleSoundParamsList.push_back(CreateSoundParam(vehicle, soundPriority)); + } + } + else + { + if (vehicleSoundParamsList.size() < MaxVehicleSounds) + { + // Shift all sound params down one if using a free space + vehicleSoundParamsList.insert(soundParamIter, CreateSoundParam(vehicle, soundPriority)); + } + else + { + *soundParamIter = CreateSoundParam(vehicle, soundPriority); + } + } + } + + static void VehicleSoundsUpdateWindowSetup() + { + g_music_tracking_viewport = nullptr; + + WindowBase* window = Ui::Windows::WindowGetListening(); + if (window == nullptr) + { + return; + } + + Viewport* viewport = WindowGetViewport(window); + if (viewport == nullptr) + { + return; + } + + g_music_tracking_viewport = viewport; + gWindowAudioExclusive = window; + if (viewport->zoom <= ZoomLevel{ 0 }) + gVolumeAdjustZoom = 0; + else if (viewport->zoom == ZoomLevel{ 1 }) + gVolumeAdjustZoom = 35; + else + gVolumeAdjustZoom = 70; + } + + static uint8_t VehicleSoundsUpdateGetPanVolume(VehicleSoundParams* sound_params) + { + uint8_t vol1 = 0xFF; + uint8_t vol2 = 0xFF; + + int16_t pan_y = std::abs(sound_params->pan_y); + pan_y = std::min(static_cast(0xFFF), pan_y); + pan_y -= 0x800; + if (pan_y > 0) + { + pan_y = (0x400 - pan_y) / 4; + vol1 = LoByte(pan_y); + if (static_cast(HiByte(pan_y)) != 0) + { + vol1 = 0xFF; + if (static_cast(HiByte(pan_y)) < 0) + { + vol1 = 0; + } + } + } + + int16_t pan_x = std::abs(sound_params->pan_x); + pan_x = std::min(static_cast(0xFFF), pan_x); + pan_x -= 0x800; + + if (pan_x > 0) + { + pan_x = (0x400 - pan_x) / 4; + vol2 = LoByte(pan_x); + if (static_cast(HiByte(pan_x)) != 0) + { + vol2 = 0xFF; + if (static_cast(HiByte(pan_x)) < 0) + { + vol2 = 0; + } + } + } + + vol1 = std::min(vol1, vol2); + return std::max(0, vol1 - gVolumeAdjustZoom); + } + + /* Returns the vehicle sound for a sound_param. + * + * If already playing returns sound. + * If not playing allocates a sound slot to sound_param->id. + * If no free slots returns nullptr. + */ + static VehicleSound* VehicleSoundsUpdateGetVehicleSound(VehicleSoundParams* sound_params) + { + // Search for already playing vehicle sound + for (auto& vehicleSound : gVehicleSoundList) + { + if (vehicleSound.id == sound_params->id) + return &vehicleSound; + } + + // No sound already playing + for (auto& vehicleSound : gVehicleSoundList) + { + // Use free slot + if (vehicleSound.id == SoundIdNull) + { + vehicleSound.id = sound_params->id; + vehicleSound.TrackSound.Id = SoundId::Null; + vehicleSound.OtherSound.Id = SoundId::Null; + vehicleSound.volume = 0x30; + return &vehicleSound; + } + } + + return nullptr; + } + + static bool IsLoopingSound(SoundId id) + { + switch (id) + { + case SoundId::LiftClassic: + case SoundId::TrackFrictionClassicWood: + case SoundId::FrictionClassic: + case SoundId::LiftFrictionWheels: + case SoundId::GoKartEngine: + case SoundId::TrackFrictionTrain: + case SoundId::TrackFrictionWater: + case SoundId::LiftArrow: + case SoundId::LiftWood: + case SoundId::TrackFrictionWood: + case SoundId::LiftWildMouse: + case SoundId::LiftBM: + case SoundId::TrackFrictionBM: + case SoundId::LiftRMC: + case SoundId::TrackFrictionRMC: + return true; + default: + return false; + } + } + + static bool IsFixedFrequencySound(SoundId id) + { + switch (id) + { + case SoundId::Scream1: + case SoundId::Scream2: + case SoundId::Scream3: + case SoundId::Scream4: + case SoundId::Scream5: + case SoundId::Scream6: + case SoundId::Scream7: + case SoundId::Scream8: + case SoundId::TrainWhistle: + case SoundId::TrainDeparting: + case SoundId::Tram: + return true; + default: + return false; + } + } + + static bool IsSpecialFrequencySound(SoundId id) + { + switch (id) + { + case SoundId::TrackFrictionBM: + case SoundId::TrackFrictionRMC: + return true; + default: + return false; + } + } + + enum class SoundType + { + TrackNoises, + OtherNoises, // e.g. Screams + }; + + template static uint16_t SoundFrequency(const SoundId id, uint16_t baseFrequency) + { + if constexpr (type == SoundType::TrackNoises) + { + if (IsSpecialFrequencySound(id)) + { + return (baseFrequency / 2) + 4000; + } + return baseFrequency; + } + else + { + if (IsFixedFrequencySound(id)) + { + return 22050; + } + return std::min((baseFrequency * 2) - 3248, 25700); + } + } + + template static bool ShouldUpdateChannelRate(const SoundId id) + { + return type == SoundType::TrackNoises || !IsFixedFrequencySound(id); + } + + template + static void UpdateSound(const SoundId id, int32_t volume, VehicleSoundParams* sound_params, Sound& sound, uint8_t panVol) + { + volume *= panVol; + volume = volume / 8; + volume = std::max(volume - 0x1FFF, -10000); + + if (sound.Channel != nullptr && sound.Channel->IsDone()) + { + sound.Id = SoundId::Null; + sound.Channel = nullptr; + } + if (id != sound.Id && sound.Id != SoundId::Null) + { + sound.Id = SoundId::Null; + sound.Channel->Stop(); + } + if (id == SoundId::Null) + { + return; + } + + if (sound.Id == SoundId::Null) + { + auto frequency = SoundFrequency(id, sound_params->frequency); + auto looping = IsLoopingSound(id); + auto pan = sound_params->pan_x; + auto channel = CreateAudioChannel( + id, looping, DStoMixerVolume(volume), DStoMixerPan(pan), DStoMixerRate(frequency), false); + 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 = SoundId::Null; + } + return; + } + if (volume != sound.Volume) + { + sound.Volume = volume; + sound.Channel->SetVolume(DStoMixerVolume(volume)); + } + if (sound_params->pan_x != sound.Pan) + { + sound.Pan = sound_params->pan_x; + sound.Channel->SetPan(DStoMixerPan(sound_params->pan_x)); + } + if (!(GetGameState().CurrentTicks & 3) && sound_params->frequency != sound.Frequency) + { + sound.Frequency = sound_params->frequency; + if (ShouldUpdateChannelRate(id)) + { + uint16_t frequency = SoundFrequency(id, sound_params->frequency); + sound.Channel->SetRate(DStoMixerRate(frequency)); + } + } + } + + /** + * + * rct2: 0x006BBC6B + */ + void UpdateVehicleSounds() + { + PROFILED_FUNCTION(); + + if (!IsAvailable()) + return; + + FixedVector vehicleSoundParamsList; + + VehicleSoundsUpdateWindowSetup(); + + for (auto vehicle : TrainManager::View()) + { + UpdateSoundParams(*vehicle, vehicleSoundParamsList); + } + + // Stop all playing sounds that no longer have priority to play after vehicle_update_sound_params + for (auto& vehicleSound : gVehicleSoundList) + { + if (vehicleSound.id != SoundIdNull) + { + bool keepPlaying = false; + for (auto vehicleSoundParams : vehicleSoundParamsList) + { + if (vehicleSound.id == vehicleSoundParams.id) + { + keepPlaying = true; + break; + } + } + + if (keepPlaying) + continue; + + if (vehicleSound.TrackSound.Id != SoundId::Null) + { + vehicleSound.TrackSound.Channel->Stop(); + } + if (vehicleSound.OtherSound.Id != SoundId::Null) + { + vehicleSound.OtherSound.Channel->Stop(); + } + vehicleSound.id = SoundIdNull; + } + } + + for (auto& vehicleSoundParams : vehicleSoundParamsList) + { + uint8_t panVol = VehicleSoundsUpdateGetPanVolume(&vehicleSoundParams); + + auto* vehicleSound = VehicleSoundsUpdateGetVehicleSound(&vehicleSoundParams); + // No free vehicle sound slots (RCT2 corrupts the pointer here) + if (vehicleSound == nullptr) + continue; + + // Move the Sound Volume towards the SoundsParam Volume + int32_t tempvolume = vehicleSound->volume; + if (tempvolume != vehicleSoundParams.volume) + { + if (tempvolume < vehicleSoundParams.volume) + { + tempvolume += 4; + } + else + { + tempvolume -= 4; + } + } + vehicleSound->volume = tempvolume; + panVol = std::max(0, panVol - tempvolume); + + Vehicle* vehicle = GetEntity(EntityId::FromUnderlying(vehicleSoundParams.id)); + if (vehicle != nullptr) + { + UpdateSound( + vehicle->sound1_id, vehicle->sound1_volume, &vehicleSoundParams, vehicleSound->TrackSound, panVol); + UpdateSound( + vehicle->sound2_id, vehicle->sound2_volume, &vehicleSoundParams, vehicleSound->OtherSound, panVol); + } + } + } +} // namespace OpenRCT2::Audio diff --git a/src/openrct2-ui/ride/VehicleSounds.h b/src/openrct2-ui/ride/VehicleSounds.h new file mode 100644 index 0000000000..509aa6006a --- /dev/null +++ b/src/openrct2-ui/ride/VehicleSounds.h @@ -0,0 +1,6 @@ +#pragma once + +namespace OpenRCT2::Audio +{ + void UpdateVehicleSounds(); +} diff --git a/src/openrct2/interface/Window.cpp b/src/openrct2/interface/Window.cpp index 4d7b2898a1..b1f34b9fb6 100644 --- a/src/openrct2/interface/Window.cpp +++ b/src/openrct2/interface/Window.cpp @@ -1841,31 +1841,6 @@ Viewport* WindowGetViewport(WindowBase* w) return w->viewport; } -WindowBase* WindowGetListening() -{ - for (auto it = g_window_list.rbegin(); it != g_window_list.rend(); it++) - { - auto& w = **it; - if (w.flags & WF_DEAD) - continue; - - auto viewport = w.viewport; - if (viewport != nullptr) - { - if (viewport->flags & VIEWPORT_FLAG_SOUND_ON) - { - return &w; - } - } - } - return nullptr; -} - -WindowClass WindowGetClassification(const WindowBase& window) -{ - return window.classification; -} - /** * * rct2: 0x006EAF26 diff --git a/src/openrct2/interface/Window.h b/src/openrct2/interface/Window.h index 800a20d703..2f43fd04c7 100644 --- a/src/openrct2/interface/Window.h +++ b/src/openrct2/interface/Window.h @@ -643,6 +643,3 @@ money64 PlaceProvisionalTrackPiece( const CoordsXYZ& trackPos); extern RideConstructionState _rideConstructionState2; - -WindowBase* WindowGetListening(); -WindowClass WindowGetClassification(const WindowBase& window); diff --git a/src/openrct2/ride/Vehicle.cpp b/src/openrct2/ride/Vehicle.cpp index 06a6a5442b..ffe7b8e429 100644 --- a/src/openrct2/ride/Vehicle.cpp +++ b/src/openrct2/ride/Vehicle.cpp @@ -35,6 +35,8 @@ #include "../scenario/Scenario.h" #include "../scripting/HookEngine.h" #include "../scripting/ScriptEngine.h" +#include "../ui/UiContext.h" +#include "../ui/WindowManager.h" #include "../util/Util.h" #include "../windows/Intent.h" #include "../world/Map.h" @@ -577,596 +579,10 @@ Vehicle* TryGetVehicle(EntityId spriteIndex) return TryGetEntity(spriteIndex); } -namespace -{ - template class TrainIterator; - template class Train - { - public: - explicit Train(T* vehicle) - : FirstCar(vehicle) - { - assert(FirstCar->IsHead()); - } - int32_t Mass(); - - friend class TrainIterator; - using iterator = TrainIterator; - iterator begin() - { - return iterator{ FirstCar }; - } - iterator end() - { - return iterator{}; - } - - private: - T* FirstCar; - }; - template class TrainIterator - { - public: - using iterator = TrainIterator; - using iterator_category = std::forward_iterator_tag; - using value_type = T; - using pointer = T*; - using reference = T&; - - TrainIterator() = default; - explicit TrainIterator(T* vehicle) - : Current(vehicle) - { - } - reference operator*() - { - return *Current; - } - iterator& operator++() - { - Current = GetEntity(NextVehicleId); - if (Current != nullptr) - { - NextVehicleId = Current->next_vehicle_on_train; - } - return *this; - } - iterator operator++(int) - { - iterator temp = *this; - ++*this; - return temp; - } - bool operator!=(const iterator& other) - { - return Current != other.Current; - } - - private: - T* Current = nullptr; - EntityId NextVehicleId = EntityId::GetNull(); - }; -} // namespace - -template int32_t Train::Mass() -{ - int32_t totalMass = 0; - for (const auto& vehicle : *this) - { - totalMass += vehicle.mass; - } - - return totalMass; -} - -bool Vehicle::SoundCanPlay() const -{ - if (gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) - return false; - - if ((gScreenFlags & SCREEN_FLAGS_TRACK_DESIGNER) && GetGameState().EditorStep != EditorStep::RollercoasterDesigner) - return false; - - if (sound1_id == OpenRCT2::Audio::SoundId::Null && sound2_id == OpenRCT2::Audio::SoundId::Null) - return false; - - if (x == LOCATION_NULL) - return false; - - if (g_music_tracking_viewport == nullptr) - return false; - - const auto quarter_w = g_music_tracking_viewport->view_width / 4; - const auto quarter_h = g_music_tracking_viewport->view_height / 4; - - auto left = g_music_tracking_viewport->viewPos.x; - auto bottom = g_music_tracking_viewport->viewPos.y; - - if (WindowGetClassification(*gWindowAudioExclusive) == WindowClass::MainWindow) - { - left -= quarter_w; - bottom -= quarter_h; - } - - if (left >= SpriteData.SpriteRect.GetRight() || bottom >= SpriteData.SpriteRect.GetBottom()) - return false; - - auto right = g_music_tracking_viewport->view_width + left; - auto top = g_music_tracking_viewport->view_height + bottom; - - if (WindowGetClassification(*gWindowAudioExclusive) == WindowClass::MainWindow) - { - right += quarter_w + quarter_w; - top += quarter_h + quarter_h; - } - - if (right < SpriteData.SpriteRect.GetRight() || top < SpriteData.SpriteRect.GetTop()) - return false; - - return true; -} - -/** - * - * rct2: 0x006BC2F3 - */ -uint16_t Vehicle::GetSoundPriority() const -{ - int32_t result = Train(this).Mass() + (std::abs(velocity) >> 13); - - for (const auto& vehicleSound : OpenRCT2::Audio::gVehicleSoundList) - { - if (vehicleSound.id == Id.ToUnderlying()) - { - // Vehicle sounds will get higher priority if they are already playing - return result + 300; - } - } - - return result; -} - -OpenRCT2::Audio::VehicleSoundParams Vehicle::CreateSoundParam(uint16_t priority) const -{ - OpenRCT2::Audio::VehicleSoundParams param; - param.priority = priority; - int32_t panX = (SpriteData.SpriteRect.GetLeft() / 2) + (SpriteData.SpriteRect.GetRight() / 2) - - g_music_tracking_viewport->viewPos.x; - panX = g_music_tracking_viewport->zoom.ApplyInversedTo(panX); - panX += g_music_tracking_viewport->pos.x; - - uint16_t screenWidth = ContextGetWidth(); - if (screenWidth < 64) - { - screenWidth = 64; - } - param.pan_x = ((((panX * 65536) / screenWidth) - 0x8000) >> 4); - - int32_t panY = (SpriteData.SpriteRect.GetTop() / 2) + (SpriteData.SpriteRect.GetBottom() / 2) - - g_music_tracking_viewport->viewPos.y; - panY = g_music_tracking_viewport->zoom.ApplyInversedTo(panY); - panY += g_music_tracking_viewport->pos.y; - - uint16_t screenHeight = ContextGetHeight(); - if (screenHeight < 64) - { - screenHeight = 64; - } - param.pan_y = ((((panY * 65536) / screenHeight) - 0x8000) >> 4); - - int32_t frequency = std::abs(velocity); - - const auto* rideType = GetRideEntry(); - if (rideType != nullptr) - { - if (rideType->Cars[vehicle_type].double_sound_frequency & 1) - { - frequency *= 2; - } - } - - // * 0.0105133... - frequency >>= 5; // /32 - frequency *= 5512; - frequency >>= 14; // /16384 - - frequency += 11025; - frequency += 16 * sound_vector_factor; - param.frequency = static_cast(frequency); - param.id = Id.ToUnderlying(); - param.volume = 0; - - if (x != LOCATION_NULL) - { - auto surfaceElement = MapGetSurfaceElementAt(CoordsXY{ x, y }); - - // vehicle underground - if (surfaceElement != nullptr && surfaceElement->GetBaseZ() > z) - { - param.volume = 0x30; - } - } - return param; -} - -/** - * - * rct2: 0x006BB9FF - */ -void Vehicle::UpdateSoundParams(std::vector& vehicleSoundParamsList) const -{ - if (!SoundCanPlay()) - return; - - uint16_t soundPriority = GetSoundPriority(); - // Find a sound param of lower priority to use - auto soundParamIter = std::find_if( - vehicleSoundParamsList.begin(), vehicleSoundParamsList.end(), - [soundPriority](const auto& param) { return soundPriority > param.priority; }); - - if (soundParamIter == std::end(vehicleSoundParamsList)) - { - if (vehicleSoundParamsList.size() < OpenRCT2::Audio::MaxVehicleSounds) - { - vehicleSoundParamsList.push_back(CreateSoundParam(soundPriority)); - } - } - else - { - if (vehicleSoundParamsList.size() < OpenRCT2::Audio::MaxVehicleSounds) - { - // Shift all sound params down one if using a free space - vehicleSoundParamsList.insert(soundParamIter, CreateSoundParam(soundPriority)); - } - else - { - *soundParamIter = CreateSoundParam(soundPriority); - } - } -} - -static void vehicle_sounds_update_window_setup() -{ - g_music_tracking_viewport = nullptr; - - WindowBase* window = WindowGetListening(); - if (window == nullptr) - { - return; - } - - Viewport* viewport = WindowGetViewport(window); - if (viewport == nullptr) - { - return; - } - - g_music_tracking_viewport = viewport; - gWindowAudioExclusive = window; - if (viewport->zoom <= ZoomLevel{ 0 }) - OpenRCT2::Audio::gVolumeAdjustZoom = 0; - else if (viewport->zoom == ZoomLevel{ 1 }) - OpenRCT2::Audio::gVolumeAdjustZoom = 35; - else - OpenRCT2::Audio::gVolumeAdjustZoom = 70; -} - -static uint8_t vehicle_sounds_update_get_pan_volume(OpenRCT2::Audio::VehicleSoundParams* sound_params) -{ - uint8_t vol1 = 0xFF; - uint8_t vol2 = 0xFF; - - int16_t pan_y = std::abs(sound_params->pan_y); - pan_y = std::min(static_cast(0xFFF), pan_y); - pan_y -= 0x800; - if (pan_y > 0) - { - pan_y = (0x400 - pan_y) / 4; - vol1 = LoByte(pan_y); - if (static_cast(HiByte(pan_y)) != 0) - { - vol1 = 0xFF; - if (static_cast(HiByte(pan_y)) < 0) - { - vol1 = 0; - } - } - } - - int16_t pan_x = std::abs(sound_params->pan_x); - pan_x = std::min(static_cast(0xFFF), pan_x); - pan_x -= 0x800; - - if (pan_x > 0) - { - pan_x = (0x400 - pan_x) / 4; - vol2 = LoByte(pan_x); - if (static_cast(HiByte(pan_x)) != 0) - { - vol2 = 0xFF; - if (static_cast(HiByte(pan_x)) < 0) - { - vol2 = 0; - } - } - } - - vol1 = std::min(vol1, vol2); - return std::max(0, vol1 - OpenRCT2::Audio::gVolumeAdjustZoom); -} - -/* Returns the vehicle sound for a sound_param. - * - * If already playing returns sound. - * If not playing allocates a sound slot to sound_param->id. - * If no free slots returns nullptr. - */ -static OpenRCT2::Audio::VehicleSound* vehicle_sounds_update_get_vehicle_sound(OpenRCT2::Audio::VehicleSoundParams* sound_params) -{ - // Search for already playing vehicle sound - for (auto& vehicleSound : OpenRCT2::Audio::gVehicleSoundList) - { - if (vehicleSound.id == sound_params->id) - return &vehicleSound; - } - - // No sound already playing - for (auto& vehicleSound : OpenRCT2::Audio::gVehicleSoundList) - { - // Use free slot - if (vehicleSound.id == OpenRCT2::Audio::SoundIdNull) - { - vehicleSound.id = sound_params->id; - vehicleSound.TrackSound.Id = OpenRCT2::Audio::SoundId::Null; - vehicleSound.OtherSound.Id = OpenRCT2::Audio::SoundId::Null; - vehicleSound.volume = 0x30; - return &vehicleSound; - } - } - - return nullptr; -} - -static bool IsLoopingSound(SoundId id) -{ - switch (id) - { - case SoundId::LiftClassic: - case SoundId::TrackFrictionClassicWood: - case SoundId::FrictionClassic: - case SoundId::LiftFrictionWheels: - case SoundId::GoKartEngine: - case SoundId::TrackFrictionTrain: - case SoundId::TrackFrictionWater: - case SoundId::LiftArrow: - case SoundId::LiftWood: - case SoundId::TrackFrictionWood: - case SoundId::LiftWildMouse: - case SoundId::LiftBM: - case SoundId::TrackFrictionBM: - case SoundId::LiftRMC: - case SoundId::TrackFrictionRMC: - return true; - default: - return false; - } -} - -static bool IsFixedFrequencySound(SoundId id) -{ - switch (id) - { - case SoundId::Scream1: - case SoundId::Scream2: - case SoundId::Scream3: - case SoundId::Scream4: - case SoundId::Scream5: - case SoundId::Scream6: - case SoundId::Scream7: - case SoundId::Scream8: - case SoundId::TrainWhistle: - case SoundId::TrainDeparting: - case SoundId::Tram: - return true; - default: - return false; - } -} - -static bool IsSpecialFrequencySound(SoundId id) -{ - switch (id) - { - case SoundId::TrackFrictionBM: - case SoundId::TrackFrictionRMC: - return true; - default: - return false; - } -} - -enum class SoundType -{ - TrackNoises, - OtherNoises, // e.g. Screams -}; - -template static uint16_t SoundFrequency(const OpenRCT2::Audio::SoundId id, uint16_t baseFrequency) -{ - if constexpr (type == SoundType::TrackNoises) - { - if (IsSpecialFrequencySound(id)) - { - return (baseFrequency / 2) + 4000; - } - return baseFrequency; - } - else - { - if (IsFixedFrequencySound(id)) - { - return 22050; - } - return std::min((baseFrequency * 2) - 3248, 25700); - } -} - -template static bool ShouldUpdateChannelRate(const OpenRCT2::Audio::SoundId id) -{ - return type == SoundType::TrackNoises || !IsFixedFrequencySound(id); -} - -template -static void UpdateSound( - const OpenRCT2::Audio::SoundId id, int32_t volume, OpenRCT2::Audio::VehicleSoundParams* sound_params, - OpenRCT2::Audio::Sound& sound, uint8_t panVol) -{ - volume *= panVol; - volume = volume / 8; - volume = std::max(volume - 0x1FFF, -10000); - - if (sound.Channel != nullptr && sound.Channel->IsDone()) - { - sound.Id = OpenRCT2::Audio::SoundId::Null; - sound.Channel = nullptr; - } - if (id != sound.Id && sound.Id != OpenRCT2::Audio::SoundId::Null) - { - sound.Id = OpenRCT2::Audio::SoundId::Null; - sound.Channel->Stop(); - } - if (id == OpenRCT2::Audio::SoundId::Null) - { - return; - } - - if (sound.Id == OpenRCT2::Audio::SoundId::Null) - { - auto frequency = SoundFrequency(id, sound_params->frequency); - auto looping = IsLoopingSound(id); - auto pan = sound_params->pan_x; - auto channel = CreateAudioChannel( - id, looping, DStoMixerVolume(volume), DStoMixerPan(pan), DStoMixerRate(frequency), false); - 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) - { - sound.Volume = volume; - sound.Channel->SetVolume(DStoMixerVolume(volume)); - } - if (sound_params->pan_x != sound.Pan) - { - sound.Pan = sound_params->pan_x; - sound.Channel->SetPan(DStoMixerPan(sound_params->pan_x)); - } - if (!(GetGameState().CurrentTicks & 3) && sound_params->frequency != sound.Frequency) - { - sound.Frequency = sound_params->frequency; - if (ShouldUpdateChannelRate(id)) - { - uint16_t frequency = SoundFrequency(id, sound_params->frequency); - sound.Channel->SetRate(DStoMixerRate(frequency)); - } - } -} - -/** - * - * rct2: 0x006BBC6B - */ void VehicleSoundsUpdate() { - PROFILED_FUNCTION(); - - if (!OpenRCT2::Audio::IsAvailable()) - return; - - std::vector vehicleSoundParamsList; - vehicleSoundParamsList.reserve(OpenRCT2::Audio::MaxVehicleSounds); - - vehicle_sounds_update_window_setup(); - - for (auto vehicle : TrainManager::View()) - { - vehicle->UpdateSoundParams(vehicleSoundParamsList); - } - - // Stop all playing sounds that no longer have priority to play after vehicle_update_sound_params - for (auto& vehicle_sound : OpenRCT2::Audio::gVehicleSoundList) - { - if (vehicle_sound.id != OpenRCT2::Audio::SoundIdNull) - { - bool keepPlaying = false; - for (auto vehicleSoundParams : vehicleSoundParamsList) - { - if (vehicle_sound.id == vehicleSoundParams.id) - { - keepPlaying = true; - break; - } - } - - if (keepPlaying) - continue; - - if (vehicle_sound.TrackSound.Id != OpenRCT2::Audio::SoundId::Null) - { - vehicle_sound.TrackSound.Channel->Stop(); - } - if (vehicle_sound.OtherSound.Id != OpenRCT2::Audio::SoundId::Null) - { - vehicle_sound.OtherSound.Channel->Stop(); - } - vehicle_sound.id = OpenRCT2::Audio::SoundIdNull; - } - } - - for (auto& vehicleSoundParams : vehicleSoundParamsList) - { - uint8_t panVol = vehicle_sounds_update_get_pan_volume(&vehicleSoundParams); - - auto* vehicleSound = vehicle_sounds_update_get_vehicle_sound(&vehicleSoundParams); - // No free vehicle sound slots (RCT2 corrupts the pointer here) - if (vehicleSound == nullptr) - continue; - - // Move the Sound Volume towards the SoundsParam Volume - int32_t tempvolume = vehicleSound->volume; - if (tempvolume != vehicleSoundParams.volume) - { - if (tempvolume < vehicleSoundParams.volume) - { - tempvolume += 4; - } - else - { - tempvolume -= 4; - } - } - vehicleSound->volume = tempvolume; - panVol = std::max(0, panVol - tempvolume); - - Vehicle* vehicle = GetEntity(EntityId::FromUnderlying(vehicleSoundParams.id)); - if (vehicle != nullptr) - { - UpdateSound( - vehicle->sound1_id, vehicle->sound1_volume, &vehicleSoundParams, vehicleSound->TrackSound, panVol); - UpdateSound( - vehicle->sound2_id, vehicle->sound2_volume, &vehicleSoundParams, vehicleSound->OtherSound, panVol); - } - } + auto windowManager = OpenRCT2::GetContext()->GetUiContext()->GetWindowManager(); + windowManager->BroadcastIntent(Intent(INTENT_ACTION_UPDATE_VEHICLE_SOUNDS)); } /** diff --git a/src/openrct2/ride/Vehicle.h b/src/openrct2/ride/Vehicle.h index 3e25225f94..95dd53b5f6 100644 --- a/src/openrct2/ride/Vehicle.h +++ b/src/openrct2/ride/Vehicle.h @@ -222,7 +222,6 @@ struct Vehicle : EntityBase Vehicle* GetCar(size_t carIndex) const; void SetState(Vehicle::Status vehicleStatus, uint8_t subState = 0); bool IsGhost() const; - void UpdateSoundParams(std::vector& vehicleSoundParamsList) const; std::optional DodgemsCarWouldCollideAt(const CoordsXY& coords) const; int32_t UpdateTrackMotion(int32_t* outStation); int32_t CableLiftUpdateTrackMotion(); @@ -281,11 +280,8 @@ struct Vehicle : EntityBase friend void UpdateRotatingEnterprise(Vehicle& vehicle); private: - bool SoundCanPlay() const; - uint16_t GetSoundPriority() const; const VehicleInfo* GetMoveInfo() const; uint16_t GetTrackProgress() const; - OpenRCT2::Audio::VehicleSoundParams CreateSoundParam(uint16_t priority) const; void CableLiftUpdate(); bool CableLiftUpdateTrackMotionForwards(); bool CableLiftUpdateTrackMotionBackwards(); diff --git a/src/openrct2/windows/Intent.h b/src/openrct2/windows/Intent.h index ae2a699fd9..96e635c82e 100644 --- a/src/openrct2/windows/Intent.h +++ b/src/openrct2/windows/Intent.h @@ -43,6 +43,7 @@ enum IntentAction INTENT_ACTION_UPDATE_CASH, INTENT_ACTION_UPDATE_BANNER, INTENT_ACTION_UPDATE_RESEARCH, + INTENT_ACTION_UPDATE_VEHICLE_SOUNDS, INTENT_ACTION_TRACK_DESIGN_REMOVE_PROVISIONAL, INTENT_ACTION_TRACK_DESIGN_RESTORE_PROVISIONAL, INTENT_ACTION_SET_MAP_TOOLTIP,