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 "AudioContext.h"
11 #include "AudioFormat.h"
12 
13 #include <SDL.h>
14 #include <algorithm>
15 #include <openrct2/audio/AudioSource.h>
16 #include <openrct2/common.h>
17 #include <vector>
18 
19 namespace OpenRCT2::Audio
20 {
21     /**
22      * An audio source where raw PCM data is initially loaded into RAM from
23      * a file and then streamed.
24      */
25     class MemoryAudioSource final : public ISDLAudioSource
26     {
27     private:
28         AudioFormat _format = {};
29         std::vector<uint8_t> _data;
30         uint8_t* _dataSDL = nullptr;
31         size_t _length = 0;
32 
GetData()33         const uint8_t* GetData()
34         {
35             return _dataSDL != nullptr ? _dataSDL : _data.data();
36         }
37 
38     public:
~MemoryAudioSource()39         ~MemoryAudioSource() override
40         {
41             Unload();
42         }
43 
GetLength() const44         [[nodiscard]] uint64_t GetLength() const override
45         {
46             return _length;
47         }
48 
GetFormat() const49         [[nodiscard]] AudioFormat GetFormat() const override
50         {
51             return _format;
52         }
53 
Read(void * dst,uint64_t offset,size_t len)54         size_t Read(void* dst, uint64_t offset, size_t len) override
55         {
56             size_t bytesToRead = 0;
57             if (offset < _length)
58             {
59                 bytesToRead = static_cast<size_t>(std::min<uint64_t>(len, _length - offset));
60 
61                 auto src = GetData();
62                 if (src != nullptr)
63                 {
64                     std::copy_n(src + offset, bytesToRead, reinterpret_cast<uint8_t*>(dst));
65                 }
66             }
67             return bytesToRead;
68         }
69 
LoadWAV(const utf8 * path)70         bool LoadWAV(const utf8* path)
71         {
72             log_verbose("MemoryAudioSource::LoadWAV(%s)", path);
73 
74             Unload();
75 
76             bool result = false;
77             SDL_RWops* rw = SDL_RWFromFile(path, "rb");
78             if (rw != nullptr)
79             {
80                 SDL_AudioSpec audiospec = {};
81                 uint32_t audioLen;
82                 SDL_AudioSpec* spec = SDL_LoadWAV_RW(rw, false, &audiospec, &_dataSDL, &audioLen);
83                 if (spec != nullptr)
84                 {
85                     _format.freq = spec->freq;
86                     _format.format = spec->format;
87                     _format.channels = spec->channels;
88                     _length = audioLen;
89                     result = true;
90                 }
91                 else
92                 {
93                     log_verbose("Error loading %s, unsupported WAV format", path);
94                 }
95                 SDL_RWclose(rw);
96             }
97             else
98             {
99                 log_verbose("Error loading %s", path);
100             }
101             return result;
102         }
103 
LoadCSS1(const utf8 * path,size_t index)104         bool LoadCSS1(const utf8* path, size_t index)
105         {
106             log_verbose("MemoryAudioSource::LoadCSS1(%s, %d)", path, index);
107 
108             Unload();
109 
110             bool result = false;
111             SDL_RWops* rw = SDL_RWFromFile(path, "rb");
112             if (rw != nullptr)
113             {
114                 uint32_t numSounds{};
115                 SDL_RWread(rw, &numSounds, sizeof(numSounds), 1);
116                 if (index < numSounds)
117                 {
118                     SDL_RWseek(rw, index * 4, RW_SEEK_CUR);
119 
120                     uint32_t pcmOffset{};
121                     SDL_RWread(rw, &pcmOffset, sizeof(pcmOffset), 1);
122                     SDL_RWseek(rw, pcmOffset, RW_SEEK_SET);
123 
124                     uint32_t pcmSize{};
125                     SDL_RWread(rw, &pcmSize, sizeof(pcmSize), 1);
126                     _length = pcmSize;
127 
128                     WaveFormatEx waveFormat{};
129                     SDL_RWread(rw, &waveFormat, sizeof(waveFormat), 1);
130                     _format.freq = waveFormat.frequency;
131                     _format.format = AUDIO_S16LSB;
132                     _format.channels = waveFormat.channels;
133 
134                     try
135                     {
136                         _data.resize(_length);
137                         SDL_RWread(rw, _data.data(), _length, 1);
138                         result = true;
139                     }
140                     catch (const std::bad_alloc&)
141                     {
142                         log_verbose("Unable to allocate data");
143                     }
144                 }
145                 SDL_RWclose(rw);
146             }
147             else
148             {
149                 log_verbose("Unable to load %s", path);
150             }
151             return result;
152         }
153 
Convert(const AudioFormat * format)154         bool Convert(const AudioFormat* format)
155         {
156             if (*format != _format)
157             {
158                 SDL_AudioCVT cvt;
159                 if (SDL_BuildAudioCVT(
160                         &cvt, _format.format, _format.channels, _format.freq, format->format, format->channels, format->freq)
161                     >= 0)
162                 {
163                     auto src = GetData();
164                     auto cvtBuffer = std::vector<uint8_t>(_length * cvt.len_mult);
165                     std::copy_n(src, _length, cvtBuffer.data());
166                     cvt.len = static_cast<int32_t>(_length);
167                     cvt.buf = cvtBuffer.data();
168                     if (SDL_ConvertAudio(&cvt) >= 0)
169                     {
170                         cvtBuffer.resize(cvt.len_cvt);
171 
172                         Unload();
173                         _data = std::move(cvtBuffer);
174                         _length = cvt.len_cvt;
175                         _format = *format;
176                         return true;
177                     }
178                 }
179             }
180             return false;
181         }
182 
183     private:
Unload()184         void Unload()
185         {
186             // Free our data
187             _data.clear();
188             _data.shrink_to_fit();
189 
190             // Free SDL2's data
191             SDL_FreeWAV(_dataSDL);
192             _dataSDL = nullptr;
193 
194             _length = 0;
195         }
196     };
197 
CreateMemoryFromCSS1(const std::string & path,size_t index,const AudioFormat * targetFormat)198     IAudioSource* AudioSource::CreateMemoryFromCSS1(const std::string& path, size_t index, const AudioFormat* targetFormat)
199     {
200         auto source = new MemoryAudioSource();
201         if (source->LoadCSS1(path.c_str(), index))
202         {
203             if (targetFormat != nullptr && source->GetFormat() != *targetFormat)
204             {
205                 if (!source->Convert(targetFormat))
206                 {
207                     delete source;
208                     source = nullptr;
209                 }
210             }
211         }
212         else
213         {
214             delete source;
215             source = nullptr;
216         }
217         return source;
218     }
219 
CreateMemoryFromWAV(const std::string & path,const AudioFormat * targetFormat)220     IAudioSource* AudioSource::CreateMemoryFromWAV(const std::string& path, const AudioFormat* targetFormat)
221     {
222         auto source = new MemoryAudioSource();
223         if (source->LoadWAV(path.c_str()))
224         {
225             if (targetFormat != nullptr && source->GetFormat() != *targetFormat)
226             {
227                 if (!source->Convert(targetFormat))
228                 {
229                     SafeDelete(source);
230                 }
231             }
232         }
233         else
234         {
235             delete source;
236             source = nullptr;
237         }
238         return source;
239     }
240 } // namespace OpenRCT2::Audio
241