1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org>
4 
5   This software is provided 'as-is', without any express or implied
6   warranty.  In no event will the authors be held liable for any damages
7   arising from the use of this software.
8 
9   Permission is granted to anyone to use this software for any purpose,
10   including commercial applications, and to alter it and redistribute it
11   freely, subject to the following restrictions:
12 
13   1. The origin of this software must not be misrepresented; you must not
14      claim that you wrote the original software. If you use this software
15      in a product, an acknowledgment in the product documentation would be
16      appreciated but is not required.
17   2. Altered source versions must be plainly marked as such, and must not be
18      misrepresented as being the original software.
19   3. This notice may not be removed or altered from any source distribution.
20 */
21 #include "../../SDL_internal.h"
22 
23 #if SDL_AUDIO_DRIVER_OS2
24 
25 /* Allow access to a raw mixing buffer */
26 
27 #include "../../core/os2/SDL_os2.h"
28 
29 #include "SDL_audio.h"
30 #include "../SDL_audio_c.h"
31 #include "SDL_os2audio.h"
32 
33 /*
34 void lockIncr(volatile int *piVal);
35 #pragma aux lockIncr = \
36   "lock add [eax], 1 "\
37   parm [eax];
38 
39 void lockDecr(volatile int *piVal);
40 #pragma aux lockDecr = \
41   "lock sub [eax], 1 "\
42   parm [eax];
43 */
44 
_getEnvULong(const char * name,ULONG ulMax,ULONG ulDefault)45 static ULONG _getEnvULong(const char *name, ULONG ulMax, ULONG ulDefault)
46 {
47     ULONG   ulValue;
48     char*   end;
49     char*   envval = SDL_getenv(name);
50 
51     if (envval == NULL)
52         return ulDefault;
53 
54     ulValue = SDL_strtoul(envval, &end, 10);
55     return (end == envval) || (ulValue > ulMax)? ulDefault : ulMax;
56 }
57 
_MCIError(const char * func,ULONG ulResult)58 static int _MCIError(const char *func, ULONG ulResult)
59 {
60     CHAR    acBuf[128];
61     mciGetErrorString(ulResult, acBuf, sizeof(acBuf));
62     return SDL_SetError("[%s] %s", func, acBuf);
63 }
64 
_mixIOError(const char * function,ULONG ulRC)65 static void _mixIOError(const char *function, ULONG ulRC)
66 {
67     debug_os2("%s() - failed, rc = 0x%X (%s)",
68               function, ulRC,
69               (ulRC == MCIERR_INVALID_MODE)   ? "Mixer mode does not match request" :
70               (ulRC == MCIERR_INVALID_BUFFER) ? "Caller sent an invalid buffer"     : "unknown");
71 }
72 
cbAudioWriteEvent(ULONG ulStatus,PMCI_MIX_BUFFER pBuffer,ULONG ulFlags)73 static LONG APIENTRY cbAudioWriteEvent(ULONG ulStatus, PMCI_MIX_BUFFER pBuffer,
74                                        ULONG ulFlags)
75 {
76     SDL_PrivateAudioData *pAData = (SDL_PrivateAudioData *)pBuffer->ulUserParm;
77     ULONG   ulRC;
78 
79     if (ulFlags != MIX_WRITE_COMPLETE) {
80         debug_os2("flags = 0x%X", ulFlags);
81         return 0;
82     }
83 
84     /*lockDecr((int *)&pAData->ulQueuedBuf);*/
85     ulRC = DosPostEventSem(pAData->hevBuf);
86     if (ulRC != NO_ERROR && ulRC != ERROR_ALREADY_POSTED) {
87         debug_os2("DosPostEventSem(), rc = %u", ulRC);
88     }
89 
90     return 1; /* return value doesn't seem to matter. */
91 }
92 
cbAudioReadEvent(ULONG ulStatus,PMCI_MIX_BUFFER pBuffer,ULONG ulFlags)93 static LONG APIENTRY cbAudioReadEvent(ULONG ulStatus, PMCI_MIX_BUFFER pBuffer,
94                                       ULONG ulFlags)
95 {
96     SDL_PrivateAudioData *pAData = (SDL_PrivateAudioData *)pBuffer->ulUserParm;
97     ULONG   ulRC;
98 
99     if (ulFlags != MIX_READ_COMPLETE) {
100         debug_os2("flags = 0x%X", ulFlags);
101         return 0;
102     }
103 
104     pAData->stMCIMixSetup.pmixRead(pAData->stMCIMixSetup.ulMixHandle, pBuffer, 1);
105 
106     ulRC = DosPostEventSem(pAData->hevBuf);
107     if (ulRC != NO_ERROR && ulRC != ERROR_ALREADY_POSTED) {
108         debug_os2("DosPostEventSem(), rc = %u", ulRC);
109     }
110 
111     return 1;
112 }
113 
114 
OS2_DetectDevices(void)115 static void OS2_DetectDevices(void)
116 {
117     MCI_SYSINFO_PARMS       stMCISysInfo;
118     CHAR                    acBuf[256];
119     ULONG                   ulDevicesNum;
120     MCI_SYSINFO_LOGDEVICE   stLogDevice;
121     MCI_SYSINFO_PARMS       stSysInfoParams;
122     ULONG                   ulRC;
123     ULONG                   ulHandle = 0;
124 
125     acBuf[0] = '\0';
126     stMCISysInfo.pszReturn    = acBuf;
127     stMCISysInfo.ulRetSize    = sizeof(acBuf);
128     stMCISysInfo.usDeviceType = MCI_DEVTYPE_AUDIO_AMPMIX;
129     ulRC = mciSendCommand(0, MCI_SYSINFO, MCI_WAIT | MCI_SYSINFO_QUANTITY,
130                           &stMCISysInfo, 0);
131     if (ulRC != NO_ERROR) {
132         debug_os2("MCI_SYSINFO, MCI_SYSINFO_QUANTITY - failed, rc = 0x%X", ulRC);
133         return;
134     }
135 
136     ulDevicesNum = SDL_strtoul(stMCISysInfo.pszReturn, NULL, 10);
137 
138     for (stSysInfoParams.ulNumber = 0; stSysInfoParams.ulNumber < ulDevicesNum;
139          stSysInfoParams.ulNumber++) {
140         /* Get device install name. */
141         stSysInfoParams.pszReturn    = acBuf;
142         stSysInfoParams.ulRetSize    = sizeof(acBuf);
143         stSysInfoParams.usDeviceType = MCI_DEVTYPE_AUDIO_AMPMIX;
144         ulRC = mciSendCommand(0, MCI_SYSINFO, MCI_WAIT | MCI_SYSINFO_INSTALLNAME,
145                               &stSysInfoParams, 0);
146         if (ulRC != NO_ERROR) {
147             debug_os2("MCI_SYSINFO, MCI_SYSINFO_INSTALLNAME - failed, rc = 0x%X", ulRC);
148             continue;
149         }
150 
151         /* Get textual product description. */
152         stSysInfoParams.ulItem = MCI_SYSINFO_QUERY_DRIVER;
153         stSysInfoParams.pSysInfoParm = &stLogDevice;
154         SDL_strlcpy(stLogDevice.szInstallName, stSysInfoParams.pszReturn, MAX_DEVICE_NAME);
155         ulRC = mciSendCommand(0, MCI_SYSINFO, MCI_WAIT | MCI_SYSINFO_ITEM,
156                               &stSysInfoParams, 0);
157         if (ulRC != NO_ERROR) {
158             debug_os2("MCI_SYSINFO, MCI_SYSINFO_ITEM - failed, rc = 0x%X", ulRC);
159             continue;
160         }
161 
162         ulHandle++;
163         SDL_AddAudioDevice(0, stLogDevice.szProductInfo, NULL, (void *)(ulHandle));
164         ulHandle++;
165         SDL_AddAudioDevice(1, stLogDevice.szProductInfo, NULL, (void *)(ulHandle));
166     }
167 }
168 
OS2_WaitDevice(_THIS)169 static void OS2_WaitDevice(_THIS)
170 {
171     SDL_PrivateAudioData *pAData = (SDL_PrivateAudioData *)_this->hidden;
172     ULONG   ulRC;
173 
174     /* Wait for an audio chunk to finish */
175     ulRC = DosWaitEventSem(pAData->hevBuf, 5000);
176     if (ulRC != NO_ERROR) {
177         debug_os2("DosWaitEventSem(), rc = %u", ulRC);
178     }
179 }
180 
OS2_GetDeviceBuf(_THIS)181 static Uint8 *OS2_GetDeviceBuf(_THIS)
182 {
183     SDL_PrivateAudioData *pAData = (SDL_PrivateAudioData *)_this->hidden;
184     return (Uint8 *) pAData->aMixBuffers[pAData->ulNextBuf].pBuffer;
185 }
186 
OS2_PlayDevice(_THIS)187 static void OS2_PlayDevice(_THIS)
188 {
189     SDL_PrivateAudioData *pAData = (SDL_PrivateAudioData *)_this->hidden;
190     ULONG                 ulRC;
191     PMCI_MIX_BUFFER       pMixBuffer = &pAData->aMixBuffers[pAData->ulNextBuf];
192 
193     /* Queue it up */
194     /*lockIncr((int *)&pAData->ulQueuedBuf);*/
195     ulRC = pAData->stMCIMixSetup.pmixWrite(pAData->stMCIMixSetup.ulMixHandle,
196                                            pMixBuffer, 1);
197     if (ulRC != MCIERR_SUCCESS) {
198         _mixIOError("pmixWrite", ulRC);
199     } else {
200         pAData->ulNextBuf = (pAData->ulNextBuf + 1) % pAData->cMixBuffers;
201     }
202 }
203 
OS2_CloseDevice(_THIS)204 static void OS2_CloseDevice(_THIS)
205 {
206     SDL_PrivateAudioData *pAData = (SDL_PrivateAudioData *)_this->hidden;
207     MCI_GENERIC_PARMS     sMCIGenericParms;
208     ULONG                 ulRC;
209 
210     if (pAData == NULL)
211         return;
212 
213     /* Close up audio */
214     if (pAData->usDeviceId != (USHORT)~0) { /* Device is open. */
215         if (pAData->stMCIMixSetup.ulBitsPerSample != 0) { /* Mixer was initialized. */
216             ulRC = mciSendCommand(pAData->usDeviceId, MCI_MIXSETUP,
217                                   MCI_WAIT | MCI_MIXSETUP_DEINIT,
218                                   &pAData->stMCIMixSetup, 0);
219             if (ulRC != MCIERR_SUCCESS) {
220                 debug_os2("MCI_MIXSETUP, MCI_MIXSETUP_DEINIT - failed");
221             }
222         }
223 
224         if (pAData->cMixBuffers != 0) { /* Buffers was allocated. */
225             MCI_BUFFER_PARMS    stMCIBuffer;
226 
227             stMCIBuffer.ulBufferSize = pAData->aMixBuffers[0].ulBufferLength;
228             stMCIBuffer.ulNumBuffers = pAData->cMixBuffers;
229             stMCIBuffer.pBufList = pAData->aMixBuffers;
230 
231             ulRC = mciSendCommand(pAData->usDeviceId, MCI_BUFFER,
232                                   MCI_WAIT | MCI_DEALLOCATE_MEMORY, &stMCIBuffer, 0);
233             if (ulRC != MCIERR_SUCCESS) {
234                 debug_os2("MCI_BUFFER, MCI_DEALLOCATE_MEMORY - failed");
235             }
236         }
237 
238         ulRC = mciSendCommand(pAData->usDeviceId, MCI_CLOSE, MCI_WAIT,
239                               &sMCIGenericParms, 0);
240         if (ulRC != MCIERR_SUCCESS) {
241             debug_os2("MCI_CLOSE - failed");
242         }
243     }
244 
245     if (pAData->hevBuf != NULLHANDLE)
246         DosCloseEventSem(pAData->hevBuf);
247 
248     SDL_free(pAData);
249 }
250 
OS2_OpenDevice(_THIS,void * handle,const char * devname,int iscapture)251 static int OS2_OpenDevice(_THIS, void *handle, const char *devname,
252                           int iscapture)
253 {
254     SDL_PrivateAudioData *pAData;
255     SDL_AudioFormat       SDLAudioFmt;
256     MCI_AMP_OPEN_PARMS    stMCIAmpOpen;
257     MCI_BUFFER_PARMS      stMCIBuffer;
258     ULONG                 ulRC;
259     ULONG                 ulIdx;
260     BOOL                  new_freq;
261 
262     new_freq = FALSE;
263     SDL_zero(stMCIAmpOpen);
264     SDL_zero(stMCIBuffer);
265 
266     for (SDLAudioFmt = SDL_FirstAudioFormat(_this->spec.format);
267          SDLAudioFmt != 0; SDLAudioFmt = SDL_NextAudioFormat()) {
268         if (SDLAudioFmt == AUDIO_U8 || SDLAudioFmt == AUDIO_S16)
269             break;
270     }
271     if (SDLAudioFmt == 0) {
272         debug_os2("Unsupported audio format, AUDIO_S16 used");
273         SDLAudioFmt = AUDIO_S16;
274     }
275 
276     pAData = (SDL_PrivateAudioData *) SDL_calloc(1, sizeof(struct SDL_PrivateAudioData));
277     if (pAData == NULL)
278         return SDL_OutOfMemory();
279     _this->hidden = pAData;
280 
281     ulRC = DosCreateEventSem(NULL, &pAData->hevBuf, DCE_AUTORESET, TRUE);
282     if (ulRC != NO_ERROR) {
283         debug_os2("DosCreateEventSem() failed, rc = %u", ulRC);
284         return -1;
285     }
286 
287     /* Open audio device */
288     stMCIAmpOpen.usDeviceID = (handle != NULL) ? ((ULONG)handle - 1) : 0;
289     stMCIAmpOpen.pszDeviceType = (PSZ)MCI_DEVTYPE_AUDIO_AMPMIX;
290     ulRC = mciSendCommand(0, MCI_OPEN,
291                           (_getEnvULong("SDL_AUDIO_SHARE", 1, 0) != 0)?
292                            MCI_WAIT | MCI_OPEN_TYPE_ID | MCI_OPEN_SHAREABLE :
293                            MCI_WAIT | MCI_OPEN_TYPE_ID,
294                           &stMCIAmpOpen,  0);
295     if (ulRC != MCIERR_SUCCESS) {
296         stMCIAmpOpen.usDeviceID = (USHORT)~0;
297         return _MCIError("MCI_OPEN", ulRC);
298     }
299     pAData->usDeviceId = stMCIAmpOpen.usDeviceID;
300 
301     if (iscapture != 0) {
302         MCI_CONNECTOR_PARMS stMCIConnector;
303         MCI_AMP_SET_PARMS   stMCIAmpSet;
304         BOOL                fLineIn = _getEnvULong("SDL_AUDIO_LINEIN", 1, 0);
305 
306         /* Set particular connector. */
307         SDL_zero(stMCIConnector);
308         stMCIConnector.ulConnectorType = (fLineIn)? MCI_LINE_IN_CONNECTOR :
309                                                     MCI_MICROPHONE_CONNECTOR;
310         mciSendCommand(stMCIAmpOpen.usDeviceID, MCI_CONNECTOR,
311                        MCI_WAIT | MCI_ENABLE_CONNECTOR |
312                        MCI_CONNECTOR_TYPE, &stMCIConnector, 0);
313 
314         /* Disable monitor. */
315         SDL_zero(stMCIAmpSet);
316         stMCIAmpSet.ulItem = MCI_AMP_SET_MONITOR;
317         mciSendCommand(stMCIAmpOpen.usDeviceID, MCI_SET,
318                        MCI_WAIT | MCI_SET_OFF | MCI_SET_ITEM,
319                        &stMCIAmpSet, 0);
320 
321         /* Set record volume. */
322         stMCIAmpSet.ulLevel = _getEnvULong("SDL_AUDIO_RECVOL", 100, 90);
323         stMCIAmpSet.ulItem  = MCI_AMP_SET_AUDIO;
324         stMCIAmpSet.ulAudio = MCI_SET_AUDIO_ALL; /* Both cnannels. */
325         stMCIAmpSet.ulValue = (fLineIn) ? MCI_LINE_IN_CONNECTOR :
326                                           MCI_MICROPHONE_CONNECTOR ;
327 
328         mciSendCommand(stMCIAmpOpen.usDeviceID, MCI_SET,
329                        MCI_WAIT | MCI_SET_AUDIO | MCI_AMP_SET_GAIN,
330                        &stMCIAmpSet, 0);
331     }
332 
333     _this->spec.format = SDLAudioFmt;
334     _this->spec.channels = _this->spec.channels > 1 ? 2 : 1;
335     if (_this->spec.freq < 8000) {
336         _this->spec.freq = 8000;
337         new_freq = TRUE;
338     } else if (_this->spec.freq > 48000) {
339         _this->spec.freq = 48000;
340         new_freq = TRUE;
341     }
342 
343     /* Setup mixer. */
344     pAData->stMCIMixSetup.ulFormatTag     = MCI_WAVE_FORMAT_PCM;
345     pAData->stMCIMixSetup.ulBitsPerSample = SDL_AUDIO_BITSIZE(SDLAudioFmt);
346     pAData->stMCIMixSetup.ulSamplesPerSec = _this->spec.freq;
347     pAData->stMCIMixSetup.ulChannels      = _this->spec.channels;
348     pAData->stMCIMixSetup.ulDeviceType    = MCI_DEVTYPE_WAVEFORM_AUDIO;
349     if (iscapture == 0) {
350         pAData->stMCIMixSetup.ulFormatMode= MCI_PLAY;
351         pAData->stMCIMixSetup.pmixEvent   = cbAudioWriteEvent;
352     } else {
353         pAData->stMCIMixSetup.ulFormatMode= MCI_RECORD;
354         pAData->stMCIMixSetup.pmixEvent   = cbAudioReadEvent;
355     }
356 
357     ulRC = mciSendCommand(pAData->usDeviceId, MCI_MIXSETUP,
358                           MCI_WAIT | MCI_MIXSETUP_INIT, &pAData->stMCIMixSetup, 0);
359     if (ulRC != MCIERR_SUCCESS && _this->spec.freq > 44100) {
360         new_freq = TRUE;
361         pAData->stMCIMixSetup.ulSamplesPerSec = 44100;
362         _this->spec.freq = 44100;
363         ulRC = mciSendCommand(pAData->usDeviceId, MCI_MIXSETUP,
364                               MCI_WAIT | MCI_MIXSETUP_INIT, &pAData->stMCIMixSetup, 0);
365     }
366 
367     debug_os2("Setup mixer [BPS: %u, Freq.: %u, Channels: %u]: %s",
368               pAData->stMCIMixSetup.ulBitsPerSample,
369               pAData->stMCIMixSetup.ulSamplesPerSec,
370               pAData->stMCIMixSetup.ulChannels,
371               (ulRC == MCIERR_SUCCESS)? "SUCCESS" : "FAIL");
372 
373     if (ulRC != MCIERR_SUCCESS) {
374         pAData->stMCIMixSetup.ulBitsPerSample = 0;
375         return _MCIError("MCI_MIXSETUP", ulRC);
376     }
377 
378     if (_this->spec.samples == 0 || new_freq == TRUE) {
379     /* also see SDL_audio.c:prepare_audiospec() */
380     /* Pick a default of ~46 ms at desired frequency */
381         Uint32 samples = (_this->spec.freq / 1000) * 46;
382         Uint32 power2 = 1;
383         while (power2 < samples) {
384             power2 <<= 1;
385         }
386         _this->spec.samples = power2;
387     }
388     /* Update the fragment size as size in bytes */
389     SDL_CalculateAudioSpec(&_this->spec);
390 
391     /* Allocate memory buffers */
392     stMCIBuffer.ulBufferSize = _this->spec.size;/* (_this->spec.freq / 1000) * 100 */
393     stMCIBuffer.ulNumBuffers = NUM_BUFFERS;
394     stMCIBuffer.pBufList     = pAData->aMixBuffers;
395 
396     ulRC = mciSendCommand(pAData->usDeviceId, MCI_BUFFER,
397                           MCI_WAIT | MCI_ALLOCATE_MEMORY, &stMCIBuffer, 0);
398     if (ulRC != MCIERR_SUCCESS) {
399         return _MCIError("MCI_BUFFER", ulRC);
400     }
401     pAData->cMixBuffers = stMCIBuffer.ulNumBuffers;
402     _this->spec.size = stMCIBuffer.ulBufferSize;
403 
404     /* Fill all device buffers with data */
405     for (ulIdx = 0; ulIdx < stMCIBuffer.ulNumBuffers; ulIdx++) {
406         pAData->aMixBuffers[ulIdx].ulFlags        = 0;
407         pAData->aMixBuffers[ulIdx].ulBufferLength = stMCIBuffer.ulBufferSize;
408         pAData->aMixBuffers[ulIdx].ulUserParm     = (ULONG)pAData;
409 
410         SDL_memset(((PMCI_MIX_BUFFER)stMCIBuffer.pBufList)[ulIdx].pBuffer,
411                    _this->spec.silence, stMCIBuffer.ulBufferSize);
412     }
413 
414     /* Write buffers to kick off the amp mixer */
415     ulRC = pAData->stMCIMixSetup.pmixWrite(pAData->stMCIMixSetup.ulMixHandle,
416                                            pAData->aMixBuffers, 1);
417     if (ulRC != MCIERR_SUCCESS) {
418         _mixIOError("pmixWrite", ulRC);
419         return -1;
420     }
421 
422     return 0;
423 }
424 
425 
OS2_Init(SDL_AudioDriverImpl * impl)426 static int OS2_Init(SDL_AudioDriverImpl * impl)
427 {
428     /* Set the function pointers */
429     impl->DetectDevices = OS2_DetectDevices;
430     impl->OpenDevice    = OS2_OpenDevice;
431     impl->PlayDevice    = OS2_PlayDevice;
432     impl->WaitDevice    = OS2_WaitDevice;
433     impl->GetDeviceBuf  = OS2_GetDeviceBuf;
434     impl->CloseDevice   = OS2_CloseDevice;
435 
436     /* TODO: IMPLEMENT CAPTURE SUPPORT:
437     impl->CaptureFromDevice = ;
438     impl->FlushCapture = ;
439     impl->HasCaptureSupport = SDL_TRUE;
440     */
441     return 1; /* this audio target is available. */
442 }
443 
444 
445 AudioBootStrap OS2AUDIO_bootstrap = {
446     "DART", "OS/2 DART", OS2_Init, 0
447 };
448 
449 #endif /* SDL_AUDIO_DRIVER_OS2 */
450 
451 /* vi: set ts=4 sw=4 expandtab: */
452