1 /** @file cdaudio.cpp Compact Disc-Digital Audio (CD-DA) / "Redbook" playback.
2 *
3 * Uses the Windows API MCI interface.
4 *
5 * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
6 * @authors Copyright © 2006-2013 Daniel Swanson <danij@dengine.net>
7 *
8 * @par License
9 * GPL: http://www.gnu.org/licenses/gpl.html
10 *
11 * <small>This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by the
13 * Free Software Foundation; either version 2 of the License, or (at your
14 * option) any later version. This program is distributed in the hope that it
15 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
16 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
17 * Public License for more details. You should have received a copy of the GNU
18 * General Public License along with this program; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA</small>
21 */
22
23 #include "dswinmm.h"
24 #include <de/c_wrapper.h>
25 #include <de/timer.h>
26 #include <cmath>
27 #include <cstdio>
28
29 #define DEVICEID "mycd"
30
31 static int cdInited = false;
32
33 // Currently playing track info:
34 static int cdCurrentTrack = 0;
35 static dd_bool cdLooping;
36 static double cdStartTime, cdPauseTime, cdTrackLength;
37
38 /**
39 * Execute an MCI command string.
40 *
41 * Returns @c true iff successful.
42 */
sendMCICmd(char * returnInfo,int returnLength,char const * format,...)43 static int sendMCICmd(char *returnInfo, int returnLength, char const *format, ...)
44 {
45 char buf[300];
46 va_list args;
47
48 va_start(args, format);
49 dd_vsnprintf(buf, sizeof(buf), format, args);
50 va_end(args);
51
52 if (MCIERROR error = mciSendStringA(buf, returnInfo, returnLength, NULL))
53 {
54 if (error == MCIERR_HARDWARE)
55 {
56 // Hardware is not cooperating.
57 App_Log(DE2_DEV_AUDIO_VERBOSE, "[WinMM] CD playback hardware is not ready");
58 return false;
59 }
60 mciGetErrorStringA(error, buf, 300);
61 App_Log(DE2_AUDIO_ERROR, "[WinMM] CD playback error: %s", buf);
62 return false;
63 }
64
65 return true;
66 }
67
68 /**
69 * @return Length of the track in seconds.
70 */
getTrackLength(int track)71 static int getTrackLength(int track)
72 {
73 char lenString[80];
74
75 if(!sendMCICmd(lenString, 80, "status " DEVICEID " length track %i",
76 track))
77 return 0;
78
79 int minutes, seconds;
80 sscanf(lenString, "%i:%i", &minutes, &seconds);
81 return minutes * 60 + seconds;
82 }
83
isPlaying()84 static int isPlaying()
85 {
86 char lenString[80];
87
88 if(!sendMCICmd(lenString, 80, "status " DEVICEID " mode wait"))
89 return false;
90
91 if(strcmp(lenString, "playing") == 0)
92 return true;
93
94 return false;
95 }
96
DM_CDAudio_Set(int prop,float value)97 void DM_CDAudio_Set(int prop, float value)
98 {
99 if(!cdInited) return;
100
101 switch(prop)
102 {
103 case MUSIP_VOLUME: {
104 // Straighten the volume curve.
105 int val = MINMAX_OF(0, int(value * 255 + .5f), 255);
106 val <<= 8; // Make it a word.
107 val = (int) (255.9980469 * sqrt(value));
108 mixer4i(MIX_CDAUDIO, MIX_SET, MIX_VOLUME, val);
109 break; }
110
111 default: break;
112 }
113 }
114
DM_CDAudio_Get(int prop,void * ptr)115 int DM_CDAudio_Get(int prop, void *ptr)
116 {
117 if(!cdInited) return false;
118
119 switch(prop)
120 {
121 case MUSIP_ID:
122 if(ptr)
123 {
124 strcpy((char*) ptr, "WinMM::CD");
125 return true;
126 }
127 break;
128
129 case MUSIP_PLAYING:
130 return (cdInited && isPlaying()? true : false);
131
132 default: break;
133 }
134
135 return false;
136 }
137
DM_CDAudio_Init()138 int DM_CDAudio_Init()
139 {
140 if(cdInited) return true;
141
142 if(!sendMCICmd(0, 0, "open cdaudio alias " DEVICEID))
143 return false;
144
145 if(!sendMCICmd(0, 0, "set " DEVICEID " time format tmsf"))
146 return false;
147
148 cdCurrentTrack = 0;
149 cdLooping = false;
150 cdStartTime = cdPauseTime = cdTrackLength = 0;
151
152 // Successful initialization.
153 return cdInited = true;
154 }
155
DM_CDAudio_Shutdown()156 void DM_CDAudio_Shutdown()
157 {
158 if(!cdInited) return;
159
160 DM_CDAudio_Stop();
161 sendMCICmd(0, 0, "close " DEVICEID);
162
163 cdInited = false;
164 }
165
DM_CDAudio_Update()166 void DM_CDAudio_Update()
167 {
168 if(!cdInited) return;
169
170 // Check for looping.
171 if(cdCurrentTrack && cdLooping &&
172 Timer_Seconds() - cdStartTime > cdTrackLength)
173 {
174 // Restart the track.
175 DM_CDAudio_Play(cdCurrentTrack, true);
176 }
177 }
178
DM_CDAudio_Play(int track,int looped)179 int DM_CDAudio_Play(int track, int looped)
180 {
181 if(!cdInited) return false;
182
183 // Get the length of the track.
184 int len = getTrackLength(track);
185 cdTrackLength = len;
186 if(!len) return false; // Hmm?!
187
188 // Play it!
189 if(!sendMCICmd(0, 0, "play " DEVICEID " from %i to %i", track,
190 MCI_MAKE_TMSF(track, 0, len, 0)))
191 return false;
192
193 // Success!
194 cdLooping = (looped? true:false);
195 cdStartTime = Timer_Seconds();
196 return cdCurrentTrack = track;
197 }
198
DM_CDAudio_Pause(int pause)199 void DM_CDAudio_Pause(int pause)
200 {
201 if(!cdInited) return;
202
203 sendMCICmd(0, 0, "%s " DEVICEID, pause ? "pause" : "play");
204 if(pause)
205 {
206 cdPauseTime = Timer_Seconds();
207 }
208 else
209 {
210 cdStartTime += Timer_Seconds() - cdPauseTime;
211 }
212 }
213
DM_CDAudio_Stop()214 void DM_CDAudio_Stop()
215 {
216 if(!cdInited || !cdCurrentTrack) return;
217
218 cdCurrentTrack = 0;
219 sendMCICmd(0, 0, "stop " DEVICEID);
220 }
221