Subversion Repositories Games.Descent

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 pmbaty 1
/*
2
 * Portions of this file are copyright Rebirth contributors and licensed as
3
 * described in COPYING.txt.
4
 * Portions of this file are copyright Parallax Software and licensed
5
 * according to the Parallax license below.
6
 * See COPYING.txt for license details.
7
 
8
THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
9
SOFTWARE CORPORATION ("PARALLAX").  PARALLAX, IN DISTRIBUTING THE CODE TO
10
END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
11
ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
12
IN USING, DISPLAYING,  AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
13
SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
14
FREE PURPOSES.  IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
15
CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES.  THE END-USER UNDERSTANDS
16
AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
17
COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION.  ALL RIGHTS RESERVED.
18
*/
19
 
20
/*
21
 *
22
 * Routines to manage the songs in Descent.
23
 *
24
 */
25
 
26
 
27
#include <stdio.h>
28
#include <stdlib.h>
29
#include <string.h>
30
 
31
#include "dxxerror.h"
32
#include "pstypes.h"
33
#include "songs.h"
34
#include "strutil.h"
35
#include "digi.h"
36
#include "rbaudio.h"
37
#if DXX_USE_SDLMIXER
38
#include "digi_mixer_music.h"
39
#endif
40
#include "jukebox.h"
41
#include "config.h"
42
#include "timer.h"
43
#include "u_mem.h"
44
#include "args.h"
45
#include "physfsx.h"
46
#include "game.h"
47
#include "console.h"
48
#include <memory>
49
 
50
namespace dcx {
51
 
52
namespace {
53
 
54
class user_configured_level_songs : std::unique_ptr<bim_song_info[]>
55
{
56
        using base_type = std::unique_ptr<bim_song_info[]>;
57
        unsigned count;
58
public:
59
        using base_type::operator[];
60
        using base_type::operator bool;
61
        user_configured_level_songs() = default;
62
        user_configured_level_songs(const std::size_t length) :
63
                base_type(std::make_unique<bim_song_info[]>(length)), count(length)
64
        {
65
        }
66
        unsigned size() const
67
        {
68
                return count;
69
        }
70
        void reset()
71
        {
72
                count = 0;
73
                base_type::reset();
74
        }
75
        void resize(const std::size_t length)
76
        {
77
                base_type::operator=(std::make_unique<bim_song_info[]>(length));
78
                count = length;
79
        }
80
        user_configured_level_songs &operator=(user_configured_level_songs &&) = default;
81
        void operator=(std::vector<bim_song_info> &&v);
82
};
83
 
84
void user_configured_level_songs::operator=(std::vector<bim_song_info> &&v)
85
{
86
        if (v.empty())
87
        {
88
                /* If the vector is empty, use `reset` so that this object
89
                 * stores `nullptr` instead of a pointer to a zero-length array.
90
                 * Storing `nullptr` causes `operator bool()` to return false.
91
                 * Storing a zero-length array causes `operator bool()` to
92
                 * return true.
93
                 *
94
                 * A true return causes a divide-by-zero later when the object
95
                 * is considered true, but then reports a length of zero.
96
                 */
97
                reset();
98
                return;
99
        }
100
        resize(v.size());
101
        std::move(v.begin(), v.end(), &this->operator[](0u));
102
}
103
 
104
int Songs_initialized = 0;
105
static int Song_playing = -1; // -1 if no song playing, else the Descent song number
106
#if DXX_USE_SDL_REDBOOK_AUDIO
107
static int Redbook_playing = 0; // Redbook track num differs from Song_playing. We need this for Redbook repeat hooks.
108
#endif
109
 
110
user_configured_level_songs BIMSongs;
111
user_configured_level_songs BIMSecretSongs;
112
 
113
}
114
 
115
#define EXTMUSIC_VOLUME_SCALE   (255)
116
 
117
//takes volume in range 0..8
118
//NOTE that we do not check what is playing right now (except Redbook) This is because here we don't (want) know WHAT we're playing - let the subfunctions do it (i.e. digi_win32_set_music_volume() knows if a MIDI plays or not)
119
void songs_set_volume(int volume)
120
{
121
#ifdef _WIN32
122
        digi_win32_set_midi_volume(volume);
123
#endif
124
#if DXX_USE_SDL_REDBOOK_AUDIO
125
        if (GameCfg.MusicType == MUSIC_TYPE_REDBOOK)
126
        {
127
                RBASetVolume(0);
128
                RBASetVolume(volume);
129
        }
130
#else
131
        (void)volume;
132
#endif
133
#if DXX_USE_SDLMIXER
134
        mix_set_music_volume(volume);
135
#endif
136
}
137
 
138
static int is_valid_song_extension(const char* dot)
139
{
140
        return (!d_stricmp(dot, SONG_EXT_HMP)
141
#if DXX_USE_SDLMIXER
142
                        ||
143
                        !d_stricmp(dot, SONG_EXT_MID) ||
144
                        !d_stricmp(dot, SONG_EXT_OGG) ||
145
                        !d_stricmp(dot, SONG_EXT_FLAC) ||
146
                        !d_stricmp(dot, SONG_EXT_MP3)
147
#endif
148
                        );
149
}
150
 
151
template <std::size_t N>
152
static inline void assign_builtin_song(bim_song_info &song, const char (&str)[N])
153
{
154
        strncpy(song.filename, str, sizeof(song.filename));
155
}
156
 
157
static void add_song(std::vector<bim_song_info> &songs, const char *const input)
158
{
159
        songs.emplace_back();
160
        auto &filename = songs.back().filename;
161
        sscanf(input, "%15s", filename);
162
 
163
        if (const char *dot = strrchr(filename, '.'))
164
        {
165
                ++ dot;
166
                if (is_valid_song_extension(dot))
167
                        return;
168
        }
169
        songs.pop_back();
170
}
171
 
172
}
173
 
