1 // Emacs style mode select -*- C++ -*-
2 //-----------------------------------------------------------------------------
3 //
4 // Copyright (C) 1998-2000 by DooM Legacy Team.
5 //
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
15 //-----------------------------------------------------------------------------
16 /// \file
17 /// \brief cd music interface (uses MCI).
18
19 #include "../doomdef.h"
20 #include "../doomstat.h"
21 #ifdef _WINDOWS
22 #include "win_main.h"
23 #include <mmsystem.h>
24
25 #include "../command.h"
26 #include "../doomtype.h"
27 #include "../i_sound.h"
28 #include "../i_system.h"
29
30 #include "../s_sound.h"
31
32 #define MAX_CD_TRACKS 255
33
34 typedef struct {
35 BOOL IsAudio;
36 DWORD Start, End;
37 DWORD Length; // minutes
38 } CDTrack;
39
40 // -------
41 // private
42 // -------
43 static CDTrack m_nTracks[MAX_CD_TRACKS];
44 static int m_nTracksCount; // up to MAX_CD_TRACKS
45 static MCI_STATUS_PARMS m_MCIStatus;
46 static MCI_OPEN_PARMS m_MCIOpen;
47
48 // ------
49 // protos
50 // ------
51 static void Command_Cd_f (void);
52
53
54 // -------------------
55 // MCIErrorMessageBox
56 // Retrieve error message corresponding to return value from
57 // mciSendCommand() or mciSenString()
58 // -------------------
MCIErrorMessageBox(MCIERROR iErrorCode)59 static VOID MCIErrorMessageBox (MCIERROR iErrorCode)
60 {
61 char szErrorText[128];
62 if (!mciGetErrorStringA (iErrorCode, szErrorText, sizeof (szErrorText)))
63 wsprintfA(szErrorText,"MCI CD Audio Unknown Error #%lu\n", iErrorCode);
64 I_OutputMsg("%s", szErrorText);
65 /*MessageBox (GetActiveWindow(), szTemp+1, "LEGACY",
66 MB_OK | MB_ICONSTOP);*/
67 }
68
69
70 // --------
71 // CD_Reset
72 // --------
CD_Reset(VOID)73 static VOID CD_Reset (VOID)
74 {
75 // no win32 equivalent
76 //faB: for DOS, some odd drivers like to be reset sometimes.. useless in MCI I guess
77 }
78
79
80 // ----------------
81 // CD_ReadTrackInfo
82 // Read in number of tracks, and length of each track in minutes/seconds
83 // returns true if error
84 // ----------------
CD_ReadTrackInfo(VOID)85 static BOOL CD_ReadTrackInfo(VOID)
86 {
87 UINT nTrackLength;
88 INT i;
89 MCIERROR iErr;
90
91 m_nTracksCount = 0;
92
93 m_MCIStatus.dwItem = MCI_STATUS_NUMBER_OF_TRACKS;
94 iErr = mciSendCommand(m_MCIOpen.wDeviceID, MCI_STATUS, MCI_STATUS_ITEM|MCI_WAIT, (DWORD_PTR)&m_MCIStatus);
95 if (iErr)
96 {
97 MCIErrorMessageBox (iErr);
98 return FALSE;
99 }
100 m_nTracksCount = (int)m_MCIStatus.dwReturn;
101 if (m_nTracksCount > MAX_CD_TRACKS)
102 m_nTracksCount = MAX_CD_TRACKS;
103
104 for (i = 0; i < m_nTracksCount; i++)
105 {
106 m_MCIStatus.dwTrack = (DWORD)(i+1);
107 m_MCIStatus.dwItem = MCI_STATUS_LENGTH;
108 iErr = mciSendCommand(m_MCIOpen.wDeviceID, MCI_STATUS, MCI_TRACK|MCI_STATUS_ITEM|MCI_WAIT, (DWORD_PTR)&m_MCIStatus);
109 if (iErr)
110 {
111 MCIErrorMessageBox (iErr);
112 return FALSE;
113 }
114 nTrackLength = (DWORD)(MCI_MSF_MINUTE(m_MCIStatus.dwReturn)*60 + MCI_MSF_SECOND(m_MCIStatus.dwReturn));
115 m_nTracks[i].Length = nTrackLength;
116
117 m_MCIStatus.dwItem = MCI_CDA_STATUS_TYPE_TRACK;
118 iErr = mciSendCommand(m_MCIOpen.wDeviceID, MCI_STATUS, MCI_TRACK|MCI_STATUS_ITEM|MCI_WAIT, (DWORD_PTR)&m_MCIStatus);
119 if (iErr)
120 {
121 MCIErrorMessageBox (iErr);
122 return FALSE;
123 }
124 m_nTracks[i].IsAudio = (m_MCIStatus.dwReturn == MCI_CDA_TRACK_AUDIO);
125 }
126
127 return TRUE;
128 }
129
130
131 // ------------
132 // CD_TotalTime
133 // returns total time for all audio tracks in seconds
134 // ------------
CD_TotalTime(VOID)135 static UINT CD_TotalTime(VOID)
136 {
137 UINT nTotalLength = 0;
138 INT nTrack;
139 for (nTrack = 0; nTrack < m_nTracksCount; nTrack++)
140 {
141 if (m_nTracks[nTrack].IsAudio)
142 nTotalLength += m_nTracks[nTrack].Length;
143 }
144 return nTotalLength;
145 }
146
147
148 //======================================================================
149 // CD AUDIO MUSIC SUBSYSTEM
150 //======================================================================
151
152 UINT8 cdaudio_started = 0; // for system startup/shutdown
153
154 static BOOL cdPlaying = FALSE;
155 static INT cdPlayTrack; // when cdPlaying is true
156 static BOOL cdLooping = FALSE;
157 static BYTE cdRemap[MAX_CD_TRACKS];
158 static BOOL cdEnabled = TRUE; // cd info available
159 static BOOL cdValid; // true when last cd audio info was ok
160 static BOOL wasPlaying;
161 //static INT cdVolume = 0; // current cd volume (0-31)
162
163 // 0-31 like Music & Sfx, though CD hardware volume is 0-255.
164 consvar_t cd_volume = CVAR_INIT ("cd_volume","18",CV_SAVE,soundvolume_cons_t, NULL);
165
166 // allow Update for next/loop track
167 // some crap cd drivers take up to
168 // a second for a simple 'busy' check..
169 // (on those Update can be disabled)
170 consvar_t cdUpdate = CVAR_INIT ("cd_update","1",CV_SAVE, NULL, NULL);
171
172 #if (__GNUC__ > 6)
173 #pragma GCC diagnostic push
174 #pragma GCC diagnostic ignored "-Wformat-overflow"
175 #endif
176 // hour,minutes,seconds
hms(UINT seconds)177 static LPSTR hms(UINT seconds)
178 {
179 UINT hours, minutes;
180 static CHAR s[9];
181
182 minutes = seconds / 60;
183 seconds %= 60;
184 hours = minutes / 60;
185 minutes %= 60;
186 if (hours > 0)
187 sprintf (s, "%lu:%02lu:%02lu", (long unsigned int)hours, (long unsigned int)minutes, (long unsigned int)seconds);
188 else
189 sprintf (s, "%2lu:%02lu", (long unsigned int)minutes, (long unsigned int)seconds);
190 return s;
191 }
192 #if (__GNUC__ > 6)
193 #pragma GCC diagnostic pop
194 #endif
195
Command_Cd_f(void)196 static void Command_Cd_f(void)
197 {
198 LPCSTR s;
199 int i,j;
200
201 if (!cdaudio_started)
202 return;
203
204 if (COM_Argc() < 2)
205 {
206 CONS_Printf (M_GetText(
207 "cd [on] [off] [remap] [reset] [select]\n"
208 " [open] [info] [play <track>] [resume]\n"
209 " [stop] [pause] [loop <track>]\n"));
210 return;
211 }
212
213 s = COM_Argv(1);
214
215 // activate cd music
216 if (!strncmp(s,"on",2))
217 {
218 cdEnabled = TRUE;
219 return;
220 }
221
222 // stop/deactivate cd music
223 if (!strncmp(s, "off", 3))
224 {
225 if (cdPlaying)
226 I_StopCD();
227 cdEnabled = FALSE;
228 return;
229 }
230
231 // remap tracks
232 if (!strncmp(s, "remap", 5))
233 {
234 i = (int)COM_Argc() - 2;
235 if (i <= 0)
236 {
237 CONS_Printf(M_GetText("CD tracks remapped in that order :\n"));
238 for (j = 1; j < MAX_CD_TRACKS; j++)
239 if (cdRemap[j] != j)
240 CONS_Printf(" %2d -> %2d\n", j, cdRemap[j]);
241 return;
242 }
243 for (j = 1; j <= i; j++)
244 cdRemap[j] = (UINT8)atoi(COM_Argv(j+1));
245 return;
246 }
247
248 // reset the CD driver, useful on some odd cd's
249 if (!strncmp(s,"reset",5))
250 {
251 cdEnabled = TRUE;
252 if (cdPlaying)
253 I_StopCD ();
254 for (i = 0; i < MAX_CD_TRACKS; i++)
255 cdRemap[i] = (UINT8)i;
256 CD_Reset();
257 cdValid = CD_ReadTrackInfo();
258 return;
259 }
260
261 // any other command is not allowed until we could retrieve cd information
262 if (!cdValid)
263 {
264 CONS_Printf(M_GetText("CD is not ready.\n"));
265 return;
266 }
267
268 /* faB: not with MCI, didn't find it, useless anyway
269 if (!strncmp(s,"open",4))
270 {
271 if (cdPlaying)
272 I_StopCD ();
273 bcd_open_door();
274 cdValid = FALSE;
275 return;
276 }*/
277
278 if (!strncmp(s,"info",4))
279 {
280 if (!CD_ReadTrackInfo())
281 {
282 cdValid = FALSE;
283 return;
284 }
285
286 cdValid = TRUE;
287
288 if (m_nTracksCount <= 0)
289 CONS_Printf(M_GetText("No audio tracks\n"));
290 else
291 {
292 // display list of tracks
293 // highlight current playing track
294 for (i = 0; i < m_nTracksCount; i++)
295 {
296 CONS_Printf("%s%2d. %s %s\n",
297 cdPlaying && (cdPlayTrack == i) ? "\x82 " : " ",
298 i+1, m_nTracks[i].IsAudio ? M_GetText("audio") : M_GetText("data "),
299 hms(m_nTracks[i].Length));
300 }
301 CONS_Printf(M_GetText("\x82Total time : %s\n"), hms(CD_TotalTime()));
302 }
303 if (cdPlaying)
304 {
305 CONS_Printf(M_GetText("Currently %s track %u\n"), cdLooping ? M_GetText("looping") : M_GetText("playing"), cdPlayTrack);
306 }
307 return;
308 }
309
310 if (!strncmp(s,"play",4))
311 {
312 I_PlayCD ((UINT8)atoi (COM_Argv (2)), false);
313 return;
314 }
315
316 if (!strncmp(s,"stop",4))
317 {
318 I_StopCD ();
319 return;
320 }
321
322 if (!strncmp(s,"loop",4))
323 {
324 I_PlayCD((UINT8)atoi (COM_Argv (2)), true);
325 return;
326 }
327
328 if (!strncmp(s,"resume",4))
329 {
330 I_ResumeCD ();
331 return;
332 }
333
334 CONS_Printf (M_GetText("Invalid CD command \"CD %s\"\n"), s);
335 }
336
337
338 // ------------
339 // I_ShutdownCD
340 // Shutdown CD Audio subsystem, release whatever was allocated
341 // ------------
I_ShutdownCD(void)342 void I_ShutdownCD(void)
343 {
344 MCIERROR iErr;
345
346 if (!cdaudio_started)
347 return;
348
349 CONS_Printf("I_ShutdownCD: ");
350
351 I_StopCD();
352
353 // closes MCI CD
354 iErr = mciSendCommand(m_MCIOpen.wDeviceID, MCI_CLOSE, 0, 0);
355 if (iErr)
356 MCIErrorMessageBox (iErr);
357 }
358
359
360 // --------
361 // I_InitCD
362 // Init CD Audio subsystem
363 // --------
I_InitCD(void)364 void I_InitCD(void)
365 {
366 MCI_SET_PARMS mciSet;
367 MCIERROR iErr;
368 int i;
369
370 // We don't have an open device yet
371 m_MCIOpen.wDeviceID = 0;
372 m_nTracksCount = 0;
373
374 cdaudio_started = false;
375
376 m_MCIOpen.lpstrDeviceType = (LPCTSTR)MCI_DEVTYPE_CD_AUDIO;
377 iErr = mciSendCommand(0, MCI_OPEN, MCI_OPEN_TYPE|MCI_OPEN_TYPE_ID, (DWORD_PTR)&m_MCIOpen);
378 if (iErr)
379 {
380 MCIErrorMessageBox (iErr);
381 return;
382 }
383
384 // Set the time format to track/minute/second/frame (TMSF).
385 mciSet.dwTimeFormat = MCI_FORMAT_TMSF;
386 iErr = mciSendCommand(m_MCIOpen.wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD_PTR)&mciSet);
387 if (iErr)
388 {
389 MCIErrorMessageBox (iErr);
390 mciSendCommand(m_MCIOpen.wDeviceID, MCI_CLOSE, 0, 0);
391 return;
392 }
393
394 I_AddExitFunc (I_ShutdownCD);
395 cdaudio_started = true;
396
397 CONS_Printf (M_GetText("CD audio Initialized\n"));
398
399 // last saved in config.cfg
400 i = cd_volume.value;
401 //I_SetVolumeCD (0); // initialize to 0 for some odd cd drivers
402 I_SetVolumeCD (i); // now set the last saved volume
403
404 for (i = 0; i < MAX_CD_TRACKS; i++)
405 cdRemap[i] = (UINT8)i;
406
407 if (!CD_ReadTrackInfo())
408 {
409 CONS_Printf(M_GetText("No CD in drive\n"));
410 cdEnabled = FALSE;
411 cdValid = FALSE;
412 }
413 else
414 {
415 cdEnabled = TRUE;
416 cdValid = TRUE;
417 }
418
419 COM_AddCommand ("cd", Command_Cd_f);
420 }
421
422
423
424 // loop/go to next track when track is finished (if cd_update var is true)
425 // update the volume when it has changed (from console/menu)
I_UpdateCD(void)426 void I_UpdateCD(void)
427 {
428 /// \todo check for cd change and restart music ?
429 }
430
431
432 //
I_PlayCD(UINT8 nTrack,UINT8 bLooping)433 void I_PlayCD(UINT8 nTrack, UINT8 bLooping)
434 {
435 MCI_PLAY_PARMS mciPlay;
436 MCIERROR iErr;
437
438 if (!cdaudio_started || !cdEnabled)
439 return;
440
441 //faB: try again if it didn't work (just free the user of typing 'cd reset' command)
442 if (!cdValid)
443 cdValid = CD_ReadTrackInfo();
444 if (!cdValid)
445 return;
446
447 // tracks start at 0 in the code..
448 nTrack--;
449 if (nTrack >= m_nTracksCount)
450 nTrack = (UINT8) (nTrack % m_nTracksCount);
451
452 nTrack = cdRemap[nTrack];
453
454 if (cdPlaying)
455 {
456 if (cdPlayTrack == nTrack)
457 return;
458 I_StopCD ();
459 }
460
461 cdPlayTrack = nTrack;
462
463 if (!m_nTracks[nTrack].IsAudio)
464 {
465 //I_OutputMsg("\x82""CD Play: not an audio track\n"); // Tails 03-25-2001
466 return;
467 }
468
469 cdLooping = bLooping;
470
471 //faB: stop MIDI music, MIDI music will restart if volume is upped later
472 cv_digmusicvolume.value = 0;
473 cv_midimusicvolume.value = 0;
474 I_StopSong();
475
476 //faB: I don't use the notify message, I'm trying to minimize the delay
477 mciPlay.dwCallback = (DWORD)((size_t)hWndMain);
478 mciPlay.dwFrom = MCI_MAKE_TMSF(nTrack+1, 0, 0, 0);
479 iErr = mciSendCommand(m_MCIOpen.wDeviceID, MCI_PLAY, MCI_FROM|MCI_NOTIFY, (DWORD_PTR)&mciPlay);
480 if (iErr)
481 {
482 MCIErrorMessageBox (iErr);
483 cdValid = FALSE;
484 cdPlaying = FALSE;
485 return;
486 }
487
488 cdPlaying = TRUE;
489 }
490
491
492 // pause cd music
I_StopCD(void)493 void I_StopCD(void)
494 {
495 MCIERROR iErr;
496
497 if (!cdaudio_started || !cdEnabled)
498 return;
499
500 iErr = mciSendCommand(m_MCIOpen.wDeviceID, MCI_PAUSE, MCI_WAIT, 0);
501 if (iErr)
502 MCIErrorMessageBox (iErr);
503 else
504 {
505 wasPlaying = cdPlaying;
506 cdPlaying = FALSE;
507 }
508 }
509
510
511 // continue after a pause
I_ResumeCD(void)512 void I_ResumeCD(void)
513 {
514 MCIERROR iErr;
515
516 if (!cdaudio_started || !cdEnabled)
517 return;
518
519 if (!cdValid)
520 return;
521
522 if (!wasPlaying)
523 return;
524
525 iErr = mciSendCommand(m_MCIOpen.wDeviceID, MCI_RESUME, MCI_WAIT, 0);
526 if (iErr)
527 MCIErrorMessageBox (iErr);
528 else
529 cdPlaying = TRUE;
530 }
531
532
533 // volume : logical cd audio volume 0-31 (hardware is 0-255)
I_SetVolumeCD(INT32 volume)534 boolean I_SetVolumeCD (INT32 volume)
535 {
536 UNREFERENCED_PARAMETER(volume);
537 return false;
538 }
539 #endif
540