Implement ride music objects and refactor

This commit is contained in:
Ted John 2021-01-09 20:54:02 +00:00
parent db4841ca45
commit 2f39442d25
35 changed files with 1401 additions and 674 deletions

View File

@ -70,6 +70,11 @@ namespace OpenRCT2::Audio
return AudioSource::CreateStreamFromWAV(path);
}
IAudioSource* CreateStreamFromWAV(std::unique_ptr<std::istream> stream) override
{
return AudioSource::CreateStreamFromWAV(std::move(stream));
}
void StartTitleMusic() override
{
}

View File

@ -9,6 +9,7 @@
#pragma once
#include <istream>
#include <memory>
#include <openrct2/audio/AudioChannel.h>
#include <openrct2/audio/AudioSource.h>
@ -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<std::istream> stream);
} // namespace AudioSource
namespace AudioChannel

View File

@ -202,4 +202,47 @@ namespace OpenRCT2::Audio
}
return source;
}
IAudioSource* AudioSource::CreateStreamFromWAV(std::unique_ptr<std::istream> stream)
{
using streamptr = std::unique_ptr<std::istream>*;
auto data = new std::unique_ptr<std::istream>(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<streamptr>(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<Sint64>((*ptr)->tellg());
};
rw->read = [](SDL_RWops* ctx, void* buf, size_t size, size_t maxnum) {
auto ptr = static_cast<streamptr>(ctx->hidden.unknown.data1);
(*ptr)->read(static_cast<char*>(buf), size * maxnum);
return static_cast<size_t>((*ptr)->gcount() / size);
};
rw->size = [](SDL_RWops* ctx) {
auto ptr = static_cast<streamptr>(ctx->hidden.unknown.data1);
return static_cast<Sint64>((*ptr)->tellg());
};
rw->close = [](SDL_RWops* ctx) {
auto ptr = static_cast<streamptr>(ctx->hidden.unknown.data1);
delete ptr;
ctx->hidden.unknown.data1 = nullptr;
delete ctx;
return 0;
};
return CreateStreamFromWAV(rw);
}
} // namespace OpenRCT2::Audio

View File

@ -33,6 +33,7 @@
#include <openrct2/localisation/LocalisationService.h>
#include <openrct2/network/network.h>
#include <openrct2/platform/Platform2.h>
#include <openrct2/ride/RideAudio.h>
#include <openrct2/scenario/Scenario.h>
#include <openrct2/sprites.h>
#include <openrct2/title/TitleScreen.h>
@ -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();

View File

@ -36,6 +36,7 @@
#include <openrct2/localisation/LocalisationService.h>
#include <openrct2/localisation/StringIds.h>
#include <openrct2/network/network.h>
#include <openrct2/object/MusicObject.h>
#include <openrct2/object/ObjectManager.h>
#include <openrct2/object/ObjectRepository.h>
#include <openrct2/object/StationObject.h>
@ -52,6 +53,8 @@
#include <openrct2/sprites.h>
#include <openrct2/windows/Intent.h>
#include <openrct2/world/Park.h>
#include <vector>
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<ObjectEntryIndex> 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<MusicObject*>(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<size_t>::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<MusicObject*>(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<MusicObject*>(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<int32_t>(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<size_t>(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<MusicObject*>(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;

View File

@ -175,6 +175,7 @@ namespace OpenRCT2
gfx_object_check_all_images_freed();
gfx_unload_g2();
gfx_unload_g1();
Audio::Close();
config_release();
Instance = nullptr;

View File

@ -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))
{

View File

@ -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<uint32_t>();
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();
}

View File

@ -11,6 +11,7 @@
#include "../common.h"
#include <istream>
#include <memory>
#include <string>
#include <vector>
@ -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<std::istream> stream) abstract;
virtual void StartTitleMusic() abstract;

View File

@ -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<std::istream> 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);

View File

@ -11,6 +11,9 @@
#include "../common.h"
#include <istream>
#include <memory>
#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<std::istream> stream, int32_t loop);
void Mixer_SetVolume(float volume);
int32_t DStoMixerVolume(int32_t volume);

View File

@ -31,6 +31,11 @@ namespace OpenRCT2::Audio
return nullptr;
}
IAudioSource* CreateStreamFromWAV(std::unique_ptr<std::istream>) override
{
return nullptr;
}
void StartTitleMusic() override
{
}

View File

@ -12,12 +12,13 @@
#include "../common.h"
#include "../ride/RideTypes.h"
#include <vector>
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

View File

@ -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)