174
namespace dsx {
175
 
176
// Set up everything for our music
177
// NOTE: you might think this is done once per runtime but it's not! It's done for EACH song so that each mission can have it's own descent.sng structure. We COULD optimize that by only doing this once per mission.
178
static void songs_init()
179
{
180
        Songs_initialized = 0;
181
 
182
        BIMSongs.reset();
183
        BIMSecretSongs.reset();
184
 
185
        int canUseExtensions = 0;
186
        // try dxx-r.sng - a songfile specifically for dxx which level authors CAN use (dxx does not care if descent.sng contains MP3/OGG/etc. as well) besides the normal descent.sng containing files other versions of the game cannot play. this way a mission can contain a DOS-Descent compatible OST (hmp files) as well as a OST using MP3, OGG, etc.
187
        auto fp = PHYSFSX_openReadBuffered("dxx-r.sng");
188
 
189
        if (!fp) // try to open regular descent.sng
190
                fp = PHYSFSX_openReadBuffered( "descent.sng" );
191
        else
192
                canUseExtensions = 1; // can use extensions ONLY if dxx-r.sng
193
 
194
        unsigned i = 0;
195
        if (!fp) // No descent.sng available. Define a default song-set
196
        {
197
                constexpr std::size_t predef = 30; // define 30 songs - period
198
 
199
                user_configured_level_songs builtin_songs(predef);
200
 
201
                assign_builtin_song(builtin_songs[SONG_TITLE], "descent.hmp");
202
                assign_builtin_song(builtin_songs[SONG_BRIEFING], "briefing.hmp");
203
                assign_builtin_song(builtin_songs[SONG_CREDITS], "credits.hmp");
204
                assign_builtin_song(builtin_songs[SONG_ENDLEVEL], "endlevel.hmp");      // can't find it? give a warning
205
                assign_builtin_song(builtin_songs[SONG_ENDGAME], "endgame.hmp");        // ditto
206
 
207
                for (i = SONG_FIRST_LEVEL_SONG; i < predef; i++) {
208
                        auto &s = builtin_songs[i];
209
                        snprintf(s.filename, sizeof(s.filename), "game%02d.hmp", i - SONG_FIRST_LEVEL_SONG + 1);
210
                        if (!PHYSFSX_exists(s.filename,1) &&
211
                                !PHYSFSX_exists((snprintf(s.filename, sizeof(s.filename), "game%d.hmp", i - SONG_FIRST_LEVEL_SONG), s.filename), 1))
212
                        {
213
                                s = {}; // music not available
214
                                break;
215
                        }
216
                }
217
                BIMSongs = std::move(builtin_songs);
218
        }
219
        else
220
        {
221
                PHYSFSX_gets_line_t<81> inputline;
222
                std::vector<bim_song_info> main_songs, secret_songs;
223
                while (PHYSFSX_fgets(inputline, fp))
224
                {
225
                        if (!inputline[0])
226
                                continue;
227
                        {
228
                                if (canUseExtensions)
229
                                {
230
                                        auto &secret_label = "!Rebirth.secret ";
231
                                        constexpr auto secret_label_len = sizeof(secret_label) - 1;
232
                                        // extension stuffs
233
                                        if (!strncmp(inputline, secret_label, secret_label_len)) {
234
                                                add_song(secret_songs, &inputline[secret_label_len]);
235
                                                continue;
236
                                        }
237
                                }
238
 
239
                                add_song(main_songs, inputline);
240
                        }
241
                }
242
#if defined(DXX_BUILD_DESCENT_I)
243
                // HACK: If Descent.hog is patched from 1.0 to 1.5, descent.sng is turncated. So let's patch it up here
244
                constexpr std::size_t truncated_song_count = 12;
245
                if (!canUseExtensions &&
246
                        main_songs.size() == truncated_song_count &&
247
                        PHYSFS_fileLength(fp) == 422)
248
                {
249
                        main_songs.resize(truncated_song_count + 15);
250
                        for (i = 12; i <= 26; i++)
251
                        {
252
                                auto &s = main_songs[i];
253
                                snprintf(s.filename, sizeof(s.filename), "game%02d.hmp", i - 4);
254
                        }
255
                }
256
#endif
257
                BIMSongs = std::move(main_songs);
258
                BIMSecretSongs = std::move(secret_songs);
259
        }
260
 
261
        Songs_initialized = 1;
262
        fp.reset();
263
 
264
        if (CGameArg.SndNoMusic)
265
                GameCfg.MusicType = MUSIC_TYPE_NONE;
266
 
267
        // If SDL_Mixer is not supported (or deactivated), switch to no-music type if SDL_mixer-related music type was selected
268
        else if (CGameArg.SndDisableSdlMixer)
269
        {
270
#ifndef _WIN32
271
                if (GameCfg.MusicType == MUSIC_TYPE_BUILTIN)
272
                        GameCfg.MusicType = MUSIC_TYPE_NONE;
273
#endif
274
                if (GameCfg.MusicType == MUSIC_TYPE_CUSTOM)
275
                        GameCfg.MusicType = MUSIC_TYPE_NONE;
276
        }
277
 
278
        switch (GameCfg.MusicType)
279
        {
280
#if DXX_USE_SDL_REDBOOK_AUDIO
281
                case MUSIC_TYPE_REDBOOK:
282
                        RBAInit();
283
                        break;
284
#endif
285
                case MUSIC_TYPE_CUSTOM:
286
                        jukebox_load();
287
                        break;
288
        }
289
 
290
        songs_set_volume(GameCfg.MusicVolume);
291
}
292
}
293
 
