Subversion Repositories Games.Descent

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 pmbaty 1
/*
2
 * This file is part of the DXX-Rebirth project <https://www.dxx-rebirth.com/>.
3
 * It is copyright by its individual contributors, as recorded in the
4
 * project's Git history.  See COPYING.txt at the top level for license
5
 * terms and a link to the Git history.
6
 */
7
/*
8
 *
9
 * CD Audio functions for Win32
10
 *
11
 *
12
 */
13
 
14
#include <algorithm>
15
#include <stdio.h>
16
#include <stdlib.h>
17
#include <windows.h>
18
#include <mmsystem.h>
19
#include <climits>
20
 
21
#include "pstypes.h"
22
#include "dxxerror.h"
23
#include "args.h"
24
#include "rbaudio.h"
25
#include "console.h"
26
#include "fwd-gr.h"
27
#include "timer.h"
28
 
29
#if SDL_MAJOR_VERSION == 1
30
// we don't want this on SDL1 because there we have common/arch/sdl/rbaudio.cpp
31
// #pragma message("Skipping win32/rbaudio because of SDL1")
32
 
33
#elif SDL_MAJOR_VERSION == 2
34
 
35
namespace dcx {
36
#define CD_FPS 75
37
 
38
static UINT wCDDeviceID = 0U;
39
static bool initialised;
40
static DWORD playEnd;
41
static DWORD lastFrames;
42
static bool isPaused;
43
 
44
void RBAExit()
45
{
46
        if (wCDDeviceID)
47
        {
48
                initialised = false;
49
                mciSendCommand(wCDDeviceID, MCI_CLOSE, MCI_WAIT, 0);
50
                wCDDeviceID = 0U;
51
        }
52
}
53
 
54
static bool mci_HasMedia()
55
{
56
        if (!wCDDeviceID) return false;
57
 
58
        MCIERROR mciError;
59
        MCI_STATUS_PARMS mciStatusParms;
60
 
61
        mciStatusParms.dwItem = MCI_STATUS_MEDIA_PRESENT;
62
        if ((mciError = mciSendCommand(wCDDeviceID, MCI_STATUS, MCI_STATUS_ITEM, reinterpret_cast<DWORD_PTR>(&mciStatusParms))))
63
        {
64
                Warning("RBAudio win32/MCI: cannot determine MCI media status (%lx)", mciError);
65
                RBAExit();
66
                return false;
67
        }
68
        return mciStatusParms.dwReturn != 0;
69
}
70
 
71
static unsigned mci_TotalFramesMsf(const DWORD msf)
72
{
73
        return (MCI_MSF_MINUTE(msf) * 60 + MCI_MSF_SECOND(msf)) * CD_FPS + MCI_MSF_FRAME(msf);
74
}
75
 
76
static unsigned mci_FramesToMsf(const int frames)
77
{
78
        int m = frames / (CD_FPS * 60);
79
        int s = (frames / CD_FPS) % 60;
80
        int f = frames % CD_FPS;
81
        return MCI_MAKE_MSF(m, s, f);
82
}
83
 
84
static unsigned mci_GetTrackOffset(const int track)
85
{
86
        MCIERROR mciError;
87
        MCI_STATUS_PARMS mciStatusParms;
88
 
89
        mciStatusParms.dwItem = MCI_STATUS_POSITION;
90
        mciStatusParms.dwTrack = track;
91
        if ((mciError = mciSendCommand(wCDDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK, reinterpret_cast<DWORD_PTR>(&mciStatusParms))))
92
        {
93
                Warning("RBAudio win32/MCI: cannot determine track %i offset (%lx)", track, mciError);
94
                return -1;
95
        }
96
        // dwReturn is a 32-bit value in MSF format, so DWORD_PTR > DWORD is not a problem
97
        return mci_TotalFramesMsf(mciStatusParms.dwReturn);
98
}
99
 
100
static unsigned mci_GetTrackLength(const int track)
101
{
102
        MCIERROR mciError;
103
        MCI_STATUS_PARMS mciStatusParms;
104
 
105
        mciStatusParms.dwItem = MCI_STATUS_LENGTH;
106
        mciStatusParms.dwTrack = track;
107
        if ((mciError = mciSendCommand(wCDDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK, reinterpret_cast<DWORD_PTR>(&mciStatusParms))))
108
        {
109
                Warning("RBAudio win32/MCI: cannot determine track %i length (%lx)", track, mciError);
110
                return -1;
111
        }
112
        // dwReturn is a 32-bit value in MSF format, so DWORD_PTR > DWORD is not a problem
113
        return mci_TotalFramesMsf(mciStatusParms.dwReturn);
114
}
115
 
116
static unsigned mci_GetTotalLength()
117
{
118
        MCIERROR mciError;
119
        MCI_STATUS_PARMS mciStatusParms;
120
 
121
        mciStatusParms.dwItem = MCI_STATUS_LENGTH;
122
        if ((mciError = mciSendCommand(wCDDeviceID, MCI_STATUS, MCI_STATUS_ITEM, reinterpret_cast<DWORD_PTR>(&mciStatusParms))))
123
        {
124
                Warning("RBAudio win32/MCI: cannot determine media length (%lx)", mciError);
125
                return -1;
126
        }
127
        return mci_TotalFramesMsf(mciStatusParms.dwReturn);
128
}
129
 
130
void RBAInit()
131
{
132
        MCIERROR mciError;
133
        MCI_OPEN_PARMS mciOpenParms;
134
        MCI_SET_PARMS mciSetParms;
135
 
136
        if (initialised) return;
137
 
138
        mciOpenParms.lpstrDeviceType = "cdaudio";
139
        if ((mciError = mciSendCommand(0, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_SHAREABLE, reinterpret_cast<DWORD_PTR>(&mciOpenParms))))
140
        {
141
                con_puts(CON_NORMAL, "RBAudio win32/MCI: cannot find MCI cdaudio (no CD drive?)");
142
                return;
143
        }
144
 
145
        wCDDeviceID = mciOpenParms.wDeviceID;
146
 
147
        if (!mci_HasMedia())
148
        {
149
                con_puts(CON_NORMAL, "RBAudio win32/MCI: no media in CD drive.");
150
                RBAExit();
151
                return;
152
        }
153
 
154
        mciSetParms.dwTimeFormat = MCI_FORMAT_MSF;
155
        if ((mciError = mciSendCommand(wCDDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, reinterpret_cast<DWORD_PTR>(&mciSetParms))))
156
        {
157
                Warning("RBAudio win32/MCI: cannot set time format for CD to MSF (strange)");
158
                RBAExit();
159
                return;
160
        }
161
 
162
        initialised = true;
163
        RBAList();
164
}
165
 
166
int RBAEnabled()
167
{
168
        return initialised;
169
}
170
 
171
static void (*redbook_finished_hook)() = nullptr;
172
 
173
int RBAPlayTrack(int a)
174
{
175
        if (!wCDDeviceID)
176
                return 0;
177
 
178
        if (mci_HasMedia())
179
        {
180
                MCIERROR mciError;
181
                MCI_PLAY_PARMS mciPlayParms;
182
                DWORD playStart;
183
 
184
                con_printf(CON_VERBOSE, "RBAudio win32/MCI: Playing track %i", a);
185
 
186
                playStart = mci_GetTrackOffset(a);
187
                playEnd = playStart + mci_GetTrackLength(a);
188
 
189
                mciPlayParms.dwFrom = mci_FramesToMsf(playStart);
190
                mciPlayParms.dwTo = mci_FramesToMsf(playEnd);
191
 
192
                if ((mciError = mciSendCommand(wCDDeviceID, MCI_PLAY, MCI_FROM | MCI_TO, reinterpret_cast<DWORD_PTR>(&mciPlayParms))))
193
                {
194
                        Warning("RBAudio win32/MCI: could not play track (%lx)", mciError);
195
                        return 0;
196
                }
197
 
198
                return 1;
199
        }
200
        return 0;
201
}
202
 
203
// plays tracks first through last, inclusive
204
int RBAPlayTracks(int first, int last, void (*hook_finished)(void))
205
{
206
        if (!wCDDeviceID)
207
                return 0;
208
 
209
        if (mci_HasMedia())
210
        {
211
                MCIERROR mciError;
212
                MCI_PLAY_PARMS mciPlayParms;
213
 
214
                redbook_finished_hook = hook_finished;
215
 
216
                con_printf(CON_VERBOSE, "RBAudio win32/MCI: Playing tracks %i to %i", first, last);
217
 
218
                playEnd = mci_GetTrackOffset(last) + mci_GetTrackLength(last);
219
 
220
                mciPlayParms.dwFrom = mci_FramesToMsf(mci_GetTrackOffset(first));
221
                mciPlayParms.dwTo = mci_FramesToMsf(playEnd);
222
 
223
                if ((mciError = mciSendCommand(wCDDeviceID, MCI_PLAY, MCI_FROM | MCI_TO, reinterpret_cast<DWORD_PTR>(&mciPlayParms))))
224
                {
225
                        Warning("RBAudio win32/MCI: could not play tracks (%lx)", mciError);
226
                        return 0;
227
                }
228
 
229
                return 1;
230
        }
231
        return 0;
232
}
233
 
234
void RBAStop()
235
{
236
        if (!wCDDeviceID) return;
237
 
238
        MCIERROR mciError;
239
 
240
        if ((mciError = mciSendCommand(wCDDeviceID, MCI_STOP, 0, 0)))
241
        {
242
                Warning("RBAudio win32/MCI: could not stop music (%lx)", mciError);
243
        }
244
        redbook_finished_hook = nullptr;
245
}
246
 
247
void RBAEjectDisk()
248
{
249
        if (!wCDDeviceID) return;
250
 
251
        MCIERROR mciError;
252
 
253
        if ((mciError = mciSendCommand(wCDDeviceID, MCI_SET, MCI_SET_DOOR_OPEN | MCI_WAIT, 0)))
254
        {
255
                Warning("RBAudio win32/MCI: could not open CD tray (%lx)", mciError);
256
        }
257
        initialised = false;
258
}
259
 
260
void RBASetVolume(int)
261
{
262
        // MCI does not support this
263
}
264
 
265
void RBAPause()
266
{
267
        if (!wCDDeviceID) return;
268
 
269
        MCIERROR mciError;
270
 
271
        if ((mciError = mciSendCommand(wCDDeviceID, MCI_PAUSE, 0, 0)))
272
        {
273
                Warning("RBAudio win32/MCI: could not pause music (%lx)", mciError);
274
                return;
275
        }
276
        con_puts(CON_VERBOSE, "RBAudio win32/MCI: Playback paused");
277
        isPaused = true;
278
}
279
 
280
int RBAResume()
281
{
282
        if (!wCDDeviceID) return -1;
283
 
284
        MCIERROR mciError;
285
 
286
        if ((mciError = mciSendCommand(wCDDeviceID, MCI_RESUME, 0, 0)))
287
        {
288
                Warning("RBAudio win32/MCI: could not resume music (%lx)", mciError);
289
                return -1;
290
        }
291
        con_puts(CON_VERBOSE, "RBAudio win32/MCI: Playback resumed");
292
        isPaused = false;
293
        return 1;
294
}
295
 
296
int RBAPauseResume()
297
{
298
        if (!wCDDeviceID) return 0;
299
 
300
        MCIERROR mciError;
301
        MCI_STATUS_PARMS mciStatusParms;
302
 
303
        mciStatusParms.dwItem = MCI_STATUS_MODE;
304
        if ((mciError = mciSendCommand(wCDDeviceID, MCI_STATUS, MCI_STATUS_ITEM, reinterpret_cast<DWORD_PTR>(&mciStatusParms))))
305
        {
306
                Warning("RBAudio win32/MCI: cannot determine MCI media status (%lx)", mciError);
307
                return 0;
308
        }
309
 
310
        if (mciStatusParms.dwReturn == MCI_MODE_PLAY)
311
        {
312
                con_puts(CON_VERBOSE, "RBAudio win32/MCI: Toggle Playback pause");
313
                RBAPause();
314
        }
315
        // MCI may also use MCI_MODE_STOP for pause instead of MCI_MODE_PAUSE
316
        else if (mciStatusParms.dwReturn == MCI_MODE_PAUSE || (isPaused && mciStatusParms.dwReturn == MCI_MODE_STOP))
317
        {
318
                con_puts(CON_VERBOSE, "RBAudio win32/MCI: Toggle Playback resume");
319
                return RBAResume() > 0;
320
        }
321
        else
322
                return 0;
323
 
324
        return 1;
325
}
326
 
327
int RBAGetNumberOfTracks()
328
{
329
        if (!wCDDeviceID) return -1;
330
 
331
        MCIERROR mciError;
332
        MCI_STATUS_PARMS mciStatusParms;
333
 
334
        mciStatusParms.dwItem = MCI_STATUS_NUMBER_OF_TRACKS;
335
        if ((mciError = mciSendCommand(wCDDeviceID, MCI_STATUS, MCI_STATUS_ITEM, reinterpret_cast<DWORD_PTR>(&mciStatusParms))))
336
        {
337
                Warning("RBAudio win32/MCI: could not get track count (%lx)", mciError);
338
                return -1;
339
        }
340
 
341
        return static_cast<int>(mciStatusParms.dwReturn);
342
}
343
 
344
// check if we need to call the 'finished' hook
345
// needs to go in all event loops
346
// MCI has a hook function via Win32 messages, but it doesn't work for CDs
347
//   for whatever reason
348
void RBACheckFinishedHook()
349
{
350
        static fix64 last_check_time = 0;
351
 
352
        if (!wCDDeviceID) return;
353
 
354
        if ((timer_query() - last_check_time) >= F2_0)
355
        {
356
                MCIERROR mciError;
357
                MCI_STATUS_PARMS mciStatusParms;
358
 
359
                mciStatusParms.dwItem = MCI_STATUS_POSITION;
360
                if ((mciError = mciSendCommand(wCDDeviceID, MCI_STATUS, MCI_STATUS_ITEM, reinterpret_cast<DWORD_PTR>(&mciStatusParms))))
361
                {
362
                        Warning("RBAudio win32/MCI: cannot determine MCI position (%lx)", mciError);
363
                        return;
364
                }
365
 
366
                // time for a hack. for some reason, MCI sometimes stops
367
                // from around a few to few dozen frames before it should,
368
                // so we will check if we haven't moved from the last check
369
                // and allow a bit of a leeway when checking if so.
370
 
371
                DWORD checkValue = playEnd;
372
                // dwReturn is a 32-bit value in MSF format, so DWORD_PTR > DWORD is not a problem
373
                DWORD thisFrames = mci_TotalFramesMsf(mciStatusParms.dwReturn);
374
 
375
                if (thisFrames == lastFrames)
376
                        checkValue = checkValue < 64 ? 0 : checkValue - 64; // prevent underflow
377
 
378
                if (redbook_finished_hook && playEnd > 0 && thisFrames >= checkValue)
379
                {
380
                        con_puts(CON_VERBOSE, "RBAudio win32/MCI: Playback done, calling finished-hook");
381
                        redbook_finished_hook();
382
                }
383
                lastFrames = thisFrames;
384
                last_check_time = timer_query();
385
        }
386
}
387
 
388
// return the track number currently playing.  Useful if RBAPlayTracks()
389
// is called.  Returns 0 if no track playing, else track number
390
int RBAGetTrackNum()
391
{
392
        if (!wCDDeviceID) return 0;
393
 
394
        MCIERROR mciError;
395
        MCI_STATUS_PARMS mciStatusParms;
396
 
397
        mciStatusParms.dwItem = MCI_STATUS_MODE;
398
        if ((mciError = mciSendCommand(wCDDeviceID, MCI_STATUS, MCI_STATUS_ITEM, reinterpret_cast<DWORD_PTR>(&mciStatusParms))))
399
        {
400
                Warning("RBAudio win32/MCI: cannot determine MCI media status (%lx)", mciError);
401
                return 0;
402
        }
403
 
404
        if (mciStatusParms.dwReturn != MCI_MODE_PLAY)
405
                return 0;
406
 
407
        mciStatusParms.dwItem = MCI_STATUS_CURRENT_TRACK;
408
        if ((mciError = mciSendCommand(wCDDeviceID, MCI_STATUS, MCI_STATUS_ITEM, reinterpret_cast<DWORD_PTR>(&mciStatusParms))))
409
        {
410
                Warning("RBAudio win32/MCI: cannot determine MCI track number (%lx)", mciError);
411
                return 0;
412
        }
413
 
414
        return static_cast<int>(mciStatusParms.dwReturn);
415
}
416
 
417
int RBAPeekPlayStatus()
418
{
419
        if (!wCDDeviceID) return 0;
420
 
421
        MCIERROR mciError;
422
        MCI_STATUS_PARMS mciStatusParms;
423
 
424
        mciStatusParms.dwItem = MCI_STATUS_MODE;
425
        if ((mciError = mciSendCommand(wCDDeviceID, MCI_STATUS, MCI_STATUS_ITEM, reinterpret_cast<DWORD_PTR>(&mciStatusParms))))
426
        {
427
                Warning("RBAudio win32/MCI: cannot determine MCI media status (%lx)", mciError);
428
                return 0;
429
        }
430
 
431
        if (mciStatusParms.dwReturn == MCI_MODE_PLAY)
432
                return 1;
433
        // MCI may also use MCI_MODE_STOP for pause instead of MCI_MODE_PAUSE
434
        else if (mciStatusParms.dwReturn == MCI_MODE_PAUSE || (isPaused && mciStatusParms.dwReturn == MCI_MODE_STOP))
435
                return -1; // hack so it doesn't keep restarting paused music; -1 is still truthy, unlike 0
436
        else
437
                return 0;
438
}
439
 
440
static int cddb_sum(int n)
441
{
442
        int ret;
443
 
444
        /* For backward compatibility this algorithm must not change */
445
 
446
        ret = 0;
447
 
448
        while (n > 0) {
449
                ret = ret + (n % 10);
450
                n = n / 10;
451
        }
452
 
453
        return (ret);
454
}
455
 
456
unsigned long RBAGetDiscID()
457
{
458
        int i, t = 0, n = 0, trackCount, totalLength;
459
 
460
        if (!wCDDeviceID)
461
                return 0;
462
 
463
        MCIERROR mciError;
464
        trackCount = RBAGetNumberOfTracks();
465
        totalLength = mci_GetTotalLength();
466
        if (totalLength < 0)
467
        {
468
                Warning("RBAudio win32/MCI: cannot determine total length (%lx)", mciError);
469
                return 0;
470
        }
471
 
472
        /* For backward compatibility this algorithm must not change */
473
 
474
        i = 1;
475
 
476
        while (i <= trackCount)
477
        {
478
                int offset = mci_GetTrackOffset(i);
479
                if (offset < 0)
480
                {
481
                        Warning("RBAudio win32/MCI: cannot determine track %i offset (%lx)", i, mciError);
482
                        return 0;
483
                }
484
                n += cddb_sum(offset / CD_FPS);
485
                i++;
486
        }
487
 
488
        t = totalLength / CD_FPS;
489
 
490
        return ((n % 0xff) << 24 | t << 8 | trackCount);
491
}
492
 
493
void RBAList(void)
494
{
495
        if (!wCDDeviceID) return;
496
 
497
        MCIERROR mciError;
498
        MCI_STATUS_PARMS mciStatusParms;
499
        int trackCount = RBAGetNumberOfTracks();
500
 
501
        for (int i = 1; i <= trackCount; ++i) {
502
                int isAudioTrack, length, offset;
503
                mciStatusParms.dwTrack = i;
504
 
505
                mciStatusParms.dwItem = MCI_CDA_STATUS_TYPE_TRACK;
506
                if ((mciError = mciSendCommand(wCDDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK, reinterpret_cast<DWORD_PTR>(&mciStatusParms))))
507
                {
508
                        Warning("RBAudio win32/MCI: cannot determine track %d type (%lx)", i, mciError);
509
                        continue;
510
                }
511
                isAudioTrack = mciStatusParms.dwReturn == MCI_CDA_TRACK_AUDIO;
512
 
513
                offset = mci_GetTrackOffset(i);
514
                if (offset < 0)
515
                {
516
                        continue;
517
                }
518
 
519
                length = mci_GetTrackLength(i);
520
                if (length < 0)
521
                {
522
                        continue;
523
                }
524
 
525
                con_printf(CON_VERBOSE, "RBAudio win32/MCI: CD track %d, type %s, length %d, offset %d", i, isAudioTrack ? "audio" : "data", length, offset);
526
        }
527
}
528
 
529
}
530
 
531
#endif