/***************************************************************************** * 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. *****************************************************************************/ #include "audio.h" #include "../Context.h" #include "../Intro.h" #include "../OpenRCT2.h" #include "../config/Config.h" #include "../core/File.h" #include "../core/FileStream.hpp" #include "../core/Memory.hpp" #include "../core/String.hpp" #include "../interface/Viewport.h" #include "../localisation/Language.h" #include "../localisation/StringIds.h" #include "../peep/Peep.h" #include "../ride/Ride.h" #include "../ui/UiContext.h" #include "../util/Util.h" #include "AudioContext.h" #include "AudioMixer.h" #include using namespace OpenRCT2::Audio; struct AudioParams { bool in_range; int32_t volume; int32_t pan; }; audio_device* gAudioDevices = nullptr; int32_t gAudioDeviceCount; int32_t gAudioCurrentDevice = -1; bool gGameSoundsOff = false; int32_t gVolumeAdjustZoom = 0; void* gTitleMusicChannel = nullptr; void* gRainSoundChannel = nullptr; rct_ride_music gRideMusicList[AUDIO_MAX_RIDE_MUSIC]; rct_ride_music_params gRideMusicParamsList[AUDIO_MAX_RIDE_MUSIC]; rct_ride_music_params* gRideMusicParamsListEnd; rct_vehicle_sound gVehicleSoundList[AUDIO_MAX_VEHICLE_SOUNDS]; rct_vehicle_sound_params gVehicleSoundParamsList[AUDIO_MAX_VEHICLE_SOUNDS]; rct_vehicle_sound_params* gVehicleSoundParamsListEnd; // clang-format off static int32_t SoundVolumeAdjust[RCT2SoundCount] = { 0, // LiftClassic 0, // TrackFrictionClassicWood 0, // FrictionClassic 0, // Scream1 0, // Click1 0, // Click2 0, // PlaceItem 0, // Scream2 0, // Scream3 0, // Scream4 0, // Scream5 0, // Scream6 0, // LiftFrictionWheels -400, // Purchase 0, // Crash 0, // LayingOutWater 0, // Water1 0, // Water2 0, // TrainWhistle 0, // TrainDeparting -1000, // WaterSplash 0, // GoKartEngine -800, // RideLaunch1 -1700, // RideLaunch2 -700, // Cough1 -700, // Cough2 -700, // Cough3 -700, // Cough4 0, // Rain 0, // Thunder1 0, // Thunder2 0, // TrackFrictionTrain 0, // TrackFrictionWater 0, // BalloonPop -700, // MechanicFix 0, // Scream7 -2500, // ToiletFlush original value: -1000 0, // Click3 0, // Quack 0, // NewsItem 0, // WindowOpen -900, // Laugh1 -900, // Laugh2 -900, // Laugh3 0, // Applause -600, // HauntedHouseScare -700, // HauntedHouseScream1 -700, // HauntedHouseScream2 -2550, // BlockBrakeClose -2900, // BlockBrakeRelease 0, // Error -3400, // BrakeRelease 0, // LiftArrow 0, // LiftWood 0, // TrackFrictionWood 0, // LiftWildMouse 0, // LiftBM 0, // TrackFrictionBM 0, // Scream8 0, // Tram -2000, // DoorOpen -2700, // DoorClose -700 // Portcullis }; // clang-format on static AudioParams audio_get_params_from_location(SoundId soundId, const CoordsXYZ& location); void audio_init() { if (str_is_null_or_empty(gConfigSound.device)) { Mixer_Init(nullptr); gAudioCurrentDevice = 0; } else { Mixer_Init(gConfigSound.device); audio_populate_devices(); for (int32_t i = 0; i < gAudioDeviceCount; i++) { if (String::Equals(gAudioDevices[i].name, gConfigSound.device)) { gAudioCurrentDevice = i; } } } } void audio_populate_devices() { SafeFree(gAudioDevices); auto audioContext = OpenRCT2::GetContext()->GetAudioContext(); std::vector devices = audioContext->GetOutputDevices(); // Replace blanks with localised unknown string for (auto& device : devices) { if (device.empty()) { device = language_get_string(STR_OPTIONS_SOUND_VALUE_DEFAULT); } } #ifndef __linux__ // The first device is always system default on Windows and macOS std::string defaultDevice = language_get_string(STR_OPTIONS_SOUND_VALUE_DEFAULT); devices.insert(devices.begin(), defaultDevice); #endif gAudioDeviceCount = (int32_t)devices.size(); gAudioDevices = Memory::AllocateArray(gAudioDeviceCount); for (int32_t i = 0; i < gAudioDeviceCount; i++) { auto device = &gAudioDevices[i]; String::Set(device->name, sizeof(device->name), devices[i].c_str()); } } void audio_play_sound_at_location(SoundId soundId, const CoordsXYZ& loc) { if (gGameSoundsOff) return; AudioParams params = audio_get_params_from_location(soundId, loc); if (params.in_range) { audio_play_sound(soundId, params.volume, params.pan); } } /** * Returns the audio parameters to use when playing the specified sound at a virtual location. * @param soundId The sound effect to be played. * @param location The location at which the sound effect is to be played. * @return The audio parameters to be used when playing this sound effect. */ static AudioParams audio_get_params_from_location(SoundId soundId, const CoordsXYZ& location) { int32_t volumeDown = 0; AudioParams params; params.in_range = true; params.volume = 0; params.pan = 0; auto element = map_get_surface_element_at(location); if (element && (element->GetBaseZ()) - 5 > location.z) { volumeDown = 10; } uint8_t rotation = get_current_rotation(); auto pos2 = translate_3d_to_2d_with_z(rotation, location); rct_viewport* viewport = nullptr; while ((viewport = window_get_previous_viewport(viewport)) != nullptr) { if (viewport->flags & VIEWPORT_FLAG_SOUND_ON) { int16_t vy = pos2.y - viewport->view_y; int16_t vx = pos2.x - viewport->view_x; params.pan = viewport->x + (vx >> viewport->zoom); params.volume = SoundVolumeAdjust[static_cast(soundId)] + ((-1024 * viewport->zoom - 1) * (1 << volumeDown)) + 1; if (vy < 0 || vy >= viewport->view_height || vx < 0 || vx >= viewport->view_width || params.volume < -10000) { params.in_range = false; return params; } } } return params; } void audio_play_sound(SoundId soundId, int32_t volume, int32_t pan) { if (gGameSoundsOff) return; int32_t mixerPan = 0; if (pan != AUDIO_PLAY_AT_CENTRE) { int32_t x2 = pan << 16; uint16_t screenWidth = std::max(64, OpenRCT2::GetContext()->GetUiContext()->GetWidth()); mixerPan = ((x2 / screenWidth) - 0x8000) >> 4; } Mixer_Play_Effect(soundId, MIXER_LOOP_NONE, DStoMixerVolume(volume), DStoMixerPan(mixerPan), 1, 1); } void audio_start_title_music() { if (gGameSoundsOff || !(gScreenFlags & SCREEN_FLAGS_TITLE_DEMO) || gIntroState != INTRO_STATE_NONE) { audio_stop_title_music(); return; } if (gTitleMusicChannel != nullptr) { return; } int32_t pathId; switch (gConfigSound.title_music) { case 1: pathId = PATH_ID_CSS50; break; case 2: pathId = PATH_ID_CSS17; break; case 3: pathId = (util_rand() & 1) ? PATH_ID_CSS50 : PATH_ID_CSS17; break; default: return; } gTitleMusicChannel = Mixer_Play_Music(pathId, MIXER_LOOP_INFINITE, true); if (gTitleMusicChannel != nullptr) { Mixer_Channel_SetGroup(gTitleMusicChannel, MIXER_GROUP_TITLE_MUSIC); } } void audio_stop_ride_music() { 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 audio_stop_all_music_and_sounds() { audio_stop_title_music(); audio_stop_vehicle_sounds(); audio_stop_ride_music(); peep_stop_crowd_noise(); audio_stop_rain_sound(); } void audio_stop_title_music() { if (gTitleMusicChannel != nullptr) { Mixer_Stop_Channel(gTitleMusicChannel); gTitleMusicChannel = nullptr; } } void audio_stop_rain_sound() { if (gRainSoundChannel != nullptr) { Mixer_Stop_Channel(gRainSoundChannel); gRainSoundChannel = nullptr; } } void audio_init_ride_sounds_and_info() { int32_t deviceNum = 0; audio_init_ride_sounds(deviceNum); for (auto& rideMusicInfo : gRideMusicInfoList) { const utf8* path = context_get_path_legacy(rideMusicInfo.path_id); if (File::Exists(path)) { try { auto fs = FileStream(path, FILE_MODE_OPEN); uint32_t head = fs.ReadValue(); if (head == 0x78787878) { rideMusicInfo.length = 0; } // The length used to be hardcoded, but we stopped doing that to allow replacement. if (rideMusicInfo.length == 0) { rideMusicInfo.length = fs.GetLength(); } } catch (const std::exception&) { } } } } void audio_init_ride_sounds(int32_t device) { audio_close(); for (auto& vehicleSound : gVehicleSoundList) { vehicleSound.id = SOUND_ID_NULL; } gAudioCurrentDevice = device; config_save_default(); for (auto& rideMusic : gRideMusicList) { rideMusic.ride_id = RIDE_ID_NULL; } } void audio_close() { peep_stop_crowd_noise(); audio_stop_title_music(); audio_stop_ride_music(); audio_stop_rain_sound(); gAudioCurrentDevice = -1; } void audio_toggle_all_sounds() { gConfigSound.master_sound_enabled = !gConfigSound.master_sound_enabled; if (gConfigSound.master_sound_enabled) { audio_unpause_sounds(); } else { audio_stop_title_music(); audio_pause_sounds(); } window_invalidate_by_class(WC_OPTIONS); } void audio_pause_sounds() { gGameSoundsOff = true; audio_stop_vehicle_sounds(); audio_stop_ride_music(); peep_stop_crowd_noise(); audio_stop_rain_sound(); } void audio_unpause_sounds() { gGameSoundsOff = false; } void audio_stop_vehicle_sounds() { if (gAudioCurrentDevice == -1) { return; } for (auto& vehicleSound : gVehicleSoundList) { if (vehicleSound.id != SOUND_ID_NULL) { vehicleSound.id = SOUND_ID_NULL; if (vehicleSound.sound1_id != SoundId::Null) { Mixer_Stop_Channel(vehicleSound.sound1_channel); } if (vehicleSound.sound2_id != SoundId::Null) { Mixer_Stop_Channel(vehicleSound.sound2_channel); } } } }