294
void songs_uninit()
295
{
296
        songs_stop_all();
297
#if DXX_USE_SDLMIXER
298
        jukebox_unload();
299
#endif
300
        BIMSecretSongs.reset();
301
        BIMSongs.reset();
302
        Songs_initialized = 0;
303
}
304
 
305
//stop any songs - builtin, redbook or jukebox - that are currently playing
306
void songs_stop_all(void)
307
{
308
#ifdef _WIN32
309
        digi_win32_stop_midi_song();    // Stop midi song, if playing
310
#endif
311
#if DXX_USE_SDL_REDBOOK_AUDIO
312
        RBAStop();
313
#endif
314
#if DXX_USE_SDLMIXER
315
        mix_stop_music();
316
#endif
317
 
318
        Song_playing = -1;
319
}
320
 
321
void songs_pause(void)
322
{
323
#ifdef _WIN32
324
        digi_win32_pause_midi_song();
325
#endif
326
#if DXX_USE_SDL_REDBOOK_AUDIO
327
        if (GameCfg.MusicType == MUSIC_TYPE_REDBOOK)
328
                RBAPause();
329
#endif
330
#if DXX_USE_SDLMIXER
331
        mix_pause_music();
332
#endif
333
}
334
 
335
void songs_resume(void)
336
{
337
#ifdef _WIN32
338
        digi_win32_resume_midi_song();
339
#endif
340
#if DXX_USE_SDL_REDBOOK_AUDIO
341
        if (GameCfg.MusicType == MUSIC_TYPE_REDBOOK)
342
                RBAResume();
343
#endif
344
#if DXX_USE_SDLMIXER
345
        mix_resume_music();
346
#endif
347
}
348
 
