1 /***************************************************************************** 2 * Copyright (c) 2014-2020 OpenRCT2 developers 3 * 4 * For a complete list of all authors, please refer to contributors.md 5 * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 6 * 7 * OpenRCT2 is licensed under the GNU General Public License version 3. 8 *****************************************************************************/ 9 10 #include "RideAudio.h" 11 12 #include "../Context.h" 13 #include "../OpenRCT2.h" 14 #include "../audio/AudioMixer.h" 15 #include "../audio/audio.h" 16 #include "../config/Config.h" 17 #include "../object/MusicObject.h" 18 #include "../object/ObjectManager.h" 19 #include "Ride.h" 20 21 #include <algorithm> 22 #include <vector> 23 24 using namespace OpenRCT2; 25 using namespace OpenRCT2::Audio; 26 27 namespace OpenRCT2::RideAudio 28 { 29 constexpr uint8_t TUNE_ID_NULL = 0xFF; 30 constexpr size_t MAX_RIDE_MUSIC_CHANNELS = 32; 31 32 /** 33 * Represents a particular instance of ride music that can be heard in a viewport. 34 * These are created each frame via enumerating each ride / viewport. 35 */ 36 struct ViewportRideMusicInstance 37 { 38 ride_id_t RideId; 39 uint8_t TrackIndex{}; 40 41 size_t Offset{}; 42 int16_t Volume{}; 43 int16_t Pan{}; 44 uint16_t Frequency{}; 45 }; 46 47 /** 48 * Represents an audio channel to play a particular ride's music track. 49 */ 50 struct RideMusicChannel 51 { 52 ride_id_t RideId{}; 53 uint8_t TrackIndex{}; 54 55 size_t Offset{}; 56 int16_t Volume{}; 57 int16_t Pan{}; 58 uint16_t Frequency{}; 59 60 void* Channel{}; 61 RideMusicChannelOpenRCT2::RideAudio::RideMusicChannel62 RideMusicChannel(const ViewportRideMusicInstance& instance, void* channel) 63 { 64 RideId = instance.RideId; 65 TrackIndex = instance.TrackIndex; 66 67 Offset = std::max<size_t>(0, instance.Offset - 10000); 68 Volume = instance.Volume; 69 Pan = instance.Pan; 70 Frequency = instance.Frequency; 71 72 Channel = channel; 73 74 Mixer_Channel_SetOffset(channel, Offset); 75 Mixer_Channel_Volume(channel, DStoMixerVolume(Volume)); 76 Mixer_Channel_Pan(channel, DStoMixerPan(Pan)); 77 Mixer_Channel_Rate(channel, DStoMixerRate(Frequency)); 78 } 79 80 RideMusicChannel(const RideMusicChannel&) = delete; 81 RideMusicChannelOpenRCT2::RideAudio::RideMusicChannel82 RideMusicChannel(RideMusicChannel&& src) noexcept 83 { 84 *this = std::move(src); 85 } 86 operator =OpenRCT2::RideAudio::RideMusicChannel87 RideMusicChannel& operator=(RideMusicChannel&& src) noexcept 88 { 89 RideId = src.RideId; 90 TrackIndex = src.TrackIndex; 91 92 Offset = src.Offset; 93 Volume = src.Volume; 94 Pan = src.Pan; 95 Frequency = src.Frequency; 96 97 if (Channel != nullptr) 98 { 99 Mixer_Stop_Channel(Channel); 100 } 101 Channel = src.Channel; 102 src.Channel = nullptr; 103 104 return *this; 105 } 106 ~RideMusicChannelOpenRCT2::RideAudio::RideMusicChannel107 ~RideMusicChannel() 108 { 109 if (Channel != nullptr) 110 { 111 Mixer_Stop_Channel(Channel); 112 Channel = nullptr; 113 } 114 } 115 IsPlayingOpenRCT2::RideAudio::RideMusicChannel116 bool IsPlaying() const 117 { 118 if (Channel != nullptr) 119 { 120 return Mixer_Channel_IsPlaying(Channel); 121 } 122 return false; 123 } 124 GetOffsetOpenRCT2::RideAudio::RideMusicChannel125 size_t GetOffset() const 126 { 127 if (Channel != nullptr) 128 { 129 return Mixer_Channel_GetOffset(Channel); 130 } 131 return 0; 132 } 133 UpdateOpenRCT2::RideAudio::RideMusicChannel134 void Update(const ViewportRideMusicInstance& instance) 135 { 136 if (Volume != instance.Volume) 137 { 138 Volume = instance.Volume; 139 if (Channel != nullptr) 140 { 141 Mixer_Channel_Volume(Channel, DStoMixerVolume(Volume)); 142 } 143 } 144 if (Pan != instance.Pan) 145 { 146 Pan = instance.Pan; 147 if (Channel != nullptr) 148 { 149 Mixer_Channel_Pan(Channel, DStoMixerPan(Pan)); 150 } 151 } 152 if (Frequency != instance.Frequency) 153 { 154 Frequency = instance.Frequency; 155 if (Channel != nullptr) 156 { 157 Mixer_Channel_Rate(Channel, DStoMixerRate(Frequency)); 158 } 159 } 160 } 161 }; 162 163 static std::vector<ViewportRideMusicInstance> _musicInstances; 164 static std::vector<RideMusicChannel> _musicChannels; 165 StopAllChannels()166 void StopAllChannels() 167 { 168 _musicChannels.clear(); 169 } 170 ClearAllViewportInstances()171 void ClearAllViewportInstances() 172 { 173 _musicInstances.clear(); 174 } 175 StartRideMusicChannel(const ViewportRideMusicInstance & instance)176 static void StartRideMusicChannel(const ViewportRideMusicInstance& instance) 177 { 178 // Create new music channel 179 auto ride = get_ride(instance.RideId); 180 if (ride->type == RIDE_TYPE_CIRCUS) 181 { 182 auto channel = Mixer_Play_Music(PATH_ID_CSS24, MIXER_LOOP_NONE, true); 183 if (channel != nullptr) 184 { 185 // Move circus music to the sound mixer group 186 Mixer_Channel_SetGroup(channel, Audio::MixerGroup::Sound); 187 188 _musicChannels.emplace_back(instance, channel); 189 } 190 } 191 else 192 { 193 auto& objManager = GetContext()->GetObjectManager(); 194 auto musicObj = static_cast<MusicObject*>(objManager.GetLoadedObject(ObjectType::Music, ride->music)); 195 if (musicObj != nullptr) 196 { 197 auto track = musicObj->GetTrack(instance.TrackIndex); 198 if (track != nullptr) 199 { 200 auto stream = track->Asset.GetStream(); 201 auto channel = Mixer_Play_Music(std::move(stream), MIXER_LOOP_NONE); 202 if (channel != nullptr) 203 { 204 _musicChannels.emplace_back(instance, channel); 205 } 206 } 207 } 208 } 209 } 210 StopInactiveRideMusicChannels()211 static void StopInactiveRideMusicChannels() 212 { 213 _musicChannels.erase( 214 std::remove_if( 215 _musicChannels.begin(), _musicChannels.end(), 216 [](const auto& channel) { 217 auto found = std::any_of(_musicInstances.begin(), _musicInstances.end(), [&channel](const auto& instance) { 218 return instance.RideId == channel.RideId && instance.TrackIndex == channel.TrackIndex; 219 }); 220 if (!found || !channel.IsPlaying()) 221 { 222 return true; 223 } 224 225 return false; 226 }), 227 _musicChannels.end()); 228 } 229 UpdateRideMusicChannelForMusicParams(const ViewportRideMusicInstance & instance)230 static void UpdateRideMusicChannelForMusicParams(const ViewportRideMusicInstance& instance) 231 { 232 // Find existing music channel 233 auto foundChannel = std::find_if( 234 _musicChannels.begin(), _musicChannels.end(), [&instance](const RideMusicChannel& channel) { 235 return channel.RideId == instance.RideId && channel.TrackIndex == instance.TrackIndex; 236 }); 237 238 if (foundChannel != _musicChannels.end()) 239 { 240 foundChannel->Update(instance); 241 } 242 else if (_musicChannels.size() < MAX_RIDE_MUSIC_CHANNELS) 243 { 244 StartRideMusicChannel(instance); 245 } 246 } 247 248 /** 249 * Start, update and stop audio channels for each ride music instance that can be heard across all viewports. 250 */ UpdateMusicChannels()251 void UpdateMusicChannels() 252 { 253 if ((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) != 0 || (gScreenFlags & SCREEN_FLAGS_TITLE_DEMO) != 0) 254 return; 255 256 // TODO Allow circus music (CSS24) to play if ride music is disabled (that should be sound) 257 if (gGameSoundsOff || !gConfigSound.ride_music_enabled) 258 return; 259 260 StopInactiveRideMusicChannels(); 261 for (const auto& instance : _musicInstances) 262 { 263 UpdateRideMusicChannelForMusicParams(instance); 264 } 265 } 266 RideMusicGetTrackOffsetLength(const Ride & ride)267 static std::pair<size_t, size_t> RideMusicGetTrackOffsetLength(const Ride& ride) 268 { 269 if (ride.type == RIDE_TYPE_CIRCUS) 270 { 271 return { 1378, 12427456 }; 272 } 273 274 auto& objManager = GetContext()->GetObjectManager(); 275 auto musicObj = static_cast<MusicObject*>(objManager.GetLoadedObject(ObjectType::Music, ride.music)); 276 if (musicObj != nullptr) 277 { 278 auto numTracks = musicObj->GetTrackCount(); 279 if (ride.music_tune_id < numTracks) 280 { 281 auto track = musicObj->GetTrack(ride.music_tune_id); 282 return { track->BytesPerTick, track->Size }; 283 } 284 } 285 return { 0, 0 }; 286 } 287 RideUpdateMusicPosition(Ride & ride)288 static void RideUpdateMusicPosition(Ride& ride) 289 { 290 auto [trackOffset, trackLength] = RideMusicGetTrackOffsetLength(ride); 291 auto position = ride.music_position + trackOffset; 292 if (position < trackLength) 293 { 294 ride.music_position = position; 295 } 296 else 297 { 298 ride.music_tune_id = TUNE_ID_NULL; 299 ride.music_position = 0; 300 } 301 } 302 RideUpdateMusicPosition(Ride & ride,size_t offset,size_t length,int16_t volume,int16_t pan,uint16_t sampleRate)303 static void RideUpdateMusicPosition( 304 Ride& ride, size_t offset, size_t length, int16_t volume, int16_t pan, uint16_t sampleRate) 305 { 306 if (offset < length) 307 { 308 if (_musicInstances.size() < MAX_RIDE_MUSIC_CHANNELS) 309 { 310 auto& instance = _musicInstances.emplace_back(); 311 instance.RideId = ride.id; 312 instance.TrackIndex = ride.music_tune_id; 313 instance.Offset = offset; 314 instance.Volume = volume; 315 instance.Pan = pan; 316 instance.Frequency = sampleRate; 317 } 318 ride.music_position = static_cast<uint32_t>(offset); 319 } 320 else 321 { 322 ride.music_tune_id = TUNE_ID_NULL; 323 ride.music_position = 0; 324 } 325 } 326 RideUpdateMusicPosition(Ride & ride,int16_t volume,int16_t pan,uint16_t sampleRate)327 static void RideUpdateMusicPosition(Ride& ride, int16_t volume, int16_t pan, uint16_t sampleRate) 328 { 329 auto foundChannel = std::find_if(_musicChannels.begin(), _musicChannels.end(), [&ride](const auto& channel) { 330 return channel.RideId == ride.id && channel.TrackIndex == ride.music_tune_id; 331 }); 332 333 auto [trackOffset, trackLength] = RideMusicGetTrackOffsetLength(ride); 334 if (foundChannel != _musicChannels.end()) 335 { 336 if (foundChannel->IsPlaying()) 337 { 338 // Since we have a real music channel, use the offset from that 339 auto newOffset = foundChannel->GetOffset(); 340 RideUpdateMusicPosition(ride, newOffset, trackLength, volume, pan, sampleRate); 341 } 342 else 343 { 344 // We had a real music channel, but it isn't playing anymore, so stop the track 345 ride.music_position = 0; 346 ride.music_tune_id = TUNE_ID_NULL; 347 } 348 } 349 else 350 { 351 // We do not have a real music channel, so simulate the playing of the music track 352 auto newOffset = ride.music_position + trackOffset; 353 RideUpdateMusicPosition(ride, newOffset, trackLength, volume, pan, sampleRate); 354 } 355 } 356 CalculateVolume(int32_t pan)357 static uint8_t CalculateVolume(int32_t pan) 358 { 359 uint8_t result = 255; 360 int32_t v = std::min(std::abs(pan), 6143) - 2048; 361 if (v > 0) 362 { 363 v = -((v / 4) - 1024) / 4; 364 result = static_cast<uint8_t>(std::clamp(v, 0, 255)); 365 } 366 return result; 367 } 368 369 /** 370 * Register an instance of audible ride music for this frame at the given coordinates. 371 */ UpdateMusicInstance(Ride & ride,const CoordsXYZ & rideCoords,uint16_t sampleRate)372 void UpdateMusicInstance(Ride& ride, const CoordsXYZ& rideCoords, uint16_t sampleRate) 373 { 374 if (!(gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) && !gGameSoundsOff && g_music_tracking_viewport != nullptr) 375 { 376 auto rotatedCoords = translate_3d_to_2d_with_z(get_current_rotation(), rideCoords); 377 auto viewport = g_music_tracking_viewport; 378 auto viewWidth = viewport->view_width; 379 auto viewWidth2 = viewWidth * 2; 380 auto viewX = viewport->viewPos.x - viewWidth2; 381 auto viewY = viewport->viewPos.y - viewWidth; 382 auto viewX2 = viewWidth2 + viewWidth2 + viewport->view_width + viewX; 383 auto viewY2 = viewWidth + viewWidth + viewport->view_height + viewY; 384 if (viewX >= rotatedCoords.x || viewY >= rotatedCoords.y || viewX2 < rotatedCoords.x || viewY2 < rotatedCoords.y) 385 { 386 RideUpdateMusicPosition(ride); 387 } 388 else 389 { 390 auto x2 = (viewport->pos.x + ((rotatedCoords.x - viewport->viewPos.x) / viewport->zoom)) * 0x10000; 391 auto screenWidth = std::max(context_get_width(), 64); 392 auto panX = ((x2 / screenWidth) - 0x8000) >> 4; 393 394 auto y2 = (viewport->pos.y + ((rotatedCoords.y - viewport->viewPos.y) / viewport->zoom)) * 0x10000; 395 auto screenHeight = std::max(context_get_height(), 64); 396 auto panY = ((y2 / screenHeight) - 0x8000) >> 4; 397 398 auto volX = CalculateVolume(panX); 399 auto volY = CalculateVolume(panY); 400 auto volXY = std::min(volX, volY); 401 if (volXY < gVolumeAdjustZoom * 3) 402 { 403 volXY = 0; 404 } 405 else 406 { 407 volXY = volXY - (gVolumeAdjustZoom * 3); 408 } 409 410 int16_t newVolume = -((static_cast<uint8_t>(-volXY - 1) * static_cast<uint8_t>(-volXY - 1)) / 16) - 700; 411 if (volXY != 0 && newVolume >= -4000) 412 { 413 auto newPan = std::clamp(panX, -10000, 10000); 414 RideUpdateMusicPosition(ride, newVolume, newPan, sampleRate); 415 } 416 else 417 { 418 RideUpdateMusicPosition(ride); 419 } 420 } 421 } 422 } 423 } // namespace OpenRCT2::RideAudio 424