blob: 389caad7d58c556b690bafa0659140867d1d6381 [file] [log] [blame]
/*
* Sample MIXER Wine Driver for Mac OS X (based on OSS mixer)
*
* Copyright 1997 Marcus Meissner
* 1999,2001 Eric Pouech
* 2006,2007 Emmanuel Maillard
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include "config.h"
#include "wine/port.h"
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#define NONAMELESSUNION
#define NONAMELESSSTRUCT
#include "windef.h"
#include "winbase.h"
#include "winnls.h"
#include "mmddk.h"
#include "coreaudio.h"
#include "wine/unicode.h"
#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(mixer);
#if defined(HAVE_COREAUDIO_COREAUDIO_H)
#include <CoreAudio/CoreAudio.h>
#include <CoreFoundation/CoreFoundation.h>
#define WINE_MIXER_NAME "CoreAudio Mixer"
#define InputDevice (1 << 0)
#define OutputDevice (1 << 1)
#define IsInput(dir) ((dir) & InputDevice)
#define IsOutput(dir) ((dir) & OutputDevice)
#define ControlsPerLine 2 /* number of control per line : volume & (mute | onoff) */
#define IDControlVolume 0
#define IDControlMute 1
typedef struct tagMixerLine
{
char *name;
int direction;
int numChannels;
int componentType;
AudioDeviceID deviceID;
} MixerLine;
typedef struct tagMixerCtrl
{
DWORD dwLineID;
MIXERCONTROLW ctrl;
} MixerCtrl;
typedef struct tagCoreAudio_Mixer
{
MIXERCAPSW caps;
MixerCtrl *mixerCtrls;
MixerLine *lines;
DWORD numCtrl;
} CoreAudio_Mixer;
static CoreAudio_Mixer mixer;
static int numMixers = 1;
/**************************************************************************
*/
static const char * getMessage(UINT uMsg)
{
#define MSG_TO_STR(x) case x: return #x;
switch (uMsg) {
MSG_TO_STR(DRVM_INIT);
MSG_TO_STR(DRVM_EXIT);
MSG_TO_STR(DRVM_ENABLE);
MSG_TO_STR(DRVM_DISABLE);
MSG_TO_STR(MXDM_GETDEVCAPS);
MSG_TO_STR(MXDM_GETLINEINFO);
MSG_TO_STR(MXDM_GETNUMDEVS);
MSG_TO_STR(MXDM_OPEN);
MSG_TO_STR(MXDM_CLOSE);
MSG_TO_STR(MXDM_GETLINECONTROLS);
MSG_TO_STR(MXDM_GETCONTROLDETAILS);
MSG_TO_STR(MXDM_SETCONTROLDETAILS);
}
#undef MSG_TO_STR
return wine_dbg_sprintf("UNKNOWN(%08x)", uMsg);
}
static const char * getControlType(DWORD dwControlType)
{
#define TYPE_TO_STR(x) case x: return #x;
switch (dwControlType) {
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_CUSTOM);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BOOLEANMETER);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SIGNEDMETER);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PEAKMETER);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_UNSIGNEDMETER);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BOOLEAN);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_ONOFF);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MUTE);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MONO);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_LOUDNESS);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_STEREOENH);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BASS_BOOST);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BUTTON);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_DECIBELS);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SIGNED);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_UNSIGNED);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PERCENT);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SLIDER);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_PAN);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_QSOUNDPAN);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_FADER);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_VOLUME);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_BASS);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_TREBLE);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_EQUALIZER);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_SINGLESELECT);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MUX);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MULTIPLESELECT);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MIXER);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MICROTIME);
TYPE_TO_STR(MIXERCONTROL_CONTROLTYPE_MILLITIME);
}
#undef TYPE_TO_STR
return wine_dbg_sprintf("UNKNOWN(%08x)", dwControlType);
}
static const char * getComponentType(DWORD dwComponentType)
{
#define TYPE_TO_STR(x) case x: return #x;
switch (dwComponentType) {
TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_UNDEFINED);
TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_DIGITAL);
TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_LINE);
TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_MONITOR);
TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_SPEAKERS);
TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_HEADPHONES);
TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_TELEPHONE);
TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_WAVEIN);
TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_DST_VOICEIN);
TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED);
TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_DIGITAL);
TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_LINE);
TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE);
TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER);
TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC);
TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_TELEPHONE);
TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_PCSPEAKER);
TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT);
TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_AUXILIARY);
TYPE_TO_STR(MIXERLINE_COMPONENTTYPE_SRC_ANALOG);
}
#undef TYPE_TO_STR
return wine_dbg_sprintf("UNKNOWN(%08x)", dwComponentType);
}
static const char * getTargetType(DWORD dwType)
{
#define TYPE_TO_STR(x) case x: return #x;
switch (dwType) {
TYPE_TO_STR(MIXERLINE_TARGETTYPE_UNDEFINED);
TYPE_TO_STR(MIXERLINE_TARGETTYPE_WAVEOUT);
TYPE_TO_STR(MIXERLINE_TARGETTYPE_WAVEIN);
TYPE_TO_STR(MIXERLINE_TARGETTYPE_MIDIOUT);
TYPE_TO_STR(MIXERLINE_TARGETTYPE_MIDIIN);
TYPE_TO_STR(MIXERLINE_TARGETTYPE_AUX);
}
#undef TYPE_TO_STR
return wine_dbg_sprintf("UNKNOWN(%08x)", dwType);
}
/* FIXME is there a better way ? */
static DWORD DeviceComponentType(char *name)
{
if (strcmp(name, "Built-in Microphone") == 0)
return MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE;
if (strcmp(name, "Built-in Line Input") == 0)
return MIXERLINE_COMPONENTTYPE_SRC_LINE;
if (strcmp(name, "Built-in Output") == 0)
return MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;
return MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED;
}
static BOOL DeviceHasMute(AudioDeviceID deviceID, Boolean isInput)
{
Boolean writable = false;
OSStatus err = noErr;
err = AudioDeviceGetPropertyInfo(deviceID, 0, isInput, kAudioDevicePropertyMute, NULL, NULL);
if (err == noErr)
{
/* check if we can set it */
err = AudioDeviceGetPropertyInfo(deviceID, 0, isInput, kAudioDevicePropertyMute, NULL, &writable);
if (err == noErr)
return writable;
}
return FALSE;
}
/*
* Getters
*/
static BOOL MIX_LineGetVolume(DWORD lineID, DWORD channels, Float32 *left, Float32 *right)
{
MixerLine *line = &mixer.lines[lineID];
UInt32 size = sizeof(Float32);
OSStatus err = noErr;
*left = *right = 0.0;
err = AudioDeviceGetProperty(line->deviceID, 1, IsInput(line->direction), kAudioDevicePropertyVolumeScalar, &size, left);
if (err != noErr)
return FALSE;
if (channels == 2)
{
size = sizeof(Float32);
err = AudioDeviceGetProperty(line->deviceID, 2, IsInput(line->direction), kAudioDevicePropertyVolumeScalar, &size, right);
if (err != noErr)
return FALSE;
}
TRACE("lineID %d channels %d return left %f right %f\n", lineID, channels, *left, *right);
return (err == noErr);
}
static BOOL MIX_LineGetMute(DWORD lineID, BOOL *muted)
{
MixerLine *line = &mixer.lines[lineID];
UInt32 size = sizeof(UInt32);
UInt32 val = 0;
OSStatus err = noErr;
err = AudioDeviceGetProperty(line->deviceID, 0, IsInput(line->direction), kAudioDevicePropertyMute, &size, &val);
*muted = val;
return (err == noErr);
}
/*
* Setters
*/
static BOOL MIX_LineSetVolume(DWORD lineID, DWORD channels, Float32 left, Float32 right)
{
MixerLine *line = &mixer.lines[lineID];
UInt32 size = sizeof(Float32);
OSStatus err = noErr;
TRACE("lineID %d channels %d left %f right %f\n", lineID, channels, left, right);
if (channels == 2)
{
err = AudioDeviceSetProperty(line->deviceID, NULL, 1, IsInput(line->direction), kAudioDevicePropertyVolumeScalar, size, &left);
if (err != noErr)
return FALSE;
err = AudioDeviceSetProperty(line->deviceID, NULL, 2, IsInput(line->direction), kAudioDevicePropertyVolumeScalar, size, &right);
}
else
{
/*
FIXME Using master channel failed ?? return kAudioHardwareUnknownPropertyError
err = AudioDeviceSetProperty(line->deviceID, NULL, 0, IsInput(line->direction), kAudioDevicePropertyVolumeScalar, size, &left);
*/
right = left;
err = AudioDeviceSetProperty(line->deviceID, NULL, 1, IsInput(line->direction), kAudioDevicePropertyVolumeScalar, size, &left);
if (err != noErr)
return FALSE;
err = AudioDeviceSetProperty(line->deviceID, NULL, 2, IsInput(line->direction), kAudioDevicePropertyVolumeScalar, size, &right);
}
return (err == noErr);
}
static BOOL MIX_LineSetMute(DWORD lineID, BOOL mute)
{
MixerLine *line = &mixer.lines[lineID];
UInt32 val = mute;
UInt32 size = sizeof(UInt32);
OSStatus err = noErr;
err = AudioDeviceSetProperty(line->deviceID, 0, 0, IsInput(line->direction), kAudioDevicePropertyMute, size, &val);
return (err == noErr);
}
static void MIX_FillControls(void)
{
int i;
int ctrl = 0;
MixerLine *line;
for (i = 0; i < mixer.caps.cDestinations; i++)
{
line = &mixer.lines[i];
mixer.mixerCtrls[ctrl].dwLineID = i;
mixer.mixerCtrls[ctrl].ctrl.cbStruct = sizeof(MIXERCONTROLW);
mixer.mixerCtrls[ctrl].ctrl.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
mixer.mixerCtrls[ctrl].ctrl.dwControlID = ctrl;
mixer.mixerCtrls[ctrl].ctrl.Bounds.s1.dwMinimum = 0;
mixer.mixerCtrls[ctrl].ctrl.Bounds.s1.dwMaximum = 65535;
mixer.mixerCtrls[ctrl].ctrl.Metrics.cSteps = 656;
ctrl++;
mixer.mixerCtrls[ctrl].dwLineID = i;
if ( !DeviceHasMute(line->deviceID, IsInput(line->direction)) )
mixer.mixerCtrls[ctrl].ctrl.fdwControl |= MIXERCONTROL_CONTROLF_DISABLED;
mixer.mixerCtrls[ctrl].ctrl.cbStruct = sizeof(MIXERCONTROLW);
mixer.mixerCtrls[ctrl].ctrl.dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE;
mixer.mixerCtrls[ctrl].ctrl.dwControlID = ctrl;
mixer.mixerCtrls[ctrl].ctrl.Bounds.s1.dwMinimum = 0;
mixer.mixerCtrls[ctrl].ctrl.Bounds.s1.dwMaximum = 1;
ctrl++;
}
assert(ctrl == mixer.numCtrl);
}
/**************************************************************************
* CoreAudio_MixerInit
*/
LONG CoreAudio_MixerInit(void)
{
OSStatus status;
UInt32 propertySize;
AudioDeviceID *deviceArray = NULL;
char name[MAXPNAMELEN];
int i;
int numLines;
AudioStreamBasicDescription streamDescription;
/* Find number of lines */
status = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, &propertySize, NULL);
if (status)
{
ERR("AudioHardwareGetPropertyInfo for kAudioHardwarePropertyDevices return %s\n", wine_dbgstr_fourcc(status));
return DRV_FAILURE;
}
numLines = propertySize / sizeof(AudioDeviceID);
mixer.mixerCtrls = NULL;
mixer.lines = NULL;
mixer.numCtrl = 0;
mixer.caps.cDestinations = numLines;
mixer.caps.wMid = 0xAA;
mixer.caps.wPid = 0x55;
mixer.caps.vDriverVersion = 0x0100;
MultiByteToWideChar(CP_ACP, 0, WINE_MIXER_NAME, -1, mixer.caps.szPname, sizeof(mixer.caps.szPname) / sizeof(WCHAR));
mixer.caps.fdwSupport = 0; /* No bits defined yet */
mixer.lines = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(MixerLine) * numLines);
if (!mixer.lines)
goto error;
deviceArray = HeapAlloc(GetProcessHeap(), 0, sizeof(AudioDeviceID) * numLines);
propertySize = sizeof(AudioDeviceID) * numLines;
status = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, &propertySize, deviceArray);
if (status)
{
ERR("AudioHardwareGetProperty for kAudioHardwarePropertyDevices return %s\n", wine_dbgstr_fourcc(status));
goto error;
}
for (i = 0; i < numLines; i++)
{
Boolean write;
MixerLine *line = &mixer.lines[i];
line->deviceID = deviceArray[i];
propertySize = MAXPNAMELEN;
status = AudioDeviceGetProperty(line->deviceID, 0 , FALSE, kAudioDevicePropertyDeviceName, &propertySize, name);
if (status) {
ERR("AudioHardwareGetProperty for kAudioDevicePropertyDeviceName return %s\n", wine_dbgstr_fourcc(status));
goto error;
}
line->name = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, strlen(name) + 1);
if (!line->name)
goto error;
memcpy(line->name, name, strlen(name));
line->componentType = DeviceComponentType(line->name);
/* check for directions */
/* Output ? */
propertySize = sizeof(UInt32);
status = AudioDeviceGetPropertyInfo(line->deviceID, 0, FALSE, kAudioDevicePropertyStreams, &propertySize, &write );
if (status) {
ERR("AudioDeviceGetPropertyInfo for kAudioDevicePropertyDataSource return %s\n", wine_dbgstr_fourcc(status));
goto error;
}
if ( (propertySize / sizeof(AudioStreamID)) != 0)
{
line->direction |= OutputDevice;
/* Check the number of channel for the stream */
propertySize = sizeof(streamDescription);
status = AudioDeviceGetProperty(line->deviceID, 0, FALSE , kAudioDevicePropertyStreamFormat, &propertySize, &streamDescription);
if (status != noErr) {
ERR("AudioHardwareGetProperty for kAudioDevicePropertyStreamFormat return %s\n", wine_dbgstr_fourcc(status));
goto error;
}
line->numChannels = streamDescription.mChannelsPerFrame;
}
else
{
/* Input ? */
propertySize = sizeof(UInt32);
status = AudioDeviceGetPropertyInfo(line->deviceID, 0, TRUE, kAudioDevicePropertyStreams, &propertySize, &write );
if (status) {
ERR("AudioDeviceGetPropertyInfo for kAudioDevicePropertyStreams return %s\n", wine_dbgstr_fourcc(status));
goto error;
}
if ( (propertySize / sizeof(AudioStreamID)) != 0)
{
line->direction |= InputDevice;
/* Check the number of channel for the stream */
propertySize = sizeof(streamDescription);
status = AudioDeviceGetProperty(line->deviceID, 0, TRUE, kAudioDevicePropertyStreamFormat, &propertySize, &streamDescription);
if (status != noErr) {
ERR("AudioHardwareGetProperty for kAudioDevicePropertyStreamFormat return %s\n", wine_dbgstr_fourcc(status));
goto error;
}
line->numChannels = streamDescription.mChannelsPerFrame;
}
}
mixer.numCtrl += ControlsPerLine; /* volume & (mute | onoff) */
}
mixer.mixerCtrls = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(MixerCtrl) * mixer.numCtrl);
if (!mixer.mixerCtrls)
goto error;
MIX_FillControls();
HeapFree(GetProcessHeap(), 0, deviceArray);
return DRV_SUCCESS;
error:
if (mixer.lines)
{
int i;
for (i = 0; i < mixer.caps.cDestinations; i++)
{
HeapFree(GetProcessHeap(), 0, mixer.lines[i].name);
}
HeapFree(GetProcessHeap(), 0, mixer.lines);
}
HeapFree(GetProcessHeap(), 0, deviceArray);
if (mixer.mixerCtrls)
HeapFree(GetProcessHeap(), 0, mixer.mixerCtrls);
return DRV_FAILURE;
}
/**************************************************************************
* CoreAudio_MixerRelease
*/
void CoreAudio_MixerRelease(void)
{
TRACE("()\n");
if (mixer.lines)
{
int i;
for (i = 0; i < mixer.caps.cDestinations; i++)
{
HeapFree(GetProcessHeap(), 0, mixer.lines[i].name);
}
HeapFree(GetProcessHeap(), 0, mixer.lines);
}
if (mixer.mixerCtrls)
HeapFree(GetProcessHeap(), 0, mixer.mixerCtrls);
}
/**************************************************************************
* MIX_Open [internal]
*/
static DWORD MIX_Open(WORD wDevID, LPMIXEROPENDESC lpMod, DWORD_PTR flags)
{
TRACE("wDevID=%d lpMod=%p dwSize=%08lx\n", wDevID, lpMod, flags);
if (lpMod == NULL) {
WARN("invalid parameter: lpMod == NULL\n");
return MMSYSERR_INVALPARAM;
}
if (wDevID >= numMixers) {
WARN("bad device ID: %04X\n", wDevID);
return MMSYSERR_BADDEVICEID;
}
return MMSYSERR_NOERROR;
}
/**************************************************************************
* MIX_GetNumDevs [internal]
*/
static DWORD MIX_GetNumDevs(void)
{
TRACE("()\n");
return numMixers;
}
static DWORD MIX_GetDevCaps(WORD wDevID, LPMIXERCAPSW lpCaps, DWORD_PTR dwSize)
{
TRACE("wDevID=%d lpCaps=%p\n", wDevID, lpCaps);
if (lpCaps == NULL) {
WARN("Invalid Parameter\n");
return MMSYSERR_INVALPARAM;
}
if (wDevID >= numMixers) {
WARN("bad device ID : %d\n", wDevID);
return MMSYSERR_BADDEVICEID;
}
memcpy(lpCaps, &mixer.caps, min(dwSize, sizeof(*lpCaps)));
return MMSYSERR_NOERROR;
}
/**************************************************************************
* MIX_GetLineInfo [internal]
*/
static DWORD MIX_GetLineInfo(WORD wDevID, LPMIXERLINEW lpMl, DWORD_PTR fdwInfo)
{
int i;
DWORD ret = MMSYSERR_ERROR;
MixerLine *line = NULL;
TRACE("%04X, %p, %08lx\n", wDevID, lpMl, fdwInfo);
if (lpMl == NULL) {
WARN("invalid parameter: lpMl = NULL\n");
return MMSYSERR_INVALPARAM;
}
if (lpMl->cbStruct != sizeof(*lpMl)) {
WARN("invalid parameter: lpMl->cbStruct\n");
return MMSYSERR_INVALPARAM;
}
if (wDevID >= numMixers) {
WARN("bad device ID: %04X\n", wDevID);
return MMSYSERR_BADDEVICEID;
}
/* FIXME: set all the variables correctly... the lines below
* are very wrong...
*/
lpMl->dwUser = 0;
switch (fdwInfo & MIXER_GETLINEINFOF_QUERYMASK)
{
case MIXER_GETLINEINFOF_DESTINATION:
TRACE("MIXER_GETLINEINFOF_DESTINATION %d\n", lpMl->dwDestination);
if ( (lpMl->dwDestination >= 0) && (lpMl->dwDestination < mixer.caps.cDestinations) )
{
lpMl->dwLineID = lpMl->dwDestination;
line = &mixer.lines[lpMl->dwDestination];
}
else ret = MIXERR_INVALLINE;
break;
case MIXER_GETLINEINFOF_COMPONENTTYPE:
TRACE("MIXER_GETLINEINFOF_COMPONENTTYPE %s\n", getComponentType(lpMl->dwComponentType));
for (i = 0; i < mixer.caps.cDestinations; i++)
{
if (mixer.lines[i].componentType == lpMl->dwComponentType)
{
lpMl->dwDestination = lpMl->dwLineID = i;
line = &mixer.lines[i];
break;
}
}
if (line == NULL)
{
WARN("can't find component type %s\n", getComponentType(lpMl->dwComponentType));
ret = MIXERR_INVALVALUE;
}
break;
case MIXER_GETLINEINFOF_SOURCE:
FIXME("MIXER_GETLINEINFOF_SOURCE %d dst=%d\n", lpMl->dwSource, lpMl->dwDestination);
break;
case MIXER_GETLINEINFOF_LINEID:
TRACE("MIXER_GETLINEINFOF_LINEID %d\n", lpMl->dwLineID);
if ( (lpMl->dwLineID >= 0) && (lpMl->dwLineID < mixer.caps.cDestinations) )
{
lpMl->dwDestination = lpMl->dwLineID;
line = &mixer.lines[lpMl->dwLineID];
}
else ret = MIXERR_INVALLINE;
break;
case MIXER_GETLINEINFOF_TARGETTYPE:
FIXME("MIXER_GETLINEINFOF_TARGETTYPE (%s)\n", getTargetType(lpMl->Target.dwType));
switch (lpMl->Target.dwType) {
case MIXERLINE_TARGETTYPE_UNDEFINED:
case MIXERLINE_TARGETTYPE_WAVEOUT:
case MIXERLINE_TARGETTYPE_WAVEIN:
case MIXERLINE_TARGETTYPE_MIDIOUT:
case MIXERLINE_TARGETTYPE_MIDIIN:
case MIXERLINE_TARGETTYPE_AUX:
default:
FIXME("Unhandled target type (%s)\n",
getTargetType(lpMl->Target.dwType));
return MMSYSERR_INVALPARAM;
}
break;
default:
WARN("Unknown flag (%08lx)\n", fdwInfo & MIXER_GETLINEINFOF_QUERYMASK);
break;
}
if (line)
{
lpMl->dwComponentType = line->componentType;
lpMl->cChannels = line->numChannels;
lpMl->cControls = ControlsPerLine;
/* FIXME check there with CoreAudio */
lpMl->cConnections = 1;
lpMl->fdwLine = MIXERLINE_LINEF_ACTIVE;
MultiByteToWideChar(CP_ACP, 0, line->name, -1, lpMl->szShortName, sizeof(lpMl->szShortName) / sizeof(WCHAR));
MultiByteToWideChar(CP_ACP, 0, line->name, -1, lpMl->szName, sizeof(lpMl->szName) / sizeof(WCHAR));
if ( IsInput(line->direction) )
lpMl->Target.dwType = MIXERLINE_TARGETTYPE_WAVEIN;
else
lpMl->Target.dwType = MIXERLINE_TARGETTYPE_WAVEOUT;
lpMl->Target.dwDeviceID = line->deviceID;
lpMl->Target.wMid = mixer.caps.wMid;
lpMl->Target.wPid = mixer.caps.wPid;
lpMl->Target.vDriverVersion = mixer.caps.vDriverVersion;
MultiByteToWideChar(CP_ACP, 0, WINE_MIXER_NAME, -1, lpMl->Target.szPname, sizeof(lpMl->Target.szPname) / sizeof(WCHAR));
ret = MMSYSERR_NOERROR;
}
return ret;
}
/**************************************************************************
* MIX_GetLineControls [internal]
*/
static DWORD MIX_GetLineControls(WORD wDevID, LPMIXERLINECONTROLSW lpMlc, DWORD_PTR flags)
{
DWORD ret = MMSYSERR_NOTENABLED;
int ctrl = 0;
TRACE("%04X, %p, %08lX\n", wDevID, lpMlc, flags);
if (lpMlc == NULL) {
WARN("invalid parameter: lpMlc == NULL\n");
return MMSYSERR_INVALPARAM;
}
if (lpMlc->cbStruct < sizeof(*lpMlc)) {
WARN("invalid parameter: lpMlc->cbStruct = %d\n", lpMlc->cbStruct);
return MMSYSERR_INVALPARAM;
}
if (lpMlc->cbmxctrl < sizeof(MIXERCONTROLW)) {
WARN("invalid parameter: lpMlc->cbmxctrl = %d\n", lpMlc->cbmxctrl);
return MMSYSERR_INVALPARAM;
}
if (wDevID >= numMixers) {
WARN("bad device ID: %04X\n", wDevID);
return MMSYSERR_BADDEVICEID;
}
switch (flags & MIXER_GETLINECONTROLSF_QUERYMASK)
{
case MIXER_GETLINECONTROLSF_ALL:
FIXME("dwLineID=%d MIXER_GETLINECONTROLSF_ALL (%d)\n", lpMlc->dwLineID, lpMlc->cControls);
if (lpMlc->cControls != ControlsPerLine)
{
WARN("invalid parameter lpMlc->cControls %d\n", lpMlc->cControls);
ret = MMSYSERR_INVALPARAM;
}
else
{
if ( (lpMlc->dwLineID >= 0) && (lpMlc->dwLineID < mixer.caps.cDestinations) )
{
int i;
for (i = 0; i < lpMlc->cControls; i++)
{
lpMlc->pamxctrl[i] = mixer.mixerCtrls[lpMlc->dwLineID * i].ctrl;
}
ret = MMSYSERR_NOERROR;
}
else ret = MIXERR_INVALLINE;
}
break;
case MIXER_GETLINECONTROLSF_ONEBYID:
TRACE("dwLineID=%d MIXER_GETLINECONTROLSF_ONEBYID (%d)\n", lpMlc->dwLineID, lpMlc->u.dwControlID);
if ( lpMlc->u.dwControlID >= 0 && lpMlc->u.dwControlID < mixer.numCtrl )
{
lpMlc->pamxctrl[0] = mixer.mixerCtrls[lpMlc->u.dwControlID].ctrl;
ret = MMSYSERR_NOERROR;
}
else ret = MIXERR_INVALVALUE;
break;
case MIXER_GETLINECONTROLSF_ONEBYTYPE:
TRACE("dwLineID=%d MIXER_GETLINECONTROLSF_ONEBYTYPE (%s)\n", lpMlc->dwLineID, getControlType(lpMlc->u.dwControlType));
if ( (lpMlc->dwLineID < 0) || (lpMlc->dwLineID >= mixer.caps.cDestinations) )
{
ret = MIXERR_INVALLINE;
break;
}
if (lpMlc->u.dwControlType == MIXERCONTROL_CONTROLTYPE_VOLUME)
{
ctrl = (lpMlc->dwLineID * ControlsPerLine) + IDControlVolume;
lpMlc->pamxctrl[0] = mixer.mixerCtrls[ctrl].ctrl;
ret = MMSYSERR_NOERROR;
}
else
if (lpMlc->u.dwControlType == MIXERCONTROL_CONTROLTYPE_MUTE)
{
ctrl = (lpMlc->dwLineID * ControlsPerLine) + IDControlMute;
lpMlc->pamxctrl[0] = mixer.mixerCtrls[ctrl].ctrl;
ret = MMSYSERR_NOERROR;
}
break;
default:
ERR("Unknown flag %08lx\n", flags & MIXER_GETLINECONTROLSF_QUERYMASK);
ret = MMSYSERR_INVALPARAM;
}
return ret;
}
/**************************************************************************
* MIX_GetControlDetails [internal]
*/
static DWORD MIX_GetControlDetails(WORD wDevID, LPMIXERCONTROLDETAILS lpmcd, DWORD_PTR fdwDetails)
{
DWORD ret = MMSYSERR_NOTSUPPORTED;
DWORD dwControlType;
TRACE("%04X, %p, %08lx\n", wDevID, lpmcd, fdwDetails);
if (lpmcd == NULL) {
TRACE("invalid parameter: lpmcd == NULL\n");
return MMSYSERR_INVALPARAM;
}
if (wDevID >= numMixers) {
WARN("bad device ID: %04X\n", wDevID);
return MMSYSERR_BADDEVICEID;
}
if ( (fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK) != MIXER_GETCONTROLDETAILSF_VALUE )
{
WARN("Unknown/unimplement GetControlDetails flag (%08lx)\n", fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK);
return MMSYSERR_NOTSUPPORTED;
}
if ( lpmcd->dwControlID < 0 || lpmcd->dwControlID >= mixer.numCtrl )
{
WARN("bad control ID: %d\n", lpmcd->dwControlID);
return MIXERR_INVALVALUE;
}
TRACE("MIXER_GETCONTROLDETAILSF_VALUE %d\n", lpmcd->dwControlID);
dwControlType = mixer.mixerCtrls[lpmcd->dwControlID].ctrl.dwControlType;
switch (dwControlType)
{
case MIXERCONTROL_CONTROLTYPE_VOLUME:
FIXME("controlType : %s channels %d\n", getControlType(dwControlType), lpmcd->cChannels);
{
LPMIXERCONTROLDETAILS_UNSIGNED mcdu;
Float32 left, right;
if (lpmcd->cbDetails != sizeof(MIXERCONTROLDETAILS_UNSIGNED)) {
WARN("invalid parameter: lpmcd->cbDetails == %d\n", lpmcd->cbDetails);
return MMSYSERR_INVALPARAM;
}
if ( MIX_LineGetVolume(mixer.mixerCtrls[lpmcd->dwControlID].dwLineID, lpmcd->cChannels, &left, &right) )
{
mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)lpmcd->paDetails;
switch (lpmcd->cChannels)
{
case 1:
/* mono... so R = L */
mcdu->dwValue = left * 65535;
TRACE("Reading RL = %d\n", mcdu->dwValue);
break;
case 2:
/* stereo, left is paDetails[0] */
mcdu->dwValue = left * 65535;
TRACE("Reading L = %d\n", mcdu->dwValue);
mcdu++;
mcdu->dwValue = right * 65535;
TRACE("Reading R = %d\n", mcdu->dwValue);
break;
default:
WARN("Unsupported cChannels (%d)\n", lpmcd->cChannels);
return MMSYSERR_INVALPARAM;
}
TRACE("=> %08x\n", mcdu->dwValue);
ret = MMSYSERR_NOERROR;
}
}
break;
case MIXERCONTROL_CONTROLTYPE_MUTE:
case MIXERCONTROL_CONTROLTYPE_ONOFF:
FIXME("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(dwControlType), lpmcd->cChannels);
{
LPMIXERCONTROLDETAILS_BOOLEAN mcdb;
BOOL muted;
if (lpmcd->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN)) {
WARN("invalid parameter: lpmcd->cbDetails = %d\n", lpmcd->cbDetails);
return MMSYSERR_INVALPARAM;
}
mcdb = (LPMIXERCONTROLDETAILS_BOOLEAN)lpmcd->paDetails;
if ( MIX_LineGetMute(mixer.mixerCtrls[lpmcd->dwControlID].dwLineID, &muted) )
{
mcdb->fValue = muted;
TRACE("=> %s\n", mcdb->fValue ? "on" : "off");
ret = MMSYSERR_NOERROR;
}
}
break;
case MIXERCONTROL_CONTROLTYPE_MIXER:
case MIXERCONTROL_CONTROLTYPE_MUX:
default:
FIXME("controlType : %s\n", getControlType(dwControlType));
break;
}
return ret;
}
/**************************************************************************
* MIX_SetControlDetails [internal]
*/
static DWORD MIX_SetControlDetails(WORD wDevID, LPMIXERCONTROLDETAILS lpmcd, DWORD_PTR fdwDetails)
{
DWORD ret = MMSYSERR_NOTSUPPORTED;
DWORD dwControlType;
TRACE("%04X, %p, %08lx\n", wDevID, lpmcd, fdwDetails);
if (lpmcd == NULL) {
TRACE("invalid parameter: lpmcd == NULL\n");
return MMSYSERR_INVALPARAM;
}
if (wDevID >= numMixers) {
WARN("bad device ID: %04X\n", wDevID);
return MMSYSERR_BADDEVICEID;
}
if ( (fdwDetails & MIXER_SETCONTROLDETAILSF_QUERYMASK) != MIXER_GETCONTROLDETAILSF_VALUE )
{
WARN("Unknown SetControlDetails flag (%08lx)\n", fdwDetails & MIXER_SETCONTROLDETAILSF_QUERYMASK);
return MMSYSERR_NOTSUPPORTED;
}
TRACE("MIXER_SETCONTROLDETAILSF_VALUE dwControlID=%d\n", lpmcd->dwControlID);
dwControlType = mixer.mixerCtrls[lpmcd->dwControlID].ctrl.dwControlType;
switch (dwControlType)
{
case MIXERCONTROL_CONTROLTYPE_VOLUME:
FIXME("controlType : %s\n", getControlType(dwControlType));
{
LPMIXERCONTROLDETAILS_UNSIGNED mcdu;
Float32 left, right = 0;
if (lpmcd->cbDetails != sizeof(MIXERCONTROLDETAILS_UNSIGNED)) {
WARN("invalid parameter: lpmcd->cbDetails == %d\n", lpmcd->cbDetails);
return MMSYSERR_INVALPARAM;
}
mcdu = (LPMIXERCONTROLDETAILS_UNSIGNED)lpmcd->paDetails;
switch (lpmcd->cChannels)
{
case 1:
/* mono... so R = L */
TRACE("Setting RL to %d\n", mcdu->dwValue);
left = (Float32) mcdu->dwValue / 65535.0;
break;
case 2:
/* stereo, left is paDetails[0] */
TRACE("Setting L to %d\n", mcdu->dwValue);
left = (Float32) mcdu->dwValue / 65535.0;
mcdu++;
TRACE("Setting R to %d\n", mcdu->dwValue);
right = (Float32) mcdu->dwValue / 65535.0;
break;
default:
WARN("Unsupported cChannels (%d)\n", lpmcd->cChannels);
return MMSYSERR_INVALPARAM;
}
if ( MIX_LineSetVolume(mixer.mixerCtrls[lpmcd->dwControlID].dwLineID, lpmcd->cChannels, left, right) )
ret = MMSYSERR_NOERROR;
}
break;
case MIXERCONTROL_CONTROLTYPE_MUTE:
case MIXERCONTROL_CONTROLTYPE_ONOFF:
TRACE("%s MIXERCONTROLDETAILS_BOOLEAN[%u]\n", getControlType(dwControlType), lpmcd->cChannels);
{
LPMIXERCONTROLDETAILS_BOOLEAN mcdb;
if (lpmcd->cbDetails != sizeof(MIXERCONTROLDETAILS_BOOLEAN)) {
WARN("invalid parameter: cbDetails\n");
return MMSYSERR_INVALPARAM;
}
mcdb = (LPMIXERCONTROLDETAILS_BOOLEAN)lpmcd->paDetails;
if ( MIX_LineSetMute(mixer.mixerCtrls[lpmcd->dwControlID].dwLineID, mcdb->fValue) )
ret = MMSYSERR_NOERROR;
}
break;
case MIXERCONTROL_CONTROLTYPE_MIXER:
case MIXERCONTROL_CONTROLTYPE_MUX:
default:
FIXME("controlType : %s\n", getControlType(dwControlType));
ret = MMSYSERR_NOTSUPPORTED;
break;
}
return ret;
}
/**************************************************************************
* mxdMessage
*/
DWORD WINAPI CoreAudio_mxdMessage(UINT wDevID, UINT wMsg, DWORD_PTR dwUser,
DWORD_PTR dwParam1, DWORD_PTR dwParam2)
{
TRACE("(%04X, %s, %08lX, %08lX, %08lX);\n", wDevID, getMessage(wMsg),
dwUser, dwParam1, dwParam2);
switch (wMsg)
{
case DRVM_INIT:
case DRVM_EXIT:
case DRVM_ENABLE:
case DRVM_DISABLE:
/* FIXME: Pretend this is supported */
return 0;
case MXDM_OPEN:
return MIX_Open(wDevID, (LPMIXEROPENDESC)dwParam1, dwParam2);
case MXDM_CLOSE:
return MMSYSERR_NOERROR;
case MXDM_GETNUMDEVS:
return MIX_GetNumDevs();
case MXDM_GETDEVCAPS:
return MIX_GetDevCaps(wDevID, (LPMIXERCAPSW)dwParam1, dwParam2);
case MXDM_GETLINEINFO:
return MIX_GetLineInfo(wDevID, (LPMIXERLINEW)dwParam1, dwParam2);
case MXDM_GETLINECONTROLS:
return MIX_GetLineControls(wDevID, (LPMIXERLINECONTROLSW)dwParam1, dwParam2);
case MXDM_GETCONTROLDETAILS:
return MIX_GetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2);
case MXDM_SETCONTROLDETAILS:
return MIX_SetControlDetails(wDevID, (LPMIXERCONTROLDETAILS)dwParam1, dwParam2);
default:
WARN("unknown message %d!\n", wMsg);
return MMSYSERR_NOTSUPPORTED;
}
}
#else
DWORD WINAPI CoreAudio_mxdMessage(UINT wDevID, UINT wMsg, DWORD_PTR dwUser,
DWORD_PTR dwParam1, DWORD_PTR dwParam2)
{
TRACE("(%04X, %04x, %08lX, %08lX, %08lX);\n", wDevID, wMsg, dwUser, dwParam1, dwParam2);
return MMSYSERR_NOTENABLED;
}
#endif /* HAVE_COREAUDIO_COREAUDIO_H */