349
void songs_pause_resume(void)
350
{
351
#if DXX_USE_SDL_REDBOOK_AUDIO
352
        if (GameCfg.MusicType == MUSIC_TYPE_REDBOOK)
353
                RBAPauseResume();
354
#endif
355
#if DXX_USE_SDLMIXER
356
        mix_pause_resume_music();
357
#endif
358
}
359
 
360
#if defined(DXX_BUILD_DESCENT_I)
361
/*
362
 * This list may not be exhaustive!!
363
 */
364
#define D1_MAC_OEM_DISCID       0xde0feb0e // Descent CD that came with the Mac Performa 6400, hope mine isn't scratched [too much]
365
 
366
#if DXX_USE_SDL_REDBOOK_AUDIO
367
#define REDBOOK_ENDLEVEL_TRACK          4
368
#define REDBOOK_ENDGAME_TRACK           (RBAGetNumberOfTracks())
369
#define REDBOOK_FIRST_LEVEL_TRACK       (songs_have_cd() ? 6 : 1)
370
#endif
371
#elif defined(DXX_BUILD_DESCENT_II)
372
/*
373
 * Some of these have different Track listings!
374
 * Which one is the "correct" order?
375
 */
376
#define D2_1_DISCID         0x7d0ff809 // Descent II
377
#define D2_2_DISCID         0xe010a30e // Descent II
378
#define D2_3_DISCID         0xd410070d // Descent II
379
#define D2_4_DISCID         0xc610080d // Descent II
380
#define D2_DEF_DISCID       0x87102209 // Definitive collection Disc 2
381
#define D2_OEM_DISCID       0xac0bc30d // Destination: Quartzon
382
#define D2_OEM2_DISCID      0xc40c0a0d // Destination: Quartzon
383
#define D2_VERTIGO_DISCID   0x53078208 // Vertigo
384
#define D2_VERTIGO2_DISCID  0x64071408 // Vertigo + DMB
385
#define D2_MAC_DISCID       0xb70ee40e // Macintosh
386
#define D2_IPLAY_DISCID     0x22115710 // iPlay for Macintosh
387
 
388
#if DXX_USE_SDL_REDBOOK_AUDIO
389
#define REDBOOK_TITLE_TRACK         2
390
#define REDBOOK_CREDITS_TRACK       3
391
#define REDBOOK_FIRST_LEVEL_TRACK   (songs_have_cd() ? 4 : 1)
392
#endif
393
#endif
394
 
