1 // SONIC ROBO BLAST 2
2 //-----------------------------------------------------------------------------
3 // Copyright (C) 2014-2020 by Sonic Team Junior.
4 //
5 // This program is free software distributed under the
6 // terms of the GNU General Public License, version 2.
7 // See the 'LICENSE' file for more details.
8 //-----------------------------------------------------------------------------
9 /// \file
10 /// \brief SDL Mixer interface for sound
11
12 #ifdef HAVE_LIBGME
13 #ifdef HAVE_ZLIB
14 #ifndef _MSC_VER
15 #ifndef _LARGEFILE64_SOURCE
16 #define _LARGEFILE64_SOURCE
17 #endif
18 #endif
19
20 #ifndef _LFS64_LARGEFILE
21 #define _LFS64_LARGEFILE
22 #endif
23
24 #ifndef _FILE_OFFSET_BITS
25 #define _FILE_OFFSET_BITS 0
26 #endif
27
28 #include <zlib.h>
29 #endif // HAVE_ZLIB
30 #endif // HAVE_LIBGME
31
32 #include "../doomdef.h"
33 #include "../doomstat.h" // menuactive
34
35 #if defined(HAVE_SDL) && defined(HAVE_MIXER) && SOUND==SOUND_MIXER
36
37 #include "../sounds.h"
38 #include "../s_sound.h"
39 #include "../i_sound.h"
40 #include "../w_wad.h"
41 #include "../z_zone.h"
42 #include "../byteptr.h"
43
44 #ifdef _MSC_VER
45 #pragma warning(disable : 4214 4244)
46 #endif
47 #include "SDL.h"
48 #ifdef _MSC_VER
49 #pragma warning(default : 4214 4244)
50 #endif
51
52 #ifdef HAVE_MIXERX
53 #include "SDL_mixer_ext.h"
54 #else
55 #include "SDL_mixer.h"
56 #endif
57
58 /* This is the version number macro for the current SDL_mixer version: */
59 #ifndef SDL_MIXER_COMPILEDVERSION
60 #define SDL_MIXER_COMPILEDVERSION \
61 SDL_VERSIONNUM(MIX_MAJOR_VERSION, MIX_MINOR_VERSION, MIX_PATCHLEVEL)
62 #endif
63
64 /* This macro will evaluate to true if compiled with SDL_mixer at least X.Y.Z */
65 #ifndef SDL_MIXER_VERSION_ATLEAST
66 #define SDL_MIXER_VERSION_ATLEAST(X, Y, Z) \
67 (SDL_MIXER_COMPILEDVERSION >= SDL_VERSIONNUM(X, Y, Z))
68 #endif
69
70 // thanks alam for making the buildbots happy!
71 #if SDL_MIXER_VERSION_ATLEAST(2,0,2)
72 #define MUS_MP3_MAD MUS_MP3_MAD_UNUSED
73 #define MUS_MODPLUG MUS_MODPLUG_UNUSED
74 #endif
75
76 #ifdef HAVE_LIBGME
77 #include "gme/gme.h"
78 #define GME_TREBLE 5.0f
79 #define GME_BASS 1.0f
80 #endif // HAVE_LIBGME
81
82 static UINT16 BUFFERSIZE = 2048;
83 static UINT16 SAMPLERATE = 44100;
84
85 #ifdef HAVE_OPENMPT
86 #include "libopenmpt/libopenmpt.h"
87 #endif
88
89 /// ------------------------
90 /// Audio Declarations
91 /// ------------------------
92
93 UINT8 sound_started = false;
94
95 static Mix_Music *music;
96 static UINT8 music_volume, sfx_volume, internal_volume;
97 static float loop_point;
98 static float song_length; // length in seconds
99 static boolean songpaused;
100 static UINT32 music_bytes;
101 static boolean is_looping;
102
103 // fading
104 static boolean is_fading;
105 static UINT8 fading_source;
106 static UINT8 fading_target;
107 static UINT32 fading_timer;
108 static UINT32 fading_duration;
109 static INT32 fading_id;
110 static void (*fading_callback)(void);
111 static boolean fading_nocleanup;
112
113 #ifdef HAVE_LIBGME
114 static Music_Emu *gme;
115 static UINT16 current_track;
116 #endif
117
118 #ifdef HAVE_OPENMPT
119 static int mod_err = OPENMPT_ERROR_OK;
120 static const char *mod_err_str;
121 static UINT16 current_subsong;
122 static size_t probesize;
123 static int result;
124 #endif
125
126 #ifdef HAVE_MIXERX
Midiplayer_Onchange(void)127 static void Midiplayer_Onchange(void)
128 {
129 boolean restart = false;
130
131 if (I_SongType() != MU_NONE && I_SongType() != MU_MID_EX && I_SongType() != MU_MID)
132 return;
133
134 if (Mix_GetMidiPlayer() != cv_midiplayer.value)
135 {
136 if (Mix_SetMidiPlayer(cv_midiplayer.value)) // <> 0 means error
137 CONS_Alert(CONS_ERROR, "Midi player error: %s", Mix_GetError());
138 else
139 restart = true;
140 }
141
142 if (stricmp(Mix_GetSoundFonts(), cv_midisoundfontpath.string))
143 {
144 if (!Mix_SetSoundFonts(cv_midisoundfontpath.string)) // == 0 means error
145 CONS_Alert(CONS_ERROR, "Sound font error: %s", Mix_GetError());
146 else
147 restart = true;
148 }
149
150 Mix_Timidity_addToPathList(cv_miditimiditypath.string);
151
152 if (restart)
153 S_StartEx(true);
154 }
155
MidiSoundfontPath_Onchange(void)156 static void MidiSoundfontPath_Onchange(void)
157 {
158 if (Mix_GetMidiPlayer() != MIDI_Fluidsynth || (I_SongType() != MU_NONE && I_SongType() != MU_MID_EX))
159 return;
160
161 if (stricmp(Mix_GetSoundFonts(), cv_midisoundfontpath.string))
162 {
163 char *miditoken;
164 char *source = strdup(cv_midisoundfontpath.string);
165 boolean proceed = true;
166 // check if file exists; menu calls this method at every keystroke
167
168 // get first token
169 miditoken = strtok(source, ";");
170
171 while (miditoken != NULL)
172 {
173 SDL_RWops *rw = SDL_RWFromFile(miditoken, "r");
174 if (rw != NULL)
175 SDL_RWclose(rw);
176 else
177 {
178 proceed = false;
179 break;
180 }
181 miditoken = strtok(NULL, ";");
182 }
183
184 free(source);
185
186 if (proceed)
187 {
188 if (!Mix_SetSoundFonts(cv_midisoundfontpath.string))
189 CONS_Alert(CONS_ERROR, "Sound font error: %s", Mix_GetError());
190 else
191 S_StartEx(true);
192 }
193 }
194 }
195
196 // make sure that s_sound.c does not already verify these
197 // which happens when: defined(HAVE_MIXERX) && !defined(HAVE_MIXER)
198 static CV_PossibleValue_t midiplayer_cons_t[] = {{MIDI_OPNMIDI, "OPNMIDI"}, {MIDI_Fluidsynth, "Fluidsynth"}, {MIDI_Timidity, "Timidity"}, {MIDI_Native, "Native"}, {0, NULL}};
199 consvar_t cv_midiplayer = CVAR_INIT ("midiplayer", "OPNMIDI" /*MIDI_OPNMIDI*/, CV_CALL|CV_NOINIT|CV_SAVE, midiplayer_cons_t, Midiplayer_Onchange);
200 consvar_t cv_midisoundfontpath = CVAR_INIT ("midisoundfont", "sf2/8bitsf.SF2", CV_CALL|CV_NOINIT|CV_SAVE, NULL, MidiSoundfontPath_Onchange);
201 consvar_t cv_miditimiditypath = CVAR_INIT ("midisoundbank", "./timidity", CV_SAVE, NULL, NULL);
202 #endif
203
var_cleanup(void)204 static void var_cleanup(void)
205 {
206 song_length = loop_point = 0.0f;
207 music_bytes = fading_source = fading_target =\
208 fading_timer = fading_duration = 0;
209
210 songpaused = is_looping =\
211 is_fading = false;
212
213 // HACK: See music_loop, where we want the fade timing to proceed after a non-looping
214 // song has stopped playing
215 if (!fading_nocleanup)
216 fading_callback = NULL;
217 else
218 fading_nocleanup = false; // use it once, set it back immediately
219
220 internal_volume = 100;
221 }
222
223 #if defined (HAVE_LIBGME) && defined (HAVE_ZLIB)
get_zlib_error(int zErr)224 static const char* get_zlib_error(int zErr)
225 {
226 switch (zErr)
227 {
228 case Z_ERRNO:
229 return "Z_ERRNO";
230 case Z_STREAM_ERROR:
231 return "Z_STREAM_ERROR";
232 case Z_DATA_ERROR:
233 return "Z_DATA_ERROR";
234 case Z_MEM_ERROR:
235 return "Z_MEM_ERROR";
236 case Z_BUF_ERROR:
237 return "Z_BUF_ERROR";
238 case Z_VERSION_ERROR:
239 return "Z_VERSION_ERROR";
240 default:
241 return "unknown error";
242 }
243 }
244 #endif
245
246 /// ------------------------
247 /// Audio System
248 /// ------------------------
249
I_StartupSound(void)250 void I_StartupSound(void)
251 {
252 //I_Assert(!sound_started);
253 if (sound_started)
254 return;
255
256 #ifdef _WIN32
257 // Force DirectSound instead of WASAPI
258 // SDL 2.0.6+ defaults to the latter and it screws up our sound effects
259 SDL_setenv("SDL_AUDIODRIVER", "directsound", 1);
260 #endif
261
262 // EE inits audio first so we're following along.
263 if (SDL_WasInit(SDL_INIT_AUDIO) == SDL_INIT_AUDIO)
264 {
265 CONS_Debug(DBG_DETAILED, "SDL Audio already started\n");
266 return;
267 }
268 else if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0)
269 {
270 CONS_Alert(CONS_ERROR, "Error initializing SDL Audio: %s\n", SDL_GetError());
271 // call to start audio failed -- we do not have it
272 return;
273 }
274
275 fading_nocleanup = false;
276
277 var_cleanup();
278
279 music = NULL;
280 music_volume = sfx_volume = 0;
281
282 #ifdef HAVE_MIXERX
283 Mix_SetMidiPlayer(cv_midiplayer.value);
284 Mix_SetSoundFonts(cv_midisoundfontpath.string);
285 Mix_Timidity_addToPathList(cv_miditimiditypath.string);
286 #endif
287 #if SDL_MIXER_VERSION_ATLEAST(1,2,11)
288 Mix_Init(MIX_INIT_FLAC|MIX_INIT_MP3|MIX_INIT_OGG|MIX_INIT_MOD);
289 #endif
290
291 if (Mix_OpenAudio(SAMPLERATE, AUDIO_S16SYS, 2, BUFFERSIZE) < 0)
292 {
293 CONS_Alert(CONS_ERROR, "Error starting SDL_Mixer: %s\n", Mix_GetError());
294 // call to start audio failed -- we do not have it
295 return;
296 }
297
298 #ifdef HAVE_OPENMPT
299 CONS_Printf("libopenmpt version: %s\n", openmpt_get_string("library_version"));
300 CONS_Printf("libopenmpt build date: %s\n", openmpt_get_string("build"));
301 #endif
302
303 sound_started = true;
304 songpaused = false;
305 Mix_AllocateChannels(256);
306 }
307
I_ShutdownSound(void)308 void I_ShutdownSound(void)
309 {
310 if (!sound_started)
311 return; // not an error condition
312 sound_started = false;
313
314 Mix_CloseAudio();
315 #if SDL_MIXER_VERSION_ATLEAST(1,2,11)
316 Mix_Quit();
317 #endif
318
319 SDL_QuitSubSystem(SDL_INIT_AUDIO);
320
321 #ifdef HAVE_LIBGME
322 if (gme)
323 gme_delete(gme);
324 #endif
325 #ifdef HAVE_OPENMPT
326 if (openmpt_mhandle)
327 openmpt_module_destroy(openmpt_mhandle);
328 #endif
329 }
330
I_UpdateSound(void)331 void I_UpdateSound(void)
332 {
333 }
334
335 /// ------------------------
336 /// SFX
337 /// ------------------------
338
339 // this is as fast as I can possibly make it.
340 // sorry. more asm needed.
ds2chunk(void * stream)341 static Mix_Chunk *ds2chunk(void *stream)
342 {
343 UINT16 ver,freq;
344 UINT32 samples, i, newsamples;
345 UINT8 *sound;
346
347 SINT8 *s;
348 INT16 *d;
349 INT16 o;
350 fixed_t step, frac;
351
352 // lump header
353 ver = READUINT16(stream); // sound version format?
354 if (ver != 3) // It should be 3 if it's a doomsound...
355 return NULL; // onos! it's not a doomsound!
356 freq = READUINT16(stream);
357 samples = READUINT32(stream);
358
359 // convert from signed 8bit ???hz to signed 16bit 44100hz.
360 switch(freq)
361 {
362 case 44100:
363 if (samples >= UINT32_MAX>>2)
364 return NULL; // would wrap, can't store.
365 newsamples = samples;
366 break;
367 case 22050:
368 if (samples >= UINT32_MAX>>3)
369 return NULL; // would wrap, can't store.
370 newsamples = samples<<1;
371 break;
372 case 11025:
373 if (samples >= UINT32_MAX>>4)
374 return NULL; // would wrap, can't store.
375 newsamples = samples<<2;
376 break;
377 default:
378 frac = (44100 << FRACBITS) / (UINT32)freq;
379 if (!(frac & 0xFFFF)) // other solid multiples (change if FRACBITS != 16)
380 newsamples = samples * (frac >> FRACBITS);
381 else // strange and unusual fractional frequency steps, plus anything higher than 44100hz.
382 newsamples = FixedMul(FixedDiv(samples, freq), 44100) + 1; // add 1 to counter truncation.
383 if (newsamples >= UINT32_MAX>>2)
384 return NULL; // would and/or did wrap, can't store.
385 break;
386 }
387 sound = Z_Malloc(newsamples<<2, PU_SOUND, NULL); // samples * frequency shift * bytes per sample * channels
388
389 s = (SINT8 *)stream;
390 d = (INT16 *)sound;
391
392 i = 0;
393 switch(freq)
394 {
395 case 44100: // already at the same rate? well that makes it simple.
396 while(i++ < samples)
397 {
398 o = ((INT16)(*s++)+0x80)<<8; // changed signedness and shift up to 16 bits
399 *d++ = o; // left channel
400 *d++ = o; // right channel
401 }
402 break;
403 case 22050: // unwrap 2x
404 while(i++ < samples)
405 {
406 o = ((INT16)(*s++)+0x80)<<8; // changed signedness and shift up to 16 bits
407 *d++ = o; // left channel
408 *d++ = o; // right channel
409 *d++ = o; // left channel
410 *d++ = o; // right channel
411 }
412 break;
413 case 11025: // unwrap 4x
414 while(i++ < samples)
415 {
416 o = ((INT16)(*s++)+0x80)<<8; // changed signedness and shift up to 16 bits
417 *d++ = o; // left channel
418 *d++ = o; // right channel
419 *d++ = o; // left channel
420 *d++ = o; // right channel
421 *d++ = o; // left channel
422 *d++ = o; // right channel
423 *d++ = o; // left channel
424 *d++ = o; // right channel
425 }
426 break;
427 default: // convert arbitrary hz to 44100.
428 step = 0;
429 frac = ((UINT32)freq << FRACBITS) / 44100 + 1; //Add 1 to counter truncation.
430 while (i < samples)
431 {
432 o = (INT16)(*s+0x80)<<8; // changed signedness and shift up to 16 bits
433 while (step < FRACUNIT) // this is as fast as I can make it.
434 {
435 *d++ = o; // left channel
436 *d++ = o; // right channel
437 step += frac;
438 }
439 do {
440 i++; s++;
441 step -= FRACUNIT;
442 } while (step >= FRACUNIT);
443 }
444 break;
445 }
446
447 // return Mixer Chunk.
448 return Mix_QuickLoad_RAW(sound, (Uint32)((UINT8*)d-sound));
449 }
450
I_GetSfx(sfxinfo_t * sfx)451 void *I_GetSfx(sfxinfo_t *sfx)
452 {
453 void *lump;
454 Mix_Chunk *chunk;
455 SDL_RWops *rw;
456 #ifdef HAVE_LIBGME
457 Music_Emu *emu;
458 gme_info_t *info;
459 #endif
460
461 if (sfx->lumpnum == LUMPERROR)
462 sfx->lumpnum = S_GetSfxLumpNum(sfx);
463 sfx->length = W_LumpLength(sfx->lumpnum);
464
465 lump = W_CacheLumpNum(sfx->lumpnum, PU_SOUND);
466
467 // convert from standard DoomSound format.
468 chunk = ds2chunk(lump);
469 if (chunk)
470 {
471 Z_Free(lump);
472 return chunk;
473 }
474
475 // Not a doom sound? Try something else.
476 #ifdef HAVE_LIBGME
477 // VGZ format
478 if (((UINT8 *)lump)[0] == 0x1F
479 && ((UINT8 *)lump)[1] == 0x8B)
480 {
481 #ifdef HAVE_ZLIB
482 UINT8 *inflatedData;
483 size_t inflatedLen;
484 z_stream stream;
485 int zErr; // Somewhere to handle any error messages zlib tosses out
486
487 memset(&stream, 0x00, sizeof (z_stream)); // Init zlib stream
488 // Begin the inflation process
489 inflatedLen = *(UINT32 *)lump + (sfx->length-4); // Last 4 bytes are the decompressed size, typically
490 inflatedData = (UINT8 *)Z_Malloc(inflatedLen, PU_SOUND, NULL); // Make room for the decompressed data
491 stream.total_in = stream.avail_in = sfx->length;
492 stream.total_out = stream.avail_out = inflatedLen;
493 stream.next_in = (UINT8 *)lump;
494 stream.next_out = inflatedData;
495
496 zErr = inflateInit2(&stream, 32 + MAX_WBITS);
497 if (zErr == Z_OK) // We're good to go
498 {
499 zErr = inflate(&stream, Z_FINISH);
500 if (zErr == Z_STREAM_END) {
501 // Run GME on new data
502 if (!gme_open_data(inflatedData, inflatedLen, &emu, SAMPLERATE))
503 {
504 short *mem;
505 UINT32 len;
506 gme_equalizer_t eq = {GME_TREBLE, GME_BASS, 0,0,0,0,0,0,0,0};
507
508 Z_Free(inflatedData); // GME supposedly makes a copy for itself, so we don't need this lying around
509 Z_Free(lump); // We're done with the uninflated lump now, too.
510
511 gme_start_track(emu, 0);
512 gme_set_equalizer(emu, &eq);
513 gme_track_info(emu, &info, 0);
514
515 len = (info->play_length * 441 / 10) << 2;
516 mem = Z_Malloc(len, PU_SOUND, 0);
517 gme_play(emu, len >> 1, mem);
518 gme_free_info(info);
519 gme_delete(emu);
520
521 return Mix_QuickLoad_RAW((Uint8 *)mem, len);
522 }
523 }
524 else
525 CONS_Alert(CONS_ERROR,"Encountered %s when running inflate: %s\n", get_zlib_error(zErr), stream.msg);
526 (void)inflateEnd(&stream);
527 }
528 else // Hold up, zlib's got a problem
529 CONS_Alert(CONS_ERROR,"Encountered %s when running inflateInit: %s\n", get_zlib_error(zErr), stream.msg);
530 Z_Free(inflatedData); // GME didn't open jack, but don't let that stop us from freeing this up
531 #else
532 return NULL; // No zlib support
533 #endif
534 }
535 // Try to read it as a GME sound
536 else if (!gme_open_data(lump, sfx->length, &emu, SAMPLERATE))
537 {
538 short *mem;
539 UINT32 len;
540 gme_equalizer_t eq = {GME_TREBLE, GME_BASS, 0,0,0,0,0,0,0,0};
541
542 Z_Free(lump);
543
544 gme_start_track(emu, 0);
545 gme_set_equalizer(emu, &eq);
546 gme_track_info(emu, &info, 0);
547
548 len = (info->play_length * 441 / 10) << 2;
549 mem = Z_Malloc(len, PU_SOUND, 0);
550 gme_play(emu, len >> 1, mem);
551 gme_free_info(info);
552 gme_delete(emu);
553
554 return Mix_QuickLoad_RAW((Uint8 *)mem, len);
555 }
556 #endif
557
558 // Try to load it as a WAVE or OGG using Mixer.
559 rw = SDL_RWFromMem(lump, sfx->length);
560 if (rw != NULL)
561 {
562 chunk = Mix_LoadWAV_RW(rw, 1);
563 return chunk;
564 }
565
566 return NULL; // haven't been able to get anything
567 }
568
I_FreeSfx(sfxinfo_t * sfx)569 void I_FreeSfx(sfxinfo_t *sfx)
570 {
571 if (sfx->data)
572 {
573 Mix_Chunk *chunk = (Mix_Chunk*)sfx->data;
574 UINT8 *abufdata = NULL;
575 if (chunk->allocated == 0)
576 {
577 // We allocated the data in this chunk, so get the abuf from mixer, then let it free the chunk, THEN we free the data
578 // I believe this should ensure the sound is not playing when we free it
579 abufdata = chunk->abuf;
580 }
581 Mix_FreeChunk(sfx->data);
582 if (abufdata)
583 {
584 // I'm going to assume we used Z_Malloc to allocate this data.
585 Z_Free(abufdata);
586 }
587 }
588 sfx->data = NULL;
589 sfx->lumpnum = LUMPERROR;
590 }
591
I_StartSound(sfxenum_t id,UINT8 vol,UINT8 sep,UINT8 pitch,UINT8 priority,INT32 channel)592 INT32 I_StartSound(sfxenum_t id, UINT8 vol, UINT8 sep, UINT8 pitch, UINT8 priority, INT32 channel)
593 {
594 UINT8 volume = (((UINT16)vol + 1) * (UINT16)sfx_volume) / 62; // (256 * 31) / 62 == 127
595 INT32 handle = Mix_PlayChannel(channel, S_sfx[id].data, 0);
596 Mix_Volume(handle, volume);
597 Mix_SetPanning(handle, min((UINT16)(0xff-sep)<<1, 0xff), min((UINT16)(sep)<<1, 0xff));
598 (void)pitch; // Mixer can't handle pitch
599 (void)priority; // priority and channel management is handled by SRB2...
600 return handle;
601 }
602
I_StopSound(INT32 handle)603 void I_StopSound(INT32 handle)
604 {
605 Mix_HaltChannel(handle);
606 }
607
I_SoundIsPlaying(INT32 handle)608 boolean I_SoundIsPlaying(INT32 handle)
609 {
610 return Mix_Playing(handle);
611 }
612
I_UpdateSoundParams(INT32 handle,UINT8 vol,UINT8 sep,UINT8 pitch)613 void I_UpdateSoundParams(INT32 handle, UINT8 vol, UINT8 sep, UINT8 pitch)
614 {
615 UINT8 volume = (((UINT16)vol + 1) * (UINT16)sfx_volume) / 62; // (256 * 31) / 62 == 127
616 Mix_Volume(handle, volume);
617 Mix_SetPanning(handle, min((UINT16)(0xff-sep)<<1, 0xff), min((UINT16)(sep)<<1, 0xff));
618 (void)pitch;
619 }
620
I_SetSfxVolume(UINT8 volume)621 void I_SetSfxVolume(UINT8 volume)
622 {
623 sfx_volume = volume;
624 }
625
626 /// ------------------------
627 /// Music Utilities
628 /// ------------------------
629
get_real_volume(UINT8 volume)630 static UINT32 get_real_volume(UINT8 volume)
631 {
632 #ifdef _WIN32
633 if (I_SongType() == MU_MID)
634 // HACK: Until we stop using native MIDI,
635 // disable volume changes
636 return ((UINT32)31*128/31); // volume = 31
637 else
638 #endif
639 // convert volume to mixer's 128 scale
640 // then apply internal_volume as a percentage
641 return ((UINT32)volume*128/31) * (UINT32)internal_volume / 100;
642 }
643
get_adjusted_position(UINT32 position)644 static UINT32 get_adjusted_position(UINT32 position)
645 {
646 // all in milliseconds
647 UINT32 length = I_GetSongLength();
648 UINT32 looppoint = I_GetSongLoopPoint();
649 if (length)
650 return position >= length ? (position % (length-looppoint)) : position;
651 else
652 return position;
653 }
654
do_fading_callback(void)655 static void do_fading_callback(void)
656 {
657 if (fading_callback)
658 (*fading_callback)();
659 fading_callback = NULL;
660 }
661
662 /// ------------------------
663 /// Music Hooks
664 /// ------------------------
665
count_music_bytes(int chan,void * stream,int len,void * udata)666 static void count_music_bytes(int chan, void *stream, int len, void *udata)
667 {
668 (void)chan;
669 (void)stream;
670 (void)udata;
671
672 if (!music || I_SongType() == MU_GME || I_SongType() == MU_MOD || I_SongType() == MU_MID)
673 return;
674 music_bytes += len;
675 }
676
music_loop(void)677 static void music_loop(void)
678 {
679 if (is_looping)
680 {
681 Mix_PlayMusic(music, 0);
682 Mix_SetMusicPosition(loop_point);
683 music_bytes = (UINT32)(loop_point*44100.0L*4); //assume 44.1khz, 4-byte length (see I_GetSongPosition)
684 }
685 else
686 {
687 // HACK: Let fade timing proceed beyond the end of a
688 // non-looping song. This is a specific case where the timing
689 // should persist after stopping a song, so I don't believe
690 // this should apply every time the user stops a song.
691 // This is auto-unset in var_cleanup, called by I_StopSong
692 fading_nocleanup = true;
693 I_StopSong();
694 }
695 }
696
music_fade(UINT32 interval,void * param)697 static UINT32 music_fade(UINT32 interval, void *param)
698 {
699 (void)param;
700
701 if (!is_fading ||
702 internal_volume == fading_target ||
703 fading_duration == 0)
704 {
705 I_StopFadingSong();
706 do_fading_callback();
707 return 0;
708 }
709 else if (songpaused) // don't decrement timer
710 return interval;
711 else if ((fading_timer -= 10) <= 0)
712 {
713 internal_volume = fading_target;
714 Mix_VolumeMusic(get_real_volume(music_volume));
715 I_StopFadingSong();
716 do_fading_callback();
717 return 0;
718 }
719 else
720 {
721 UINT8 delta = abs(fading_target - fading_source);
722 fixed_t factor = FixedDiv(fading_duration - fading_timer, fading_duration);
723 if (fading_target < fading_source)
724 internal_volume = max(min(internal_volume, fading_source - FixedMul(delta, factor)), fading_target);
725 else if (fading_target > fading_source)
726 internal_volume = min(max(internal_volume, fading_source + FixedMul(delta, factor)), fading_target);
727 Mix_VolumeMusic(get_real_volume(music_volume));
728 return interval;
729 }
730 }
731
732 #ifdef HAVE_LIBGME
mix_gme(void * udata,Uint8 * stream,int len)733 static void mix_gme(void *udata, Uint8 *stream, int len)
734 {
735 int i;
736 short *p;
737
738 (void)udata;
739
740 // no gme? no music.
741 if (!gme || gme_track_ended(gme) || songpaused)
742 return;
743
744 // play gme into stream
745 gme_play(gme, len/2, (short *)stream);
746
747 // Limiter to prevent music from being disorted with some formats
748 if (music_volume >= 18)
749 music_volume = 18;
750
751 // apply volume to stream
752 for (i = 0, p = (short *)stream; i < len/2; i++, p++)
753 *p = ((INT32)*p) * (music_volume*internal_volume/100)*2 / 40;
754 }
755 #endif
756
757 #ifdef HAVE_OPENMPT
mix_openmpt(void * udata,Uint8 * stream,int len)758 static void mix_openmpt(void *udata, Uint8 *stream, int len)
759 {
760 int i;
761 short *p;
762
763 (void)udata;
764
765 if (!openmpt_mhandle || songpaused)
766 return;
767
768 // Play module into stream
769 openmpt_module_read_interleaved_stereo(openmpt_mhandle, SAMPLERATE, BUFFERSIZE, (short *)stream);
770
771 // Limiter to prevent music from being disorted with some formats
772 if (music_volume >= 18)
773 music_volume = 18;
774
775 // apply volume to stream
776 for (i = 0, p = (short *)stream; i < len/2; i++, p++)
777 *p = ((INT32)*p) * (music_volume*internal_volume/100)*2 / 40;
778 }
779 #endif
780
781 /// ------------------------
782 /// Music System
783 /// ------------------------
784
I_InitMusic(void)785 void I_InitMusic(void)
786 {
787 }
788
I_ShutdownMusic(void)789 void I_ShutdownMusic(void)
790 {
791 I_UnloadSong();
792 }
793
794 /// ------------------------
795 /// Music Properties
796 /// ------------------------
797
I_SongType(void)798 musictype_t I_SongType(void)
799 {
800 #ifdef HAVE_LIBGME
801 if (gme)
802 return MU_GME;
803 else
804 #endif
805 #ifdef HAVE_OPENMPT
806 if (openmpt_mhandle)
807 return MU_MOD_EX;
808 #endif
809 if (!music)
810 return MU_NONE;
811 else if (Mix_GetMusicType(music) == MUS_MID)
812 {
813 #ifdef HAVE_MIXERX
814 if (Mix_GetMidiPlayer() != MIDI_Native)
815 return MU_MID_EX;
816 else
817 #endif
818 return MU_MID;
819 }
820 else if (Mix_GetMusicType(music) == MUS_MOD || Mix_GetMusicType(music) == MUS_MODPLUG)
821 return MU_MOD;
822 else if (Mix_GetMusicType(music) == MUS_MP3 || Mix_GetMusicType(music) == MUS_MP3_MAD)
823 return MU_MP3;
824 else
825 return (musictype_t)Mix_GetMusicType(music);
826 }
827
I_SongPlaying(void)828 boolean I_SongPlaying(void)
829 {
830 return (
831 #ifdef HAVE_LIBGME
832 (I_SongType() == MU_GME && gme) ||
833 #endif
834 #ifdef HAVE_OPENMPT
835 (I_SongType() == MU_MOD_EX && openmpt_mhandle) ||
836 #endif
837 music != NULL
838 );
839 }
840
I_SongPaused(void)841 boolean I_SongPaused(void)
842 {
843 return songpaused;
844 }
845
846 /// ------------------------
847 /// Music Effects
848 /// ------------------------
849
I_SetSongSpeed(float speed)850 boolean I_SetSongSpeed(float speed)
851 {
852 if (speed > 250.0f)
853 speed = 250.0f; //limit speed up to 250x
854 #ifdef HAVE_LIBGME
855 if (gme)
856 {
857 SDL_LockAudio();
858 gme_set_tempo(gme, speed);
859 SDL_UnlockAudio();
860 return true;
861 }
862 else
863 #endif
864 #ifdef HAVE_OPENMPT
865 if (openmpt_mhandle)
866 {
867 if (speed > 4.0f)
868 speed = 4.0f; // Limit this to 4x to prevent crashing, stupid fix but... ~SteelT 27/9/19
869 #if OPENMPT_API_VERSION_MAJOR < 1 && OPENMPT_API_VERSION_MINOR < 5
870 {
871 // deprecated in 0.5.0
872 char modspd[13];
873 sprintf(modspd, "%g", speed);
874 openmpt_module_ctl_set(openmpt_mhandle, "play.tempo_factor", modspd);
875 }
876 #else
877 openmpt_module_ctl_set_floatingpoint(openmpt_mhandle, "play.tempo_factor", (double)speed);
878 #endif
879 return true;
880 }
881 #else
882 (void)speed;
883 return false;
884 #endif
885 return false;
886 }
887
888 /// ------------------------
889 /// MUSIC SEEKING
890 /// ------------------------
891
I_GetSongLength(void)892 UINT32 I_GetSongLength(void)
893 {
894 INT32 length;
895
896 #ifdef HAVE_LIBGME
897 if (gme)
898 {
899 gme_info_t *info;
900 gme_err_t gme_e = gme_track_info(gme, &info, current_track);
901
902 if (gme_e != NULL)
903 {
904 CONS_Alert(CONS_ERROR, "GME error: %s\n", gme_e);
905 length = 0;
906 }
907 else
908 {
909 // reconstruct info->play_length, from GME source
910 // we only want intro + 1 loop, not 2
911 length = info->length;
912 if (length <= 0)
913 {
914 length = info->intro_length + info->loop_length; // intro + 1 loop
915 if (length <= 0)
916 length = 150 * 1000; // 2.5 minutes
917 }
918 }
919
920 gme_free_info(info);
921 return max(length, 0);
922 }
923 else
924 #endif
925 #ifdef HAVE_OPENMPT
926 if (openmpt_mhandle)
927 return (UINT32)(openmpt_module_get_duration_seconds(openmpt_mhandle) * 1000.);
928 else
929 #endif
930 if (!music || I_SongType() == MU_MOD || I_SongType() == MU_MID)
931 return 0;
932 else
933 {
934 #ifdef HAVE_MIXERX
935 double xlength = Mix_GetMusicTotalTime(music);
936 if (xlength >= 0)
937 return (UINT32)(xlength*1000);
938 #endif
939 // VERY IMPORTANT to set your LENGTHMS= in your song files, folks!
940 // SDL mixer can't read music length itself.
941 length = (UINT32)(song_length*1000);
942 if (!length)
943 CONS_Debug(DBG_DETAILED, "Getting music length: music is missing LENGTHMS= tag. Needed for seeking.\n");
944 return length;
945 }
946 }
947
I_SetSongLoopPoint(UINT32 looppoint)948 boolean I_SetSongLoopPoint(UINT32 looppoint)
949 {
950 if (!music || I_SongType() == MU_GME || I_SongType() == MU_MOD || I_SongType() == MU_MID || !is_looping)
951 return false;
952 else
953 {
954 UINT32 length = I_GetSongLength();
955
956 if (length > 0)
957 looppoint %= length;
958
959 loop_point = max((float)(looppoint / 1000.0L), 0);
960 return true;
961 }
962 }
963
I_GetSongLoopPoint(void)964 UINT32 I_GetSongLoopPoint(void)
965 {
966 #ifdef HAVE_LIBGME
967 if (gme)
968 {
969 INT32 looppoint;
970 gme_info_t *info;
971 gme_err_t gme_e = gme_track_info(gme, &info, current_track);
972
973 if (gme_e != NULL)
974 {
975 CONS_Alert(CONS_ERROR, "GME error: %s\n", gme_e);
976 looppoint = 0;
977 }
978 else
979 looppoint = info->intro_length > 0 ? info->intro_length : 0;
980
981 gme_free_info(info);
982 return max(looppoint, 0);
983 }
984 else
985 #endif
986 if (!music || I_SongType() == MU_MOD || I_SongType() == MU_MID)
987 return 0;
988 else
989 return (UINT32)(loop_point * 1000);
990 }
991
I_SetSongPosition(UINT32 position)992 boolean I_SetSongPosition(UINT32 position)
993 {
994 UINT32 length;
995 #ifdef HAVE_LIBGME
996 if (gme)
997 {
998 // this is unstable, so fail silently
999 return true;
1000 // this isn't required technically, but GME thread-locks for a second
1001 // if you seek too high from the counter
1002 // length = I_GetSongLength();
1003 // if (length)
1004 // position = get_adjusted_position(position);
1005
1006 // SDL_LockAudio();
1007 // gme_err_t gme_e = gme_seek(gme, position);
1008 // SDL_UnlockAudio();
1009
1010 // if (gme_e != NULL)
1011 // {
1012 // CONS_Alert(CONS_ERROR, "GME error: %s\n", gme_e);
1013 // return false;
1014 // }
1015 // else
1016 // return true;
1017 }
1018 else
1019 #endif
1020 #ifdef HAVE_OPENMPT
1021 if (openmpt_mhandle)
1022 {
1023 // This isn't 100% correct because we don't account for loop points because we can't get them.
1024 // But if you seek past end of song, OpenMPT seeks to 0. So adjust the position anyway.
1025 openmpt_module_set_position_seconds(openmpt_mhandle, (double)(get_adjusted_position(position)/1000.0L)); // returns new position
1026 return true;
1027 }
1028 else
1029 #endif
1030 if (!music || I_SongType() == MU_MID)
1031 return false;
1032 else if (I_SongType() == MU_MOD)
1033 return Mix_SetMusicPosition(position); // Goes by channels
1034 else
1035 {
1036 // Because SDL mixer can't identify song length, if you have
1037 // a position input greater than the real length, then
1038 // music_bytes becomes inaccurate.
1039
1040 length = I_GetSongLength(); // get it in MS
1041 if (length)
1042 position = get_adjusted_position(position);
1043
1044 Mix_RewindMusic(); // needed for mp3
1045 if(Mix_SetMusicPosition((float)(position/1000.0L)) == 0)
1046 music_bytes = (UINT32)(position/1000.0L*44100.0L*4); //assume 44.1khz, 4-byte length (see I_GetSongPosition)
1047 else
1048 // NOTE: This block fires on incorrect song format,
1049 // NOT if position input is greater than song length.
1050 music_bytes = 0;
1051
1052 return true;
1053 }
1054 }
1055
I_GetSongPosition(void)1056 UINT32 I_GetSongPosition(void)
1057 {
1058 #ifdef HAVE_LIBGME
1059 if (gme)
1060 {
1061 INT32 position = gme_tell(gme);
1062
1063 gme_info_t *info;
1064 gme_err_t gme_e = gme_track_info(gme, &info, current_track);
1065
1066 if (gme_e != NULL)
1067 {
1068 CONS_Alert(CONS_ERROR, "GME error: %s\n", gme_e);
1069 return position;
1070 }
1071 else
1072 {
1073 // adjust position, since GME's counter keeps going past loop
1074 if (info->length > 0)
1075 position %= info->length;
1076 else if (info->intro_length + info->loop_length > 0)
1077 position = position >= (info->intro_length + info->loop_length) ? (position % info->loop_length) : position;
1078 else
1079 position %= 150 * 1000; // 2.5 minutes
1080 }
1081
1082 gme_free_info(info);
1083 return max(position, 0);
1084 }
1085 else
1086 #endif
1087 #ifdef HAVE_OPENMPT
1088 if (openmpt_mhandle)
1089 // This will be incorrect if we adjust for length because we can't get loop points.
1090 // So return unadjusted. See note in SetMusicPosition: we adjust for that.
1091 return (UINT32)(openmpt_module_get_position_seconds(openmpt_mhandle)*1000.);
1092 //return get_adjusted_position((UINT32)(openmpt_module_get_position_seconds(openmpt_mhandle)*1000.));
1093 else
1094 #endif
1095 if (!music || I_SongType() == MU_MID)
1096 return 0;
1097 else
1098 {
1099 #ifdef HAVE_MIXERX
1100 double xposition = Mix_GetMusicPosition(music);
1101 if (xposition >= 0)
1102 return (UINT32)(xposition*1000);
1103 #endif
1104 return (UINT32)(music_bytes/44100.0L*1000.0L/4); //assume 44.1khz
1105 // 4 = byte length for 16-bit samples (AUDIO_S16SYS), stereo (2-channel)
1106 // This is hardcoded in I_StartupSound. Other formats for factor:
1107 // 8M: 1 | 8S: 2 | 16M: 2 | 16S: 4
1108 }
1109 }
1110
1111 /// ------------------------
1112 /// Music Playback
1113 /// ------------------------
1114
I_LoadSong(char * data,size_t len)1115 boolean I_LoadSong(char *data, size_t len)
1116 {
1117 const char *key1 = "LOOP";
1118 const char *key2 = "POINT=";
1119 const char *key3 = "MS=";
1120 const size_t key1len = strlen(key1);
1121 const size_t key2len = strlen(key2);
1122 const size_t key3len = strlen(key3);
1123 char *p = data;
1124 SDL_RWops *rw;
1125
1126 if (music
1127 #ifdef HAVE_LIBGME
1128 || gme
1129 #endif
1130 #ifdef HAVE_OPENMPT
1131 || openmpt_mhandle
1132 #endif
1133 )
1134 I_UnloadSong();
1135
1136 // always do this whether or not a music already exists
1137 var_cleanup();
1138
1139 #ifdef HAVE_LIBGME
1140 if ((UINT8)data[0] == 0x1F
1141 && (UINT8)data[1] == 0x8B)
1142 {
1143 #ifdef HAVE_ZLIB
1144 UINT8 *inflatedData;
1145 size_t inflatedLen;
1146 z_stream stream;
1147 int zErr; // Somewhere to handle any error messages zlib tosses out
1148
1149 memset(&stream, 0x00, sizeof (z_stream)); // Init zlib stream
1150 // Begin the inflation process
1151 inflatedLen = *(UINT32 *)(data + (len-4)); // Last 4 bytes are the decompressed size, typically
1152 inflatedData = (UINT8 *)Z_Calloc(inflatedLen, PU_MUSIC, NULL); // Make room for the decompressed data
1153 stream.total_in = stream.avail_in = len;
1154 stream.total_out = stream.avail_out = inflatedLen;
1155 stream.next_in = (UINT8 *)data;
1156 stream.next_out = inflatedData;
1157
1158 zErr = inflateInit2(&stream, 32 + MAX_WBITS);
1159 if (zErr == Z_OK) // We're good to go
1160 {
1161 zErr = inflate(&stream, Z_FINISH);
1162 if (zErr == Z_STREAM_END)
1163 {
1164 // Run GME on new data
1165 if (!gme_open_data(inflatedData, inflatedLen, &gme, SAMPLERATE))
1166 {
1167 Z_Free(inflatedData); // GME supposedly makes a copy for itself, so we don't need this lying around
1168 return true;
1169 }
1170 }
1171 else
1172 CONS_Alert(CONS_ERROR, "Encountered %s when running inflate: %s\n", get_zlib_error(zErr), stream.msg);
1173 (void)inflateEnd(&stream);
1174 }
1175 else // Hold up, zlib's got a problem
1176 CONS_Alert(CONS_ERROR, "Encountered %s when running inflateInit: %s\n", get_zlib_error(zErr), stream.msg);
1177 Z_Free(inflatedData); // GME didn't open jack, but don't let that stop us from freeing this up
1178 return false;
1179 #else
1180 CONS_Alert(CONS_ERROR, "Cannot decompress VGZ; no zlib support\n");
1181 return false;
1182 #endif
1183 }
1184 else if (!gme_open_data(data, len, &gme, SAMPLERATE))
1185 return true;
1186 #endif
1187
1188 #ifdef HAVE_MIXERX
1189 if (Mix_GetMidiPlayer() != cv_midiplayer.value)
1190 Mix_SetMidiPlayer(cv_midiplayer.value);
1191 if (stricmp(Mix_GetSoundFonts(), cv_midisoundfontpath.string))
1192 Mix_SetSoundFonts(cv_midisoundfontpath.string);
1193 Mix_Timidity_addToPathList(cv_miditimiditypath.string); // this overwrites previous custom path
1194 #endif
1195
1196 #ifdef HAVE_OPENMPT
1197 /*
1198 If the size of the data to be checked is bigger than the recommended size (> 2048 bytes)
1199 Let's just set the probe size to the recommended size
1200 Otherwise let's give it the full data size
1201 */
1202
1203 if (len > openmpt_probe_file_header_get_recommended_size())
1204 probesize = openmpt_probe_file_header_get_recommended_size();
1205 else
1206 probesize = len;
1207
1208 result = openmpt_probe_file_header(OPENMPT_PROBE_FILE_HEADER_FLAGS_DEFAULT, data, probesize, len, NULL, NULL, NULL, NULL, NULL, NULL);
1209
1210 if (result == OPENMPT_PROBE_FILE_HEADER_RESULT_SUCCESS) // We only cared if it succeeded, continue on if not.
1211 {
1212 openmpt_mhandle = openmpt_module_create_from_memory2(data, len, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1213 if (!openmpt_mhandle) // Failed to create module handle? Show error and return!
1214 {
1215 mod_err = openmpt_module_error_get_last(openmpt_mhandle);
1216 mod_err_str = openmpt_error_string(mod_err);
1217 CONS_Alert(CONS_ERROR, "openmpt_module_create_from_memory2: %s\n", mod_err_str);
1218 return false;
1219 }
1220 else
1221 return true; // All good and we're ready for music playback!
1222 }
1223 #endif
1224
1225 // Let's see if Mixer is able to load this.
1226 rw = SDL_RWFromMem(data, len);
1227 {
1228 music = Mix_LoadMUS_RW(rw, 1);
1229 }
1230 if (!music)
1231 {
1232 CONS_Alert(CONS_ERROR, "Mix_LoadMUS_RW: %s\n", Mix_GetError());
1233 return false;
1234 }
1235
1236 // Find the OGG loop point.
1237 loop_point = 0.0f;
1238 song_length = 0.0f;
1239
1240 while ((UINT32)(p - data) < len)
1241 {
1242 if (fpclassify(loop_point) == FP_ZERO && !strncmp(p, key1, key1len))
1243 {
1244 p += key1len; // skip LOOP
1245 if (!strncmp(p, key2, key2len)) // is it LOOPPOINT=?
1246 {
1247 p += key2len; // skip POINT=
1248 loop_point = (float)((44.1L+atoi(p)) / 44100.0L); // LOOPPOINT works by sample count.
1249 // because SDL_Mixer is USELESS and can't even tell us
1250 // something simple like the frequency of the streaming music,
1251 // we are unfortunately forced to assume that ALL MUSIC is 44100hz.
1252 // This means a lot of tracks that are only 22050hz for a reasonable downloadable file size will loop VERY badly.
1253 }
1254 else if (!strncmp(p, key3, key3len)) // is it LOOPMS=?
1255 {
1256 p += key3len; // skip MS=
1257 loop_point = (float)(atoi(p) / 1000.0L); // LOOPMS works by real time, as miliseconds.
1258 // Everything that uses LOOPMS will work perfectly with SDL_Mixer.
1259 }
1260 }
1261
1262 if (fpclassify(loop_point) != FP_ZERO) // Got what we needed
1263 break;
1264 else // continue searching
1265 p++;
1266 }
1267 return true;
1268 }
1269
I_UnloadSong(void)1270 void I_UnloadSong(void)
1271 {
1272 I_StopSong();
1273
1274 #ifdef HAVE_LIBGME
1275 if (gme)
1276 {
1277 gme_delete(gme);
1278 gme = NULL;
1279 }
1280 #endif
1281 #ifdef HAVE_OPENMPT
1282 if (openmpt_mhandle)
1283 {
1284 openmpt_module_destroy(openmpt_mhandle);
1285 openmpt_mhandle = NULL;
1286 }
1287 #endif
1288 if (music)
1289 {
1290 Mix_FreeMusic(music);
1291 music = NULL;
1292 }
1293 }
1294
I_PlaySong(boolean looping)1295 boolean I_PlaySong(boolean looping)
1296 {
1297 #ifdef HAVE_LIBGME
1298 if (gme)
1299 {
1300 gme_equalizer_t eq = {GME_TREBLE, GME_BASS, 0,0,0,0,0,0,0,0};
1301 #if defined (GME_VERSION) && GME_VERSION >= 0x000603
1302 if (looping)
1303 gme_set_autoload_playback_limit(gme, 0);
1304 #endif
1305 gme_set_equalizer(gme, &eq);
1306 gme_start_track(gme, 0);
1307 current_track = 0;
1308 Mix_HookMusic(mix_gme, gme);
1309 return true;
1310 }
1311 else
1312 #endif
1313 #ifdef HAVE_OPENMPT
1314 if (openmpt_mhandle)
1315 {
1316 openmpt_module_select_subsong(openmpt_mhandle, 0);
1317 openmpt_module_set_render_param(openmpt_mhandle, OPENMPT_MODULE_RENDER_INTERPOLATIONFILTER_LENGTH, cv_modfilter.value);
1318 if (looping)
1319 openmpt_module_set_repeat_count(openmpt_mhandle, -1); // Always repeat
1320 current_subsong = 0;
1321 Mix_HookMusic(mix_openmpt, openmpt_mhandle);
1322 return true;
1323 }
1324 else
1325 #endif
1326 if (!music)
1327 return false;
1328
1329 if (fpclassify(song_length) == FP_ZERO && (I_SongType() == MU_OGG || I_SongType() == MU_MP3 || I_SongType() == MU_FLAC))
1330 CONS_Debug(DBG_DETAILED, "This song is missing a LENGTHMS= tag! Required to make seeking work properly.\n");
1331
1332 if (I_SongType() != MU_MOD && I_SongType() != MU_MID && Mix_PlayMusic(music, 0) == -1)
1333 {
1334 CONS_Alert(CONS_ERROR, "Mix_PlayMusic: %s\n", Mix_GetError());
1335 return false;
1336 }
1337 else if ((I_SongType() == MU_MOD || I_SongType() == MU_MID || I_SongType() == MU_MID_EX) && Mix_PlayMusic(music, looping ? -1 : 0) == -1) // if MOD, loop forever
1338 {
1339 CONS_Alert(CONS_ERROR, "Mix_PlayMusic: %s\n", Mix_GetError());
1340 return false;
1341 }
1342
1343 is_looping = looping;
1344
1345 I_SetMusicVolume(music_volume);
1346
1347 if (I_SongType() != MU_MOD && I_SongType() != MU_MID && I_SongType() != MU_MID_EX)
1348 Mix_HookMusicFinished(music_loop); // don't bother counting if MOD
1349
1350 if(I_SongType() != MU_MOD && I_SongType() != MU_MID && I_SongType() != MU_MID_EX && !Mix_RegisterEffect(MIX_CHANNEL_POST, count_music_bytes, NULL, NULL))
1351 CONS_Alert(CONS_WARNING, "Error registering SDL music position counter: %s\n", Mix_GetError());
1352
1353 return true;
1354 }
1355
I_StopSong(void)1356 void I_StopSong(void)
1357 {
1358 // HACK: See music_loop on why we want fade timing to proceed
1359 // after end of song
1360 if (!fading_nocleanup)
1361 I_StopFadingSong();
1362
1363 #ifdef HAVE_LIBGME
1364 if (gme)
1365 {
1366 Mix_HookMusic(NULL, NULL);
1367 current_track = -1;
1368 }
1369 #endif
1370 #ifdef HAVE_OPENMPT
1371 if (openmpt_mhandle)
1372 {
1373 Mix_HookMusic(NULL, NULL);
1374 current_subsong = -1;
1375 }
1376 #endif
1377 if (music)
1378 {
1379 Mix_UnregisterEffect(MIX_CHANNEL_POST, count_music_bytes);
1380 Mix_HookMusicFinished(NULL);
1381 Mix_HaltMusic();
1382 }
1383
1384 var_cleanup();
1385 }
1386
I_PauseSong(void)1387 void I_PauseSong(void)
1388 {
1389 if(I_SongType() == MU_MID) // really, SDL Mixer? why can't you pause MIDI???
1390 return;
1391
1392 if(I_SongType() != MU_GME && I_SongType() != MU_MOD && I_SongType() != MU_MID)
1393 Mix_UnregisterEffect(MIX_CHANNEL_POST, count_music_bytes);
1394
1395 Mix_PauseMusic();
1396 songpaused = true;
1397 }
1398
I_ResumeSong(void)1399 void I_ResumeSong(void)
1400 {
1401 if (I_SongType() == MU_MID)
1402 return;
1403
1404 if (I_SongType() != MU_GME && I_SongType() != MU_MOD && I_SongType() != MU_MID)
1405 {
1406 while(Mix_UnregisterEffect(MIX_CHANNEL_POST, count_music_bytes) != 0) { }
1407 // HACK: fixes issue of multiple effect callbacks being registered
1408
1409 if(music && I_SongType() != MU_MOD && I_SongType() != MU_MID && !Mix_RegisterEffect(MIX_CHANNEL_POST, count_music_bytes, NULL, NULL))
1410 CONS_Alert(CONS_WARNING, "Error registering SDL music position counter: %s\n", Mix_GetError());
1411 }
1412
1413 Mix_ResumeMusic();
1414 songpaused = false;
1415 }
1416
I_SetMusicVolume(UINT8 volume)1417 void I_SetMusicVolume(UINT8 volume)
1418 {
1419 if (!I_SongPlaying())
1420 return;
1421
1422 #ifdef _WIN32
1423 if (I_SongType() == MU_MID)
1424 // HACK: Until we stop using native MIDI,
1425 // disable volume changes
1426 music_volume = 31;
1427 else
1428 #endif
1429 music_volume = volume;
1430
1431 Mix_VolumeMusic(get_real_volume(music_volume));
1432 }
1433
I_SetSongTrack(int track)1434 boolean I_SetSongTrack(int track)
1435 {
1436 #ifdef HAVE_LIBGME
1437 // If the specified track is within the number of tracks playing, then change it
1438 if (gme)
1439 {
1440 if (current_track == track)
1441 return false;
1442 SDL_LockAudio();
1443 if (track >= 0 && track < gme_track_count(gme)-1)
1444 {
1445 gme_err_t gme_e = gme_start_track(gme, track);
1446 if (gme_e != NULL)
1447 {
1448 CONS_Alert(CONS_ERROR, "GME error: %s\n", gme_e);
1449 return false;
1450 }
1451 current_track = track;
1452 SDL_UnlockAudio();
1453 return true;
1454 }
1455 SDL_UnlockAudio();
1456 return false;
1457 }
1458 else
1459 #endif
1460 #ifdef HAVE_OPENMPT
1461 if (openmpt_mhandle)
1462 {
1463 if (current_subsong == track)
1464 return false;
1465 SDL_LockAudio();
1466 if (track >= 0 && track < openmpt_module_get_num_subsongs(openmpt_mhandle))
1467 {
1468 openmpt_module_select_subsong(openmpt_mhandle, track);
1469 current_subsong = track;
1470 SDL_UnlockAudio();
1471 return true;
1472 }
1473 SDL_UnlockAudio();
1474
1475 return false;
1476 }
1477 #endif
1478 if (I_SongType() == MU_MOD)
1479 return !Mix_SetMusicPosition(track);
1480 (void)track;
1481 return false;
1482 }
1483
1484 /// ------------------------
1485 /// MUSIC FADING
1486 /// ------------------------
1487
I_SetInternalMusicVolume(UINT8 volume)1488 void I_SetInternalMusicVolume(UINT8 volume)
1489 {
1490 internal_volume = volume;
1491 if (!I_SongPlaying())
1492 return;
1493 Mix_VolumeMusic(get_real_volume(music_volume));
1494 }
1495
I_StopFadingSong(void)1496 void I_StopFadingSong(void)
1497 {
1498 if (fading_id)
1499 SDL_RemoveTimer(fading_id);
1500 is_fading = false;
1501 fading_source = fading_target = fading_timer = fading_duration = fading_id = 0;
1502 // don't unset fading_nocleanup here just yet; fading_callback is cleaned up
1503 // in var_cleanup()
1504 }
1505
I_FadeSongFromVolume(UINT8 target_volume,UINT8 source_volume,UINT32 ms,void (* callback)(void))1506 boolean I_FadeSongFromVolume(UINT8 target_volume, UINT8 source_volume, UINT32 ms, void (*callback)(void))
1507 {
1508 INT16 volume_delta;
1509
1510 source_volume = min(source_volume, 100);
1511 volume_delta = (INT16)(target_volume - source_volume);
1512
1513 I_StopFadingSong();
1514
1515 if (!ms && volume_delta)
1516 {
1517 I_SetInternalMusicVolume(target_volume);
1518 if (callback)
1519 (*callback)();
1520 return true;
1521
1522 }
1523 else if (!volume_delta)
1524 {
1525 if (callback)
1526 (*callback)();
1527 return true;
1528 }
1529
1530 // Round MS to nearest 10
1531 // If n - lower > higher - n, then round up
1532 ms = (ms - ((ms / 10) * 10) > (((ms / 10) * 10) + 10) - ms) ?
1533 (((ms / 10) * 10) + 10) // higher
1534 : ((ms / 10) * 10); // lower
1535
1536 if (!ms)
1537 I_SetInternalMusicVolume(target_volume);
1538 else if (source_volume != target_volume)
1539 {
1540 fading_id = SDL_AddTimer(10, music_fade, NULL);
1541 if (fading_id)
1542 {
1543 is_fading = true;
1544 fading_timer = fading_duration = ms;
1545 fading_source = source_volume;
1546 fading_target = target_volume;
1547 fading_callback = callback;
1548
1549 if (internal_volume != source_volume)
1550 I_SetInternalMusicVolume(source_volume);
1551 }
1552 }
1553
1554 return is_fading;
1555 }
1556
I_FadeSong(UINT8 target_volume,UINT32 ms,void (* callback)(void))1557 boolean I_FadeSong(UINT8 target_volume, UINT32 ms, void (*callback)(void))
1558 {
1559 return I_FadeSongFromVolume(target_volume, internal_volume, ms, callback);
1560 }
1561
I_FadeOutStopSong(UINT32 ms)1562 boolean I_FadeOutStopSong(UINT32 ms)
1563 {
1564 return I_FadeSongFromVolume(0, internal_volume, ms, &I_StopSong);
1565 }
1566
I_FadeInPlaySong(UINT32 ms,boolean looping)1567 boolean I_FadeInPlaySong(UINT32 ms, boolean looping)
1568 {
1569 if (I_PlaySong(looping))
1570 return I_FadeSongFromVolume(100, 0, ms, NULL);
1571 else
1572 return false;
1573 }
1574 #endif
1575