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