395
// 0 otherwise
396
namespace dsx {
397
#if DXX_USE_SDL_REDBOOK_AUDIO
398
static int songs_have_cd()
399
{
400
        unsigned long discid;
401
 
402
        if (GameCfg.OrigTrackOrder)
403
                return 1;
404
 
405
        if (!(GameCfg.MusicType == MUSIC_TYPE_REDBOOK))
406
                return 0;
407
 
408
        discid = RBAGetDiscID();
409
        con_printf(CON_DEBUG, "CD-ROM disc ID is 0x%08lx", discid);
410
 
411
        switch (discid) {
412
#if defined(DXX_BUILD_DESCENT_I)
413
                case D1_MAC_OEM_DISCID: // Doesn't work with your Mac Descent CD? Please tell!
414
                        return 1;
415
#elif defined(DXX_BUILD_DESCENT_II)
416
        case D2_1_DISCID:
417
        case D2_2_DISCID:
418
        case D2_3_DISCID:
419
        case D2_4_DISCID:
420
        case D2_DEF_DISCID:
421
        case D2_OEM_DISCID:
422
        case D2_OEM2_DISCID:
423
        case D2_VERTIGO_DISCID:
424
        case D2_VERTIGO2_DISCID:
425
        case D2_MAC_DISCID:
426
        case D2_IPLAY_DISCID:
427
                return 1;
428
#endif
429
                default:
430
                        return 0;
431
        }
432
}
433
#endif
434
}
435
 
436
#if defined(DXX_BUILD_DESCENT_I)
437
#if DXX_USE_SDL_REDBOOK_AUDIO
438
static void redbook_repeat_func()
439
{
440
        pause_game_world_time p;
441
        RBAPlayTracks(Redbook_playing, 0, redbook_repeat_func);
442
}
443
#endif
444
#elif defined(DXX_BUILD_DESCENT_II)
445
#if DXX_USE_SDL_REDBOOK_AUDIO || DXX_USE_SDLMIXER
446
static void play_credits_track()
447
{
448
        pause_game_world_time p;
449
        songs_play_song(SONG_CREDITS, 1);
450
}
451
#endif
452
#endif
453
 
454
// play a filename as music, depending on filename extension.
455
int songs_play_file(const char *filename, int repeat, void (*hook_finished_track)())
456
{
457
        songs_stop_all();
458
#if defined(_WIN32) || DXX_USE_SDLMIXER
459
        const char *fptr = strrchr(filename, '.');
460
        if (fptr == NULL)
461
                return 0;
462
 
463
        ++ fptr;
464
        if (!d_stricmp(fptr, SONG_EXT_HMP))
465
        {
466
#if defined(_WIN32)
467
                return digi_win32_play_midi_song( filename, repeat );
468
#elif DXX_USE_SDLMIXER
469
                return mix_play_file( filename, repeat, hook_finished_track );
470
#else
471
                return 0;
472
#endif
473
        }
474
#if DXX_USE_SDLMIXER
475
        else if ( !d_stricmp(fptr, SONG_EXT_MID) ||
476
                        !d_stricmp(fptr, SONG_EXT_OGG) ||
477
                        !d_stricmp(fptr, SONG_EXT_FLAC) ||
478
                        !d_stricmp(fptr, SONG_EXT_MP3) )
479
        {
480
                return mix_play_file( filename, repeat, hook_finished_track );
481
        }
482
#endif
483
#endif
484
        return 0;
485
}
486
 
