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