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