487
namespace dsx {
488
int songs_play_song( int songnum, int repeat )
489
{
490
        songs_init();
491
        if (!Songs_initialized)
492
                return 0;
493
 
494
        switch (GameCfg.MusicType)
495
        {
496
                case MUSIC_TYPE_BUILTIN:
497
                {
498
                        // EXCEPTION: If SONG_ENDLEVEL is not available, continue playing level song.
499
                        auto &s = BIMSongs[songnum];
500
                        if (Song_playing >= SONG_FIRST_LEVEL_SONG && songnum == SONG_ENDLEVEL && !PHYSFSX_exists(s.filename, 1))
501
                                return Song_playing;
502
 
503
                        Song_playing = -1;
504
                        if (songs_play_file(s.filename, repeat, NULL))
505
                                Song_playing = songnum;
506
                        break;
507
                }
508
#if DXX_USE_SDL_REDBOOK_AUDIO
509
                case MUSIC_TYPE_REDBOOK:
510
                {
511
                        int num_tracks = RBAGetNumberOfTracks();
512
 
513
#if defined(DXX_BUILD_DESCENT_I)
514
                        Song_playing = -1;
515
                        if ((songnum < SONG_ENDGAME) && (songnum + 2 <= num_tracks))
516
                        {
517
                                if (RBAPlayTracks(songnum + 2, songnum + 2, repeat ? redbook_repeat_func : NULL))
518
                                {
519
                                        Redbook_playing = songnum + 2;
520
                                        Song_playing = songnum;
521
                                }
522
                        }
523
                        else if ((songnum == SONG_ENDGAME) && (REDBOOK_ENDGAME_TRACK <= num_tracks)) // The endgame track is the last track
524
                        {
525
                                if (RBAPlayTracks(REDBOOK_ENDGAME_TRACK, REDBOOK_ENDGAME_TRACK, repeat ? redbook_repeat_func : NULL))
526
                                {
527
                                        Redbook_playing = REDBOOK_ENDGAME_TRACK;
528
                                        Song_playing = songnum;
529
                                }
530
                        }
531
                        else if ((songnum > SONG_ENDGAME) && (songnum + 1 <= num_tracks))
532
                        {
533
                                if (RBAPlayTracks(songnum + 1, songnum + 1, repeat ? redbook_repeat_func : NULL))
534
                                {
535
                                        Redbook_playing = songnum + 1;
536
                                        Song_playing = songnum;
537
                                }
538
                        }
539
#elif defined(DXX_BUILD_DESCENT_II)
540
                        //Song_playing = -1;            // keep playing current music if chosen song is unavailable (e.g. SONG_ENDLEVEL)
541
                        if ((songnum == SONG_TITLE) && (REDBOOK_TITLE_TRACK <= num_tracks))
542
                        {
543
                                if (RBAPlayTracks(REDBOOK_TITLE_TRACK, REDBOOK_TITLE_TRACK, repeat ? play_credits_track : NULL))
544
                                {
545
                                        Redbook_playing = REDBOOK_TITLE_TRACK;
546
                                        Song_playing = songnum;
547
                                }
548
                        }
549
                        else if ((songnum == SONG_CREDITS) && (REDBOOK_CREDITS_TRACK <= num_tracks))
550
                        {
551
                                if (RBAPlayTracks(REDBOOK_CREDITS_TRACK, REDBOOK_CREDITS_TRACK, repeat ? play_credits_track : NULL))
552
                                {
553
                                        Redbook_playing = REDBOOK_CREDITS_TRACK;
554
                                        Song_playing = songnum;
555
                                }
556
                        }
557
#endif
558
                        break;
559
                }
560
#endif
561
#if DXX_USE_SDLMIXER
562
                case MUSIC_TYPE_CUSTOM:
563
                {
564
                        // EXCEPTION: If SONG_ENDLEVEL is undefined, continue playing level song.
565
                        if (Song_playing >= SONG_FIRST_LEVEL_SONG && songnum == SONG_ENDLEVEL && !CGameCfg.CMMiscMusic[songnum][0])
566
                                return Song_playing;
567
 
568
                        Song_playing = -1;
569
#if defined(DXX_BUILD_DESCENT_I)
570
                        int play = songs_play_file(CGameCfg.CMMiscMusic[songnum].data(), repeat, NULL);
571
#elif defined(DXX_BUILD_DESCENT_II)
572
                        int use_credits_track = (songnum == SONG_TITLE && GameCfg.OrigTrackOrder);
573
                        int play = songs_play_file(CGameCfg.CMMiscMusic[songnum].data(),
574
                                                          // Play the credits track after the title track and loop the credits track if original CD track order was chosen
575
                                                          use_credits_track ? 0 : repeat,
576
                                                          use_credits_track ? play_credits_track : NULL);
577
#endif
578
                        if (play)
579
                                Song_playing = songnum;
580
                        break;
581
                }
582
#endif
583
                default:
584
                        Song_playing = -1;
585
                        break;
586
        }
587
 
588
        return Song_playing;
589
}
590
}
591
 
