1 /*
2 ** i_music.cpp
3 ** Plays music
4 **
5 **---------------------------------------------------------------------------
6 ** Copyright 1998-2016 Randy Heit
7 ** Copyright 2005-2019 Christoph Oelckers
8 ** All rights reserved.
9 **
10 ** Redistribution and use in source and binary forms, with or without
11 ** modification, are permitted provided that the following conditions
12 ** are met:
13 **
14 ** 1. Redistributions of source code must retain the above copyright
15 ** notice, this list of conditions and the following disclaimer.
16 ** 2. Redistributions in binary form must reproduce the above copyright
17 ** notice, this list of conditions and the following disclaimer in the
18 ** documentation and/or other materials provided with the distribution.
19 ** 3. The name of the author may not be used to endorse or promote products
20 ** derived from this software without specific prior written permission.
21 **
22 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
23 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
24 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
25 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
26 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
27 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
31 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 **---------------------------------------------------------------------------
33 **
34 */
35
36 #include <stdint.h>
37 #include <vector>
38 #include <string>
39 #include <zlib.h>
40 #include "m_swap.h"
41 #include "zmusic_internal.h"
42 #include "midiconfig.h"
43 #include "musinfo.h"
44 #include "streamsources/streamsource.h"
45 #include "midisources/midisource.h"
46 #include "critsec.h"
47
48 #define GZIP_ID1 31
49 #define GZIP_ID2 139
50 #define GZIP_CM 8
51 #define GZIP_ID MAKE_ID(GZIP_ID1,GZIP_ID2,GZIP_CM,0)
52
53 #define GZIP_FTEXT 1
54 #define GZIP_FHCRC 2
55 #define GZIP_FEXTRA 4
56 #define GZIP_FNAME 8
57 #define GZIP_FCOMMENT 16
58
59 class MIDIDevice;
60 class OPLmusicFile;
61 class StreamSource;
62 class MusInfo;
63
64 MusInfo *OpenStreamSong(StreamSource *source);
65 const char *GME_CheckFormat(uint32_t header);
66 MusInfo* CDDA_OpenSong(MusicIO::FileInterface* reader);
67 MusInfo* CD_OpenSong(int track, int id);
68 MusInfo* CreateMIDIStreamer(MIDISource *source, EMidiDevice devtype, const char* args);
69
70 //==========================================================================
71 //
72 // ungzip
73 //
74 // VGZ files are compressed with gzip, so we need to uncompress them before
75 // handing them to GME.
76 //
77 //==========================================================================
78
ungzip(uint8_t * data,int complen,std::vector<uint8_t> & newdata)79 static bool ungzip(uint8_t *data, int complen, std::vector<uint8_t> &newdata)
80 {
81 const uint8_t *max = data + complen - 8;
82 const uint8_t *compstart = data + 10;
83 uint8_t flags = data[3];
84 unsigned isize;
85 z_stream stream;
86 int err;
87
88 // Find start of compressed data stream
89 if (flags & GZIP_FEXTRA)
90 {
91 compstart += 2 + LittleShort(*(uint16_t *)(data + 10));
92 }
93 if (flags & GZIP_FNAME)
94 {
95 while (compstart < max && *compstart != 0)
96 {
97 compstart++;
98 }
99 }
100 if (flags & GZIP_FCOMMENT)
101 {
102 while (compstart < max && *compstart != 0)
103 {
104 compstart++;
105 }
106 }
107 if (flags & GZIP_FHCRC)
108 {
109 compstart += 2;
110 }
111 if (compstart >= max - 1)
112 {
113 return false;
114 }
115
116 // Decompress
117 isize = LittleLong(*(uint32_t *)(data + complen - 4));
118 newdata.resize(isize);
119
120 stream.next_in = (Bytef *)compstart;
121 stream.avail_in = (uInt)(max - compstart);
122 stream.next_out = &newdata[0];
123 stream.avail_out = isize;
124 stream.zalloc = (alloc_func)0;
125 stream.zfree = (free_func)0;
126
127 err = inflateInit2(&stream, -MAX_WBITS);
128 if (err != Z_OK)
129 {
130 return false;
131 }
132 err = inflate(&stream, Z_FINISH);
133 if (err != Z_STREAM_END)
134 {
135 inflateEnd(&stream);
136 return false;
137 }
138 err = inflateEnd(&stream);
139 if (err != Z_OK)
140 {
141 return false;
142 }
143 return true;
144 }
145
146
147 //==========================================================================
148 //
149 // identify a music lump's type and set up a player for it
150 //
151 //==========================================================================
152
ZMusic_OpenSongInternal(MusicIO::FileInterface * reader,EMidiDevice device,const char * Args)153 static MusInfo *ZMusic_OpenSongInternal (MusicIO::FileInterface *reader, EMidiDevice device, const char *Args)
154 {
155 MusInfo *info = nullptr;
156 StreamSource *streamsource = nullptr;
157 const char *fmt;
158 uint32_t id[32/4];
159
160 if(reader->read(id, 32) != 32 || reader->seek(-32, SEEK_CUR) != 0)
161 {
162 SetError("Unable to read header");
163 reader->close();
164 return nullptr;
165 }
166 try
167 {
168 // Check for gzip compression. Some formats are expected to have players
169 // that can handle it, so it simplifies things if we make all songs
170 // gzippable.
171 if ((id[0] & MAKE_ID(255, 255, 255, 0)) == GZIP_ID)
172 {
173 // swap out the reader with one that reads the decompressed content.
174 auto zreader = new MusicIO::VectorReader([reader](std::vector<uint8_t>& array)
175 {
176 bool res = false;
177 auto len = reader->filelength();
178 uint8_t* gzipped = new uint8_t[len];
179 if (reader->read(gzipped, len) == len)
180 {
181 res = ungzip(gzipped, (int)len, array);
182 }
183 delete[] gzipped;
184 });
185 reader->close();
186 reader = zreader;
187
188
189 if (reader->read(id, 32) != 32 || reader->seek(-32, SEEK_CUR) != 0)
190 {
191 reader->close();
192 return nullptr;
193 }
194 }
195
196 EMIDIType miditype = ZMusic_IdentifyMIDIType(id, sizeof(id));
197 if (miditype != MIDI_NOTMIDI)
198 {
199 std::vector<uint8_t> data(reader->filelength());
200 if (reader->read(data.data(), (long)data.size()) != (long)data.size())
201 {
202 SetError("Failed to read MIDI data");
203 reader->close();
204 return nullptr;
205 }
206 auto source = ZMusic_CreateMIDISource(data.data(), data.size(), miditype);
207 if (source == nullptr)
208 {
209 reader->close();
210 return nullptr;
211 }
212 if (!source->isValid())
213 {
214 SetError("Invalid data in MIDI file");
215 delete source;
216 return nullptr;
217 }
218
219 #ifndef HAVE_SYSTEM_MIDI
220 // some platforms don't support MDEV_STANDARD so map to MDEV_SNDSYS
221 if (device == MDEV_STANDARD)
222 device = MDEV_SNDSYS;
223 #endif
224
225 info = CreateMIDIStreamer(source, device, Args? Args : "");
226 }
227
228 // Check for CDDA "format"
229 else if ((id[0] == MAKE_ID('R', 'I', 'F', 'F') && id[2] == MAKE_ID('C', 'D', 'D', 'A')))
230 {
231 // This is a CDDA file
232 info = CDDA_OpenSong(reader);
233 }
234
235 // Check for various raw OPL formats
236 else
237 {
238 #ifdef HAVE_OPL
239 if (
240 (id[0] == MAKE_ID('R', 'A', 'W', 'A') && id[1] == MAKE_ID('D', 'A', 'T', 'A')) || // Rdos Raw OPL
241 (id[0] == MAKE_ID('D', 'B', 'R', 'A') && id[1] == MAKE_ID('W', 'O', 'P', 'L')) || // DosBox Raw OPL
242 (id[0] == MAKE_ID('A', 'D', 'L', 'I') && *((uint8_t*)id + 4) == 'B')) // Martin Fernandez's modified IMF
243 {
244 streamsource = OPL_OpenSong(reader, &oplConfig);
245
246 }
247 else
248 #endif
249 if ((id[0] == MAKE_ID('R', 'I', 'F', 'F') && id[2] == MAKE_ID('C', 'D', 'X', 'A')))
250 {
251 streamsource = XA_OpenSong(reader); // this takes over the reader.
252 reader = nullptr; // We do not own this anymore.
253 }
254 // Check for game music
255 else if ((fmt = GME_CheckFormat(id[0])) != nullptr && fmt[0] != '\0')
256 {
257 streamsource = GME_OpenSong(reader, fmt, miscConfig.snd_outputrate);
258 }
259 // Check for module formats
260 else
261 {
262 streamsource = MOD_OpenSong(reader, miscConfig.snd_outputrate);
263 }
264 if (streamsource == nullptr)
265 {
266 streamsource = SndFile_OpenSong(reader); // this only takes over the reader if it succeeds. We need to look out for this.
267 if (streamsource != nullptr) reader = nullptr;
268 }
269
270 if (streamsource)
271 {
272 info = OpenStreamSong(streamsource);
273 }
274 }
275
276 if (!info)
277 {
278 // File could not be identified as music.
279 if (reader) reader->close();
280 SetError("Unable to identify as music");
281 return nullptr;
282 }
283
284 if (info && !info->IsValid())
285 {
286 delete info;
287 SetError("Unable to identify as music");
288 info = nullptr;
289 }
290 if (reader) reader->close();
291 return info;
292 }
293 catch (const std::exception &ex)
294 {
295 // Make sure the reader is closed if this function abnormally terminates
296 if (reader) reader->close();
297 SetError(ex.what());
298 return nullptr;
299 }
300 }
301
ZMusic_OpenSongFile(const char * filename,EMidiDevice device,const char * Args)302 DLL_EXPORT ZMusic_MusicStream ZMusic_OpenSongFile(const char* filename, EMidiDevice device, const char* Args)
303 {
304 auto f = MusicIO::utf8_fopen(filename, "rb");
305 if (!f)
306 {
307 SetError("File not found");
308 return nullptr;
309 }
310 auto fr = new MusicIO::StdioFileReader;
311 fr->f = f;
312 return ZMusic_OpenSongInternal(fr, device, Args);
313 }
314
ZMusic_OpenSongMem(const void * mem,size_t size,EMidiDevice device,const char * Args)315 DLL_EXPORT ZMusic_MusicStream ZMusic_OpenSongMem(const void* mem, size_t size, EMidiDevice device, const char* Args)
316 {
317 if (!mem || !size)
318 {
319 SetError("Invalid data");
320 return nullptr;
321 }
322 // Data must be copied because it may be used as a streaming source and we cannot guarantee that the client memory stays valid. We also have no means to free it.
323 auto mr = new MusicIO::VectorReader((uint8_t*)mem, (long)size);
324 return ZMusic_OpenSongInternal(mr, device, Args);
325 }
326
ZMusic_OpenSong(ZMusicCustomReader * reader,EMidiDevice device,const char * Args)327 DLL_EXPORT ZMusic_MusicStream ZMusic_OpenSong(ZMusicCustomReader* reader, EMidiDevice device, const char* Args)
328 {
329 if (!reader)
330 {
331 SetError("No reader protocol specified");
332 return nullptr;
333 }
334 auto cr = new CustomFileReader(reader); // Oh no! We just put another wrapper around the client's wrapper!
335 return ZMusic_OpenSongInternal(cr, device, Args);
336 }
337
338
339 //==========================================================================
340 //
341 // play CD music
342 //
343 //==========================================================================
344
ZMusic_OpenCDSong(int track,int id)345 DLL_EXPORT MusInfo *ZMusic_OpenCDSong (int track, int id)
346 {
347 MusInfo *info = CD_OpenSong (track, id);
348
349 if (info && !info->IsValid ())
350 {
351 delete info;
352 info = nullptr;
353 SetError("Unable to open CD Audio");
354 }
355
356 return info;
357 }
358
359 //==========================================================================
360 //
361 // streaming callback
362 //
363 //==========================================================================
364
ZMusic_FillStream(MusInfo * song,void * buff,int len)365 DLL_EXPORT zmusic_bool ZMusic_FillStream(MusInfo* song, void* buff, int len)
366 {
367 if (song == nullptr) return false;
368 std::lock_guard<FCriticalSection> lock(song->CritSec);
369 return song->ServiceStream(buff, len);
370 }
371
372 //==========================================================================
373 //
374 // starts playback
375 //
376 //==========================================================================
377
ZMusic_Start(MusInfo * song,int subsong,zmusic_bool loop)378 DLL_EXPORT zmusic_bool ZMusic_Start(MusInfo *song, int subsong, zmusic_bool loop)
379 {
380 if (!song) return true; // Starting a null song is not an error! It just won't play anything.
381 try
382 {
383 song->Play(loop, subsong);
384 return true;
385 }
386 catch (const std::exception & ex)
387 {
388 SetError(ex.what());
389 return false;
390 }
391 }
392
393 //==========================================================================
394 //
395 // Utilities
396 //
397 //==========================================================================
398
ZMusic_Pause(MusInfo * song)399 DLL_EXPORT void ZMusic_Pause(MusInfo *song)
400 {
401 if (!song) return;
402 song->Pause();
403 }
404
ZMusic_Resume(MusInfo * song)405 DLL_EXPORT void ZMusic_Resume(MusInfo *song)
406 {
407 if (!song) return;
408 song->Resume();
409 }
410
ZMusic_Update(MusInfo * song)411 DLL_EXPORT void ZMusic_Update(MusInfo *song)
412 {
413 if (!song) return;
414 song->Update();
415 }
416
ZMusic_IsPlaying(MusInfo * song)417 DLL_EXPORT zmusic_bool ZMusic_IsPlaying(MusInfo *song)
418 {
419 if (!song) return false;
420 return song->IsPlaying();
421 }
422
ZMusic_Stop(MusInfo * song)423 DLL_EXPORT void ZMusic_Stop(MusInfo *song)
424 {
425 if (!song) return;
426 std::lock_guard<FCriticalSection> lock(song->CritSec);
427 song->Stop();
428 }
429
ZMusic_SetSubsong(MusInfo * song,int subsong)430 DLL_EXPORT zmusic_bool ZMusic_SetSubsong(MusInfo *song, int subsong)
431 {
432 if (!song) return false;
433 std::lock_guard<FCriticalSection> lock(song->CritSec);
434 return song->SetSubsong(subsong);
435 }
436
ZMusic_IsLooping(MusInfo * song)437 DLL_EXPORT zmusic_bool ZMusic_IsLooping(MusInfo *song)
438 {
439 if (!song) return false;
440 return song->m_Looping;
441 }
442
ZMusic_GetDeviceType(MusInfo * song)443 DLL_EXPORT int ZMusic_GetDeviceType(MusInfo* song)
444 {
445 if (!song) return false;
446 return song->GetDeviceType();
447 }
448
ZMusic_IsMIDI(MusInfo * song)449 DLL_EXPORT zmusic_bool ZMusic_IsMIDI(MusInfo *song)
450 {
451 if (!song) return false;
452 return song->IsMIDI();
453 }
454
ZMusic_GetStreamInfo(MusInfo * song,SoundStreamInfo * fmt)455 DLL_EXPORT void ZMusic_GetStreamInfo(MusInfo *song, SoundStreamInfo *fmt)
456 {
457 if (!fmt) return;
458 if (!song) *fmt = {};
459 std::lock_guard<FCriticalSection> lock(song->CritSec);
460 *fmt = song->GetStreamInfo();
461 }
462
ZMusic_Close(MusInfo * song)463 DLL_EXPORT void ZMusic_Close(MusInfo *song)
464 {
465 if (!song) return;
466 delete song;
467 }
468
ZMusic_VolumeChanged(MusInfo * song)469 DLL_EXPORT void ZMusic_VolumeChanged(MusInfo *song)
470 {
471 if (!song) return;
472 std::lock_guard<FCriticalSection> lock(song->CritSec);
473 song->MusicVolumeChanged();
474 }
475
476 static std::string staticErrorMessage;
477
ZMusic_GetStats(MusInfo * song)478 DLL_EXPORT const char *ZMusic_GetStats(MusInfo *song)
479 {
480 if (!song) return "";
481 std::lock_guard<FCriticalSection> lock(song->CritSec);
482 staticErrorMessage = song->GetStats();
483 return staticErrorMessage.c_str();
484 }
485
SetError(const char * msg)486 void SetError(const char* msg)
487 {
488 staticErrorMessage = msg;
489 }
490
ZMusic_GetLastError()491 DLL_EXPORT const char* ZMusic_GetLastError()
492 {
493 return staticErrorMessage.c_str();
494 }
495
ZMusic_WriteSMF(MIDISource * source,const char * fn,int looplimit)496 DLL_EXPORT zmusic_bool ZMusic_WriteSMF(MIDISource* source, const char *fn, int looplimit)
497 {
498 std::vector<uint8_t> midi;
499 bool success;
500
501 if (!source) return false;
502 source->CreateSMF(midi, 1);
503 auto f = MusicIO::utf8_fopen(fn, "wt");
504 if (f == nullptr) return false;
505 success = (fwrite(&midi[0], 1, midi.size(), f) == midi.size());
506 fclose(f);
507 return success;
508 }
509