View File

@ -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<typename... Args> static std::string Combine(const std::string& a, const std::string& b, Args... args)
template<typename... Args> static std::string Combine(std::string_view a, std::string_view b, Args... args)
{
return Combine(a, Combine(b, args...));
}

View File

@ -7,12 +7,60 @@
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#include "Zip.h"
#include "IStream.hpp"
#include <algorithm>
#ifndef __ANDROID__
# include "Zip.h"
# include "IStream.hpp"
# include <zip.h>
#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<size_t> 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<uint8_t> 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<size_t>(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<size_t>(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<std::istream> GetFileStream(std::string_view path) const override
{
auto index = GetIndexFromPath(path);
if (index)
{
return std::make_unique<ifilestream>(_zip, *index);
}
return {};
}
void SetFileData(std::string_view path, std::vector<uint8_t>&& 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<zip_int64_t>(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_type>(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

View File

@ -11,7 +11,9 @@
#include "../common.h"
#include <istream>
#include <memory>
#include <optional>
#include <string_view>
#include <vector>
@ -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<uint8_t> GetFileData(std::string_view path) const abstract;
virtual std::unique_ptr<std::istream> 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<size_t> GetIndexFromPath(std::string_view path) const;
bool Exists(std::string_view path) const;
};
enum class ZIP_ACCESS

View File

@ -113,6 +113,12 @@ public:
return std::vector<uint8_t>(dataPtr, dataPtr + dataSize);
}
std::unique_ptr<std::istream> GetFileStream(std::string_view path) const override
{
auto data = GetFileData(path);
return std::make_unique<memstream>(std::move(data));
}
void SetFileData(std::string_view path, std::vector<uint8_t>&& data) override
{
STUB();
@ -127,6 +133,34 @@ public:
{
STUB();
}
private:
class memstream final : public std::istream
{
private:
class vector_streambuf : public std::basic_streambuf<char, std::char_traits<char>>
{
public:
explicit vector_streambuf(const std::vector<uint8_t>& vec)
{
this->setg(
reinterpret_cast<char*>(const_cast<unsigned char*>(vec.data())),
reinterpret_cast<char*>(const_cast<unsigned char*>(vec.data())),
reinterpret_cast<char*>(const_cast<unsigned char*>(vec.data() + vec.size())));
}
};
std::vector<uint8_t> _data;
vector_streambuf _streambuf;
public:
memstream(std::vector<uint8_t>&& data)
: std::istream(&_streambuf)
, _data(data)
, _streambuf(_data)
{
}
};
};
namespace Zip

View File

@ -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++)

View File

@ -255,6 +255,7 @@
<ClInclude Include="object\FootpathObject.h" />
<ClInclude Include="object\ImageTable.h" />
<ClInclude Include="object\LargeSceneryObject.h" />
<ClInclude Include="object\MusicObject.h" />
<ClInclude Include="object\Object.h" />
<ClInclude Include="object\ObjectFactory.h" />
<ClInclude Include="object\ObjectLimits.h" />
@ -352,8 +353,8 @@
<ClInclude Include="ride\gentle\meta\ObservationTower.h" />
<ClInclude Include="ride\gentle\meta\SpaceRings.h" />
<ClInclude Include="ride\gentle\meta\SpiralSlide.h" />
<ClInclude Include="ride\MusicList.h" />
<ClInclude Include="ride\Ride.h" />
<ClInclude Include="ride\RideAudio.h" />
<ClInclude Include="ride\RideData.h" />
<ClInclude Include="ride\RideRatings.h" />
<ClInclude Include="ride\RideTypes.h" />
@ -671,6 +672,7 @@
<ClCompile Include="object\FootpathObject.cpp" />
<ClCompile Include="object\ImageTable.cpp" />
<ClCompile Include="object\LargeSceneryObject.cpp" />
<ClCompile Include="object\MusicObject.cpp" />
<ClCompile Include="object\Object.cpp" />
<ClCompile Include="object\ObjectFactory.cpp" />
<ClCompile Include="object\ObjectList.cpp" />
@ -787,8 +789,8 @@
<ClCompile Include="ride\gentle\ObservationTower.cpp" />
<ClCompile Include="ride\gentle\SpaceRings.cpp" />
<ClCompile Include="ride\gentle\SpiralSlide.cpp" />
<ClCompile Include="ride\MusicList.cpp" />
<ClCompile Include="ride\Ride.cpp" />
<ClCompile Include="ride\RideAudio.cpp" />
<ClCompile Include="ride\RideData.cpp" />
<ClCompile Include="ride\RideRatings.cpp" />
<ClCompile Include="ride\ShopItem.cpp" />