592
#if DXX_USE_SDL_REDBOOK_AUDIO
593
static void redbook_first_song_func()
594
{
595
        pause_game_world_time p;
596
        Song_playing = -1; // Playing Redbook tracks will not modify Song_playing. To repeat we must reset this so songs_play_level_song does not think we want to re-play the same song again.
597
        songs_play_level_song(1, 0);
598
}
599
#endif
600
 
601
// play track given by levelnum (depending on the music type and it's playing behaviour) or increment/decrement current track number via offset value
602
namespace dsx {
603
int songs_play_level_song( int levelnum, int offset )
604
{
605
        int songnum;
606
 
607
        Assert( levelnum != 0 );
608
 
609
        songs_init();
610
        if (!Songs_initialized)
611
                return 0;
612
 
613
        songnum = (levelnum>0)?(levelnum-1):(-levelnum);
614
 
615
        switch (GameCfg.MusicType)
616
        {
617
                case MUSIC_TYPE_BUILTIN:
618
                {
619
                        if (offset)
620
                                return Song_playing;
621
 
622
                        Song_playing = -1;
623
                        /* count_level_songs excludes songs assigned to non-levels,
624
                         * such as SONG_TITLE, SONG_BRIEFING, etc.
625
                         */
626
                        int count_level_songs;
627
                        if (levelnum < 0 && BIMSecretSongs)
628
                        {
629
                                /* Secret songs are processed separately and do not need
630
                                 * to exclude non-levels.
631
                                 */
632
                                const int secretsongnum = (-levelnum - 1) % BIMSecretSongs.size();
633
                                if (songs_play_file(BIMSecretSongs[secretsongnum].filename, 1, NULL))
634
                                        Song_playing = secretsongnum;
635
                        }
636
                        else if ((count_level_songs = BIMSongs.size() - SONG_FIRST_LEVEL_SONG) > 0)
637
                        {
638
                                songnum = SONG_FIRST_LEVEL_SONG + (songnum % count_level_songs);
639
                                if (songs_play_file(BIMSongs[songnum].filename, 1, NULL))
640
                                        Song_playing = songnum;
641
                        }
642
                        break;
643
                }
644
#if DXX_USE_SDL_REDBOOK_AUDIO
645
                case MUSIC_TYPE_REDBOOK:
646
                {
647
                        int n_tracks = RBAGetNumberOfTracks();
648
                        int tracknum;
649
 
650
                        if (!offset)
651
                        {
652
                                // we have just been told to play the same as we do already -> ignore
653
                                if (Song_playing >= SONG_FIRST_LEVEL_SONG && songnum + SONG_FIRST_LEVEL_SONG == Song_playing)
654
                                        return Song_playing;
655
 
656
#if defined(DXX_BUILD_DESCENT_I)
657
                                int bn_tracks = n_tracks;
658
#elif defined(DXX_BUILD_DESCENT_II)
659
                                int bn_tracks = n_tracks + 1;
660
#endif
661
                                tracknum = REDBOOK_FIRST_LEVEL_TRACK + ((bn_tracks<=REDBOOK_FIRST_LEVEL_TRACK) ? 0 : (songnum % (bn_tracks-REDBOOK_FIRST_LEVEL_TRACK)));
662
                        }
663
                        else
664
                        {
665
                                tracknum = Redbook_playing+offset;
666
                                if (tracknum < REDBOOK_FIRST_LEVEL_TRACK)
667
                                        tracknum = n_tracks - (REDBOOK_FIRST_LEVEL_TRACK - tracknum) + 1;
668
                                else if (tracknum > n_tracks)
669
                                        tracknum = REDBOOK_FIRST_LEVEL_TRACK + (tracknum - n_tracks) - 1;
670
                        }
671
 
672
                        Song_playing = -1;
673
                        if (RBAEnabled() && (tracknum <= n_tracks))
674
                        {
675
#if defined(DXX_BUILD_DESCENT_I)
676
                                int have_cd = songs_have_cd();
677
                                int play = RBAPlayTracks(tracknum, !have_cd?n_tracks:tracknum, have_cd ? redbook_repeat_func : redbook_first_song_func);
678
#elif defined(DXX_BUILD_DESCENT_II)
679
                                int play = RBAPlayTracks(tracknum, n_tracks, redbook_first_song_func);
680
#endif
681
                                if (play)
682
                                {
683
                                        Song_playing = songnum + SONG_FIRST_LEVEL_SONG;
684
                                        Redbook_playing = tracknum;
685
                                }
686
                        }
687
                        break;
688
                }
689
#endif
690
#if DXX_USE_SDLMIXER
691
                case MUSIC_TYPE_CUSTOM:
692
                {
693
                        if (CGameCfg.CMLevelMusicPlayOrder == LevelMusicPlayOrder::Random)
694
                                CGameCfg.CMLevelMusicTrack[0] = d_rand() % CGameCfg.CMLevelMusicTrack[1]; // simply a random selection - no check if this song has already been played. But that's how I roll!
695
                        else if (!offset)
696
                        {
697
                                if (CGameCfg.CMLevelMusicPlayOrder == LevelMusicPlayOrder::Continuous)
698
                                {
699
                                        static int last_songnum = -1;
700
 
701
                                        if (Song_playing >= SONG_FIRST_LEVEL_SONG)
702
                                                return Song_playing;
703
 
704
                                        // As soon as we start a new level, go to next track
705
                                        if (last_songnum != -1 && songnum != last_songnum)
706
                                                ((CGameCfg.CMLevelMusicTrack[0] + 1 >= CGameCfg.CMLevelMusicTrack[1])
707
                                                        ? CGameCfg.CMLevelMusicTrack[0] = 0
708
                                                        : CGameCfg.CMLevelMusicTrack[0]++);
709
                                        last_songnum = songnum;
710
                                }
711
                                else if (CGameCfg.CMLevelMusicPlayOrder == LevelMusicPlayOrder::Level)
712
                                        CGameCfg.CMLevelMusicTrack[0] = (songnum % CGameCfg.CMLevelMusicTrack[1]);
713
                        }
714
                        else
715
                        {
716
                                CGameCfg.CMLevelMusicTrack[0] += offset;
717
                                if (CGameCfg.CMLevelMusicTrack[0] < 0)
718
                                        CGameCfg.CMLevelMusicTrack[0] = CGameCfg.CMLevelMusicTrack[1] + CGameCfg.CMLevelMusicTrack[0];
719
                                if (CGameCfg.CMLevelMusicTrack[0] + 1 > CGameCfg.CMLevelMusicTrack[1])
720
                                        CGameCfg.CMLevelMusicTrack[0] = CGameCfg.CMLevelMusicTrack[0] - CGameCfg.CMLevelMusicTrack[1];
721
                        }
722
 
723
                        Song_playing = -1;
724
                        if (jukebox_play())
725
                                Song_playing = songnum + SONG_FIRST_LEVEL_SONG;
726
 
727
                        break;
728
                }
729
#endif
730
                default:
731
                        Song_playing = -1;
732
                        break;
733
        }
734
 
735
#if defined(DXX_BUILD_DESCENT_I)
736
        // If we couldn't play the song, most likely because it wasn't specified, play no music.
737
        if (Song_playing == -1)
738
                songs_stop_all();
739
#endif
740
        return Song_playing;
741
}
742
}
743
 
744
// check which song is playing, or -1 if not playing anything
745
int songs_is_playing()
746
{
747
        return Song_playing;
748
}
749