View File

@ -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 <memory>
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<uint8_t>();
}
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<uint8_t> 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);
}
}

View File

@ -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 <string>
#include <vector>
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<uint8_t> _rideTypes;
std::vector<MusicObjectTrack> _tracks;
std::optional<uint8_t> _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<uint8_t> 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);
};

View File

@ -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<uint8_t> rct_object_entry::GetSceneryType() const
}
}
class zipstreamwrapper final : public std::istream
{
private:
std::unique_ptr<IZipArchive> _zipArchive;
std::unique_ptr<std::istream> _base;
public:
zipstreamwrapper(std::unique_ptr<IZipArchive> zipArchive, std::unique_ptr<std::istream> 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<std::istream> ObjectAsset::GetStream() const
{
if (_zipPath.empty())
{
return std::make_unique<std::ifstream>(_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<zipstreamwrapper>(std::move(zipArchive), std::move(stream));
}
}
}
return {};
}
#ifdef __WARN_SUGGEST_FINAL_METHODS__
# pragma GCC diagnostic pop
#endif

View File

@ -15,6 +15,9 @@
#include "ImageTable.h"
#include "StringTable.h"
#include <fstream>
#include <istream>
#include <memory>
#include <optional>
#include <string_view>
#include <vector>
@ -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<std::istream> GetStream() const;
};
struct IReadObjectContext
{
virtual ~IReadObjectContext() = default;
@ -208,6 +234,7 @@ struct IReadObjectContext
virtual IObjectRepository& GetObjectRepository() abstract;
virtual bool ShouldLoadImages() abstract;
virtual std::vector<uint8_t> 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;

View File

@ -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<uint8_t> 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<uint8_t> 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<StationObject>(entry);
break;
case ObjectType::Music:
result = std::make_unique<MusicObject>(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);
}
}

View File

@ -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 =

View File

@ -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)

View File

@ -76,7 +76,7 @@ class ObjectFileIndex final : public FileIndex<ObjectRepositoryItem>
{
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;

View File

@ -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);

View File

@ -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<uint8_t>({ __VA_ARGS__ })
// 0x009AEF28
std::vector<uint8_t> 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

View File

@ -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 <vector>
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<uint8_t> gRideMusicStyleTuneIds[];
} // namespace OpenRCT2::Audio

View File

@ -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<TrackDesign> 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<MusicObject*>(objManager.GetLoadedObject(ObjectType::Music, ride->music));
if (musicObj != nullptr)
{
auto numTracks = musicObj->GetTrackCount();
ride->music_tune_id = static_cast<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(-vol1 - 1) * static_cast<uint8_t>(-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<size_t>(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<uint32_t>(
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<size_t>(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)

View File

@ -1137,9 +1137,6 @@ void ride_construction_invalidate_current_track();
std::optional<CoordsXYZ> 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);

View File

@ -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 <algorithm>
#include <vector>
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<size_t>(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<ViewportRideMusicInstance> _musicInstances;
static std::vector<RideMusicChannel> _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<MusicObject*>(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<size_t, size_t> RideMusicGetTrackOffsetLength(const Ride& ride)
{
if (ride.type == RIDE_TYPE_CIRCUS)
{
return { 1378, 12427456 };
}
else
{
auto& objManager = GetContext()->GetObjectManager();
auto musicObj = static_cast<MusicObject*>(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<uint32_t>(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<uint8_t>(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<uint8_t>(-volXY - 1) * static_cast<uint8_t>(-volXY - 1)) / 16) - 700;
if (volXY != 0 && newVolume >= -4000)
{
auto newPan = std::clamp(panX, -10000, 10000);
RideUpdateMusicPosition(ride, newVolume, newPan, sampleRate);
}
else
{
RideUpdateMusicPosition(ride);
}
}
}
}

View File

@ -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 <cstdint>
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);