diff --git a/pkgs/default.nix b/pkgs/default.nix index 18a9c76..f7499ba 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -84,6 +84,7 @@ in inherit (pkgs'.looking-glass-client) version src; }); mobile-config-firefox = callPackage ./mobile-config-firefox { }; + osu-wine = callPackage ./osu-wine { }; ping-exporter = callPackage ./ping-exporter { }; proton-ge = pkgs.stdenvNoCC.mkDerivation { inherit (sources.proton-ge) pname version src; diff --git a/pkgs/osu-wine/audio-revert/mmdevapi/Makefile.in b/pkgs/osu-wine/audio-revert/mmdevapi/Makefile.in new file mode 100644 index 0000000..612bc79 --- /dev/null +++ b/pkgs/osu-wine/audio-revert/mmdevapi/Makefile.in @@ -0,0 +1,9 @@ +MODULE = mmdevapi.dll +IMPORTS = uuid ole32 oleaut32 user32 advapi32 + +SOURCES = \ + audiovolume.c \ + devenum.c \ + main.c \ + mmdevapi_classes.idl \ + spatialaudio.c diff --git a/pkgs/osu-wine/audio-revert/mmdevapi/audiovolume.c b/pkgs/osu-wine/audio-revert/mmdevapi/audiovolume.c new file mode 100644 index 0000000..bbaba77 --- /dev/null +++ b/pkgs/osu-wine/audio-revert/mmdevapi/audiovolume.c @@ -0,0 +1,320 @@ +/* + * Copyright 2010 Maarten Lankhorst for CodeWeavers + * + * 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 + */ + +#define COBJMACROS + +#include + +#include "windef.h" +#include "winbase.h" +#include "winnls.h" +#include "winreg.h" +#include "wine/debug.h" + +#include "ole2.h" +#include "mmdeviceapi.h" +#include "mmsystem.h" +#include "dsound.h" +#include "audioclient.h" +#include "endpointvolume.h" +#include "audiopolicy.h" +#include "spatialaudioclient.h" + +#include "mmdevapi.h" + +WINE_DEFAULT_DEBUG_CHANNEL(mmdevapi); + +typedef struct AEVImpl { + IAudioEndpointVolumeEx IAudioEndpointVolumeEx_iface; + LONG ref; + float master_vol; + BOOL mute; +} AEVImpl; + +static inline AEVImpl *impl_from_IAudioEndpointVolumeEx(IAudioEndpointVolumeEx *iface) +{ + return CONTAINING_RECORD(iface, AEVImpl, IAudioEndpointVolumeEx_iface); +} + +static void AudioEndpointVolume_Destroy(AEVImpl *This) +{ + HeapFree(GetProcessHeap(), 0, This); +} + +static HRESULT WINAPI AEV_QueryInterface(IAudioEndpointVolumeEx *iface, REFIID riid, void **ppv) +{ + AEVImpl *This = impl_from_IAudioEndpointVolumeEx(iface); + TRACE("(%p)->(%s,%p)\n", This, debugstr_guid(riid), ppv); + if (!ppv) + return E_POINTER; + *ppv = NULL; + if (IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IAudioEndpointVolume) || + IsEqualIID(riid, &IID_IAudioEndpointVolumeEx)) { + *ppv = &This->IAudioEndpointVolumeEx_iface; + } + else + return E_NOINTERFACE; + IUnknown_AddRef((IUnknown *)*ppv); + return S_OK; +} + +static ULONG WINAPI AEV_AddRef(IAudioEndpointVolumeEx *iface) +{ + AEVImpl *This = impl_from_IAudioEndpointVolumeEx(iface); + ULONG ref = InterlockedIncrement(&This->ref); + TRACE("(%p) new ref %lu\n", This, ref); + return ref; +} + +static ULONG WINAPI AEV_Release(IAudioEndpointVolumeEx *iface) +{ + AEVImpl *This = impl_from_IAudioEndpointVolumeEx(iface); + ULONG ref = InterlockedDecrement(&This->ref); + TRACE("(%p) new ref %lu\n", This, ref); + if (!ref) + AudioEndpointVolume_Destroy(This); + return ref; +} + +static HRESULT WINAPI AEV_RegisterControlChangeNotify(IAudioEndpointVolumeEx *iface, IAudioEndpointVolumeCallback *notify) +{ + TRACE("(%p)->(%p)\n", iface, notify); + if (!notify) + return E_POINTER; + FIXME("stub\n"); + return S_OK; +} + +static HRESULT WINAPI AEV_UnregisterControlChangeNotify(IAudioEndpointVolumeEx *iface, IAudioEndpointVolumeCallback *notify) +{ + TRACE("(%p)->(%p)\n", iface, notify); + if (!notify) + return E_POINTER; + FIXME("stub\n"); + return S_OK; +} + +static HRESULT WINAPI AEV_GetChannelCount(IAudioEndpointVolumeEx *iface, UINT *count) +{ + TRACE("(%p)->(%p)\n", iface, count); + if (!count) + return E_POINTER; + FIXME("stub\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI AEV_SetMasterVolumeLevel(IAudioEndpointVolumeEx *iface, float leveldb, const GUID *ctx) +{ + AEVImpl *This = impl_from_IAudioEndpointVolumeEx(iface); + + TRACE("(%p)->(%f,%s)\n", iface, leveldb, debugstr_guid(ctx)); + + if(leveldb < -100.f || leveldb > 0.f) + return E_INVALIDARG; + + This->master_vol = leveldb; + + return S_OK; +} + +static HRESULT WINAPI AEV_SetMasterVolumeLevelScalar(IAudioEndpointVolumeEx *iface, float level, const GUID *ctx) +{ + TRACE("(%p)->(%f,%s)\n", iface, level, debugstr_guid(ctx)); + FIXME("stub\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI AEV_GetMasterVolumeLevel(IAudioEndpointVolumeEx *iface, float *leveldb) +{ + AEVImpl *This = impl_from_IAudioEndpointVolumeEx(iface); + + TRACE("(%p)->(%p)\n", iface, leveldb); + + if (!leveldb) + return E_POINTER; + + *leveldb = This->master_vol; + + return S_OK; +} + +static HRESULT WINAPI AEV_GetMasterVolumeLevelScalar(IAudioEndpointVolumeEx *iface, float *level) +{ + TRACE("(%p)->(%p)\n", iface, level); + if (!level) + return E_POINTER; + FIXME("stub\n"); + *level = 1.0; + return S_OK; +} + +static HRESULT WINAPI AEV_SetChannelVolumeLevel(IAudioEndpointVolumeEx *iface, UINT chan, float leveldb, const GUID *ctx) +{ + TRACE("(%p)->(%f,%s)\n", iface, leveldb, debugstr_guid(ctx)); + FIXME("stub\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI AEV_SetChannelVolumeLevelScalar(IAudioEndpointVolumeEx *iface, UINT chan, float level, const GUID *ctx) +{ + TRACE("(%p)->(%u,%f,%s)\n", iface, chan, level, debugstr_guid(ctx)); + FIXME("stub\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI AEV_GetChannelVolumeLevel(IAudioEndpointVolumeEx *iface, UINT chan, float *leveldb) +{ + TRACE("(%p)->(%u,%p)\n", iface, chan, leveldb); + if (!leveldb) + return E_POINTER; + FIXME("stub\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI AEV_GetChannelVolumeLevelScalar(IAudioEndpointVolumeEx *iface, UINT chan, float *level) +{ + TRACE("(%p)->(%u,%p)\n", iface, chan, level); + if (!level) + return E_POINTER; + FIXME("stub\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI AEV_SetMute(IAudioEndpointVolumeEx *iface, BOOL mute, const GUID *ctx) +{ + AEVImpl *This = impl_from_IAudioEndpointVolumeEx(iface); + HRESULT ret; + + TRACE("(%p)->(%u,%s)\n", iface, mute, debugstr_guid(ctx)); + + ret = This->mute == mute ? S_FALSE : S_OK; + + This->mute = mute; + + return ret; +} + +static HRESULT WINAPI AEV_GetMute(IAudioEndpointVolumeEx *iface, BOOL *mute) +{ + AEVImpl *This = impl_from_IAudioEndpointVolumeEx(iface); + + TRACE("(%p)->(%p)\n", iface, mute); + + if (!mute) + return E_POINTER; + + *mute = This->mute; + + return S_OK; +} + +static HRESULT WINAPI AEV_GetVolumeStepInfo(IAudioEndpointVolumeEx *iface, UINT *stepsize, UINT *stepcount) +{ + TRACE("(%p)->(%p,%p)\n", iface, stepsize, stepcount); + if (!stepsize && !stepcount) + return E_POINTER; + FIXME("stub\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI AEV_VolumeStepUp(IAudioEndpointVolumeEx *iface, const GUID *ctx) +{ + TRACE("(%p)->(%s)\n", iface, debugstr_guid(ctx)); + FIXME("stub\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI AEV_VolumeStepDown(IAudioEndpointVolumeEx *iface, const GUID *ctx) +{ + TRACE("(%p)->(%s)\n", iface, debugstr_guid(ctx)); + FIXME("stub\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI AEV_QueryHardwareSupport(IAudioEndpointVolumeEx *iface, DWORD *mask) +{ + TRACE("(%p)->(%p)\n", iface, mask); + if (!mask) + return E_POINTER; + FIXME("stub\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI AEV_GetVolumeRange(IAudioEndpointVolumeEx *iface, float *mindb, float *maxdb, float *inc) +{ + TRACE("(%p)->(%p,%p,%p)\n", iface, mindb, maxdb, inc); + + if (!mindb || !maxdb || !inc) + return E_POINTER; + + *mindb = -100.f; + *maxdb = 0.f; + *inc = 1.f; + + return S_OK; +} + +static HRESULT WINAPI AEV_GetVolumeRangeChannel(IAudioEndpointVolumeEx *iface, UINT chan, float *mindb, float *maxdb, float *inc) +{ + TRACE("(%p)->(%p,%p,%p)\n", iface, mindb, maxdb, inc); + if (!mindb || !maxdb || !inc) + return E_POINTER; + FIXME("stub\n"); + return E_NOTIMPL; +} + +static const IAudioEndpointVolumeExVtbl AEVImpl_Vtbl = { + AEV_QueryInterface, + AEV_AddRef, + AEV_Release, + AEV_RegisterControlChangeNotify, + AEV_UnregisterControlChangeNotify, + AEV_GetChannelCount, + AEV_SetMasterVolumeLevel, + AEV_SetMasterVolumeLevelScalar, + AEV_GetMasterVolumeLevel, + AEV_GetMasterVolumeLevelScalar, + AEV_SetChannelVolumeLevel, + AEV_SetChannelVolumeLevelScalar, + AEV_GetChannelVolumeLevel, + AEV_GetChannelVolumeLevelScalar, + AEV_SetMute, + AEV_GetMute, + AEV_GetVolumeStepInfo, + AEV_VolumeStepUp, + AEV_VolumeStepDown, + AEV_QueryHardwareSupport, + AEV_GetVolumeRange, + AEV_GetVolumeRangeChannel +}; + +HRESULT AudioEndpointVolume_Create(MMDevice *parent, IAudioEndpointVolumeEx **ppv) +{ + AEVImpl *This; + + *ppv = NULL; + This = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*This)); + if (!This) + return E_OUTOFMEMORY; + This->IAudioEndpointVolumeEx_iface.lpVtbl = &AEVImpl_Vtbl; + This->ref = 1; + + *ppv = &This->IAudioEndpointVolumeEx_iface; + return S_OK; +} diff --git a/pkgs/osu-wine/audio-revert/mmdevapi/devenum.c b/pkgs/osu-wine/audio-revert/mmdevapi/devenum.c new file mode 100644 index 0000000..69e13a4 --- /dev/null +++ b/pkgs/osu-wine/audio-revert/mmdevapi/devenum.c @@ -0,0 +1,1627 @@ +/* + * Copyright 2009 Maarten Lankhorst + * + * 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 + +#define NONAMELESSUNION +#define COBJMACROS +#include "windef.h" +#include "winbase.h" +#include "winnls.h" +#include "winreg.h" +#include "wine/debug.h" +#include "wine/list.h" + +#include "initguid.h" +#include "ole2.h" +#include "mmdeviceapi.h" +#include "dshow.h" +#include "dsound.h" +#include "audioclient.h" +#include "endpointvolume.h" +#include "audiopolicy.h" +#include "spatialaudioclient.h" + +#include "mmdevapi.h" +#include "devpkey.h" + +WINE_DEFAULT_DEBUG_CHANNEL(mmdevapi); + +static HKEY key_render; +static HKEY key_capture; + +typedef struct MMDevPropStoreImpl +{ + IPropertyStore IPropertyStore_iface; + LONG ref; + MMDevice *parent; + DWORD access; +} MMDevPropStore; + +typedef struct MMDevEnumImpl +{ + IMMDeviceEnumerator IMMDeviceEnumerator_iface; + LONG ref; +} MMDevEnumImpl; + +static MMDevice *MMDevice_def_rec, *MMDevice_def_play; +static const IMMDeviceEnumeratorVtbl MMDevEnumVtbl; +static const IMMDeviceCollectionVtbl MMDevColVtbl; +static const IMMDeviceVtbl MMDeviceVtbl; +static const IPropertyStoreVtbl MMDevPropVtbl; +static const IMMEndpointVtbl MMEndpointVtbl; + +static MMDevEnumImpl enumerator; +static struct list device_list = LIST_INIT(device_list); +static IMMDevice info_device; + +typedef struct MMDevColImpl +{ + IMMDeviceCollection IMMDeviceCollection_iface; + LONG ref; + EDataFlow flow; + DWORD state; +} MMDevColImpl; + +typedef struct IPropertyBagImpl { + IPropertyBag IPropertyBag_iface; + GUID devguid; +} IPropertyBagImpl; + +static const IPropertyBagVtbl PB_Vtbl; + +static HRESULT MMDevPropStore_Create(MMDevice *This, DWORD access, IPropertyStore **ppv); + +static inline MMDevPropStore *impl_from_IPropertyStore(IPropertyStore *iface) +{ + return CONTAINING_RECORD(iface, MMDevPropStore, IPropertyStore_iface); +} + +static inline MMDevEnumImpl *impl_from_IMMDeviceEnumerator(IMMDeviceEnumerator *iface) +{ + return CONTAINING_RECORD(iface, MMDevEnumImpl, IMMDeviceEnumerator_iface); +} + +static inline MMDevColImpl *impl_from_IMMDeviceCollection(IMMDeviceCollection *iface) +{ + return CONTAINING_RECORD(iface, MMDevColImpl, IMMDeviceCollection_iface); +} + +static inline IPropertyBagImpl *impl_from_IPropertyBag(IPropertyBag *iface) +{ + return CONTAINING_RECORD(iface, IPropertyBagImpl, IPropertyBag_iface); +} + +static const WCHAR propkey_formatW[] = L"{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X},%d"; + +static HRESULT MMDevPropStore_OpenPropKey(const GUID *guid, DWORD flow, HKEY *propkey) +{ + WCHAR buffer[39]; + LONG ret; + HKEY key; + StringFromGUID2(guid, buffer, 39); + if ((ret = RegOpenKeyExW(flow == eRender ? key_render : key_capture, buffer, 0, KEY_READ|KEY_WRITE|KEY_WOW64_64KEY, &key)) != ERROR_SUCCESS) + { + WARN("Opening key %s failed with %lu\n", debugstr_w(buffer), ret); + return E_FAIL; + } + ret = RegOpenKeyExW(key, L"Properties", 0, KEY_READ|KEY_WRITE|KEY_WOW64_64KEY, propkey); + RegCloseKey(key); + if (ret != ERROR_SUCCESS) + { + WARN("Opening key Properties failed with %lu\n", ret); + return E_FAIL; + } + return S_OK; +} + +static HRESULT MMDevice_GetPropValue(const GUID *devguid, DWORD flow, REFPROPERTYKEY key, PROPVARIANT *pv) +{ + WCHAR buffer[80]; + const GUID *id = &key->fmtid; + DWORD type, size; + HRESULT hr = S_OK; + HKEY regkey; + LONG ret; + + hr = MMDevPropStore_OpenPropKey(devguid, flow, ®key); + if (FAILED(hr)) + return hr; + wsprintfW( buffer, propkey_formatW, id->Data1, id->Data2, id->Data3, + id->Data4[0], id->Data4[1], id->Data4[2], id->Data4[3], + id->Data4[4], id->Data4[5], id->Data4[6], id->Data4[7], key->pid ); + ret = RegGetValueW(regkey, NULL, buffer, RRF_RT_ANY, &type, NULL, &size); + if (ret != ERROR_SUCCESS) + { + WARN("Reading %s returned %ld\n", debugstr_w(buffer), ret); + RegCloseKey(regkey); + pv->vt = VT_EMPTY; + return S_OK; + } + + switch (type) + { + case REG_SZ: + { + pv->vt = VT_LPWSTR; + pv->pwszVal = CoTaskMemAlloc(size); + if (!pv->pwszVal) + hr = E_OUTOFMEMORY; + else + RegGetValueW(regkey, NULL, buffer, RRF_RT_REG_SZ, NULL, (BYTE*)pv->pwszVal, &size); + break; + } + case REG_DWORD: + { + pv->vt = VT_UI4; + RegGetValueW(regkey, NULL, buffer, RRF_RT_REG_DWORD, NULL, (BYTE*)&pv->ulVal, &size); + break; + } + case REG_BINARY: + { + pv->vt = VT_BLOB; + pv->blob.cbSize = size; + pv->blob.pBlobData = CoTaskMemAlloc(size); + if (!pv->blob.pBlobData) + hr = E_OUTOFMEMORY; + else + RegGetValueW(regkey, NULL, buffer, RRF_RT_REG_BINARY, NULL, (BYTE*)pv->blob.pBlobData, &size); + break; + } + default: + ERR("Unknown/unhandled type: %lu\n", type); + PropVariantClear(pv); + break; + } + RegCloseKey(regkey); + return hr; +} + +static HRESULT MMDevice_SetPropValue(const GUID *devguid, DWORD flow, REFPROPERTYKEY key, REFPROPVARIANT pv) +{ + WCHAR buffer[80]; + const GUID *id = &key->fmtid; + HRESULT hr; + HKEY regkey; + LONG ret; + + hr = MMDevPropStore_OpenPropKey(devguid, flow, ®key); + if (FAILED(hr)) + return hr; + wsprintfW( buffer, propkey_formatW, id->Data1, id->Data2, id->Data3, + id->Data4[0], id->Data4[1], id->Data4[2], id->Data4[3], + id->Data4[4], id->Data4[5], id->Data4[6], id->Data4[7], key->pid ); + switch (pv->vt) + { + case VT_UI4: + { + ret = RegSetValueExW(regkey, buffer, 0, REG_DWORD, (const BYTE*)&pv->ulVal, sizeof(DWORD)); + break; + } + case VT_BLOB: + { + ret = RegSetValueExW(regkey, buffer, 0, REG_BINARY, pv->blob.pBlobData, pv->blob.cbSize); + TRACE("Blob %p %lu\n", pv->blob.pBlobData, pv->blob.cbSize); + + break; + } + case VT_LPWSTR: + { + ret = RegSetValueExW(regkey, buffer, 0, REG_SZ, (const BYTE*)pv->pwszVal, sizeof(WCHAR)*(1+lstrlenW(pv->pwszVal))); + break; + } + default: + ret = 0; + FIXME("Unhandled type %u\n", pv->vt); + hr = E_INVALIDARG; + break; + } + RegCloseKey(regkey); + TRACE("Writing %s returned %lu\n", debugstr_w(buffer), ret); + return hr; +} + +static HRESULT set_driver_prop_value(GUID *id, const EDataFlow flow, const PROPERTYKEY *prop) +{ + HRESULT hr; + PROPVARIANT pv; + + if (!drvs.pGetPropValue) + return E_NOTIMPL; + + hr = drvs.pGetPropValue(id, prop, &pv); + + if (SUCCEEDED(hr)) + { + MMDevice_SetPropValue(id, flow, prop, &pv); + PropVariantClear(&pv); + } + + return hr; +} + +struct product_name_overrides +{ + const WCHAR *id; + const WCHAR *product; +}; + +static const struct product_name_overrides product_name_overrides[] = +{ + /* Sony controllers */ + { .id = L"VID_054C&PID_0CE6", .product = L"Wireless Controller" }, +}; + +static const WCHAR *find_product_name_override(const WCHAR *device_id) +{ + const WCHAR *match_id = wcschr( device_id, '\\' ) + 1; + DWORD i; + + for (i = 0; i < ARRAY_SIZE(product_name_overrides); ++i) + if (!wcsnicmp( product_name_overrides[i].id, match_id, 17 )) + return product_name_overrides[i].product; + + return NULL; +} + +/* Creates or updates the state of a device + * If GUID is null, a random guid will be assigned + * and the device will be created + */ +static MMDevice *MMDevice_Create(WCHAR *name, GUID *id, EDataFlow flow, DWORD state, BOOL setdefault) +{ + HKEY key, root; + MMDevice *device, *cur = NULL; + WCHAR guidstr[39]; + + static const PROPERTYKEY deviceinterface_key = { + {0x233164c8, 0x1b2c, 0x4c7d, {0xbc, 0x68, 0xb6, 0x71, 0x68, 0x7a, 0x25, 0x67}}, 1 + }; + + static const PROPERTYKEY devicepath_key = { + {0xb3f8fa53, 0x0004, 0x438e, {0x90, 0x03, 0x51, 0xa4, 0x6e, 0x13, 0x9b, 0xfc}}, 2 + }; + + LIST_FOR_EACH_ENTRY(device, &device_list, MMDevice, entry) + { + if (device->flow == flow && IsEqualGUID(&device->devguid, id)){ + cur = device; + break; + } + } + + if(!cur){ + /* No device found, allocate new one */ + cur = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*cur)); + if (!cur) + return NULL; + + cur->IMMDevice_iface.lpVtbl = &MMDeviceVtbl; + cur->IMMEndpoint_iface.lpVtbl = &MMEndpointVtbl; + + InitializeCriticalSection(&cur->crst); + cur->crst.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": MMDevice.crst"); + + list_add_tail(&device_list, &cur->entry); + }else if(cur->ref > 0) + WARN("Modifying an MMDevice with postitive reference count!\n"); + + HeapFree(GetProcessHeap(), 0, cur->drv_id); + cur->drv_id = name; + + cur->flow = flow; + cur->state = state; + cur->devguid = *id; + + StringFromGUID2(&cur->devguid, guidstr, ARRAY_SIZE(guidstr)); + + if (flow == eRender) + root = key_render; + else + root = key_capture; + + if (RegCreateKeyExW(root, guidstr, 0, NULL, 0, KEY_WRITE|KEY_READ|KEY_WOW64_64KEY, NULL, &key, NULL) == ERROR_SUCCESS) + { + HKEY keyprop; + RegSetValueExW(key, L"DeviceState", 0, REG_DWORD, (const BYTE*)&state, sizeof(DWORD)); + if (!RegCreateKeyExW(key, L"Properties", 0, NULL, 0, KEY_WRITE|KEY_READ|KEY_WOW64_64KEY, NULL, &keyprop, NULL)) + { + PROPVARIANT pv; + + pv.vt = VT_LPWSTR; + pv.pwszVal = name; + + if (SUCCEEDED(set_driver_prop_value(id, flow, &devicepath_key))) { + PROPVARIANT pv2; + + PropVariantInit(&pv2); + + if (SUCCEEDED(MMDevice_GetPropValue(id, flow, &devicepath_key, &pv2)) && pv2.vt == VT_LPWSTR) { + const WCHAR *override; + if ((override = find_product_name_override(pv2.pwszVal)) != NULL) + pv.pwszVal = (WCHAR*) override; + } + + PropVariantClear(&pv2); + } + + MMDevice_SetPropValue(id, flow, (const PROPERTYKEY*)&DEVPKEY_Device_FriendlyName, &pv); + MMDevice_SetPropValue(id, flow, (const PROPERTYKEY*)&DEVPKEY_DeviceInterface_FriendlyName, &pv); + MMDevice_SetPropValue(id, flow, (const PROPERTYKEY*)&DEVPKEY_Device_DeviceDesc, &pv); + + pv.pwszVal = guidstr; + MMDevice_SetPropValue(id, flow, &deviceinterface_key, &pv); + + if (FAILED(set_driver_prop_value(id, flow, &PKEY_AudioEndpoint_FormFactor))) + { + pv.vt = VT_UI4; + pv.ulVal = (flow == eCapture) ? Microphone : Speakers; + + MMDevice_SetPropValue(id, flow, &PKEY_AudioEndpoint_FormFactor, &pv); + } + + if (flow != eCapture) + { + PROPVARIANT pv2; + + PropVariantInit(&pv2); + + /* make read-write by not overwriting if already set */ + if (FAILED(MMDevice_GetPropValue(id, flow, &PKEY_AudioEndpoint_PhysicalSpeakers, &pv2)) || pv2.vt != VT_UI4) + set_driver_prop_value(id, flow, &PKEY_AudioEndpoint_PhysicalSpeakers); + + PropVariantClear(&pv2); + } + + RegCloseKey(keyprop); + } + RegCloseKey(key); + } + + if (setdefault) + { + if (flow == eRender) + MMDevice_def_play = cur; + else + MMDevice_def_rec = cur; + } + return cur; +} + +HRESULT load_devices_from_reg(void) +{ + DWORD i = 0; + HKEY root, cur; + LONG ret; + DWORD curflow; + + ret = RegCreateKeyExW(HKEY_LOCAL_MACHINE, + L"Software\\Microsoft\\Windows\\CurrentVersion\\MMDevices\\Audio", 0, NULL, 0, + KEY_WRITE|KEY_READ|KEY_WOW64_64KEY, NULL, &root, NULL); + if (ret == ERROR_SUCCESS) + ret = RegCreateKeyExW(root, L"Capture", 0, NULL, 0, KEY_READ|KEY_WRITE|KEY_WOW64_64KEY, NULL, &key_capture, NULL); + if (ret == ERROR_SUCCESS) + ret = RegCreateKeyExW(root, L"Render", 0, NULL, 0, KEY_READ|KEY_WRITE|KEY_WOW64_64KEY, NULL, &key_render, NULL); + RegCloseKey(root); + cur = key_capture; + curflow = eCapture; + if (ret != ERROR_SUCCESS) + { + RegCloseKey(key_capture); + key_render = key_capture = NULL; + WARN("Couldn't create key: %lu\n", ret); + return E_FAIL; + } + + do { + WCHAR guidvalue[39]; + GUID guid; + DWORD len; + PROPVARIANT pv = { VT_EMPTY }; + + len = ARRAY_SIZE(guidvalue); + ret = RegEnumKeyExW(cur, i++, guidvalue, &len, NULL, NULL, NULL, NULL); + if (ret == ERROR_NO_MORE_ITEMS) + { + if (cur == key_capture) + { + cur = key_render; + curflow = eRender; + i = 0; + continue; + } + break; + } + if (ret != ERROR_SUCCESS) + continue; + if (SUCCEEDED(CLSIDFromString(guidvalue, &guid)) + && SUCCEEDED(MMDevice_GetPropValue(&guid, curflow, (const PROPERTYKEY*)&DEVPKEY_Device_FriendlyName, &pv)) + && pv.vt == VT_LPWSTR) + { + DWORD size_bytes = (lstrlenW(pv.pwszVal) + 1) * sizeof(WCHAR); + WCHAR *name = HeapAlloc(GetProcessHeap(), 0, size_bytes); + memcpy(name, pv.pwszVal, size_bytes); + MMDevice_Create(name, &guid, curflow, + DEVICE_STATE_NOTPRESENT, FALSE); + CoTaskMemFree(pv.pwszVal); + } + } while (1); + + return S_OK; +} + +static HRESULT set_format(MMDevice *dev) +{ + HRESULT hr; + IAudioClient *client; + WAVEFORMATEX *fmt; + PROPVARIANT pv = { VT_EMPTY }; + + hr = drvs.pGetAudioEndpoint(&dev->devguid, &dev->IMMDevice_iface, &client); + if(FAILED(hr)) + return hr; + + hr = IAudioClient_GetMixFormat(client, &fmt); + if(FAILED(hr)){ + IAudioClient_Release(client); + return hr; + } + + IAudioClient_Release(client); + + pv.vt = VT_BLOB; + pv.blob.cbSize = sizeof(WAVEFORMATEX) + fmt->cbSize; + pv.blob.pBlobData = (BYTE*)fmt; + MMDevice_SetPropValue(&dev->devguid, dev->flow, + &PKEY_AudioEngine_DeviceFormat, &pv); + MMDevice_SetPropValue(&dev->devguid, dev->flow, + &PKEY_AudioEngine_OEMFormat, &pv); + CoTaskMemFree(fmt); + + return S_OK; +} + +HRESULT load_driver_devices(EDataFlow flow) +{ + WCHAR **ids; + GUID *guids; + UINT num, def, i; + HRESULT hr; + + if(!drvs.pGetEndpointIDs) + return S_OK; + + hr = drvs.pGetEndpointIDs(flow, &ids, &guids, &num, &def); + if(FAILED(hr)) + return hr; + + for(i = 0; i < num; ++i){ + MMDevice *dev; + dev = MMDevice_Create(ids[i], &guids[i], flow, DEVICE_STATE_ACTIVE, + def == i); + set_format(dev); + } + + HeapFree(GetProcessHeap(), 0, guids); + HeapFree(GetProcessHeap(), 0, ids); + + return S_OK; +} + +static void MMDevice_Destroy(MMDevice *This) +{ + TRACE("Freeing %s\n", debugstr_w(This->drv_id)); + list_remove(&This->entry); + This->crst.DebugInfo->Spare[0] = 0; + DeleteCriticalSection(&This->crst); + HeapFree(GetProcessHeap(), 0, This->drv_id); + HeapFree(GetProcessHeap(), 0, This); +} + +static inline MMDevice *impl_from_IMMDevice(IMMDevice *iface) +{ + return CONTAINING_RECORD(iface, MMDevice, IMMDevice_iface); +} + +static HRESULT WINAPI MMDevice_QueryInterface(IMMDevice *iface, REFIID riid, void **ppv) +{ + MMDevice *This = impl_from_IMMDevice(iface); + TRACE("(%p)->(%s,%p)\n", iface, debugstr_guid(riid), ppv); + + if (!ppv) + return E_POINTER; + *ppv = NULL; + if (IsEqualIID(riid, &IID_IUnknown) + || IsEqualIID(riid, &IID_IMMDevice)) + *ppv = &This->IMMDevice_iface; + else if (IsEqualIID(riid, &IID_IMMEndpoint)) + *ppv = &This->IMMEndpoint_iface; + if (*ppv) + { + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI MMDevice_AddRef(IMMDevice *iface) +{ + MMDevice *This = impl_from_IMMDevice(iface); + LONG ref; + + ref = InterlockedIncrement(&This->ref); + TRACE("Refcount now %li\n", ref); + return ref; +} + +static ULONG WINAPI MMDevice_Release(IMMDevice *iface) +{ + MMDevice *This = impl_from_IMMDevice(iface); + LONG ref; + + ref = InterlockedDecrement(&This->ref); + TRACE("Refcount now %li\n", ref); + return ref; +} + +static HRESULT WINAPI MMDevice_Activate(IMMDevice *iface, REFIID riid, DWORD clsctx, PROPVARIANT *params, void **ppv) +{ + HRESULT hr = E_NOINTERFACE; + MMDevice *This = impl_from_IMMDevice(iface); + + TRACE("(%p)->(%s, %lx, %p, %p)\n", iface, debugstr_guid(riid), clsctx, params, ppv); + + if (!ppv) + return E_POINTER; + + if (IsEqualIID(riid, &IID_IAudioClient) || + IsEqualIID(riid, &IID_IAudioClient2) || + IsEqualIID(riid, &IID_IAudioClient3)){ + hr = drvs.pGetAudioEndpoint(&This->devguid, iface, (IAudioClient**)ppv); + }else if (IsEqualIID(riid, &IID_IAudioEndpointVolume) || + IsEqualIID(riid, &IID_IAudioEndpointVolumeEx)) + hr = AudioEndpointVolume_Create(This, (IAudioEndpointVolumeEx**)ppv); + else if (IsEqualIID(riid, &IID_IAudioSessionManager) + || IsEqualIID(riid, &IID_IAudioSessionManager2)) + { + hr = drvs.pGetAudioSessionManager(iface, (IAudioSessionManager2**)ppv); + } + else if (IsEqualIID(riid, &IID_IBaseFilter)) + { + if (This->flow == eRender) + hr = CoCreateInstance(&CLSID_DSoundRender, NULL, clsctx, riid, ppv); + else + ERR("Not supported for recording?\n"); + if (SUCCEEDED(hr)) + { + IPersistPropertyBag *ppb; + hr = IUnknown_QueryInterface((IUnknown*)*ppv, &IID_IPersistPropertyBag, (void **)&ppb); + if (SUCCEEDED(hr)) + { + /* ::Load cannot assume the interface stays alive after the function returns, + * so just create the interface on the stack, saves a lot of complicated code */ + IPropertyBagImpl bag = { { &PB_Vtbl } }; + bag.devguid = This->devguid; + hr = IPersistPropertyBag_Load(ppb, &bag.IPropertyBag_iface, NULL); + IPersistPropertyBag_Release(ppb); + if (FAILED(hr)) + IBaseFilter_Release((IBaseFilter*)*ppv); + } + else + { + FIXME("Wine doesn't support IPersistPropertyBag on DSoundRender yet, ignoring..\n"); + hr = S_OK; + } + } + } + else if (IsEqualIID(riid, &IID_IDeviceTopology)) + { + FIXME("IID_IDeviceTopology unsupported\n"); + } + else if (IsEqualIID(riid, &IID_IDirectSound) + || IsEqualIID(riid, &IID_IDirectSound8)) + { + if (This->flow == eRender) + hr = CoCreateInstance(&CLSID_DirectSound8, NULL, clsctx, riid, ppv); + if (SUCCEEDED(hr)) + { + hr = IDirectSound_Initialize((IDirectSound*)*ppv, &This->devguid); + if (FAILED(hr)) + IDirectSound_Release((IDirectSound*)*ppv); + } + } + else if (IsEqualIID(riid, &IID_IDirectSoundCapture)) + { + if (This->flow == eCapture) + hr = CoCreateInstance(&CLSID_DirectSoundCapture8, NULL, clsctx, riid, ppv); + if (SUCCEEDED(hr)) + { + hr = IDirectSoundCapture_Initialize((IDirectSoundCapture*)*ppv, &This->devguid); + if (FAILED(hr)) + IDirectSoundCapture_Release((IDirectSoundCapture*)*ppv); + } + } + else if (IsEqualIID(riid, &IID_ISpatialAudioClient)) + { + hr = SpatialAudioClient_Create(iface, (ISpatialAudioClient**)ppv); + } + else + ERR("Invalid/unknown iid %s\n", debugstr_guid(riid)); + + if (FAILED(hr)) + *ppv = NULL; + + TRACE("Returning %08lx\n", hr); + return hr; +} + +static HRESULT WINAPI MMDevice_OpenPropertyStore(IMMDevice *iface, DWORD access, IPropertyStore **ppv) +{ + MMDevice *This = impl_from_IMMDevice(iface); + TRACE("(%p)->(%lx,%p)\n", This, access, ppv); + + if (!ppv) + return E_POINTER; + return MMDevPropStore_Create(This, access, ppv); +} + +static HRESULT WINAPI MMDevice_GetId(IMMDevice *iface, WCHAR **itemid) +{ + MMDevice *This = impl_from_IMMDevice(iface); + WCHAR *str; + GUID *id = &This->devguid; + + TRACE("(%p)->(%p)\n", This, itemid); + if (!itemid) + return E_POINTER; + *itemid = str = CoTaskMemAlloc(56 * sizeof(WCHAR)); + if (!str) + return E_OUTOFMEMORY; + wsprintfW(str, L"{0.0.%u.00000000}.{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", + This->flow, id->Data1, id->Data2, id->Data3, + id->Data4[0], id->Data4[1], id->Data4[2], id->Data4[3], + id->Data4[4], id->Data4[5], id->Data4[6], id->Data4[7]); + TRACE("returning %s\n", wine_dbgstr_w(str)); + return S_OK; +} + +static HRESULT WINAPI MMDevice_GetState(IMMDevice *iface, DWORD *state) +{ + MMDevice *This = impl_from_IMMDevice(iface); + TRACE("(%p)->(%p)\n", iface, state); + + if (!state) + return E_POINTER; + *state = This->state; + return S_OK; +} + +static const IMMDeviceVtbl MMDeviceVtbl = +{ + MMDevice_QueryInterface, + MMDevice_AddRef, + MMDevice_Release, + MMDevice_Activate, + MMDevice_OpenPropertyStore, + MMDevice_GetId, + MMDevice_GetState +}; + +static inline MMDevice *impl_from_IMMEndpoint(IMMEndpoint *iface) +{ + return CONTAINING_RECORD(iface, MMDevice, IMMEndpoint_iface); +} + +static HRESULT WINAPI MMEndpoint_QueryInterface(IMMEndpoint *iface, REFIID riid, void **ppv) +{ + MMDevice *This = impl_from_IMMEndpoint(iface); + TRACE("(%p)->(%s, %p)\n", This, debugstr_guid(riid), ppv); + return IMMDevice_QueryInterface(&This->IMMDevice_iface, riid, ppv); +} + +static ULONG WINAPI MMEndpoint_AddRef(IMMEndpoint *iface) +{ + MMDevice *This = impl_from_IMMEndpoint(iface); + TRACE("(%p)\n", This); + return IMMDevice_AddRef(&This->IMMDevice_iface); +} + +static ULONG WINAPI MMEndpoint_Release(IMMEndpoint *iface) +{ + MMDevice *This = impl_from_IMMEndpoint(iface); + TRACE("(%p)\n", This); + return IMMDevice_Release(&This->IMMDevice_iface); +} + +static HRESULT WINAPI MMEndpoint_GetDataFlow(IMMEndpoint *iface, EDataFlow *flow) +{ + MMDevice *This = impl_from_IMMEndpoint(iface); + TRACE("(%p)->(%p)\n", This, flow); + if (!flow) + return E_POINTER; + *flow = This->flow; + return S_OK; +} + +static const IMMEndpointVtbl MMEndpointVtbl = +{ + MMEndpoint_QueryInterface, + MMEndpoint_AddRef, + MMEndpoint_Release, + MMEndpoint_GetDataFlow +}; + +static HRESULT MMDevCol_Create(IMMDeviceCollection **ppv, EDataFlow flow, DWORD state) +{ + MMDevColImpl *This; + + This = HeapAlloc(GetProcessHeap(), 0, sizeof(*This)); + *ppv = NULL; + if (!This) + return E_OUTOFMEMORY; + This->IMMDeviceCollection_iface.lpVtbl = &MMDevColVtbl; + This->ref = 1; + This->flow = flow; + This->state = state; + *ppv = &This->IMMDeviceCollection_iface; + return S_OK; +} + +static void MMDevCol_Destroy(MMDevColImpl *This) +{ + HeapFree(GetProcessHeap(), 0, This); +} + +static HRESULT WINAPI MMDevCol_QueryInterface(IMMDeviceCollection *iface, REFIID riid, void **ppv) +{ + MMDevColImpl *This = impl_from_IMMDeviceCollection(iface); + TRACE("(%p)->(%s, %p)\n", This, debugstr_guid(riid), ppv); + + if (!ppv) + return E_POINTER; + if (IsEqualIID(riid, &IID_IUnknown) + || IsEqualIID(riid, &IID_IMMDeviceCollection)) + *ppv = &This->IMMDeviceCollection_iface; + else + *ppv = NULL; + if (!*ppv) + return E_NOINTERFACE; + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; +} + +static ULONG WINAPI MMDevCol_AddRef(IMMDeviceCollection *iface) +{ + MMDevColImpl *This = impl_from_IMMDeviceCollection(iface); + LONG ref = InterlockedIncrement(&This->ref); + TRACE("Refcount now %li\n", ref); + return ref; +} + +static ULONG WINAPI MMDevCol_Release(IMMDeviceCollection *iface) +{ + MMDevColImpl *This = impl_from_IMMDeviceCollection(iface); + LONG ref = InterlockedDecrement(&This->ref); + TRACE("Refcount now %li\n", ref); + if (!ref) + MMDevCol_Destroy(This); + return ref; +} + +static HRESULT WINAPI MMDevCol_GetCount(IMMDeviceCollection *iface, UINT *numdevs) +{ + MMDevColImpl *This = impl_from_IMMDeviceCollection(iface); + MMDevice *cur; + + TRACE("(%p)->(%p)\n", This, numdevs); + if (!numdevs) + return E_POINTER; + + *numdevs = 0; + LIST_FOR_EACH_ENTRY(cur, &device_list, MMDevice, entry) + { + if ((cur->flow == This->flow || This->flow == eAll) + && (cur->state & This->state)) + ++(*numdevs); + } + return S_OK; +} + +static HRESULT WINAPI MMDevCol_Item(IMMDeviceCollection *iface, UINT n, IMMDevice **dev) +{ + MMDevColImpl *This = impl_from_IMMDeviceCollection(iface); + MMDevice *cur; + DWORD i = 0; + + TRACE("(%p)->(%u, %p)\n", This, n, dev); + if (!dev) + return E_POINTER; + + LIST_FOR_EACH_ENTRY(cur, &device_list, MMDevice, entry) + { + if ((cur->flow == This->flow || This->flow == eAll) + && (cur->state & This->state) + && i++ == n) + { + *dev = &cur->IMMDevice_iface; + IMMDevice_AddRef(*dev); + return S_OK; + } + } + WARN("Could not obtain item %u\n", n); + *dev = NULL; + return E_INVALIDARG; +} + +static const IMMDeviceCollectionVtbl MMDevColVtbl = +{ + MMDevCol_QueryInterface, + MMDevCol_AddRef, + MMDevCol_Release, + MMDevCol_GetCount, + MMDevCol_Item +}; + +HRESULT MMDevEnum_Create(REFIID riid, void **ppv) +{ + return IMMDeviceEnumerator_QueryInterface(&enumerator.IMMDeviceEnumerator_iface, riid, ppv); +} + +void MMDevEnum_Free(void) +{ + MMDevice *device, *next; + LIST_FOR_EACH_ENTRY_SAFE(device, next, &device_list, MMDevice, entry) + MMDevice_Destroy(device); + RegCloseKey(key_render); + RegCloseKey(key_capture); + key_render = key_capture = NULL; +} + +static HRESULT WINAPI MMDevEnum_QueryInterface(IMMDeviceEnumerator *iface, REFIID riid, void **ppv) +{ + MMDevEnumImpl *This = impl_from_IMMDeviceEnumerator(iface); + TRACE("(%p)->(%s, %p)\n", This, debugstr_guid(riid), ppv); + + if (!ppv) + return E_POINTER; + if (IsEqualIID(riid, &IID_IUnknown) + || IsEqualIID(riid, &IID_IMMDeviceEnumerator)) + *ppv = &This->IMMDeviceEnumerator_iface; + else + *ppv = NULL; + if (!*ppv) + return E_NOINTERFACE; + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; +} + +static ULONG WINAPI MMDevEnum_AddRef(IMMDeviceEnumerator *iface) +{ + MMDevEnumImpl *This = impl_from_IMMDeviceEnumerator(iface); + LONG ref = InterlockedIncrement(&This->ref); + TRACE("Refcount now %li\n", ref); + return ref; +} + +static ULONG WINAPI MMDevEnum_Release(IMMDeviceEnumerator *iface) +{ + MMDevEnumImpl *This = impl_from_IMMDeviceEnumerator(iface); + LONG ref = InterlockedDecrement(&This->ref); + TRACE("Refcount now %li\n", ref); + return ref; +} + +static HRESULT WINAPI MMDevEnum_EnumAudioEndpoints(IMMDeviceEnumerator *iface, EDataFlow flow, DWORD mask, IMMDeviceCollection **devices) +{ + MMDevEnumImpl *This = impl_from_IMMDeviceEnumerator(iface); + TRACE("(%p)->(%u,%lu,%p)\n", This, flow, mask, devices); + if (!devices) + return E_POINTER; + *devices = NULL; + if (flow >= EDataFlow_enum_count) + return E_INVALIDARG; + if (mask & ~DEVICE_STATEMASK_ALL) + return E_INVALIDARG; + return MMDevCol_Create(devices, flow, mask); +} + +static HRESULT WINAPI MMDevEnum_GetDefaultAudioEndpoint(IMMDeviceEnumerator *iface, EDataFlow flow, ERole role, IMMDevice **device) +{ + MMDevEnumImpl *This = impl_from_IMMDeviceEnumerator(iface); + WCHAR reg_key[256]; + HKEY key; + HRESULT hr; + + TRACE("(%p)->(%u,%u,%p)\n", This, flow, role, device); + + if (!device) + return E_POINTER; + + if((flow != eRender && flow != eCapture) || + (role != eConsole && role != eMultimedia && role != eCommunications)){ + WARN("Unknown flow (%u) or role (%u)\n", flow, role); + return E_INVALIDARG; + } + + *device = NULL; + + if(!drvs.module_name[0]) + return E_NOTFOUND; + + lstrcpyW(reg_key, drv_keyW); + lstrcatW(reg_key, L"\\"); + lstrcatW(reg_key, drvs.module_name); + + if(RegOpenKeyW(HKEY_CURRENT_USER, reg_key, &key) == ERROR_SUCCESS){ + const WCHAR *reg_x_name, *reg_vx_name; + WCHAR def_id[256]; + DWORD size = sizeof(def_id), state; + + if(flow == eRender){ + reg_x_name = L"DefaultOutput"; + reg_vx_name = L"DefaultVoiceOutput"; + }else{ + reg_x_name = L"DefaultInput"; + reg_vx_name = L"DefaultVoiceInput"; + } + + if(role == eCommunications && + RegQueryValueExW(key, reg_vx_name, 0, NULL, + (BYTE*)def_id, &size) == ERROR_SUCCESS){ + hr = IMMDeviceEnumerator_GetDevice(iface, def_id, device); + if(SUCCEEDED(hr)){ + if(SUCCEEDED(IMMDevice_GetState(*device, &state)) && + state == DEVICE_STATE_ACTIVE){ + RegCloseKey(key); + return S_OK; + } + } + + TRACE("Unable to find voice device %s\n", wine_dbgstr_w(def_id)); + } + + if(RegQueryValueExW(key, reg_x_name, 0, NULL, + (BYTE*)def_id, &size) == ERROR_SUCCESS){ + hr = IMMDeviceEnumerator_GetDevice(iface, def_id, device); + if(SUCCEEDED(hr)){ + if(SUCCEEDED(IMMDevice_GetState(*device, &state)) && + state == DEVICE_STATE_ACTIVE){ + RegCloseKey(key); + return S_OK; + } + } + + TRACE("Unable to find device %s\n", wine_dbgstr_w(def_id)); + } + + RegCloseKey(key); + } + + if (flow == eRender) + *device = &MMDevice_def_play->IMMDevice_iface; + else + *device = &MMDevice_def_rec->IMMDevice_iface; + + if (!*device) + return E_NOTFOUND; + IMMDevice_AddRef(*device); + return S_OK; +} + +static HRESULT WINAPI MMDevEnum_GetDevice(IMMDeviceEnumerator *iface, const WCHAR *name, IMMDevice **device) +{ + MMDevEnumImpl *This = impl_from_IMMDeviceEnumerator(iface); + MMDevice *impl; + IMMDevice *dev = NULL; + + TRACE("(%p)->(%s,%p)\n", This, debugstr_w(name), device); + + if(!name || !device) + return E_POINTER; + + if(!lstrcmpW(name, L"Wine info device")){ + *device = &info_device; + return S_OK; + } + + LIST_FOR_EACH_ENTRY(impl, &device_list, MMDevice, entry) + { + HRESULT hr; + WCHAR *str; + dev = &impl->IMMDevice_iface; + hr = IMMDevice_GetId(dev, &str); + if (FAILED(hr)) + { + WARN("GetId failed: %08lx\n", hr); + continue; + } + + if (str && !lstrcmpW(str, name)) + { + CoTaskMemFree(str); + IMMDevice_AddRef(dev); + *device = dev; + return S_OK; + } + CoTaskMemFree(str); + } + TRACE("Could not find device %s\n", debugstr_w(name)); + return E_INVALIDARG; +} + +struct NotificationClientWrapper { + IMMNotificationClient *client; + struct list entry; +}; + +static struct list g_notif_clients = LIST_INIT(g_notif_clients); +static HANDLE g_notif_thread; + +static CRITICAL_SECTION g_notif_lock; +static CRITICAL_SECTION_DEBUG g_notif_lock_debug = +{ + 0, 0, &g_notif_lock, + { &g_notif_lock_debug.ProcessLocksList, &g_notif_lock_debug.ProcessLocksList }, + 0, 0, { (DWORD_PTR)(__FILE__ ": g_notif_lock") } +}; +static CRITICAL_SECTION g_notif_lock = { &g_notif_lock_debug, -1, 0, 0, 0, 0 }; + +static void notify_clients(EDataFlow flow, ERole role, const WCHAR *id) +{ + struct NotificationClientWrapper *wrapper; + LIST_FOR_EACH_ENTRY(wrapper, &g_notif_clients, + struct NotificationClientWrapper, entry) + IMMNotificationClient_OnDefaultDeviceChanged(wrapper->client, flow, + role, id); + + /* Windows 7 treats changes to eConsole as changes to eMultimedia */ + if(role == eConsole) + notify_clients(flow, eMultimedia, id); +} + +static BOOL notify_if_changed(EDataFlow flow, ERole role, HKEY key, + const WCHAR *val_name, WCHAR *old_val, IMMDevice *def_dev) +{ + WCHAR new_val[64], *id; + DWORD size; + HRESULT hr; + + size = sizeof(new_val); + if(RegQueryValueExW(key, val_name, 0, NULL, + (BYTE*)new_val, &size) != ERROR_SUCCESS){ + if(old_val[0] != 0){ + /* set by user -> system default */ + if(def_dev){ + hr = IMMDevice_GetId(def_dev, &id); + if(FAILED(hr)){ + ERR("GetId failed: %08lx\n", hr); + return FALSE; + } + }else + id = NULL; + + notify_clients(flow, role, id); + old_val[0] = 0; + CoTaskMemFree(id); + + return TRUE; + } + + /* system default -> system default, noop */ + return FALSE; + } + + if(!lstrcmpW(old_val, new_val)){ + /* set by user -> same value */ + return FALSE; + } + + if(new_val[0] != 0){ + /* set by user -> different value */ + notify_clients(flow, role, new_val); + memcpy(old_val, new_val, sizeof(new_val)); + return TRUE; + } + + /* set by user -> system default */ + if(def_dev){ + hr = IMMDevice_GetId(def_dev, &id); + if(FAILED(hr)){ + ERR("GetId failed: %08lx\n", hr); + return FALSE; + } + }else + id = NULL; + + notify_clients(flow, role, id); + old_val[0] = 0; + CoTaskMemFree(id); + + return TRUE; +} + +static DWORD WINAPI notif_thread_proc(void *user) +{ + HKEY key; + WCHAR reg_key[256]; + WCHAR out_name[64], vout_name[64], in_name[64], vin_name[64]; + DWORD size; + + SetThreadDescription(GetCurrentThread(), L"wine_mmdevapi_notification"); + + lstrcpyW(reg_key, drv_keyW); + lstrcatW(reg_key, L"\\"); + lstrcatW(reg_key, drvs.module_name); + + if(RegCreateKeyExW(HKEY_CURRENT_USER, reg_key, 0, NULL, 0, + MAXIMUM_ALLOWED, NULL, &key, NULL) != ERROR_SUCCESS){ + ERR("RegCreateKeyEx failed: %lu\n", GetLastError()); + return 1; + } + + size = sizeof(out_name); + if(RegQueryValueExW(key, L"DefaultOutput", 0, NULL, (BYTE*)out_name, &size) != ERROR_SUCCESS) + out_name[0] = 0; + + size = sizeof(vout_name); + if(RegQueryValueExW(key, L"DefaultVoiceOutput", 0, NULL, (BYTE*)vout_name, &size) != ERROR_SUCCESS) + vout_name[0] = 0; + + size = sizeof(in_name); + if(RegQueryValueExW(key, L"DefaultInput", 0, NULL, (BYTE*)in_name, &size) != ERROR_SUCCESS) + in_name[0] = 0; + + size = sizeof(vin_name); + if(RegQueryValueExW(key, L"DefaultVoiceInput", 0, NULL, (BYTE*)vin_name, &size) != ERROR_SUCCESS) + vin_name[0] = 0; + + while(1){ + if(RegNotifyChangeKeyValue(key, FALSE, REG_NOTIFY_CHANGE_LAST_SET, + NULL, FALSE) != ERROR_SUCCESS){ + ERR("RegNotifyChangeKeyValue failed: %lu\n", GetLastError()); + RegCloseKey(key); + g_notif_thread = NULL; + return 1; + } + + EnterCriticalSection(&g_notif_lock); + + notify_if_changed(eRender, eConsole, key, L"DefaultOutput", + out_name, &MMDevice_def_play->IMMDevice_iface); + notify_if_changed(eRender, eCommunications, key, L"DefaultVoiceOutput", + vout_name, &MMDevice_def_play->IMMDevice_iface); + notify_if_changed(eCapture, eConsole, key, L"DefaultInput", + in_name, &MMDevice_def_rec->IMMDevice_iface); + notify_if_changed(eCapture, eCommunications, key, L"DefaultVoiceInput", + vin_name, &MMDevice_def_rec->IMMDevice_iface); + + LeaveCriticalSection(&g_notif_lock); + } + + RegCloseKey(key); + + g_notif_thread = NULL; + + return 0; +} + +static HRESULT WINAPI MMDevEnum_RegisterEndpointNotificationCallback(IMMDeviceEnumerator *iface, IMMNotificationClient *client) +{ + MMDevEnumImpl *This = impl_from_IMMDeviceEnumerator(iface); + struct NotificationClientWrapper *wrapper; + + TRACE("(%p)->(%p)\n", This, client); + + if(!client) + return E_POINTER; + + wrapper = HeapAlloc(GetProcessHeap(), 0, sizeof(*wrapper)); + if(!wrapper) + return E_OUTOFMEMORY; + + wrapper->client = client; + + EnterCriticalSection(&g_notif_lock); + + list_add_tail(&g_notif_clients, &wrapper->entry); + + if(!g_notif_thread){ + g_notif_thread = CreateThread(NULL, 0, notif_thread_proc, NULL, 0, NULL); + if(!g_notif_thread) + ERR("CreateThread failed: %lu\n", GetLastError()); + } + + LeaveCriticalSection(&g_notif_lock); + + return S_OK; +} + +static HRESULT WINAPI MMDevEnum_UnregisterEndpointNotificationCallback(IMMDeviceEnumerator *iface, IMMNotificationClient *client) +{ + MMDevEnumImpl *This = impl_from_IMMDeviceEnumerator(iface); + struct NotificationClientWrapper *wrapper; + + TRACE("(%p)->(%p)\n", This, client); + + if(!client) + return E_POINTER; + + EnterCriticalSection(&g_notif_lock); + + LIST_FOR_EACH_ENTRY(wrapper, &g_notif_clients, struct NotificationClientWrapper, entry){ + if(wrapper->client == client){ + list_remove(&wrapper->entry); + HeapFree(GetProcessHeap(), 0, wrapper); + LeaveCriticalSection(&g_notif_lock); + return S_OK; + } + } + + LeaveCriticalSection(&g_notif_lock); + + return E_NOTFOUND; +} + +static const IMMDeviceEnumeratorVtbl MMDevEnumVtbl = +{ + MMDevEnum_QueryInterface, + MMDevEnum_AddRef, + MMDevEnum_Release, + MMDevEnum_EnumAudioEndpoints, + MMDevEnum_GetDefaultAudioEndpoint, + MMDevEnum_GetDevice, + MMDevEnum_RegisterEndpointNotificationCallback, + MMDevEnum_UnregisterEndpointNotificationCallback +}; + +static MMDevEnumImpl enumerator = +{ + {&MMDevEnumVtbl}, + 1, +}; + +static HRESULT MMDevPropStore_Create(MMDevice *parent, DWORD access, IPropertyStore **ppv) +{ + MMDevPropStore *This; + if (access != STGM_READ + && access != STGM_WRITE + && access != STGM_READWRITE) + { + WARN("Invalid access %08lx\n", access); + return E_INVALIDARG; + } + This = HeapAlloc(GetProcessHeap(), 0, sizeof(*This)); + *ppv = &This->IPropertyStore_iface; + if (!This) + return E_OUTOFMEMORY; + This->IPropertyStore_iface.lpVtbl = &MMDevPropVtbl; + This->ref = 1; + This->parent = parent; + This->access = access; + return S_OK; +} + +static void MMDevPropStore_Destroy(MMDevPropStore *This) +{ + HeapFree(GetProcessHeap(), 0, This); +} + +static HRESULT WINAPI MMDevPropStore_QueryInterface(IPropertyStore *iface, REFIID riid, void **ppv) +{ + MMDevPropStore *This = impl_from_IPropertyStore(iface); + TRACE("(%p)->(%s, %p)\n", This, debugstr_guid(riid), ppv); + + if (!ppv) + return E_POINTER; + if (IsEqualIID(riid, &IID_IUnknown) + || IsEqualIID(riid, &IID_IPropertyStore)) + *ppv = &This->IPropertyStore_iface; + else + *ppv = NULL; + if (!*ppv) + return E_NOINTERFACE; + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; +} + +static ULONG WINAPI MMDevPropStore_AddRef(IPropertyStore *iface) +{ + MMDevPropStore *This = impl_from_IPropertyStore(iface); + LONG ref = InterlockedIncrement(&This->ref); + TRACE("Refcount now %li\n", ref); + return ref; +} + +static ULONG WINAPI MMDevPropStore_Release(IPropertyStore *iface) +{ + MMDevPropStore *This = impl_from_IPropertyStore(iface); + LONG ref = InterlockedDecrement(&This->ref); + TRACE("Refcount now %li\n", ref); + if (!ref) + MMDevPropStore_Destroy(This); + return ref; +} + +static HRESULT WINAPI MMDevPropStore_GetCount(IPropertyStore *iface, DWORD *nprops) +{ + MMDevPropStore *This = impl_from_IPropertyStore(iface); + WCHAR buffer[50]; + DWORD i = 0; + HKEY propkey; + HRESULT hr; + + TRACE("(%p)->(%p)\n", iface, nprops); + if (!nprops) + return E_POINTER; + hr = MMDevPropStore_OpenPropKey(&This->parent->devguid, This->parent->flow, &propkey); + if (FAILED(hr)) + return hr; + *nprops = 0; + do { + DWORD len = ARRAY_SIZE(buffer); + if (RegEnumValueW(propkey, i, buffer, &len, NULL, NULL, NULL, NULL) != ERROR_SUCCESS) + break; + i++; + } while (1); + RegCloseKey(propkey); + TRACE("Returning %li\n", i); + *nprops = i; + return S_OK; +} + +static HRESULT WINAPI MMDevPropStore_GetAt(IPropertyStore *iface, DWORD prop, PROPERTYKEY *key) +{ + MMDevPropStore *This = impl_from_IPropertyStore(iface); + WCHAR buffer[50]; + DWORD len = ARRAY_SIZE(buffer); + HRESULT hr; + HKEY propkey; + + TRACE("(%p)->(%lu,%p)\n", iface, prop, key); + if (!key) + return E_POINTER; + + hr = MMDevPropStore_OpenPropKey(&This->parent->devguid, This->parent->flow, &propkey); + if (FAILED(hr)) + return hr; + + if (RegEnumValueW(propkey, prop, buffer, &len, NULL, NULL, NULL, NULL) != ERROR_SUCCESS + || len <= 39) + { + WARN("GetAt %lu failed\n", prop); + return E_INVALIDARG; + } + RegCloseKey(propkey); + buffer[38] = 0; + CLSIDFromString(buffer, &key->fmtid); + key->pid = wcstol(&buffer[39], NULL, 10); + return S_OK; +} + +static HRESULT WINAPI MMDevPropStore_GetValue(IPropertyStore *iface, REFPROPERTYKEY key, PROPVARIANT *pv) +{ + MMDevPropStore *This = impl_from_IPropertyStore(iface); + HRESULT hres; + TRACE("(%p)->(\"%s,%lu\", %p)\n", This, key ? debugstr_guid(&key->fmtid) : NULL, key ? key->pid : 0, pv); + + if (!key || !pv) + return E_POINTER; + if (This->access != STGM_READ + && This->access != STGM_READWRITE) + return STG_E_ACCESSDENIED; + + /* Special case */ + if (IsEqualPropertyKey(*key, PKEY_AudioEndpoint_GUID)) + { + pv->vt = VT_LPWSTR; + pv->pwszVal = CoTaskMemAlloc(39 * sizeof(WCHAR)); + if (!pv->pwszVal) + return E_OUTOFMEMORY; + StringFromGUID2(&This->parent->devguid, pv->pwszVal, 39); + return S_OK; + } + + hres = MMDevice_GetPropValue(&This->parent->devguid, This->parent->flow, key, pv); + if (FAILED(hres)) + return hres; + + if (WARN_ON(mmdevapi)) + { + if ((IsEqualPropertyKey(*key, DEVPKEY_Device_FriendlyName) || + IsEqualPropertyKey(*key, DEVPKEY_DeviceInterface_FriendlyName) || + IsEqualPropertyKey(*key, DEVPKEY_Device_DeviceDesc)) && + pv->vt == VT_LPWSTR && wcslen(pv->pwszVal) > 62) + WARN("Returned name exceeds length limit of some broken apps/libs, might crash: %s\n", debugstr_w(pv->pwszVal)); + } + return hres; +} + +static HRESULT WINAPI MMDevPropStore_SetValue(IPropertyStore *iface, REFPROPERTYKEY key, REFPROPVARIANT pv) +{ + MMDevPropStore *This = impl_from_IPropertyStore(iface); + TRACE("(%p)->(\"%s,%lu\", %p)\n", This, key ? debugstr_guid(&key->fmtid) : NULL, key ? key->pid : 0, pv); + + if (!key || !pv) + return E_POINTER; + + if (This->access != STGM_WRITE + && This->access != STGM_READWRITE) + return STG_E_ACCESSDENIED; + return MMDevice_SetPropValue(&This->parent->devguid, This->parent->flow, key, pv); +} + +static HRESULT WINAPI MMDevPropStore_Commit(IPropertyStore *iface) +{ + MMDevPropStore *This = impl_from_IPropertyStore(iface); + TRACE("(%p)\n", iface); + + if (This->access != STGM_WRITE + && This->access != STGM_READWRITE) + return STG_E_ACCESSDENIED; + + /* Does nothing - for mmdevapi, the propstore values are written on SetValue, + * not on Commit. */ + + return S_OK; +} + +static const IPropertyStoreVtbl MMDevPropVtbl = +{ + MMDevPropStore_QueryInterface, + MMDevPropStore_AddRef, + MMDevPropStore_Release, + MMDevPropStore_GetCount, + MMDevPropStore_GetAt, + MMDevPropStore_GetValue, + MMDevPropStore_SetValue, + MMDevPropStore_Commit +}; + + +/* Property bag for IBaseFilter activation */ +static HRESULT WINAPI PB_QueryInterface(IPropertyBag *iface, REFIID riid, void **ppv) +{ + ERR("Should not be called\n"); + *ppv = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI PB_AddRef(IPropertyBag *iface) +{ + ERR("Should not be called\n"); + return 2; +} + +static ULONG WINAPI PB_Release(IPropertyBag *iface) +{ + ERR("Should not be called\n"); + return 1; +} + +static HRESULT WINAPI PB_Read(IPropertyBag *iface, LPCOLESTR name, VARIANT *var, IErrorLog *log) +{ + IPropertyBagImpl *This = impl_from_IPropertyBag(iface); + TRACE("Trying to read %s, type %u\n", debugstr_w(name), var->n1.n2.vt); + if (!lstrcmpW(name, L"DSGuid")) + { + WCHAR guidstr[39]; + StringFromGUID2(&This->devguid, guidstr,ARRAY_SIZE(guidstr)); + var->n1.n2.vt = VT_BSTR; + var->n1.n2.n3.bstrVal = SysAllocString(guidstr); + return S_OK; + } + ERR("Unknown property '%s' queried\n", debugstr_w(name)); + return E_FAIL; +} + +static HRESULT WINAPI PB_Write(IPropertyBag *iface, LPCOLESTR name, VARIANT *var) +{ + ERR("Should not be called\n"); + return E_FAIL; +} + +static const IPropertyBagVtbl PB_Vtbl = +{ + PB_QueryInterface, + PB_AddRef, + PB_Release, + PB_Read, + PB_Write +}; + +static ULONG WINAPI info_device_ps_AddRef(IPropertyStore *iface) +{ + return 2; +} + +static ULONG WINAPI info_device_ps_Release(IPropertyStore *iface) +{ + return 1; +} + +static HRESULT WINAPI info_device_ps_GetValue(IPropertyStore *iface, + REFPROPERTYKEY key, PROPVARIANT *pv) +{ + TRACE("(static)->(\"%s,%lu\", %p)\n", debugstr_guid(&key->fmtid), key ? key->pid : 0, pv); + + if (!key || !pv) + return E_POINTER; + + if (IsEqualPropertyKey(*key, DEVPKEY_Device_Driver)) + { + INT size = (lstrlenW(drvs.module_name) + 1) * sizeof(WCHAR); + pv->vt = VT_LPWSTR; + pv->pwszVal = CoTaskMemAlloc(size); + if (!pv->pwszVal) + return E_OUTOFMEMORY; + memcpy(pv->pwszVal, drvs.module_name, size); + return S_OK; + } + + return E_INVALIDARG; +} + +static const IPropertyStoreVtbl info_device_ps_Vtbl = +{ + NULL, + info_device_ps_AddRef, + info_device_ps_Release, + NULL, + NULL, + info_device_ps_GetValue, + NULL, + NULL +}; + +static IPropertyStore info_device_ps = { + &info_device_ps_Vtbl +}; + +static ULONG WINAPI info_device_AddRef(IMMDevice *iface) +{ + return 2; +} + +static ULONG WINAPI info_device_Release(IMMDevice *iface) +{ + return 1; +} + +static HRESULT WINAPI info_device_OpenPropertyStore(IMMDevice *iface, + DWORD access, IPropertyStore **ppv) +{ + TRACE("(static)->(%lx, %p)\n", access, ppv); + *ppv = &info_device_ps; + return S_OK; +} + +static const IMMDeviceVtbl info_device_Vtbl = +{ + NULL, + info_device_AddRef, + info_device_Release, + NULL, + info_device_OpenPropertyStore, + NULL, + NULL +}; + +static IMMDevice info_device = { + &info_device_Vtbl +}; diff --git a/pkgs/osu-wine/audio-revert/mmdevapi/main.c b/pkgs/osu-wine/audio-revert/mmdevapi/main.c new file mode 100644 index 0000000..c792661 --- /dev/null +++ b/pkgs/osu-wine/audio-revert/mmdevapi/main.c @@ -0,0 +1,470 @@ +/* + * Copyright 2009 Maarten Lankhorst + * Copyright 2011 Andrew Eikum for CodeWeavers + * + * 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 + +#define COBJMACROS +#include "windef.h" +#include "winbase.h" +#include "wingdi.h" + +#include "ole2.h" +#include "olectl.h" +#include "rpcproxy.h" +#include "propsys.h" +#include "propkeydef.h" +#include "mmdeviceapi.h" +#include "mmsystem.h" +#include "dsound.h" +#include "audioclient.h" +#include "endpointvolume.h" +#include "audiopolicy.h" +#include "devpkey.h" +#include "winreg.h" +#include "spatialaudioclient.h" + +#include "mmdevapi.h" +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(mmdevapi); + +DriverFuncs drvs; + +const WCHAR drv_keyW[] = L"Software\\Wine\\Drivers"; + +static const char *get_priority_string(int prio) +{ + switch(prio){ + case Priority_Unavailable: + return "Unavailable"; + case Priority_Low: + return "Low"; + case Priority_Neutral: + return "Neutral"; + case Priority_Preferred: + return "Preferred"; + } + return "Invalid"; +} + +static BOOL load_driver(const WCHAR *name, DriverFuncs *driver) +{ + WCHAR driver_module[264]; + + lstrcpyW(driver_module, L"wine"); + lstrcatW(driver_module, name); + lstrcatW(driver_module, L".drv"); + + TRACE("Attempting to load %s\n", wine_dbgstr_w(driver_module)); + + driver->module = LoadLibraryW(driver_module); + if(!driver->module){ + TRACE("Unable to load %s: %lu\n", wine_dbgstr_w(driver_module), + GetLastError()); + return FALSE; + } + +#define LDFC(n) do { driver->p##n = (void*)GetProcAddress(driver->module, #n);\ + if(!driver->p##n) { FreeLibrary(driver->module); return FALSE; } } while(0) + LDFC(GetPriority); + LDFC(GetEndpointIDs); + LDFC(GetAudioEndpoint); + LDFC(GetAudioSessionManager); +#undef LDFC + + /* optional - do not fail if not found */ + driver->pGetPropValue = (void*)GetProcAddress(driver->module, "GetPropValue"); + + driver->priority = driver->pGetPriority(); + lstrcpyW(driver->module_name, driver_module); + + TRACE("Successfully loaded %s with priority %s\n", + wine_dbgstr_w(driver_module), get_priority_string(driver->priority)); + + return TRUE; +} + +static BOOL WINAPI init_driver(INIT_ONCE *once, void *param, void **context) +{ + static WCHAR default_list[] = L"pulse,alsa,oss,coreaudio"; + DriverFuncs driver; + HKEY key; + WCHAR reg_list[256], *p, *next, *driver_list = default_list; + + if(RegOpenKeyW(HKEY_CURRENT_USER, drv_keyW, &key) == ERROR_SUCCESS){ + DWORD size = sizeof(reg_list); + + if(RegQueryValueExW(key, L"Audio", 0, NULL, (BYTE*)reg_list, &size) == ERROR_SUCCESS){ + if(reg_list[0] == '\0'){ + TRACE("User explicitly chose no driver\n"); + RegCloseKey(key); + return TRUE; + } + + driver_list = reg_list; + } + + RegCloseKey(key); + } + + TRACE("Loading driver list %s\n", wine_dbgstr_w(driver_list)); + for(next = p = driver_list; next; p = next + 1){ + next = wcschr(p, ','); + if(next) + *next = '\0'; + + driver.priority = Priority_Unavailable; + if(load_driver(p, &driver)){ + if(driver.priority == Priority_Unavailable) + FreeLibrary(driver.module); + else if(!drvs.module || driver.priority > drvs.priority){ + TRACE("Selecting driver %s with priority %s\n", + wine_dbgstr_w(p), get_priority_string(driver.priority)); + if(drvs.module) + FreeLibrary(drvs.module); + drvs = driver; + }else + FreeLibrary(driver.module); + }else + TRACE("Failed to load driver %s\n", wine_dbgstr_w(p)); + + if(next) + *next = ','; + } + + if (drvs.module != 0){ + load_devices_from_reg(); + load_driver_devices(eRender); + load_driver_devices(eCapture); + } + + return drvs.module != 0; +} + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) +{ + TRACE("(0x%p, %ld, %p)\n", hinstDLL, fdwReason, lpvReserved); + + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls(hinstDLL); + break; + case DLL_PROCESS_DETACH: + if(lpvReserved) + break; + MMDevEnum_Free(); + break; + } + + return TRUE; +} + +typedef HRESULT (*FnCreateInstance)(REFIID riid, LPVOID *ppobj); + +typedef struct { + IClassFactory IClassFactory_iface; + REFCLSID rclsid; + FnCreateInstance pfnCreateInstance; +} IClassFactoryImpl; + +static inline IClassFactoryImpl *impl_from_IClassFactory(IClassFactory *iface) +{ + return CONTAINING_RECORD(iface, IClassFactoryImpl, IClassFactory_iface); +} + +static HRESULT WINAPI +MMCF_QueryInterface(IClassFactory *iface, REFIID riid, void **ppobj) +{ + IClassFactoryImpl *This = impl_from_IClassFactory(iface); + TRACE("(%p, %s, %p)\n", This, debugstr_guid(riid), ppobj); + if (ppobj == NULL) + return E_POINTER; + if (IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IClassFactory)) + { + *ppobj = iface; + IClassFactory_AddRef(iface); + return S_OK; + } + *ppobj = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI MMCF_AddRef(LPCLASSFACTORY iface) +{ + return 2; +} + +static ULONG WINAPI MMCF_Release(LPCLASSFACTORY iface) +{ + /* static class, won't be freed */ + return 1; +} + +static HRESULT WINAPI MMCF_CreateInstance( + LPCLASSFACTORY iface, + LPUNKNOWN pOuter, + REFIID riid, + LPVOID *ppobj) +{ + IClassFactoryImpl *This = impl_from_IClassFactory(iface); + TRACE("(%p, %p, %s, %p)\n", This, pOuter, debugstr_guid(riid), ppobj); + + if (pOuter) + return CLASS_E_NOAGGREGATION; + + if (ppobj == NULL) { + WARN("invalid parameter\n"); + return E_POINTER; + } + *ppobj = NULL; + return This->pfnCreateInstance(riid, ppobj); +} + +static HRESULT WINAPI MMCF_LockServer(LPCLASSFACTORY iface, BOOL dolock) +{ + IClassFactoryImpl *This = impl_from_IClassFactory(iface); + FIXME("(%p, %d) stub!\n", This, dolock); + return S_OK; +} + +static const IClassFactoryVtbl MMCF_Vtbl = { + MMCF_QueryInterface, + MMCF_AddRef, + MMCF_Release, + MMCF_CreateInstance, + MMCF_LockServer +}; + +static IClassFactoryImpl MMDEVAPI_CF[] = { + { { &MMCF_Vtbl }, &CLSID_MMDeviceEnumerator, (FnCreateInstance)MMDevEnum_Create } +}; + +HRESULT WINAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv) +{ + static INIT_ONCE init_once = INIT_ONCE_STATIC_INIT; + unsigned int i = 0; + TRACE("(%s, %s, %p)\n", debugstr_guid(rclsid), debugstr_guid(riid), ppv); + + if(!InitOnceExecuteOnce(&init_once, init_driver, NULL, NULL)) { + ERR("Driver initialization failed\n"); + return E_FAIL; + } + + if (ppv == NULL) { + WARN("invalid parameter\n"); + return E_INVALIDARG; + } + + *ppv = NULL; + + if (!IsEqualIID(riid, &IID_IClassFactory) && + !IsEqualIID(riid, &IID_IUnknown)) { + WARN("no interface for %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; + } + + for (i = 0; i < ARRAY_SIZE(MMDEVAPI_CF); ++i) + { + if (IsEqualGUID(rclsid, MMDEVAPI_CF[i].rclsid)) { + IClassFactory_AddRef(&MMDEVAPI_CF[i].IClassFactory_iface); + *ppv = &MMDEVAPI_CF[i]; + return S_OK; + } + } + + WARN("(%s, %s, %p): no class found.\n", debugstr_guid(rclsid), + debugstr_guid(riid), ppv); + return CLASS_E_CLASSNOTAVAILABLE; +} + +struct activate_async_op { + IActivateAudioInterfaceAsyncOperation IActivateAudioInterfaceAsyncOperation_iface; + LONG ref; + + IActivateAudioInterfaceCompletionHandler *callback; + HRESULT result_hr; + IUnknown *result_iface; +}; + +static struct activate_async_op *impl_from_IActivateAudioInterfaceAsyncOperation(IActivateAudioInterfaceAsyncOperation *iface) +{ + return CONTAINING_RECORD(iface, struct activate_async_op, IActivateAudioInterfaceAsyncOperation_iface); +} + +static HRESULT WINAPI activate_async_op_QueryInterface(IActivateAudioInterfaceAsyncOperation *iface, + REFIID riid, void **ppv) +{ + struct activate_async_op *This = impl_from_IActivateAudioInterfaceAsyncOperation(iface); + + TRACE("(%p)->(%s, %p)\n", This, debugstr_guid(riid), ppv); + + if (!ppv) + return E_POINTER; + + if (IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IActivateAudioInterfaceAsyncOperation)) { + *ppv = &This->IActivateAudioInterfaceAsyncOperation_iface; + } else { + *ppv = NULL; + return E_NOINTERFACE; + } + + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; +} + +static ULONG WINAPI activate_async_op_AddRef(IActivateAudioInterfaceAsyncOperation *iface) +{ + struct activate_async_op *This = impl_from_IActivateAudioInterfaceAsyncOperation(iface); + LONG ref = InterlockedIncrement(&This->ref); + TRACE("(%p) refcount now %li\n", This, ref); + return ref; +} + +static ULONG WINAPI activate_async_op_Release(IActivateAudioInterfaceAsyncOperation *iface) +{ + struct activate_async_op *This = impl_from_IActivateAudioInterfaceAsyncOperation(iface); + LONG ref = InterlockedDecrement(&This->ref); + TRACE("(%p) refcount now %li\n", This, ref); + if (!ref) { + if(This->result_iface) + IUnknown_Release(This->result_iface); + IActivateAudioInterfaceCompletionHandler_Release(This->callback); + HeapFree(GetProcessHeap(), 0, This); + } + return ref; +} + +static HRESULT WINAPI activate_async_op_GetActivateResult(IActivateAudioInterfaceAsyncOperation *iface, + HRESULT *result_hr, IUnknown **result_iface) +{ + struct activate_async_op *This = impl_from_IActivateAudioInterfaceAsyncOperation(iface); + + TRACE("(%p)->(%p, %p)\n", This, result_hr, result_iface); + + *result_hr = This->result_hr; + + if(This->result_hr == S_OK){ + *result_iface = This->result_iface; + IUnknown_AddRef(*result_iface); + } + + return S_OK; +} + +static IActivateAudioInterfaceAsyncOperationVtbl IActivateAudioInterfaceAsyncOperation_vtbl = { + activate_async_op_QueryInterface, + activate_async_op_AddRef, + activate_async_op_Release, + activate_async_op_GetActivateResult, +}; + +static DWORD WINAPI activate_async_threadproc(void *user) +{ + struct activate_async_op *op = user; + + SetThreadDescription(GetCurrentThread(), L"wine_mmdevapi_activate_async"); + + IActivateAudioInterfaceCompletionHandler_ActivateCompleted(op->callback, &op->IActivateAudioInterfaceAsyncOperation_iface); + + IActivateAudioInterfaceAsyncOperation_Release(&op->IActivateAudioInterfaceAsyncOperation_iface); + + return 0; +} + +static HRESULT get_mmdevice_by_activatepath(const WCHAR *path, IMMDevice **mmdev) +{ + IMMDeviceEnumerator *devenum; + HRESULT hr; + + static const WCHAR DEVINTERFACE_AUDIO_RENDER_WSTR[] = L"{E6327CAD-DCEC-4949-AE8A-991E976A79D2}"; + static const WCHAR DEVINTERFACE_AUDIO_CAPTURE_WSTR[] = L"{2EEF81BE-33FA-4800-9670-1CD474972C3F}"; + static const WCHAR MMDEV_PATH_PREFIX[] = L"\\\\?\\SWD#MMDEVAPI#"; + + hr = MMDevEnum_Create(&IID_IMMDeviceEnumerator, (void**)&devenum); + if (FAILED(hr)) { + WARN("Failed to create MMDeviceEnumerator: %08lx\n", hr); + return hr; + } + + if (!lstrcmpiW(path, DEVINTERFACE_AUDIO_RENDER_WSTR)){ + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(devenum, eRender, eMultimedia, mmdev); + } else if (!lstrcmpiW(path, DEVINTERFACE_AUDIO_CAPTURE_WSTR)){ + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(devenum, eCapture, eMultimedia, mmdev); + } else if (!memcmp(path, MMDEV_PATH_PREFIX, sizeof(MMDEV_PATH_PREFIX) - sizeof(WCHAR))) { + WCHAR device_id[56]; /* == strlen("{0.0.1.00000000}.{fd47d9cc-4218-4135-9ce2-0c195c87405b}") + 1 */ + + lstrcpynW(device_id, path + (ARRAY_SIZE(MMDEV_PATH_PREFIX) - 1), ARRAY_SIZE(device_id)); + + hr = IMMDeviceEnumerator_GetDevice(devenum, device_id, mmdev); + } else { + FIXME("Unrecognized device id format: %s\n", debugstr_w(path)); + hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + } + + if (FAILED(hr)) { + WARN("Failed to get requested device (%s): %08lx\n", debugstr_w(path), hr); + *mmdev = NULL; + hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + } + + IMMDeviceEnumerator_Release(devenum); + + return hr; +} + +/*********************************************************************** + * ActivateAudioInterfaceAsync (MMDEVAPI.17) + */ +HRESULT WINAPI ActivateAudioInterfaceAsync(const WCHAR *path, REFIID riid, + PROPVARIANT *params, IActivateAudioInterfaceCompletionHandler *done_handler, + IActivateAudioInterfaceAsyncOperation **op_out) +{ + struct activate_async_op *op; + HANDLE ht; + IMMDevice *mmdev; + + TRACE("(%s, %s, %p, %p, %p)\n", debugstr_w(path), debugstr_guid(riid), + params, done_handler, op_out); + + op = HeapAlloc(GetProcessHeap(), 0, sizeof(*op)); + if (!op) + return E_OUTOFMEMORY; + + op->ref = 2; /* returned ref and threadproc ref */ + op->IActivateAudioInterfaceAsyncOperation_iface.lpVtbl = &IActivateAudioInterfaceAsyncOperation_vtbl; + op->callback = done_handler; + IActivateAudioInterfaceCompletionHandler_AddRef(done_handler); + + op->result_hr = get_mmdevice_by_activatepath(path, &mmdev); + if (SUCCEEDED(op->result_hr)) { + op->result_hr = IMMDevice_Activate(mmdev, riid, CLSCTX_INPROC_SERVER, params, (void**)&op->result_iface); + IMMDevice_Release(mmdev); + }else + op->result_iface = NULL; + + ht = CreateThread(NULL, 0, &activate_async_threadproc, op, 0, NULL); + CloseHandle(ht); + + *op_out = &op->IActivateAudioInterfaceAsyncOperation_iface; + + return S_OK; +} diff --git a/pkgs/osu-wine/audio-revert/mmdevapi/mmdevapi.h b/pkgs/osu-wine/audio-revert/mmdevapi/mmdevapi.h new file mode 100644 index 0000000..5505385 --- /dev/null +++ b/pkgs/osu-wine/audio-revert/mmdevapi/mmdevapi.h @@ -0,0 +1,76 @@ +/* + * Copyright 2009 Maarten Lankhorst + * + * 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 "unixlib.h" +#include +#include + +extern HRESULT MMDevEnum_Create(REFIID riid, void **ppv); +extern void MMDevEnum_Free(void); + +typedef struct _DriverFuncs { + HMODULE module; + WCHAR module_name[64]; + int priority; + + /* Returns a "priority" value for the driver. Highest priority wins. + * If multiple drivers think they are valid, they will return a + * priority value reflecting the likelihood that they are actually + * valid. See enum _DriverPriority. */ + int (WINAPI *pGetPriority)(void); + + /* ids gets an array of human-friendly endpoint names + * keys gets an array of driver-specific stuff that is used + * in GetAudioEndpoint to identify the endpoint + * it is the caller's responsibility to free both arrays, and + * all of the elements in both arrays with HeapFree() */ + HRESULT (WINAPI *pGetEndpointIDs)(EDataFlow flow, WCHAR ***ids, + GUID **guids, UINT *num, UINT *default_index); + HRESULT (WINAPI *pGetAudioEndpoint)(void *key, IMMDevice *dev, + IAudioClient **out); + HRESULT (WINAPI *pGetAudioSessionManager)(IMMDevice *device, + IAudioSessionManager2 **out); + HRESULT (WINAPI *pGetPropValue)(GUID *guid, + const PROPERTYKEY *prop, PROPVARIANT *out); +} DriverFuncs; + +extern DriverFuncs drvs; + +typedef struct MMDevice { + IMMDevice IMMDevice_iface; + IMMEndpoint IMMEndpoint_iface; + LONG ref; + + CRITICAL_SECTION crst; + + EDataFlow flow; + DWORD state; + GUID devguid; + WCHAR *drv_id; + + struct list entry; +} MMDevice; + +extern HRESULT AudioClient_Create(MMDevice *parent, IAudioClient **ppv); +extern HRESULT AudioEndpointVolume_Create(MMDevice *parent, IAudioEndpointVolumeEx **ppv); +extern HRESULT SpatialAudioClient_Create(IMMDevice *device, ISpatialAudioClient **out); + +extern HRESULT load_devices_from_reg(void); +extern HRESULT load_driver_devices(EDataFlow flow); + +extern const WCHAR drv_keyW[]; diff --git a/pkgs/osu-wine/audio-revert/mmdevapi/mmdevapi.spec b/pkgs/osu-wine/audio-revert/mmdevapi/mmdevapi.spec new file mode 100644 index 0000000..90ecaf5 --- /dev/null +++ b/pkgs/osu-wine/audio-revert/mmdevapi/mmdevapi.spec @@ -0,0 +1,21 @@ + 2 stub @ + 3 stub @ + 4 stub @ + 5 stub @ + 6 stub @ + 7 stub @ + 8 stub @ + 9 stub @ +10 stub @ +11 stub @ +12 stub @ +13 stub @ +14 stub @ +15 stub @ +16 stub @ +17 stdcall ActivateAudioInterfaceAsync( wstr ptr ptr ptr ptr ) + +@ stdcall -private DllCanUnloadNow() +@ stdcall -private DllGetClassObject( ptr ptr ptr ) +@ stdcall -private DllRegisterServer() +@ stdcall -private DllUnregisterServer() diff --git a/pkgs/osu-wine/audio-revert/mmdevapi/mmdevapi_classes.idl b/pkgs/osu-wine/audio-revert/mmdevapi/mmdevapi_classes.idl new file mode 100644 index 0000000..e364fce --- /dev/null +++ b/pkgs/osu-wine/audio-revert/mmdevapi/mmdevapi_classes.idl @@ -0,0 +1,28 @@ +/* + * COM Classes for mmdevapi + * + * Copyright 2010 Alexandre Julliard + * + * 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 + */ + +#pragma makedep register + +[ + helpstring("MMDeviceEnumerator class"), + threading(both), + uuid(bcde0395-e52f-467c-8e3d-c4579291692e) +] +coclass MMDeviceEnumerator { interface IMMDeviceEnumerator; } diff --git a/pkgs/osu-wine/audio-revert/mmdevapi/spatialaudio.c b/pkgs/osu-wine/audio-revert/mmdevapi/spatialaudio.c new file mode 100644 index 0000000..d77e0a6 --- /dev/null +++ b/pkgs/osu-wine/audio-revert/mmdevapi/spatialaudio.c @@ -0,0 +1,976 @@ +/* + * Copyright 2020 Andrew Eikum for CodeWeavers + * + * 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 + */ + +#define COBJMACROS +#define NONAMELESSUNION + +#include + +#include "windef.h" +#include "winbase.h" +#include "winnls.h" +#include "winreg.h" +#include "wine/heap.h" +#include "wine/debug.h" +#include "wine/list.h" + +#include "ole2.h" +#include "mmdeviceapi.h" +#include "mmsystem.h" +#include "audioclient.h" +#include "endpointvolume.h" +#include "audiopolicy.h" +#include "spatialaudioclient.h" + +#include "mmdevapi.h" + +WINE_DEFAULT_DEBUG_CHANNEL(mmdevapi); + +static UINT32 AudioObjectType_to_index(AudioObjectType type) +{ + UINT32 o = 0; + while(type){ + type >>= 1; + ++o; + } + return o - 2; +} + +typedef struct SpatialAudioImpl SpatialAudioImpl; +typedef struct SpatialAudioStreamImpl SpatialAudioStreamImpl; +typedef struct SpatialAudioObjectImpl SpatialAudioObjectImpl; + +struct SpatialAudioObjectImpl { + ISpatialAudioObject ISpatialAudioObject_iface; + LONG ref; + + SpatialAudioStreamImpl *sa_stream; + AudioObjectType type; + UINT32 static_idx; + + float *buf; + + struct list entry; +}; + +struct SpatialAudioStreamImpl { + ISpatialAudioObjectRenderStream ISpatialAudioObjectRenderStream_iface; + LONG ref; + CRITICAL_SECTION lock; + + SpatialAudioImpl *sa_client; + SpatialAudioObjectRenderStreamActivationParams params; + + IAudioClient *client; + IAudioRenderClient *render; + + UINT32 period_frames, update_frames; + WAVEFORMATEXTENSIBLE stream_fmtex; + + float *buf; + + UINT32 static_object_map[17]; + + struct list objects; +}; + +struct SpatialAudioImpl { + ISpatialAudioClient ISpatialAudioClient_iface; + IAudioFormatEnumerator IAudioFormatEnumerator_iface; + IMMDevice *mmdev; + LONG ref; + WAVEFORMATEXTENSIBLE object_fmtex; +}; + +static inline SpatialAudioObjectImpl *impl_from_ISpatialAudioObject(ISpatialAudioObject *iface) +{ + return CONTAINING_RECORD(iface, SpatialAudioObjectImpl, ISpatialAudioObject_iface); +} + +static inline SpatialAudioStreamImpl *impl_from_ISpatialAudioObjectRenderStream(ISpatialAudioObjectRenderStream *iface) +{ + return CONTAINING_RECORD(iface, SpatialAudioStreamImpl, ISpatialAudioObjectRenderStream_iface); +} + +static inline SpatialAudioImpl *impl_from_ISpatialAudioClient(ISpatialAudioClient *iface) +{ + return CONTAINING_RECORD(iface, SpatialAudioImpl, ISpatialAudioClient_iface); +} + +static inline SpatialAudioImpl *impl_from_IAudioFormatEnumerator(IAudioFormatEnumerator *iface) +{ + return CONTAINING_RECORD(iface, SpatialAudioImpl, IAudioFormatEnumerator_iface); +} + +static HRESULT WINAPI SAO_QueryInterface(ISpatialAudioObject *iface, + REFIID riid, void **ppv) +{ + SpatialAudioObjectImpl *This = impl_from_ISpatialAudioObject(iface); + + TRACE("(%p)->(%s,%p)\n", This, debugstr_guid(riid), ppv); + + if (!ppv) + return E_POINTER; + + *ppv = NULL; + + if (IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_ISpatialAudioObjectBase) || + IsEqualIID(riid, &IID_ISpatialAudioObject)) { + *ppv = &This->ISpatialAudioObject_iface; + } + else + return E_NOINTERFACE; + + IUnknown_AddRef((IUnknown *)*ppv); + + return S_OK; +} + +static ULONG WINAPI SAO_AddRef(ISpatialAudioObject *iface) +{ + SpatialAudioObjectImpl *This = impl_from_ISpatialAudioObject(iface); + ULONG ref = InterlockedIncrement(&This->ref); + TRACE("(%p) new ref %lu\n", This, ref); + return ref; +} + +static ULONG WINAPI SAO_Release(ISpatialAudioObject *iface) +{ + SpatialAudioObjectImpl *This = impl_from_ISpatialAudioObject(iface); + ULONG ref = InterlockedDecrement(&This->ref); + TRACE("(%p) new ref %lu\n", This, ref); + if(!ref){ + EnterCriticalSection(&This->sa_stream->lock); + list_remove(&This->entry); + LeaveCriticalSection(&This->sa_stream->lock); + + ISpatialAudioObjectRenderStream_Release(&This->sa_stream->ISpatialAudioObjectRenderStream_iface); + heap_free(This->buf); + heap_free(This); + } + return ref; +} + +static HRESULT WINAPI SAO_GetBuffer(ISpatialAudioObject *iface, + BYTE **buffer, UINT32 *bytes) +{ + SpatialAudioObjectImpl *This = impl_from_ISpatialAudioObject(iface); + + TRACE("(%p)->(%p, %p)\n", This, buffer, bytes); + + EnterCriticalSection(&This->sa_stream->lock); + + if(This->sa_stream->update_frames == ~0){ + LeaveCriticalSection(&This->sa_stream->lock); + return SPTLAUDCLNT_E_OUT_OF_ORDER; + } + + *buffer = (BYTE *)This->buf; + *bytes = This->sa_stream->update_frames * + This->sa_stream->sa_client->object_fmtex.Format.nBlockAlign; + + LeaveCriticalSection(&This->sa_stream->lock); + + return S_OK; +} + +static HRESULT WINAPI SAO_SetEndOfStream(ISpatialAudioObject *iface, UINT32 frames) +{ + SpatialAudioObjectImpl *This = impl_from_ISpatialAudioObject(iface); + FIXME("(%p)->(%u)\n", This, frames); + return E_NOTIMPL; +} + +static HRESULT WINAPI SAO_IsActive(ISpatialAudioObject *iface, BOOL *active) +{ + SpatialAudioObjectImpl *This = impl_from_ISpatialAudioObject(iface); + FIXME("(%p)->(%p)\n", This, active); + return E_NOTIMPL; +} + +static HRESULT WINAPI SAO_GetAudioObjectType(ISpatialAudioObject *iface, + AudioObjectType *type) +{ + SpatialAudioObjectImpl *This = impl_from_ISpatialAudioObject(iface); + + TRACE("(%p)->(%p)\n", This, type); + + *type = This->type; + + return S_OK; +} + +static HRESULT WINAPI SAO_SetPosition(ISpatialAudioObject *iface, float x, + float y, float z) +{ + SpatialAudioObjectImpl *This = impl_from_ISpatialAudioObject(iface); + FIXME("(%p)->(%f, %f, %f)\n", This, x, y, z); + return E_NOTIMPL; +} + +static HRESULT WINAPI SAO_SetVolume(ISpatialAudioObject *iface, float vol) +{ + SpatialAudioObjectImpl *This = impl_from_ISpatialAudioObject(iface); + FIXME("(%p)->(%f)\n", This, vol); + return E_NOTIMPL; +} + +static ISpatialAudioObjectVtbl ISpatialAudioObject_vtbl = { + SAO_QueryInterface, + SAO_AddRef, + SAO_Release, + SAO_GetBuffer, + SAO_SetEndOfStream, + SAO_IsActive, + SAO_GetAudioObjectType, + SAO_SetPosition, + SAO_SetVolume, +}; + +static HRESULT WINAPI SAORS_QueryInterface(ISpatialAudioObjectRenderStream *iface, + REFIID riid, void **ppv) +{ + SpatialAudioStreamImpl *This = impl_from_ISpatialAudioObjectRenderStream(iface); + + TRACE("(%p)->(%s,%p)\n", This, debugstr_guid(riid), ppv); + + if (!ppv) + return E_POINTER; + + *ppv = NULL; + + if (IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_ISpatialAudioObjectRenderStreamBase) || + IsEqualIID(riid, &IID_ISpatialAudioObjectRenderStream)) { + *ppv = &This->ISpatialAudioObjectRenderStream_iface; + } + else + return E_NOINTERFACE; + + IUnknown_AddRef((IUnknown *)*ppv); + + return S_OK; +} + +static ULONG WINAPI SAORS_AddRef(ISpatialAudioObjectRenderStream *iface) +{ + SpatialAudioStreamImpl *This = impl_from_ISpatialAudioObjectRenderStream(iface); + ULONG ref = InterlockedIncrement(&This->ref); + TRACE("(%p) new ref %lu\n", This, ref); + return ref; +} + +static ULONG WINAPI SAORS_Release(ISpatialAudioObjectRenderStream *iface) +{ + SpatialAudioStreamImpl *This = impl_from_ISpatialAudioObjectRenderStream(iface); + ULONG ref = InterlockedDecrement(&This->ref); + TRACE("(%p) new ref %lu\n", This, ref); + if(!ref){ + IAudioClient_Stop(This->client); + if(This->update_frames != ~0 && This->update_frames > 0) + IAudioRenderClient_ReleaseBuffer(This->render, This->update_frames, 0); + IAudioRenderClient_Release(This->render); + IAudioClient_Release(This->client); + if(This->params.NotifyObject) + ISpatialAudioObjectRenderStreamNotify_Release(This->params.NotifyObject); + heap_free((void*)This->params.ObjectFormat); + CloseHandle(This->params.EventHandle); + DeleteCriticalSection(&This->lock); + ISpatialAudioClient_Release(&This->sa_client->ISpatialAudioClient_iface); + heap_free(This); + } + return ref; +} + +static HRESULT WINAPI SAORS_GetAvailableDynamicObjectCount( + ISpatialAudioObjectRenderStream *iface, UINT32 *count) +{ + SpatialAudioStreamImpl *This = impl_from_ISpatialAudioObjectRenderStream(iface); + FIXME("(%p)->(%p)\n", This, count); + + *count = 0; + return S_OK; +} + +static HRESULT WINAPI SAORS_GetService(ISpatialAudioObjectRenderStream *iface, + REFIID riid, void **service) +{ + SpatialAudioStreamImpl *This = impl_from_ISpatialAudioObjectRenderStream(iface); + FIXME("(%p)->(%s, %p)\n", This, debugstr_guid(riid), service); + return E_NOTIMPL; +} + +static HRESULT WINAPI SAORS_Start(ISpatialAudioObjectRenderStream *iface) +{ + SpatialAudioStreamImpl *This = impl_from_ISpatialAudioObjectRenderStream(iface); + HRESULT hr; + + TRACE("(%p)->()\n", This); + + hr = IAudioClient_Start(This->client); + if(FAILED(hr)){ + WARN("IAudioClient::Start failed: %08lx\n", hr); + return hr; + } + + return S_OK; +} + +static HRESULT WINAPI SAORS_Stop(ISpatialAudioObjectRenderStream *iface) +{ + SpatialAudioStreamImpl *This = impl_from_ISpatialAudioObjectRenderStream(iface); + HRESULT hr; + + TRACE("(%p)->()\n", This); + + hr = IAudioClient_Stop(This->client); + if(FAILED(hr)){ + WARN("IAudioClient::Stop failed: %08lx\n", hr); + return hr; + } + + return S_OK; +} + +static HRESULT WINAPI SAORS_Reset(ISpatialAudioObjectRenderStream *iface) +{ + SpatialAudioStreamImpl *This = impl_from_ISpatialAudioObjectRenderStream(iface); + FIXME("(%p)->()\n", This); + return E_NOTIMPL; +} + +static HRESULT WINAPI SAORS_BeginUpdatingAudioObjects(ISpatialAudioObjectRenderStream *iface, + UINT32 *dyn_count, UINT32 *frames) +{ + static BOOL fixme_once = FALSE; + SpatialAudioStreamImpl *This = impl_from_ISpatialAudioObjectRenderStream(iface); + SpatialAudioObjectImpl *object; + HRESULT hr; + + TRACE("(%p)->(%p, %p)\n", This, dyn_count, frames); + + EnterCriticalSection(&This->lock); + + if(This->update_frames != ~0){ + LeaveCriticalSection(&This->lock); + return SPTLAUDCLNT_E_OUT_OF_ORDER; + } + + This->update_frames = This->period_frames; + + if(This->update_frames > 0){ + hr = IAudioRenderClient_GetBuffer(This->render, This->update_frames, (BYTE **)&This->buf); + if(FAILED(hr)){ + WARN("GetBuffer failed: %08lx\n", hr); + This->update_frames = ~0; + LeaveCriticalSection(&This->lock); + return hr; + } + + LIST_FOR_EACH_ENTRY(object, &This->objects, SpatialAudioObjectImpl, entry){ + memset(object->buf, 0, This->update_frames * This->sa_client->object_fmtex.Format.nBlockAlign); + } + }else if (!fixme_once){ + fixme_once = TRUE; + FIXME("Zero frame update.\n"); + } + + *dyn_count = 0; + *frames = This->update_frames; + + LeaveCriticalSection(&This->lock); + + return S_OK; +} + +static void mix_static_object(SpatialAudioStreamImpl *stream, SpatialAudioObjectImpl *object) +{ + float *in = object->buf, *out; + UINT32 i; + if(object->static_idx == ~0 || + stream->static_object_map[object->static_idx] == ~0){ + WARN("Got unmapped static object?! Not mixing. Type: 0x%x\n", object->type); + return; + } + out = stream->buf + stream->static_object_map[object->static_idx]; + for(i = 0; i < stream->update_frames; ++i){ + *out += *in; + ++in; + out += stream->stream_fmtex.Format.nChannels; + } +} + +static HRESULT WINAPI SAORS_EndUpdatingAudioObjects(ISpatialAudioObjectRenderStream *iface) +{ + SpatialAudioStreamImpl *This = impl_from_ISpatialAudioObjectRenderStream(iface); + SpatialAudioObjectImpl *object; + HRESULT hr; + + TRACE("(%p)->()\n", This); + + EnterCriticalSection(&This->lock); + + if(This->update_frames == ~0){ + LeaveCriticalSection(&This->lock); + return SPTLAUDCLNT_E_OUT_OF_ORDER; + } + + if(This->update_frames > 0){ + LIST_FOR_EACH_ENTRY(object, &This->objects, SpatialAudioObjectImpl, entry){ + if(object->type != AudioObjectType_Dynamic) + mix_static_object(This, object); + else + WARN("Don't know how to mix dynamic object yet. %p\n", object); + } + + hr = IAudioRenderClient_ReleaseBuffer(This->render, This->update_frames, 0); + if(FAILED(hr)) + WARN("ReleaseBuffer failed: %08lx\n", hr); + } + + This->update_frames = ~0; + + LeaveCriticalSection(&This->lock); + + return S_OK; +} + +static HRESULT WINAPI SAORS_ActivateSpatialAudioObject(ISpatialAudioObjectRenderStream *iface, + AudioObjectType type, ISpatialAudioObject **object) +{ + SpatialAudioStreamImpl *This = impl_from_ISpatialAudioObjectRenderStream(iface); + SpatialAudioObjectImpl *obj; + + TRACE("(%p)->(0x%x, %p)\n", This, type, object); + + if(type == AudioObjectType_Dynamic) + return SPTLAUDCLNT_E_NO_MORE_OBJECTS; + + if(type & ~This->params.StaticObjectTypeMask) + return SPTLAUDCLNT_E_STATIC_OBJECT_NOT_AVAILABLE; + + LIST_FOR_EACH_ENTRY(obj, &This->objects, SpatialAudioObjectImpl, entry){ + if(obj->static_idx == AudioObjectType_to_index(type)) + return SPTLAUDCLNT_E_OBJECT_ALREADY_ACTIVE; + } + + obj = heap_alloc_zero(sizeof(*obj)); + obj->ISpatialAudioObject_iface.lpVtbl = &ISpatialAudioObject_vtbl; + obj->ref = 1; + obj->type = type; + if(type == AudioObjectType_None){ + FIXME("AudioObjectType_None not implemented yet!\n"); + obj->static_idx = ~0; + }else{ + obj->static_idx = AudioObjectType_to_index(type); + } + + obj->sa_stream = This; + SAORS_AddRef(&This->ISpatialAudioObjectRenderStream_iface); + + obj->buf = heap_alloc_zero(This->period_frames * This->sa_client->object_fmtex.Format.nBlockAlign); + + EnterCriticalSection(&This->lock); + + list_add_tail(&This->objects, &obj->entry); + + LeaveCriticalSection(&This->lock); + + *object = &obj->ISpatialAudioObject_iface; + + return S_OK; +} + +static ISpatialAudioObjectRenderStreamVtbl ISpatialAudioObjectRenderStream_vtbl = { + SAORS_QueryInterface, + SAORS_AddRef, + SAORS_Release, + SAORS_GetAvailableDynamicObjectCount, + SAORS_GetService, + SAORS_Start, + SAORS_Stop, + SAORS_Reset, + SAORS_BeginUpdatingAudioObjects, + SAORS_EndUpdatingAudioObjects, + SAORS_ActivateSpatialAudioObject, +}; + +static HRESULT WINAPI SAC_QueryInterface(ISpatialAudioClient *iface, REFIID riid, void **ppv) +{ + SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); + + TRACE("(%p)->(%s,%p)\n", This, debugstr_guid(riid), ppv); + + if (!ppv) + return E_POINTER; + + *ppv = NULL; + + if (IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_ISpatialAudioClient)) { + *ppv = &This->ISpatialAudioClient_iface; + } + else + return E_NOINTERFACE; + + IUnknown_AddRef((IUnknown *)*ppv); + + return S_OK; +} + +static ULONG WINAPI SAC_AddRef(ISpatialAudioClient *iface) +{ + SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); + ULONG ref = InterlockedIncrement(&This->ref); + TRACE("(%p) new ref %lu\n", This, ref); + return ref; +} + +static ULONG WINAPI SAC_Release(ISpatialAudioClient *iface) +{ + SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); + ULONG ref = InterlockedDecrement(&This->ref); + TRACE("(%p) new ref %lu\n", This, ref); + if (!ref) { + IMMDevice_Release(This->mmdev); + heap_free(This); + } + return ref; +} + +static HRESULT WINAPI SAC_GetStaticObjectPosition(ISpatialAudioClient *iface, + AudioObjectType type, float *x, float *y, float *z) +{ + SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); + FIXME("(%p)->(0x%x, %p, %p, %p)\n", This, type, x, y, z); + return E_NOTIMPL; +} + +static HRESULT WINAPI SAC_GetNativeStaticObjectTypeMask(ISpatialAudioClient *iface, + AudioObjectType *mask) +{ + SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); + FIXME("(%p)->(%p)\n", This, mask); + return E_NOTIMPL; +} + +static HRESULT WINAPI SAC_GetMaxDynamicObjectCount(ISpatialAudioClient *iface, + UINT32 *value) +{ + SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); + FIXME("(%p)->(%p)\n", This, value); + + *value = 0; + + return S_OK; +} + +static HRESULT WINAPI SAC_GetSupportedAudioObjectFormatEnumerator( + ISpatialAudioClient *iface, IAudioFormatEnumerator **enumerator) +{ + SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); + + TRACE("(%p)->(%p)\n", This, enumerator); + + *enumerator = &This->IAudioFormatEnumerator_iface; + SAC_AddRef(iface); + + return S_OK; +} + +static HRESULT WINAPI SAC_GetMaxFrameCount(ISpatialAudioClient *iface, + const WAVEFORMATEX *format, UINT32 *count) +{ + SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); + + /* FIXME: should get device period from the device */ + static const REFERENCE_TIME period = 100000; + + TRACE("(%p)->(%p, %p)\n", This, format, count); + + *count = MulDiv(period, format->nSamplesPerSec, 10000000); + + return S_OK; +} + +static HRESULT WINAPI SAC_IsAudioObjectFormatSupported(ISpatialAudioClient *iface, + const WAVEFORMATEX *format) +{ + SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); + FIXME("(%p)->(%p)\n", This, format); + return E_NOTIMPL; +} + +static HRESULT WINAPI SAC_IsSpatialAudioStreamAvailable(ISpatialAudioClient *iface, + REFIID stream_uuid, const PROPVARIANT *info) +{ + SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); + FIXME("(%p)->(%s, %p)\n", This, debugstr_guid(stream_uuid), info); + return E_NOTIMPL; +} + +static WAVEFORMATEX *clone_fmtex(const WAVEFORMATEX *src) +{ + WAVEFORMATEX *r = heap_alloc(sizeof(WAVEFORMATEX) + src->cbSize); + memcpy(r, src, sizeof(WAVEFORMATEX) + src->cbSize); + return r; +} + +static const char *debugstr_fmtex(const WAVEFORMATEX *fmt) +{ + static char buf[2048]; + if(fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE){ + const WAVEFORMATEXTENSIBLE *fmtex = (const WAVEFORMATEXTENSIBLE *)fmt; + snprintf(buf, sizeof(buf), "tag: 0x%x (%s), ch: %u (mask: 0x%lx), rate: %lu, depth: %u", + fmt->wFormatTag, debugstr_guid(&fmtex->SubFormat), + fmt->nChannels, fmtex->dwChannelMask, fmt->nSamplesPerSec, + fmt->wBitsPerSample); + }else{ + snprintf(buf, sizeof(buf), "tag: 0x%x, ch: %u, rate: %lu, depth: %u", + fmt->wFormatTag, fmt->nChannels, fmt->nSamplesPerSec, + fmt->wBitsPerSample); + } + return buf; +} + +static void static_mask_to_channels(AudioObjectType static_mask, WORD *count, DWORD *mask, UINT32 *map) +{ + UINT32 out_chan = 0, map_idx = 0; + *count = 0; + *mask = 0; +#define CONVERT_MASK(f, t) \ + if(static_mask & f){ \ + *count += 1; \ + *mask |= t; \ + map[map_idx++] = out_chan++; \ + TRACE("mapping 0x%x to %u\n", f, out_chan - 1); \ + }else{ \ + map[map_idx++] = ~0; \ + } + CONVERT_MASK(AudioObjectType_FrontLeft, SPEAKER_FRONT_LEFT); + CONVERT_MASK(AudioObjectType_FrontRight, SPEAKER_FRONT_RIGHT); + CONVERT_MASK(AudioObjectType_FrontCenter, SPEAKER_FRONT_CENTER); + CONVERT_MASK(AudioObjectType_LowFrequency, SPEAKER_LOW_FREQUENCY); + CONVERT_MASK(AudioObjectType_SideLeft, SPEAKER_SIDE_LEFT); + CONVERT_MASK(AudioObjectType_SideRight, SPEAKER_SIDE_RIGHT); + CONVERT_MASK(AudioObjectType_BackLeft, SPEAKER_BACK_LEFT); + CONVERT_MASK(AudioObjectType_BackRight, SPEAKER_BACK_RIGHT); + CONVERT_MASK(AudioObjectType_TopFrontLeft, SPEAKER_TOP_FRONT_LEFT); + CONVERT_MASK(AudioObjectType_TopFrontRight, SPEAKER_TOP_FRONT_RIGHT); + CONVERT_MASK(AudioObjectType_TopBackLeft, SPEAKER_TOP_BACK_LEFT); + CONVERT_MASK(AudioObjectType_TopBackRight, SPEAKER_TOP_BACK_RIGHT); + CONVERT_MASK(AudioObjectType_BackCenter, SPEAKER_BACK_CENTER); +} + +static HRESULT activate_stream(SpatialAudioStreamImpl *stream) +{ + WAVEFORMATEXTENSIBLE *object_fmtex = (WAVEFORMATEXTENSIBLE *)stream->params.ObjectFormat; + HRESULT hr; + REFERENCE_TIME period; + + if(!(object_fmtex->Format.wFormatTag == WAVE_FORMAT_IEEE_FLOAT || + (object_fmtex->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE && + IsEqualGUID(&object_fmtex->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)))){ + FIXME("Only float formats are supported for now\n"); + return E_INVALIDARG; + } + + hr = IMMDevice_Activate(stream->sa_client->mmdev, &IID_IAudioClient, + CLSCTX_INPROC_SERVER, NULL, (void**)&stream->client); + if(FAILED(hr)){ + WARN("Activate failed: %08lx\n", hr); + return hr; + } + + hr = IAudioClient_GetDevicePeriod(stream->client, &period, NULL); + if(FAILED(hr)){ + WARN("GetDevicePeriod failed: %08lx\n", hr); + IAudioClient_Release(stream->client); + return hr; + } + + stream->stream_fmtex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + static_mask_to_channels(stream->params.StaticObjectTypeMask, + &stream->stream_fmtex.Format.nChannels, &stream->stream_fmtex.dwChannelMask, + stream->static_object_map); + stream->stream_fmtex.Format.nSamplesPerSec = stream->params.ObjectFormat->nSamplesPerSec; + stream->stream_fmtex.Format.wBitsPerSample = stream->params.ObjectFormat->wBitsPerSample; + stream->stream_fmtex.Format.nBlockAlign = (stream->stream_fmtex.Format.nChannels * stream->stream_fmtex.Format.wBitsPerSample) / 8; + stream->stream_fmtex.Format.nAvgBytesPerSec = stream->stream_fmtex.Format.nSamplesPerSec * stream->stream_fmtex.Format.nBlockAlign; + stream->stream_fmtex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); + stream->stream_fmtex.Samples.wValidBitsPerSample = stream->stream_fmtex.Format.wBitsPerSample; + stream->stream_fmtex.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + + hr = IAudioClient_Initialize(stream->client, AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST, + period, 0, &stream->stream_fmtex.Format, NULL); + if(FAILED(hr)){ + WARN("Initialize failed: %08lx\n", hr); + IAudioClient_Release(stream->client); + return hr; + } + + hr = IAudioClient_SetEventHandle(stream->client, stream->params.EventHandle); + if(FAILED(hr)){ + WARN("SetEventHandle failed: %08lx\n", hr); + IAudioClient_Release(stream->client); + return hr; + } + + hr = IAudioClient_GetService(stream->client, &IID_IAudioRenderClient, (void**)&stream->render); + if(FAILED(hr)){ + WARN("GetService(AudioRenderClient) failed: %08lx\n", hr); + IAudioClient_Release(stream->client); + return hr; + } + + stream->period_frames = MulDiv(period, stream->stream_fmtex.Format.nSamplesPerSec, 10000000); + + return S_OK; +} + +static HRESULT WINAPI SAC_ActivateSpatialAudioStream(ISpatialAudioClient *iface, + const PROPVARIANT *prop, REFIID riid, void **stream) +{ + SpatialAudioImpl *This = impl_from_ISpatialAudioClient(iface); + SpatialAudioObjectRenderStreamActivationParams *params; + HRESULT hr; + + TRACE("(%p)->(%s, %p)\n", This, debugstr_guid(riid), stream); + + if(IsEqualIID(riid, &IID_ISpatialAudioObjectRenderStream)){ + SpatialAudioStreamImpl *obj; + + if(prop && + (prop->vt != VT_BLOB || + prop->blob.cbSize != sizeof(SpatialAudioObjectRenderStreamActivationParams))){ + WARN("Got invalid params\n"); + *stream = NULL; + return E_INVALIDARG; + } + + params = (SpatialAudioObjectRenderStreamActivationParams*) prop->blob.pBlobData; + + if(params->StaticObjectTypeMask & AudioObjectType_Dynamic){ + *stream = NULL; + return E_INVALIDARG; + } + + if(params->EventHandle == INVALID_HANDLE_VALUE || + params->EventHandle == 0){ + *stream = NULL; + return E_INVALIDARG; + } + + if(!params->ObjectFormat || + memcmp(params->ObjectFormat, &This->object_fmtex.Format, sizeof(*params->ObjectFormat) + params->ObjectFormat->cbSize)){ + *stream = NULL; + return AUDCLNT_E_UNSUPPORTED_FORMAT; + } + + obj = heap_alloc_zero(sizeof(SpatialAudioStreamImpl)); + + obj->ISpatialAudioObjectRenderStream_iface.lpVtbl = &ISpatialAudioObjectRenderStream_vtbl; + obj->ref = 1; + memcpy(&obj->params, params, sizeof(obj->params)); + + obj->update_frames = ~0; + + InitializeCriticalSection(&obj->lock); + list_init(&obj->objects); + + obj->sa_client = This; + SAC_AddRef(&This->ISpatialAudioClient_iface); + + obj->params.ObjectFormat = clone_fmtex(obj->params.ObjectFormat); + + DuplicateHandle(GetCurrentProcess(), obj->params.EventHandle, + GetCurrentProcess(), &obj->params.EventHandle, 0, FALSE, + DUPLICATE_SAME_ACCESS); + + if(obj->params.NotifyObject) + ISpatialAudioObjectRenderStreamNotify_AddRef(obj->params.NotifyObject); + + if(TRACE_ON(mmdevapi)){ + TRACE("ObjectFormat: {%s}\n", debugstr_fmtex(obj->params.ObjectFormat)); + TRACE("StaticObjectTypeMask: 0x%x\n", obj->params.StaticObjectTypeMask); + TRACE("MinDynamicObjectCount: 0x%x\n", obj->params.MinDynamicObjectCount); + TRACE("MaxDynamicObjectCount: 0x%x\n", obj->params.MaxDynamicObjectCount); + TRACE("Category: 0x%x\n", obj->params.Category); + TRACE("EventHandle: %p\n", obj->params.EventHandle); + TRACE("NotifyObject: %p\n", obj->params.NotifyObject); + } + + hr = activate_stream(obj); + if(FAILED(hr)){ + if(obj->params.NotifyObject) + ISpatialAudioObjectRenderStreamNotify_Release(obj->params.NotifyObject); + DeleteCriticalSection(&obj->lock); + heap_free((void*)obj->params.ObjectFormat); + CloseHandle(obj->params.EventHandle); + ISpatialAudioClient_Release(&obj->sa_client->ISpatialAudioClient_iface); + heap_free(obj); + *stream = NULL; + return hr; + } + + *stream = &obj->ISpatialAudioObjectRenderStream_iface; + }else{ + FIXME("Unsupported audio stream IID: %s\n", debugstr_guid(riid)); + *stream = NULL; + return E_NOTIMPL; + } + + return S_OK; +} + +static ISpatialAudioClientVtbl ISpatialAudioClient_vtbl = { + SAC_QueryInterface, + SAC_AddRef, + SAC_Release, + SAC_GetStaticObjectPosition, + SAC_GetNativeStaticObjectTypeMask, + SAC_GetMaxDynamicObjectCount, + SAC_GetSupportedAudioObjectFormatEnumerator, + SAC_GetMaxFrameCount, + SAC_IsAudioObjectFormatSupported, + SAC_IsSpatialAudioStreamAvailable, + SAC_ActivateSpatialAudioStream, +}; + +static HRESULT WINAPI SAOFE_QueryInterface(IAudioFormatEnumerator *iface, + REFIID riid, void **ppvObject) +{ + SpatialAudioImpl *This = impl_from_IAudioFormatEnumerator(iface); + return SAC_QueryInterface(&This->ISpatialAudioClient_iface, riid, ppvObject); +} + +static ULONG WINAPI SAOFE_AddRef(IAudioFormatEnumerator *iface) +{ + SpatialAudioImpl *This = impl_from_IAudioFormatEnumerator(iface); + return SAC_AddRef(&This->ISpatialAudioClient_iface); +} + +static ULONG WINAPI SAOFE_Release(IAudioFormatEnumerator *iface) +{ + SpatialAudioImpl *This = impl_from_IAudioFormatEnumerator(iface); + return SAC_Release(&This->ISpatialAudioClient_iface); +} + +static HRESULT WINAPI SAOFE_GetCount(IAudioFormatEnumerator *iface, UINT32 *count) +{ + SpatialAudioImpl *This = impl_from_IAudioFormatEnumerator(iface); + + TRACE("(%p)->(%p)\n", This, count); + + *count = 1; + + return S_OK; +} + +static HRESULT WINAPI SAOFE_GetFormat(IAudioFormatEnumerator *iface, + UINT32 index, WAVEFORMATEX **format) +{ + SpatialAudioImpl *This = impl_from_IAudioFormatEnumerator(iface); + + TRACE("(%p)->(%u, %p)\n", This, index, format); + + if(index > 0) + return E_INVALIDARG; + + *format = &This->object_fmtex.Format; + + return S_OK; +} + +static IAudioFormatEnumeratorVtbl IAudioFormatEnumerator_vtbl = { + SAOFE_QueryInterface, + SAOFE_AddRef, + SAOFE_Release, + SAOFE_GetCount, + SAOFE_GetFormat, +}; + +HRESULT SpatialAudioClient_Create(IMMDevice *mmdev, ISpatialAudioClient **out) +{ + SpatialAudioImpl *obj; + IAudioClient *aclient; + WAVEFORMATEX *closest; + HRESULT hr; + + obj = heap_alloc_zero(sizeof(*obj)); + + obj->ref = 1; + obj->ISpatialAudioClient_iface.lpVtbl = &ISpatialAudioClient_vtbl; + obj->IAudioFormatEnumerator_iface.lpVtbl = &IAudioFormatEnumerator_vtbl; + + obj->object_fmtex.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; + obj->object_fmtex.Format.nChannels = 1; + obj->object_fmtex.Format.nSamplesPerSec = 48000; + obj->object_fmtex.Format.wBitsPerSample = sizeof(float) * 8; + obj->object_fmtex.Format.nBlockAlign = (obj->object_fmtex.Format.nChannels * obj->object_fmtex.Format.wBitsPerSample) / 8; + obj->object_fmtex.Format.nAvgBytesPerSec = obj->object_fmtex.Format.nSamplesPerSec * obj->object_fmtex.Format.nBlockAlign; + obj->object_fmtex.Format.cbSize = 0; + + hr = IMMDevice_Activate(mmdev, &IID_IAudioClient, + CLSCTX_INPROC_SERVER, NULL, (void**)&aclient); + if(FAILED(hr)){ + WARN("Activate failed: %08lx\n", hr); + heap_free(obj); + return hr; + } + + hr = IAudioClient_IsFormatSupported(aclient, AUDCLNT_SHAREMODE_SHARED, &obj->object_fmtex.Format, &closest); + + IAudioClient_Release(aclient); + + if(hr == S_FALSE){ + if(sizeof(WAVEFORMATEX) + closest->cbSize > sizeof(obj->object_fmtex)){ + ERR("Returned format too large: %s\n", debugstr_fmtex(closest)); + CoTaskMemFree(closest); + heap_free(obj); + return AUDCLNT_E_UNSUPPORTED_FORMAT; + }else if(!((closest->wFormatTag == WAVE_FORMAT_IEEE_FLOAT || + (closest->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + IsEqualGUID(&((WAVEFORMATEXTENSIBLE *)closest)->SubFormat, + &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))) && + closest->wBitsPerSample == 32)){ + ERR("Returned format not 32-bit float: %s\n", debugstr_fmtex(closest)); + CoTaskMemFree(closest); + heap_free(obj); + return AUDCLNT_E_UNSUPPORTED_FORMAT; + } + WARN("The audio stack doesn't support 48kHz 32bit float. Using the closest match. Audio may be glitchy. %s\n", debugstr_fmtex(closest)); + memcpy(&obj->object_fmtex, + closest, + sizeof(WAVEFORMATEX) + closest->cbSize); + CoTaskMemFree(closest); + } else if(hr != S_OK){ + WARN("Checking supported formats failed: %08lx\n", hr); + heap_free(obj); + return hr; + } + + obj->mmdev = mmdev; + IMMDevice_AddRef(mmdev); + + *out = &obj->ISpatialAudioClient_iface; + + return S_OK; +} diff --git a/pkgs/osu-wine/audio-revert/mmdevapi/tests/Makefile.in b/pkgs/osu-wine/audio-revert/mmdevapi/tests/Makefile.in new file mode 100644 index 0000000..dd180a2 --- /dev/null +++ b/pkgs/osu-wine/audio-revert/mmdevapi/tests/Makefile.in @@ -0,0 +1,10 @@ +TESTDLL = mmdevapi.dll +IMPORTS = ole32 version user32 advapi32 winmm + +C_SRCS = \ + capture.c \ + dependency.c \ + mmdevenum.c \ + propstore.c \ + render.c \ + spatialaudio.c diff --git a/pkgs/osu-wine/audio-revert/mmdevapi/tests/capture.c b/pkgs/osu-wine/audio-revert/mmdevapi/tests/capture.c new file mode 100644 index 0000000..f3b426a --- /dev/null +++ b/pkgs/osu-wine/audio-revert/mmdevapi/tests/capture.c @@ -0,0 +1,1055 @@ +/* + * Copyright 2010 Maarten Lankhorst for CodeWeavers + * + * 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 + */ + +/* This test is for audio capture specific mechanisms + * Tests: + * - IAudioClient with eCapture and IAudioCaptureClient + */ + +#include + +#include "wine/test.h" + +#define COBJMACROS + +#ifdef STANDALONE +#include "initguid.h" +#endif + +#include "unknwn.h" +#include "uuids.h" +#include "mmdeviceapi.h" +#include "audioclient.h" + +#define NULL_PTR_ERR MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, RPC_X_NULL_REF_POINTER) + +/* undocumented error code */ +#define D3D11_ERROR_4E MAKE_HRESULT(SEVERITY_ERROR, FACILITY_DIRECT3D11, 0x4e) + +static IMMDevice *dev = NULL; +static const LARGE_INTEGER ullZero; + +static void test_uninitialized(IAudioClient *ac) +{ + HRESULT hr; + UINT32 num; + REFERENCE_TIME t1; + + HANDLE handle = CreateEventW(NULL, FALSE, FALSE, NULL); + IUnknown *unk; + + hr = IAudioClient_GetBufferSize(ac, &num); + ok(hr == AUDCLNT_E_NOT_INITIALIZED, "Uninitialized GetBufferSize call returns %08lx\n", hr); + + hr = IAudioClient_GetStreamLatency(ac, &t1); + ok(hr == AUDCLNT_E_NOT_INITIALIZED, "Uninitialized GetStreamLatency call returns %08lx\n", hr); + + hr = IAudioClient_GetCurrentPadding(ac, &num); + ok(hr == AUDCLNT_E_NOT_INITIALIZED, "Uninitialized GetCurrentPadding call returns %08lx\n", hr); + + hr = IAudioClient_Start(ac); + ok(hr == AUDCLNT_E_NOT_INITIALIZED, "Uninitialized Start call returns %08lx\n", hr); + + hr = IAudioClient_Stop(ac); + ok(hr == AUDCLNT_E_NOT_INITIALIZED, "Uninitialized Stop call returns %08lx\n", hr); + + hr = IAudioClient_Reset(ac); + ok(hr == AUDCLNT_E_NOT_INITIALIZED, "Uninitialized Reset call returns %08lx\n", hr); + + hr = IAudioClient_SetEventHandle(ac, handle); + ok(hr == AUDCLNT_E_NOT_INITIALIZED, "Uninitialized SetEventHandle call returns %08lx\n", hr); + + hr = IAudioClient_GetService(ac, &IID_IAudioStreamVolume, (void**)&unk); + ok(hr == AUDCLNT_E_NOT_INITIALIZED, "Uninitialized GetService call returns %08lx\n", hr); + + CloseHandle(handle); +} + +static void test_capture(IAudioClient *ac, HANDLE handle, WAVEFORMATEX *wfx) +{ + IAudioCaptureClient *acc; + HRESULT hr; + UINT32 frames, next, pad, sum = 0; + BYTE *data; + DWORD flags; + UINT64 pos, qpc; + REFERENCE_TIME period; + + hr = IAudioClient_GetService(ac, &IID_IAudioCaptureClient, (void**)&acc); + ok(hr == S_OK, "IAudioClient_GetService(IID_IAudioCaptureClient) returns %08lx\n", hr); + if (hr != S_OK) + return; + + ok(ResetEvent(handle), "ResetEvent\n"); + + hr = IAudioCaptureClient_GetNextPacketSize(acc, &next); + ok(hr == S_OK, "IAudioCaptureClient_GetNextPacketSize returns %08lx\n", hr); + + hr = IAudioClient_GetCurrentPadding(ac, &pad); + ok(hr == S_OK, "GetCurrentPadding call returns %08lx\n", hr); + ok(next == pad, "GetNextPacketSize %u vs. GCP %u\n", next, pad); + /* later GCP will grow, while GNPS is 0 or period size */ + + hr = IAudioCaptureClient_GetNextPacketSize(acc, NULL); + ok(hr == E_POINTER, "IAudioCaptureClient_GetNextPacketSize(NULL) returns %08lx\n", hr); + + data = (void*)0xdeadf00d; + frames = 0xdeadbeef; + flags = 0xabadcafe; + hr = IAudioCaptureClient_GetBuffer(acc, &data, NULL, NULL, NULL, NULL); + ok(hr == E_POINTER, "IAudioCaptureClient_GetBuffer(data, NULL, NULL) returns %08lx\n", hr); + + hr = IAudioCaptureClient_GetBuffer(acc, NULL, &frames, NULL, NULL, NULL); + ok(hr == E_POINTER, "IAudioCaptureClient_GetBuffer(NULL, &frames, NULL) returns %08lx\n", hr); + + hr = IAudioCaptureClient_GetBuffer(acc, NULL, NULL, &flags, NULL, NULL); + ok(hr == E_POINTER, "IAudioCaptureClient_GetBuffer(NULL, NULL, &flags) returns %08lx\n", hr); + + hr = IAudioCaptureClient_GetBuffer(acc, &data, &frames, NULL, NULL, NULL); + ok(hr == E_POINTER, "IAudioCaptureClient_GetBuffer(&ata, &frames, NULL) returns %08lx\n", hr); + ok(broken((DWORD_PTR)data == 0xdeadf00d) || /* <= win8 */ + data == NULL, "data is reset to %p\n", data); + ok(frames == 0xdeadbeef, "frames is reset to %08x\n", frames); + ok(flags == 0xabadcafe, "flags is reset to %08lx\n", flags); + + hr = IAudioClient_GetDevicePeriod(ac, &period, NULL); + ok(hr == S_OK, "GetDevicePeriod failed: %08lx\n", hr); + period = MulDiv(period, wfx->nSamplesPerSec, 10000000); /* as in render.c */ + + hr = IAudioClient_Start(ac); + ok(hr == S_OK, "Start on a stopped stream returns %08lx\n", hr); + + ok(WaitForSingleObject(handle, 1000) == WAIT_OBJECT_0, "Waiting on event handle failed!\n"); + + data = (void*)0xdeadf00d; + hr = IAudioCaptureClient_GetBuffer(acc, &data, &frames, &flags, &pos, &qpc); + ok(hr == S_OK || hr == AUDCLNT_S_BUFFER_EMPTY, "Valid IAudioCaptureClient_GetBuffer returns %08lx\n", hr); + if (hr == S_OK){ + ok(frames, "Amount of frames locked is 0!\n"); + /* broken: some w7 machines return pad == 0 and DATA_DISCONTINUITY here, + * AUDCLNT_S_BUFFER_EMPTY above, yet pos == 1-2 * period rather than 0 */ + ok(pos == sum || broken(flags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY), + "Position %u expected %u\n", (UINT)pos, sum); + sum = pos; + }else if (hr == AUDCLNT_S_BUFFER_EMPTY){ + ok(!frames, "Amount of frames locked with empty buffer is %u!\n", frames); + ok(broken(data == (void*)0xdeadf00d) || /* <= win8 */ + data == NULL, "No data changed to %p\n", data); + } + + trace("Wait'ed position %d pad %u flags %lx, amount of frames locked: %u\n", + hr==S_OK ? (UINT)pos : -1, pad, flags, frames); + + hr = IAudioCaptureClient_GetNextPacketSize(acc, &next); + ok(hr == S_OK, "IAudioCaptureClient_GetNextPacketSize returns %08lx\n", hr); + ok(next == frames, "GetNextPacketSize %u vs. GetBuffer %u\n", next, frames); + + hr = IAudioCaptureClient_ReleaseBuffer(acc, frames); + ok(hr == S_OK, "Releasing buffer returns %08lx\n", hr); + + hr = IAudioCaptureClient_ReleaseBuffer(acc, 0); + ok(hr == S_OK, "Releasing 0 returns %08lx\n", hr); + + hr = IAudioCaptureClient_GetNextPacketSize(acc, &next); + ok(hr == S_OK, "IAudioCaptureClient_GetNextPacketSize returns %08lx\n", hr); + + if (frames) { + hr = IAudioCaptureClient_ReleaseBuffer(acc, frames); + ok(hr == AUDCLNT_E_OUT_OF_ORDER, "Releasing buffer twice returns %08lx\n", hr); + sum += frames; + } + + Sleep(350); /* for sure there's data now */ + + hr = IAudioClient_GetCurrentPadding(ac, &pad); + ok(hr == S_OK, "GetCurrentPadding call returns %08lx\n", hr); + + /** GetNextPacketSize + * returns either 0 or one period worth of frames + * whereas GetCurrentPadding grows when input is not consumed. */ + hr = IAudioCaptureClient_GetNextPacketSize(acc, &next); + ok(hr == S_OK, "IAudioCaptureClient_GetNextPacketSize returns %08lx\n", hr); + flaky_wine + ok(next < pad, "GetNextPacketSize %u vs. GCP %u\n", next, pad); + + hr = IAudioCaptureClient_GetBuffer(acc, &data, &frames, &flags, &pos, &qpc); + flaky_wine + ok(hr == S_OK, "Valid IAudioCaptureClient_GetBuffer returns %08lx\n", hr); + ok(next == frames, "GetNextPacketSize %u vs. GetBuffer %u\n", next, frames); + + if(hr == S_OK){ + UINT32 frames2 = frames; + UINT64 pos2, qpc2; + ok(frames, "Amount of frames locked is 0!\n"); + ok(pos == sum, "Position %u expected %u\n", (UINT)pos, sum); + + hr = IAudioCaptureClient_ReleaseBuffer(acc, 0); + ok(hr == S_OK, "Releasing 0 returns %08lx\n", hr); + + /* GCP did not decrement, no data consumed */ + hr = IAudioClient_GetCurrentPadding(ac, &frames); + ok(hr == S_OK, "GetCurrentPadding call returns %08lx\n", hr); + ok(frames == pad || frames == pad + next /* concurrent feeder */, + "GCP %u past ReleaseBuffer(0) initially %u\n", frames, pad); + + /* should re-get the same data */ + hr = IAudioCaptureClient_GetBuffer(acc, &data, &frames, &flags, &pos2, &qpc2); + ok(hr == S_OK, "Valid IAudioCaptureClient_GetBuffer returns %08lx\n", hr); + ok(frames2 == frames, "GetBuffer after ReleaseBuffer(0) %u/%u\n", frames2, frames); + ok(pos2 == pos, "Position after ReleaseBuffer(0) %u/%u\n", (UINT)pos2, (UINT)pos); + todo_wine_if(qpc2 != qpc) + /* FIXME: Some drivers fail */ + ok(qpc2 == qpc, "HPC after ReleaseBuffer(0) %u vs. %u\n", (UINT)qpc2, (UINT)qpc); + } + + /* trace after the GCP test because log output to MS-DOS console disturbs timing */ + trace("Sleep.1 position %d pad %u flags %lx, amount of frames locked: %u\n", + hr==S_OK ? (UINT)pos : -1, pad, flags, frames); + + if(hr == S_OK){ + UINT32 frames2 = 0xabadcafe; + BYTE *data2 = (void*)0xdeadf00d; + flags = 0xabadcafe; + + ok(pos == sum, "Position %u expected %u\n", (UINT)pos, sum); + + pos = qpc = 0xdeadbeef; + hr = IAudioCaptureClient_GetBuffer(acc, &data2, &frames2, &flags, &pos, &qpc); + ok(hr == AUDCLNT_E_OUT_OF_ORDER, "Out of order IAudioCaptureClient_GetBuffer returns %08lx\n", hr); + ok(frames2 == 0xabadcafe, "Out of order frames changed to %x\n", frames2); + ok(broken(data2 == (void*)0xdeadf00d) /* <= win8 */ || + data2 == NULL, "Out of order data changed to %p\n", data2); + ok(flags == 0xabadcafe, "Out of order flags changed to %lx\n", flags); + ok(pos == 0xdeadbeef, "Out of order position changed to %x\n", (UINT)pos); + ok(qpc == 0xdeadbeef, "Out of order timer changed to %x\n", (UINT)qpc); + + hr = IAudioCaptureClient_ReleaseBuffer(acc, frames+1); + ok(hr == AUDCLNT_E_INVALID_SIZE, "Releasing buffer+1 returns %08lx\n", hr); + + hr = IAudioCaptureClient_ReleaseBuffer(acc, 1); + ok(hr == AUDCLNT_E_INVALID_SIZE, "Releasing 1 returns %08lx\n", hr); + + hr = IAudioClient_Reset(ac); + ok(hr == AUDCLNT_E_NOT_STOPPED, "Reset failed: %08lx\n", hr); + } + + hr = IAudioCaptureClient_ReleaseBuffer(acc, frames); + ok(hr == S_OK, "Releasing buffer returns %08lx\n", hr); + + if (frames) { + sum += frames; + hr = IAudioCaptureClient_ReleaseBuffer(acc, frames); + ok(hr == AUDCLNT_E_OUT_OF_ORDER, "Releasing buffer twice returns %08lx\n", hr); + } + + frames = period; + flaky_wine + ok(next == frames, "GetNextPacketSize %u vs. GetDevicePeriod %u\n", next, frames); + + /* GetBufferSize is not a multiple of the period size! */ + hr = IAudioClient_GetBufferSize(ac, &next); + ok(hr == S_OK, "GetBufferSize failed: %08lx\n", hr); + trace("GetBufferSize %u period size %u\n", next, frames); + + Sleep(400); /* overrun */ + + hr = IAudioClient_GetCurrentPadding(ac, &pad); + ok(hr == S_OK, "GetCurrentPadding call returns %08lx\n", hr); + + hr = IAudioCaptureClient_GetBuffer(acc, &data, &frames, &flags, &pos, &qpc); + flaky_wine + ok(hr == S_OK, "Valid IAudioCaptureClient_GetBuffer returns %08lx\n", hr); + + trace("Overrun position %d pad %u flags %lx, amount of frames locked: %u\n", + hr==S_OK ? (UINT)pos : -1, pad, flags, frames); + + if(hr == S_OK){ + if(flags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY){ + /* Native's position is one period further than what we read. + * Perhaps that's precisely the meaning of DATA_DISCONTINUITY: + * signal when the position jump left a gap. */ + ok(pos == sum + frames, "Position %u last %u frames %u\n", (UINT)pos, sum, frames); + sum = pos; + }else{ /* win10 */ + ok(pos == sum, "Position %u last %u frames %u\n", (UINT)pos, sum, frames); + } + + ok(pad == next, "GCP %u vs. BufferSize %u\n", (UINT32)pad, next); + } + + hr = IAudioCaptureClient_ReleaseBuffer(acc, frames); + ok(hr == S_OK, "Releasing buffer returns %08lx\n", hr); + sum += frames; + + hr = IAudioClient_GetCurrentPadding(ac, &pad); + ok(hr == S_OK, "GetCurrentPadding call returns %08lx\n", hr); + + hr = IAudioCaptureClient_GetBuffer(acc, &data, &frames, &flags, &pos, &qpc); + flaky_wine + ok(hr == S_OK, "Valid IAudioCaptureClient_GetBuffer returns %08lx\n", hr); + + trace("Cont'ed position %d pad %u flags %lx, amount of frames locked: %u\n", + hr==S_OK ? (UINT)pos : -1, pad, flags, frames); + + if(hr == S_OK){ + flaky_wine + ok(pos == sum, "Position %u expected %u\n", (UINT)pos, sum); + flaky_wine + ok(!flags, "flags %lu\n", flags); + + hr = IAudioCaptureClient_ReleaseBuffer(acc, frames); + ok(hr == S_OK, "Releasing buffer returns %08lx\n", hr); + sum += frames; + } + + hr = IAudioClient_Stop(ac); + ok(hr == S_OK, "Stop on a started stream returns %08lx\n", hr); + + hr = IAudioClient_Start(ac); + ok(hr == S_OK, "Start on a stopped stream returns %08lx\n", hr); + + hr = IAudioCaptureClient_GetBuffer(acc, &data, &frames, &flags, &pos, &qpc); + flaky_wine + ok(hr == S_OK, "Valid IAudioCaptureClient_GetBuffer returns %08lx\n", hr); + + hr = IAudioClient_GetCurrentPadding(ac, &pad); + ok(hr == S_OK, "GetCurrentPadding call returns %08lx\n", hr); + + trace("Restart position %d pad %u flags %lx, amount of frames locked: %u\n", + hr==S_OK ? (UINT)pos : -1, pad, flags, frames); + flaky_wine + ok(pad > sum, "restarted GCP %u\n", pad); /* GCP is still near buffer size */ + + if(frames){ + flaky_wine + ok(pos == sum, "Position %u expected %u\n", (UINT)pos, sum); + ok(!flags, "flags %lu\n", flags); + + hr = IAudioCaptureClient_ReleaseBuffer(acc, frames); + ok(hr == S_OK, "Releasing buffer returns %08lx\n", hr); + sum += frames; + } + + hr = IAudioClient_Stop(ac); + ok(hr == S_OK, "Stop on a started stream returns %08lx\n", hr); + + hr = IAudioClient_Reset(ac); + ok(hr == S_OK, "Reset on a stopped stream returns %08lx\n", hr); + sum += pad - frames; + + hr = IAudioClient_GetCurrentPadding(ac, &pad); + ok(hr == S_OK, "GetCurrentPadding call returns %08lx\n", hr); + ok(!pad, "reset GCP %u\n", pad); + + flags = 0xabadcafe; + hr = IAudioCaptureClient_GetBuffer(acc, &data, &frames, &flags, &pos, &qpc); + ok(hr == AUDCLNT_S_BUFFER_EMPTY, + "Initial IAudioCaptureClient_GetBuffer returns %08lx\n", hr); + + trace("Reset position %d pad %u flags %lx, amount of frames locked: %u\n", + hr==S_OK ? (UINT)pos : -1, pad, flags, frames); + + if(SUCCEEDED(hr)) + IAudioCaptureClient_ReleaseBuffer(acc, frames); + + hr = IAudioClient_Start(ac); + ok(hr == S_OK, "Start on a stopped stream returns %08lx\n", hr); + + Sleep(180); + + hr = IAudioClient_GetCurrentPadding(ac, &pad); + ok(hr == S_OK, "GetCurrentPadding call returns %08lx\n", hr); + + hr = IAudioCaptureClient_GetBuffer(acc, &data, &frames, &flags, &pos, &qpc); + flaky_wine + ok(hr == S_OK, "Valid IAudioCaptureClient_GetBuffer returns %08lx\n", hr); + trace("Running position %d pad %u flags %lx, amount of frames locked: %u\n", + SUCCEEDED(hr) ? (UINT)pos : -1, pad, flags, frames); + + if(SUCCEEDED(hr)){ + /* Some w7 machines signal DATA_DISCONTINUITY here following the + * previous AUDCLNT_S_BUFFER_EMPTY, others not. What logic? */ + ok(pos >= sum, "Position %u gap %d\n", (UINT)pos, (UINT)pos - sum); + IAudioCaptureClient_ReleaseBuffer(acc, frames); + } + + IAudioCaptureClient_Release(acc); +} + +static void test_audioclient(void) +{ + IAudioClient *ac; + IUnknown *unk; + HRESULT hr; + ULONG ref; + WAVEFORMATEX *pwfx, *pwfx2; + REFERENCE_TIME t1, t2; + HANDLE handle; + + hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, + NULL, (void**)&ac); + ok(hr == S_OK, "Activation failed with %08lx\n", hr); + if(hr != S_OK) + return; + + handle = CreateEventW(NULL, FALSE, FALSE, NULL); + + hr = IAudioClient_QueryInterface(ac, &IID_IUnknown, NULL); + ok(hr == E_POINTER, "QueryInterface(NULL) returned %08lx\n", hr); + + unk = (void*)(LONG_PTR)0x12345678; + hr = IAudioClient_QueryInterface(ac, &IID_NULL, (void**)&unk); + ok(hr == E_NOINTERFACE, "QueryInterface(IID_NULL) returned %08lx\n", hr); + ok(!unk, "QueryInterface(IID_NULL) returned non-null pointer %p\n", unk); + + hr = IAudioClient_QueryInterface(ac, &IID_IUnknown, (void**)&unk); + ok(hr == S_OK, "QueryInterface(IID_IUnknown) returned %08lx\n", hr); + if (unk) + { + ref = IUnknown_Release(unk); + ok(ref == 1, "Released count is %lu\n", ref); + } + + hr = IAudioClient_QueryInterface(ac, &IID_IAudioClient, (void**)&unk); + ok(hr == S_OK, "QueryInterface(IID_IAudioClient) returned %08lx\n", hr); + if (unk) + { + ref = IUnknown_Release(unk); + ok(ref == 1, "Released count is %lu\n", ref); + } + + hr = IAudioClient_GetDevicePeriod(ac, NULL, NULL); + ok(hr == E_POINTER, "Invalid GetDevicePeriod call returns %08lx\n", hr); + + hr = IAudioClient_GetDevicePeriod(ac, &t1, NULL); + ok(hr == S_OK, "Valid GetDevicePeriod call returns %08lx\n", hr); + + hr = IAudioClient_GetDevicePeriod(ac, NULL, &t2); + ok(hr == S_OK, "Valid GetDevicePeriod call returns %08lx\n", hr); + + hr = IAudioClient_GetDevicePeriod(ac, &t1, &t2); + ok(hr == S_OK, "Valid GetDevicePeriod call returns %08lx\n", hr); + trace("Returned periods: %u.%04u ms %u.%04u ms\n", + (UINT)(t1/10000), (UINT)(t1 % 10000), + (UINT)(t2/10000), (UINT)(t2 % 10000)); + + hr = IAudioClient_GetMixFormat(ac, NULL); + ok(hr == E_POINTER, "GetMixFormat returns %08lx\n", hr); + + hr = IAudioClient_GetMixFormat(ac, &pwfx); + ok(hr == S_OK, "Valid GetMixFormat returns %08lx\n", hr); + + if (hr == S_OK) + { + trace("pwfx: %p\n", pwfx); + trace("Tag: %04x\n", pwfx->wFormatTag); + trace("bits: %u\n", pwfx->wBitsPerSample); + trace("chan: %u\n", pwfx->nChannels); + trace("rate: %lu\n", pwfx->nSamplesPerSec); + trace("align: %u\n", pwfx->nBlockAlign); + trace("extra: %u\n", pwfx->cbSize); + ok(pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE, "wFormatTag is %x\n", pwfx->wFormatTag); + if (pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE) + { + WAVEFORMATEXTENSIBLE *pwfxe = (void*)pwfx; + trace("Res: %u\n", pwfxe->Samples.wReserved); + trace("Mask: %lx\n", pwfxe->dwChannelMask); + trace("Alg: %s\n", + IsEqualGUID(&pwfxe->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM)?"PCM": + (IsEqualGUID(&pwfxe->SubFormat, + &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)?"FLOAT":"Other")); + } + + hr = IAudioClient_IsFormatSupported(ac, AUDCLNT_SHAREMODE_SHARED, pwfx, &pwfx2); + ok(hr == S_OK, "Valid IsFormatSupported(Shared) call returns %08lx\n", hr); + ok(pwfx2 == NULL, "pwfx2 is non-null\n"); + CoTaskMemFree(pwfx2); + + hr = IAudioClient_IsFormatSupported(ac, AUDCLNT_SHAREMODE_SHARED, NULL, NULL); + ok(hr == E_POINTER, "IsFormatSupported(NULL) call returns %08lx\n", hr); + + hr = IAudioClient_IsFormatSupported(ac, AUDCLNT_SHAREMODE_SHARED, pwfx, NULL); + ok(hr == E_POINTER, "IsFormatSupported(Shared,NULL) call returns %08lx\n", hr); + + hr = IAudioClient_IsFormatSupported(ac, AUDCLNT_SHAREMODE_EXCLUSIVE, pwfx, NULL); + ok(hr == S_OK || hr == AUDCLNT_E_UNSUPPORTED_FORMAT, "IsFormatSupported(Exclusive) call returns %08lx\n", hr); + + hr = IAudioClient_IsFormatSupported(ac, AUDCLNT_SHAREMODE_EXCLUSIVE, pwfx, &pwfx2); + ok(hr == S_OK || hr == AUDCLNT_E_UNSUPPORTED_FORMAT, "IsFormatSupported(Exclusive) call returns %08lx\n", hr); + ok(pwfx2 == NULL, "pwfx2 non-null on exclusive IsFormatSupported\n"); + + hr = IAudioClient_IsFormatSupported(ac, 0xffffffff, pwfx, NULL); + ok(hr == E_INVALIDARG/*w32*/ || + broken(hr == AUDCLNT_E_UNSUPPORTED_FORMAT/*w64 response from exclusive mode driver */), + "IsFormatSupported(0xffffffff) call returns %08lx\n", hr); + } + + test_uninitialized(ac); + + hr = IAudioClient_Initialize(ac, 3, 0, 5000000, 0, pwfx, NULL); + ok(broken(hr == AUDCLNT_E_NOT_INITIALIZED) || /* <= win8 */ + hr == E_INVALIDARG, "Initialize with invalid sharemode returns %08lx\n", hr); + + hr = IAudioClient_Initialize(ac, AUDCLNT_SHAREMODE_SHARED, 0xffffffff, 5000000, 0, pwfx, NULL); + ok(hr == E_INVALIDARG || hr == AUDCLNT_E_INVALID_STREAM_FLAG, "Initialize with invalid flags returns %08lx\n", hr); + + /* A period != 0 is ignored and the call succeeds. + * Since we can only initialize successfully once, skip those tests. + */ + hr = IAudioClient_Initialize(ac, AUDCLNT_SHAREMODE_SHARED, 0, 5000000, 0, NULL, NULL); + ok(hr == E_POINTER, "Initialize with null format returns %08lx\n", hr); + + hr = IAudioClient_Initialize(ac, AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, 4987654, 0, pwfx, NULL); + ok(hr == S_OK, "Valid Initialize returns %08lx\n", hr); + + if (hr != S_OK) + { + skip("Cannot initialize %08lx, remainder of tests is useless\n", hr); + goto cleanup; + } + + hr = IAudioClient_GetStreamLatency(ac, NULL); + ok(hr == E_POINTER, "GetStreamLatency(NULL) call returns %08lx\n", hr); + + hr = IAudioClient_GetStreamLatency(ac, &t1); + ok(hr == S_OK, "Valid GetStreamLatency call returns %08lx\n", hr); + trace("Returned latency: %u.%04u ms\n", + (UINT)(t1/10000), (UINT)(t1 % 10000)); + + hr = IAudioClient_Initialize(ac, AUDCLNT_SHAREMODE_SHARED, 0, 5000000, 0, pwfx, NULL); + ok(hr == AUDCLNT_E_ALREADY_INITIALIZED, "Calling Initialize twice returns %08lx\n", hr); + + hr = IAudioClient_SetEventHandle(ac, NULL); + ok(hr == E_INVALIDARG, "SetEventHandle(NULL) returns %08lx\n", hr); + + hr = IAudioClient_Start(ac); + ok(hr == AUDCLNT_E_EVENTHANDLE_NOT_SET || + hr == D3D11_ERROR_4E /* win10 */, "Start before SetEventHandle returns %08lx\n", hr); + + hr = IAudioClient_SetEventHandle(ac, handle); + ok(hr == S_OK, "SetEventHandle returns %08lx\n", hr); + + hr = IAudioClient_Reset(ac); + ok(hr == S_OK, "Reset on an already reset stream returns %08lx\n", hr); + + hr = IAudioClient_Stop(ac); + ok(hr == S_FALSE, "Stop on a stopped stream returns %08lx\n", hr); + + test_capture(ac, handle, pwfx); + +cleanup: + IAudioClient_Release(ac); + CloseHandle(handle); + CoTaskMemFree(pwfx); +} + +static void test_streamvolume(void) +{ + IAudioClient *ac; + IAudioStreamVolume *asv; + WAVEFORMATEX *fmt; + UINT32 chans, i; + HRESULT hr; + float vol, *vols; + + hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, + NULL, (void**)&ac); + ok(hr == S_OK, "Activation failed with %08lx\n", hr); + if(hr != S_OK) + return; + + hr = IAudioClient_GetMixFormat(ac, &fmt); + ok(hr == S_OK, "GetMixFormat failed: %08lx\n", hr); + + hr = IAudioClient_Initialize(ac, AUDCLNT_SHAREMODE_SHARED, 0, 5000000, + 0, fmt, NULL); + ok(hr == S_OK, "Initialize failed: %08lx\n", hr); + if(hr != S_OK) + return; + + hr = IAudioClient_GetService(ac, &IID_IAudioStreamVolume, (void**)&asv); + ok(hr == S_OK, "GetService failed: %08lx\n", hr); + + hr = IAudioStreamVolume_GetChannelCount(asv, NULL); + ok(hr == E_POINTER, "GetChannelCount gave wrong error: %08lx\n", hr); + + hr = IAudioStreamVolume_GetChannelCount(asv, &chans); + ok(hr == S_OK, "GetChannelCount failed: %08lx\n", hr); + ok(chans == fmt->nChannels, "GetChannelCount gave wrong number of channels: %d\n", chans); + + hr = IAudioStreamVolume_GetChannelVolume(asv, fmt->nChannels, NULL); + ok(hr == E_POINTER, "GetChannelVolume gave wrong error: %08lx\n", hr); + + hr = IAudioStreamVolume_GetChannelVolume(asv, fmt->nChannels, &vol); + ok(hr == E_INVALIDARG, "GetChannelVolume gave wrong error: %08lx\n", hr); + + hr = IAudioStreamVolume_GetChannelVolume(asv, 0, NULL); + ok(hr == E_POINTER, "GetChannelVolume gave wrong error: %08lx\n", hr); + + hr = IAudioStreamVolume_GetChannelVolume(asv, 0, &vol); + ok(hr == S_OK, "GetChannelVolume failed: %08lx\n", hr); + ok(vol == 1.f, "Channel volume was not 1: %f\n", vol); + + hr = IAudioStreamVolume_SetChannelVolume(asv, fmt->nChannels, -1.f); + ok(hr == E_INVALIDARG, "SetChannelVolume gave wrong error: %08lx\n", hr); + + hr = IAudioStreamVolume_SetChannelVolume(asv, 0, -1.f); + ok(hr == E_INVALIDARG, "SetChannelVolume gave wrong error: %08lx\n", hr); + + hr = IAudioStreamVolume_SetChannelVolume(asv, 0, 2.f); + ok(hr == E_INVALIDARG, "SetChannelVolume gave wrong error: %08lx\n", hr); + + hr = IAudioStreamVolume_SetChannelVolume(asv, 0, 0.2f); + ok(hr == S_OK, "SetChannelVolume failed: %08lx\n", hr); + + hr = IAudioStreamVolume_GetChannelVolume(asv, 0, &vol); + ok(hr == S_OK, "GetChannelVolume failed: %08lx\n", hr); + ok(fabsf(vol - 0.2f) < 0.05f, "Channel volume wasn't 0.2: %f\n", vol); + + hr = IAudioStreamVolume_GetAllVolumes(asv, 0, NULL); + ok(hr == E_POINTER, "GetAllVolumes gave wrong error: %08lx\n", hr); + + hr = IAudioStreamVolume_GetAllVolumes(asv, fmt->nChannels, NULL); + ok(hr == E_POINTER, "GetAllVolumes gave wrong error: %08lx\n", hr); + + vols = HeapAlloc(GetProcessHeap(), 0, fmt->nChannels * sizeof(float)); + ok(vols != NULL, "HeapAlloc failed\n"); + + hr = IAudioStreamVolume_GetAllVolumes(asv, fmt->nChannels - 1, vols); + ok(hr == E_INVALIDARG, "GetAllVolumes gave wrong error: %08lx\n", hr); + + hr = IAudioStreamVolume_GetAllVolumes(asv, fmt->nChannels, vols); + ok(hr == S_OK, "GetAllVolumes failed: %08lx\n", hr); + ok(fabsf(vols[0] - 0.2f) < 0.05f, "Channel 0 volume wasn't 0.2: %f\n", vol); + for(i = 1; i < fmt->nChannels; ++i) + ok(vols[i] == 1.f, "Channel %d volume is not 1: %f\n", i, vols[i]); + + hr = IAudioStreamVolume_SetAllVolumes(asv, 0, NULL); + ok(hr == E_POINTER, "SetAllVolumes gave wrong error: %08lx\n", hr); + + hr = IAudioStreamVolume_SetAllVolumes(asv, fmt->nChannels, NULL); + ok(hr == E_POINTER, "SetAllVolumes gave wrong error: %08lx\n", hr); + + hr = IAudioStreamVolume_SetAllVolumes(asv, fmt->nChannels - 1, vols); + ok(hr == E_INVALIDARG, "SetAllVolumes gave wrong error: %08lx\n", hr); + + hr = IAudioStreamVolume_SetAllVolumes(asv, fmt->nChannels, vols); + ok(hr == S_OK, "SetAllVolumes failed: %08lx\n", hr); + + HeapFree(GetProcessHeap(), 0, vols); + IAudioStreamVolume_Release(asv); + IAudioClient_Release(ac); + CoTaskMemFree(fmt); +} + +static void test_channelvolume(void) +{ + IAudioClient *ac; + IChannelAudioVolume *acv; + WAVEFORMATEX *fmt; + UINT32 chans, i; + HRESULT hr; + float vol, *vols; + + hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, + NULL, (void**)&ac); + ok(hr == S_OK, "Activation failed with %08lx\n", hr); + if(hr != S_OK) + return; + + hr = IAudioClient_GetMixFormat(ac, &fmt); + ok(hr == S_OK, "GetMixFormat failed: %08lx\n", hr); + + hr = IAudioClient_Initialize(ac, AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_NOPERSIST, 5000000, 0, fmt, NULL); + ok(hr == S_OK, "Initialize failed: %08lx\n", hr); + + hr = IAudioClient_GetService(ac, &IID_IChannelAudioVolume, (void**)&acv); + ok(hr == S_OK, "GetService failed: %08lx\n", hr); + if(hr != S_OK) + return; + + hr = IChannelAudioVolume_GetChannelCount(acv, NULL); + ok(hr == NULL_PTR_ERR, "GetChannelCount gave wrong error: %08lx\n", hr); + + hr = IChannelAudioVolume_GetChannelCount(acv, &chans); + ok(hr == S_OK, "GetChannelCount failed: %08lx\n", hr); + ok(chans == fmt->nChannels, "GetChannelCount gave wrong number of channels: %d\n", chans); + + hr = IChannelAudioVolume_GetChannelVolume(acv, fmt->nChannels, NULL); + ok(hr == NULL_PTR_ERR, "GetChannelVolume gave wrong error: %08lx\n", hr); + + hr = IChannelAudioVolume_GetChannelVolume(acv, fmt->nChannels, &vol); + ok(hr == E_INVALIDARG, "GetChannelVolume gave wrong error: %08lx\n", hr); + + hr = IChannelAudioVolume_GetChannelVolume(acv, 0, NULL); + ok(hr == NULL_PTR_ERR, "GetChannelVolume gave wrong error: %08lx\n", hr); + + hr = IChannelAudioVolume_GetChannelVolume(acv, 0, &vol); + ok(hr == S_OK, "GetChannelVolume failed: %08lx\n", hr); + ok(vol == 1.f, "Channel volume was not 1: %f\n", vol); + + hr = IChannelAudioVolume_SetChannelVolume(acv, fmt->nChannels, -1.f, NULL); + ok(hr == E_INVALIDARG, "SetChannelVolume gave wrong error: %08lx\n", hr); + + hr = IChannelAudioVolume_SetChannelVolume(acv, 0, -1.f, NULL); + ok(hr == E_INVALIDARG, "SetChannelVolume gave wrong error: %08lx\n", hr); + + hr = IChannelAudioVolume_SetChannelVolume(acv, 0, 2.f, NULL); + ok(hr == E_INVALIDARG, "SetChannelVolume gave wrong error: %08lx\n", hr); + + hr = IChannelAudioVolume_SetChannelVolume(acv, 0, 0.2f, NULL); + ok(hr == S_OK, "SetChannelVolume failed: %08lx\n", hr); + + hr = IChannelAudioVolume_GetChannelVolume(acv, 0, &vol); + ok(hr == S_OK, "GetChannelVolume failed: %08lx\n", hr); + ok(fabsf(vol - 0.2f) < 0.05f, "Channel volume wasn't 0.2: %f\n", vol); + + hr = IChannelAudioVolume_GetAllVolumes(acv, 0, NULL); + ok(hr == NULL_PTR_ERR, "GetAllVolumes gave wrong error: %08lx\n", hr); + + hr = IChannelAudioVolume_GetAllVolumes(acv, fmt->nChannels, NULL); + ok(hr == NULL_PTR_ERR, "GetAllVolumes gave wrong error: %08lx\n", hr); + + vols = HeapAlloc(GetProcessHeap(), 0, fmt->nChannels * sizeof(float)); + ok(vols != NULL, "HeapAlloc failed\n"); + + hr = IChannelAudioVolume_GetAllVolumes(acv, fmt->nChannels - 1, vols); + ok(hr == E_INVALIDARG, "GetAllVolumes gave wrong error: %08lx\n", hr); + + hr = IChannelAudioVolume_GetAllVolumes(acv, fmt->nChannels, vols); + ok(hr == S_OK, "GetAllVolumes failed: %08lx\n", hr); + ok(fabsf(vols[0] - 0.2f) < 0.05f, "Channel 0 volume wasn't 0.2: %f\n", vol); + for(i = 1; i < fmt->nChannels; ++i) + ok(vols[i] == 1.f, "Channel %d volume is not 1: %f\n", i, vols[i]); + + hr = IChannelAudioVolume_SetAllVolumes(acv, 0, NULL, NULL); + ok(hr == NULL_PTR_ERR, "SetAllVolumes gave wrong error: %08lx\n", hr); + + hr = IChannelAudioVolume_SetAllVolumes(acv, fmt->nChannels, NULL, NULL); + ok(hr == NULL_PTR_ERR, "SetAllVolumes gave wrong error: %08lx\n", hr); + + hr = IChannelAudioVolume_SetAllVolumes(acv, fmt->nChannels - 1, vols, NULL); + ok(hr == E_INVALIDARG, "SetAllVolumes gave wrong error: %08lx\n", hr); + + hr = IChannelAudioVolume_SetAllVolumes(acv, fmt->nChannels, vols, NULL); + ok(hr == S_OK, "SetAllVolumes failed: %08lx\n", hr); + + hr = IChannelAudioVolume_SetChannelVolume(acv, 0, 1.0f, NULL); + ok(hr == S_OK, "SetChannelVolume failed: %08lx\n", hr); + + HeapFree(GetProcessHeap(), 0, vols); + IChannelAudioVolume_Release(acv); + IAudioClient_Release(ac); + CoTaskMemFree(fmt); +} + +static void test_simplevolume(void) +{ + IAudioClient *ac; + ISimpleAudioVolume *sav; + WAVEFORMATEX *fmt; + HRESULT hr; + float vol; + BOOL mute; + + hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, + NULL, (void**)&ac); + ok(hr == S_OK, "Activation failed with %08lx\n", hr); + if(hr != S_OK) + return; + + hr = IAudioClient_GetMixFormat(ac, &fmt); + ok(hr == S_OK, "GetMixFormat failed: %08lx\n", hr); + + hr = IAudioClient_Initialize(ac, AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_NOPERSIST, 5000000, 0, fmt, NULL); + ok(hr == S_OK, "Initialize failed: %08lx\n", hr); + + hr = IAudioClient_GetService(ac, &IID_ISimpleAudioVolume, (void**)&sav); + ok(hr == S_OK, "GetService failed: %08lx\n", hr); + if(hr != S_OK) + return; + + hr = ISimpleAudioVolume_GetMasterVolume(sav, NULL); + ok(hr == NULL_PTR_ERR, "GetMasterVolume gave wrong error: %08lx\n", hr); + + hr = ISimpleAudioVolume_GetMasterVolume(sav, &vol); + ok(hr == S_OK, "GetMasterVolume failed: %08lx\n", hr); + + hr = ISimpleAudioVolume_SetMasterVolume(sav, -1.f, NULL); + ok(hr == E_INVALIDARG, "SetMasterVolume gave wrong error: %08lx\n", hr); + + hr = ISimpleAudioVolume_SetMasterVolume(sav, 2.f, NULL); + ok(hr == E_INVALIDARG, "SetMasterVolume gave wrong error: %08lx\n", hr); + + hr = ISimpleAudioVolume_SetMasterVolume(sav, 0.2f, NULL); + ok(hr == S_OK, "SetMasterVolume failed: %08lx\n", hr); + + hr = ISimpleAudioVolume_GetMasterVolume(sav, &vol); + ok(hr == S_OK, "GetMasterVolume failed: %08lx\n", hr); + ok(fabsf(vol - 0.2f) < 0.05f, "Master volume wasn't 0.2: %f\n", vol); + + hr = ISimpleAudioVolume_GetMute(sav, NULL); + ok(hr == NULL_PTR_ERR, "GetMute gave wrong error: %08lx\n", hr); + + mute = TRUE; + hr = ISimpleAudioVolume_GetMute(sav, &mute); + ok(hr == S_OK, "GetMute failed: %08lx\n", hr); + ok(mute == FALSE, "Session is already muted\n"); + + hr = ISimpleAudioVolume_SetMute(sav, TRUE, NULL); + ok(hr == S_OK, "SetMute failed: %08lx\n", hr); + + mute = FALSE; + hr = ISimpleAudioVolume_GetMute(sav, &mute); + ok(hr == S_OK, "GetMute failed: %08lx\n", hr); + ok(mute == TRUE, "Session should have been muted\n"); + + hr = ISimpleAudioVolume_GetMasterVolume(sav, &vol); + ok(hr == S_OK, "GetMasterVolume failed: %08lx\n", hr); + ok(fabsf(vol - 0.2f) < 0.05f, "Master volume wasn't 0.2: %f\n", vol); + + hr = ISimpleAudioVolume_SetMasterVolume(sav, 1.f, NULL); + ok(hr == S_OK, "SetMasterVolume failed: %08lx\n", hr); + + mute = FALSE; + hr = ISimpleAudioVolume_GetMute(sav, &mute); + ok(hr == S_OK, "GetMute failed: %08lx\n", hr); + ok(mute == TRUE, "Session should have been muted\n"); + + hr = ISimpleAudioVolume_SetMute(sav, FALSE, NULL); + ok(hr == S_OK, "SetMute failed: %08lx\n", hr); + + ISimpleAudioVolume_Release(sav); + IAudioClient_Release(ac); + CoTaskMemFree(fmt); +} + +static void test_volume_dependence(void) +{ + IAudioClient *ac, *ac2; + ISimpleAudioVolume *sav; + IChannelAudioVolume *cav; + IAudioStreamVolume *asv; + WAVEFORMATEX *fmt; + HRESULT hr; + float vol; + GUID session; + UINT32 nch; + + hr = CoCreateGuid(&session); + ok(hr == S_OK, "CoCreateGuid failed: %08lx\n", hr); + + hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, + NULL, (void**)&ac); + ok(hr == S_OK, "Activation failed with %08lx\n", hr); + if(hr != S_OK) + return; + + hr = IAudioClient_GetMixFormat(ac, &fmt); + ok(hr == S_OK, "GetMixFormat failed: %08lx\n", hr); + + hr = IAudioClient_Initialize(ac, AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_NOPERSIST, 5000000, 0, fmt, &session); + ok(hr == S_OK, "Initialize failed: %08lx\n", hr); + + hr = IAudioClient_GetService(ac, &IID_ISimpleAudioVolume, (void**)&sav); + ok(hr == S_OK, "GetService (SimpleAudioVolume) failed: %08lx\n", hr); + + hr = IAudioClient_GetService(ac, &IID_IChannelAudioVolume, (void**)&cav); + ok(hr == S_OK, "GetService (ChannelAudioVolume) failed: %08lx\n", hr); + + hr = IAudioClient_GetService(ac, &IID_IAudioStreamVolume, (void**)&asv); + ok(hr == S_OK, "GetService (AudioStreamVolume) failed: %08lx\n", hr); + if(hr != S_OK) + return; + + hr = IAudioStreamVolume_SetChannelVolume(asv, 0, 0.2f); + ok(hr == S_OK, "ASV_SetChannelVolume failed: %08lx\n", hr); + + hr = IChannelAudioVolume_SetChannelVolume(cav, 0, 0.4f, NULL); + ok(hr == S_OK, "CAV_SetChannelVolume failed: %08lx\n", hr); + + hr = ISimpleAudioVolume_SetMasterVolume(sav, 0.6f, NULL); + ok(hr == S_OK, "SAV_SetMasterVolume failed: %08lx\n", hr); + + hr = IAudioStreamVolume_GetChannelVolume(asv, 0, &vol); + ok(hr == S_OK, "ASV_GetChannelVolume failed: %08lx\n", hr); + ok(fabsf(vol - 0.2f) < 0.05f, "ASV_GetChannelVolume gave wrong volume: %f\n", vol); + + hr = IChannelAudioVolume_GetChannelVolume(cav, 0, &vol); + ok(hr == S_OK, "CAV_GetChannelVolume failed: %08lx\n", hr); + ok(fabsf(vol - 0.4f) < 0.05f, "CAV_GetChannelVolume gave wrong volume: %f\n", vol); + + hr = ISimpleAudioVolume_GetMasterVolume(sav, &vol); + ok(hr == S_OK, "SAV_GetMasterVolume failed: %08lx\n", hr); + ok(fabsf(vol - 0.6f) < 0.05f, "SAV_GetMasterVolume gave wrong volume: %f\n", vol); + + hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, + NULL, (void**)&ac2); + if(SUCCEEDED(hr)){ + IChannelAudioVolume *cav2; + IAudioStreamVolume *asv2; + + hr = IAudioClient_Initialize(ac2, AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_NOPERSIST, 5000000, 0, fmt, &session); + ok(hr == S_OK, "Initialize failed: %08lx\n", hr); + + hr = IAudioClient_GetService(ac2, &IID_IChannelAudioVolume, (void**)&cav2); + ok(hr == S_OK, "GetService failed: %08lx\n", hr); + + hr = IAudioClient_GetService(ac2, &IID_IAudioStreamVolume, (void**)&asv2); + ok(hr == S_OK, "GetService failed: %08lx\n", hr); + + hr = IChannelAudioVolume_GetChannelVolume(cav2, 0, &vol); + ok(hr == S_OK, "CAV_GetChannelVolume failed: %08lx\n", hr); + ok(fabsf(vol - 0.4f) < 0.05f, "CAV_GetChannelVolume gave wrong volume: %f\n", vol); + + hr = IAudioStreamVolume_GetChannelVolume(asv2, 0, &vol); + ok(hr == S_OK, "ASV_GetChannelVolume failed: %08lx\n", hr); + ok(vol == 1.f, "ASV_GetChannelVolume gave wrong volume: %f\n", vol); + + hr = IChannelAudioVolume_GetChannelCount(cav2, &nch); + ok(hr == S_OK, "CAV_GetChannelCount failed: %08lx\n", hr); + ok(nch == fmt->nChannels, "Got wrong channel count, expected %u: %u\n", fmt->nChannels, nch); + + hr = IAudioStreamVolume_GetChannelCount(asv2, &nch); + ok(hr == S_OK, "ASV_GetChannelCount failed: %08lx\n", hr); + ok(nch == fmt->nChannels, "Got wrong channel count, expected %u: %u\n", fmt->nChannels, nch); + + IAudioStreamVolume_Release(asv2); + IChannelAudioVolume_Release(cav2); + IAudioClient_Release(ac2); + }else + skip("Unable to open the same device twice. Skipping session volume control tests\n"); + + hr = IChannelAudioVolume_SetChannelVolume(cav, 0, 1.f, NULL); + ok(hr == S_OK, "CAV_SetChannelVolume failed: %08lx\n", hr); + + hr = ISimpleAudioVolume_SetMasterVolume(sav, 1.f, NULL); + ok(hr == S_OK, "SAV_SetMasterVolume failed: %08lx\n", hr); + + CoTaskMemFree(fmt); + ISimpleAudioVolume_Release(sav); + IChannelAudioVolume_Release(cav); + IAudioStreamVolume_Release(asv); + IAudioClient_Release(ac); +} + +static void test_marshal(void) +{ + IStream *pStream; + IAudioClient *ac, *acDest; + IAudioCaptureClient *cc, *ccDest; + WAVEFORMATEX *pwfx; + HRESULT hr; + + hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, + NULL, (void**)&ac); + ok(hr == S_OK, "Activation failed with %08lx\n", hr); + if(hr != S_OK) + return; + + hr = IAudioClient_GetMixFormat(ac, &pwfx); + ok(hr == S_OK, "GetMixFormat failed: %08lx\n", hr); + + hr = IAudioClient_Initialize(ac, AUDCLNT_SHAREMODE_SHARED, 0, 5000000, + 0, pwfx, NULL); + ok(hr == S_OK, "Initialize failed: %08lx\n", hr); + + CoTaskMemFree(pwfx); + + hr = IAudioClient_GetService(ac, &IID_IAudioCaptureClient, (void**)&cc); + ok(hr == S_OK, "GetService failed: %08lx\n", hr); + if(hr != S_OK) { + IAudioClient_Release(ac); + return; + } + + hr = CreateStreamOnHGlobal(NULL, TRUE, &pStream); + ok(hr == S_OK, "CreateStreamOnHGlobal failed 0x%08lx\n", hr); + + /* marshal IAudioClient */ + + hr = CoMarshalInterface(pStream, &IID_IAudioClient, (IUnknown*)ac, MSHCTX_INPROC, NULL, MSHLFLAGS_NORMAL); + ok(hr == S_OK, "CoMarshalInterface IAudioClient failed 0x%08lx\n", hr); + + IStream_Seek(pStream, ullZero, STREAM_SEEK_SET, NULL); + hr = CoUnmarshalInterface(pStream, &IID_IAudioClient, (void **)&acDest); + ok(hr == S_OK, "CoUnmarshalInterface IAudioClient failed 0x%08lx\n", hr); + if (hr == S_OK) + IAudioClient_Release(acDest); + + IStream_Seek(pStream, ullZero, STREAM_SEEK_SET, NULL); + /* marshal IAudioCaptureClient */ + + hr = CoMarshalInterface(pStream, &IID_IAudioCaptureClient, (IUnknown*)cc, MSHCTX_INPROC, NULL, MSHLFLAGS_NORMAL); + ok(hr == S_OK, "CoMarshalInterface IAudioCaptureClient failed 0x%08lx\n", hr); + + IStream_Seek(pStream, ullZero, STREAM_SEEK_SET, NULL); + hr = CoUnmarshalInterface(pStream, &IID_IAudioCaptureClient, (void **)&ccDest); + ok(hr == S_OK, "CoUnmarshalInterface IAudioCaptureClient failed 0x%08lx\n", hr); + if (hr == S_OK) + IAudioCaptureClient_Release(ccDest); + + IStream_Release(pStream); + + IAudioClient_Release(ac); + IAudioCaptureClient_Release(cc); + +} + +START_TEST(capture) +{ + HRESULT hr; + IMMDeviceEnumerator *mme = NULL; + + CoInitializeEx(NULL, COINIT_MULTITHREADED); + hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, (void**)&mme); + if (FAILED(hr)) + { + skip("mmdevapi not available: 0x%08lx\n", hr); + goto cleanup; + } + + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(mme, eCapture, eMultimedia, &dev); + ok(hr == S_OK || hr == E_NOTFOUND, "GetDefaultAudioEndpoint failed: 0x%08lx\n", hr); + if (hr != S_OK || !dev) + { + if (hr == E_NOTFOUND) + skip("No sound card available\n"); + else + skip("GetDefaultAudioEndpoint returns 0x%08lx\n", hr); + goto cleanup; + } + + test_audioclient(); + test_streamvolume(); + test_channelvolume(); + test_simplevolume(); + test_volume_dependence(); + test_marshal(); + + IMMDevice_Release(dev); + +cleanup: + if (mme) + IMMDeviceEnumerator_Release(mme); + CoUninitialize(); +} diff --git a/pkgs/osu-wine/audio-revert/mmdevapi/tests/dependency.c b/pkgs/osu-wine/audio-revert/mmdevapi/tests/dependency.c new file mode 100644 index 0000000..ef99d7b --- /dev/null +++ b/pkgs/osu-wine/audio-revert/mmdevapi/tests/dependency.c @@ -0,0 +1,98 @@ +/* + * Copyright 2009 Maarten Lankhorst + * + * 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 "wine/test.h" + +#define COBJMACROS + +#ifdef STANDALONE +#include "initguid.h" +#endif + +#include "unknwn.h" +#include "uuids.h" +#include "mmdeviceapi.h" +#include "dshow.h" +#include "dsound.h" + +START_TEST(dependency) +{ + HRESULT hr; + IMMDeviceEnumerator *mme = NULL; + IMMDevice *dev = NULL; + IDirectSound8 *ds8 = NULL; + IBaseFilter *bf = NULL; + + CoInitializeEx(NULL, COINIT_MULTITHREADED); + hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, (void**)&mme); + if (FAILED(hr)) + { + skip("mmdevapi not available: 0x%08lx\n", hr); + goto cleanup; + } + + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(mme, eRender, eMultimedia, &dev); + ok(hr == S_OK || hr == E_NOTFOUND, "GetDefaultAudioEndpoint failed: 0x%08lx\n", hr); + if (hr != S_OK) + { + if (hr == E_NOTFOUND) + skip("No sound card available\n"); + else + skip("GetDefaultAudioEndpoint returns 0x%08lx\n", hr); + goto cleanup; + } + + ok(!GetModuleHandleA("dsound.dll"), "dsound.dll was already loaded!\n"); + + hr = IMMDevice_Activate(dev, &IID_IDirectSound8, CLSCTX_INPROC_SERVER, NULL, (void**)&ds8); + ok(hr == S_OK, "Activating ds8 interface failed: 0x%08lx\n", hr); + if (hr == S_OK) + { + ok(GetModuleHandleA("dsound.dll") != NULL, "dsound.dll not loaded!\n"); + ok(ds8 != NULL, "ds8 pointer is null\n"); + } + if (ds8) + IDirectSound8_Release(ds8); + + ok(!GetModuleHandleA("quartz.dll"), "quartz.dll was already loaded!\n"); + hr = IMMDevice_Activate(dev, &IID_IBaseFilter, CLSCTX_INPROC_SERVER, NULL, (void**)&bf); + ok(hr == S_OK, "Activating bf failed: 0x%08lx\n", hr); + if (hr == S_OK) + { + ok(GetModuleHandleA("quartz.dll") != NULL, "quartz.dll not loaded!\n"); + ok(bf != NULL, "bf pointer is null\n"); + if (bf) + { + CLSID clsid; + hr = IBaseFilter_GetClassID(bf, &clsid); + ok(hr == S_OK, "GetClassId failed with 0x%08lx\n", hr); + if (hr == S_OK) + ok(IsEqualCLSID(&clsid, &CLSID_DSoundRender), "Wrong class id %s\n", wine_dbgstr_guid(&clsid)); + } + } + +cleanup: + if (bf) + IBaseFilter_Release(bf); + if (dev) + IMMDevice_Release(dev); + if (mme) + IMMDeviceEnumerator_Release(mme); + + CoUninitialize(); +} diff --git a/pkgs/osu-wine/audio-revert/mmdevapi/tests/mmdevenum.c b/pkgs/osu-wine/audio-revert/mmdevapi/tests/mmdevenum.c new file mode 100644 index 0000000..a5b692a --- /dev/null +++ b/pkgs/osu-wine/audio-revert/mmdevapi/tests/mmdevenum.c @@ -0,0 +1,484 @@ +/* + * Copyright 2009 Maarten Lankhorst + * + * 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 "wine/test.h" + +#define COBJMACROS + +#include "initguid.h" +#include "endpointvolume.h" +#include "mmdeviceapi.h" +#include "audioclient.h" +#include "spatialaudioclient.h" +#include "audiopolicy.h" +#include "dshow.h" +#include "dsound.h" +#include "devpkey.h" + +DEFINE_GUID(GUID_NULL,0,0,0,0,0,0,0,0,0,0,0); + +static UINT g_num_mmdevs; +static WCHAR g_device_path[MAX_PATH]; + +/* Some of the QueryInterface tests are really just to check if I got the IIDs right :) */ + +/* IMMDeviceCollection appears to have no QueryInterface method and instead forwards to mme */ +static void test_collection(IMMDeviceEnumerator *mme, IMMDeviceCollection *col) +{ + IMMDeviceCollection *col2; + IMMDeviceEnumerator *mme2; + IUnknown *unk; + HRESULT hr; + ULONG ref; + UINT numdev; + IMMDevice *dev; + + /* collection doesn't keep a ref on parent */ + IMMDeviceEnumerator_AddRef(mme); + ref = IMMDeviceEnumerator_Release(mme); + ok(ref == 2, "Reference count on parent is %lu\n", ref); + + ref = IMMDeviceCollection_AddRef(col); + IMMDeviceCollection_Release(col); + ok(ref == 2, "Invalid reference count %lu on collection\n", ref); + + hr = IMMDeviceCollection_QueryInterface(col, &IID_IUnknown, NULL); + ok(hr == E_POINTER, "Null ppv returns %08lx\n", hr); + + hr = IMMDeviceCollection_QueryInterface(col, &IID_IUnknown, (void**)&unk); + ok(hr == S_OK, "Cannot query for IID_IUnknown: 0x%08lx\n", hr); + if (hr == S_OK) + { + ok((IUnknown*)col == unk, "Pointers are not identical %p/%p/%p\n", col, unk, mme); + IUnknown_Release(unk); + } + + hr = IMMDeviceCollection_QueryInterface(col, &IID_IMMDeviceCollection, (void**)&col2); + ok(hr == S_OK, "Cannot query for IID_IMMDeviceCollection: 0x%08lx\n", hr); + if (hr == S_OK) + IMMDeviceCollection_Release(col2); + + hr = IMMDeviceCollection_QueryInterface(col, &IID_IMMDeviceEnumerator, (void**)&mme2); + ok(hr == E_NOINTERFACE, "Query for IID_IMMDeviceEnumerator returned: 0x%08lx\n", hr); + if (hr == S_OK) + IMMDeviceEnumerator_Release(mme2); + + hr = IMMDeviceCollection_GetCount(col, NULL); + ok(hr == E_POINTER, "GetCount returned 0x%08lx\n", hr); + + hr = IMMDeviceCollection_GetCount(col, &numdev); + ok(hr == S_OK, "GetCount returned 0x%08lx\n", hr); + + dev = (void*)(LONG_PTR)0x12345678; + hr = IMMDeviceCollection_Item(col, numdev, &dev); + ok(hr == E_INVALIDARG, "Asking for too high device returned 0x%08lx\n", hr); + ok(dev == NULL, "Returned non-null device\n"); + + g_num_mmdevs = numdev; + + if (numdev) + { + hr = IMMDeviceCollection_Item(col, 0, NULL); + ok(hr == E_POINTER, "Query with null pointer returned 0x%08lx\n", hr); + + hr = IMMDeviceCollection_Item(col, 0, &dev); + ok(hr == S_OK, "Valid Item returned 0x%08lx\n", hr); + ok(dev != NULL, "Device is null!\n"); + if (dev != NULL) + { + char temp[128]; + WCHAR *id = NULL; + if (IMMDevice_GetId(dev, &id) == S_OK) + { + IMMDevice *dev2; + + lstrcpyW(g_device_path, id); + temp[sizeof(temp)-1] = 0; + WideCharToMultiByte(CP_ACP, 0, id, -1, temp, sizeof(temp)-1, NULL, NULL); + trace("Device found: %s\n", temp); + + hr = IMMDeviceEnumerator_GetDevice(mme, id, &dev2); + ok(hr == S_OK, "GetDevice failed: %08lx\n", hr); + + IMMDevice_Release(dev2); + + CoTaskMemFree(id); + } + } + if (dev) + IMMDevice_Release(dev); + } + IMMDeviceCollection_Release(col); +} + +static struct { + LONG ref; + HANDLE evt; + CRITICAL_SECTION lock; + IActivateAudioInterfaceAsyncOperation *op; + DWORD main_tid; + char msg_pfx[128]; + IUnknown *result_iface; + HRESULT result_hr; +} async_activate_test; + +static HRESULT WINAPI async_activate_QueryInterface( + IActivateAudioInterfaceCompletionHandler *iface, + REFIID riid, + void **ppvObject) +{ + if(IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IAgileObject) || + IsEqualIID(riid, &IID_IActivateAudioInterfaceCompletionHandler)){ + *ppvObject = iface; + IUnknown_AddRef((IUnknown*)*ppvObject); + return S_OK; + } + + *ppvObject = NULL; + return E_NOINTERFACE; +} + +static ULONG WINAPI async_activate_AddRef( + IActivateAudioInterfaceCompletionHandler *iface) +{ + return InterlockedIncrement(&async_activate_test.ref); +} + +static ULONG WINAPI async_activate_Release( + IActivateAudioInterfaceCompletionHandler *iface) +{ + ULONG ref = InterlockedDecrement(&async_activate_test.ref); + if(ref == 1) + SetEvent(async_activate_test.evt); + return ref; +} + +static HRESULT WINAPI async_activate_ActivateCompleted( + IActivateAudioInterfaceCompletionHandler *iface, + IActivateAudioInterfaceAsyncOperation *op) +{ + HRESULT hr; + + EnterCriticalSection(&async_activate_test.lock); + ok(op == async_activate_test.op, + "%s: Got different completion operation\n", + async_activate_test.msg_pfx); + LeaveCriticalSection(&async_activate_test.lock); + + ok(GetCurrentThreadId() != async_activate_test.main_tid, + "%s: Expected callback on worker thread\n", + async_activate_test.msg_pfx); + + hr = IActivateAudioInterfaceAsyncOperation_GetActivateResult(op, + &async_activate_test.result_hr, &async_activate_test.result_iface); + ok(hr == S_OK, + "%s: GetActivateResult failed: %08lx\n", + async_activate_test.msg_pfx, hr); + + return S_OK; +} + +static IActivateAudioInterfaceCompletionHandlerVtbl async_activate_vtbl = { + async_activate_QueryInterface, + async_activate_AddRef, + async_activate_Release, + async_activate_ActivateCompleted, +}; + +static IActivateAudioInterfaceCompletionHandler async_activate_done = { + &async_activate_vtbl +}; + +static void test_ActivateAudioInterfaceAsync(void) +{ + HRESULT (* WINAPI pActivateAudioInterfaceAsync)(const WCHAR *path, + REFIID riid, PROPVARIANT *params, + IActivateAudioInterfaceCompletionHandler *done_handler, + IActivateAudioInterfaceAsyncOperation **op); + HANDLE h_mmdev; + HRESULT hr; + LPOLESTR path; + DWORD dr; + IAudioClient3 *ac3; + + h_mmdev = LoadLibraryA("mmdevapi.dll"); + + pActivateAudioInterfaceAsync = (void*)GetProcAddress(h_mmdev, "ActivateAudioInterfaceAsync"); + if (!pActivateAudioInterfaceAsync) + { + win_skip("ActivateAudioInterfaceAsync is not supported on Win <= 7\n"); + return; + } + + /* some applications look this up by ordinal */ + pActivateAudioInterfaceAsync = (void*)GetProcAddress(h_mmdev, (char *)17); + ok(pActivateAudioInterfaceAsync != NULL, "mmdevapi.ActivateAudioInterfaceAsync missing!\n"); + + async_activate_test.ref = 1; + async_activate_test.evt = CreateEventW(NULL, FALSE, FALSE, NULL); + InitializeCriticalSection(&async_activate_test.lock); + async_activate_test.op = NULL; + async_activate_test.main_tid = GetCurrentThreadId(); + async_activate_test.result_iface = NULL; + async_activate_test.result_hr = 0; + + + /* try invalid device path */ + strcpy(async_activate_test.msg_pfx, "invalid_path"); + + EnterCriticalSection(&async_activate_test.lock); + hr = pActivateAudioInterfaceAsync(L"winetest_bogus", &IID_IAudioClient3, NULL, &async_activate_done, &async_activate_test.op); + ok(hr == S_OK, "ActivateAudioInterfaceAsync failed: %08lx\n", hr); + LeaveCriticalSection(&async_activate_test.lock); + + IActivateAudioInterfaceAsyncOperation_Release(async_activate_test.op); + + dr = WaitForSingleObject(async_activate_test.evt, 1000); /* wait for all refs other than our own to be released */ + ok(dr == WAIT_OBJECT_0, "Timed out waiting for async activate to complete\n"); + ok(async_activate_test.ref == 1, "ActivateAudioInterfaceAsync leaked a handler ref: %lu\n", async_activate_test.ref); + ok(async_activate_test.result_hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), + "mmdevice activation gave wrong result: %08lx\n", async_activate_test.result_hr); + ok(async_activate_test.result_iface == NULL, "Got non-NULL iface pointer: %p\n", async_activate_test.result_iface); + + + /* device id from IMMDevice does not work */ + if(g_num_mmdevs > 0){ + strcpy(async_activate_test.msg_pfx, "mmdevice_id"); + + EnterCriticalSection(&async_activate_test.lock); + hr = pActivateAudioInterfaceAsync(g_device_path, &IID_IAudioClient3, NULL, &async_activate_done, &async_activate_test.op); + ok(hr == S_OK, "ActivateAudioInterfaceAsync failed: %08lx\n", hr); + LeaveCriticalSection(&async_activate_test.lock); + + IActivateAudioInterfaceAsyncOperation_Release(async_activate_test.op); + + dr = WaitForSingleObject(async_activate_test.evt, 1000); + ok(dr == WAIT_OBJECT_0, "Timed out waiting for async activate to complete\n"); + ok(async_activate_test.ref == 1, "ActivateAudioInterfaceAsync leaked a handler ref: %lu\n", async_activate_test.ref); + ok(async_activate_test.result_hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), + "mmdevice activation gave wrong result: %08lx\n", async_activate_test.result_hr); + ok(async_activate_test.result_iface == NULL, "Got non-NULL iface pointer: %p\n", async_activate_test.result_iface); + } + + + /* try DEVINTERFACE_AUDIO_RENDER */ + strcpy(async_activate_test.msg_pfx, "audio_render"); + StringFromIID(&DEVINTERFACE_AUDIO_RENDER, &path); + + EnterCriticalSection(&async_activate_test.lock); + hr = pActivateAudioInterfaceAsync(path, &IID_IAudioClient3, NULL, &async_activate_done, &async_activate_test.op); + ok(hr == S_OK, "ActivateAudioInterfaceAsync failed: %08lx\n", hr); + LeaveCriticalSection(&async_activate_test.lock); + + IActivateAudioInterfaceAsyncOperation_Release(async_activate_test.op); + + dr = WaitForSingleObject(async_activate_test.evt, 1000); + ok(dr == WAIT_OBJECT_0, "Timed out waiting for async activate to complete\n"); + ok(async_activate_test.ref == 1, "ActivateAudioInterfaceAsync leaked a handler ref\n"); + ok(async_activate_test.result_hr == S_OK || + (g_num_mmdevs == 0 && async_activate_test.result_hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) || /* no devices */ + broken(async_activate_test.result_hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)), /* win8 doesn't support DEVINTERFACE_AUDIO_RENDER */ + "mmdevice activation gave wrong result: %08lx\n", async_activate_test.result_hr); + + if(async_activate_test.result_hr == S_OK){ + ok(async_activate_test.result_iface != NULL, "Got NULL iface pointer on success?\n"); + + /* returned iface should be the IID we requested */ + hr = IUnknown_QueryInterface(async_activate_test.result_iface, &IID_IAudioClient3, (void**)&ac3); + ok(hr == S_OK, "Failed to query IAudioClient3: %08lx\n", hr); + ok(async_activate_test.result_iface == (IUnknown*)ac3, + "Activated interface other than IAudioClient3!\n"); + IAudioClient3_Release(ac3); + + IUnknown_Release(async_activate_test.result_iface); + } + + CoTaskMemFree(path); + + CloseHandle(async_activate_test.evt); + DeleteCriticalSection(&async_activate_test.lock); +} + +static HRESULT WINAPI notif_QueryInterface(IMMNotificationClient *iface, + const GUID *riid, void **obj) +{ + ok(0, "Unexpected QueryInterface call\n"); + return E_NOTIMPL; +} + +static ULONG WINAPI notif_AddRef(IMMNotificationClient *iface) +{ + ok(0, "Unexpected AddRef call\n"); + return 2; +} + +static ULONG WINAPI notif_Release(IMMNotificationClient *iface) +{ + ok(0, "Unexpected Release call\n"); + return 1; +} + +static HRESULT WINAPI notif_OnDeviceStateChanged(IMMNotificationClient *iface, + const WCHAR *device_id, DWORD new_state) +{ + ok(0, "Unexpected OnDeviceStateChanged call\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI notif_OnDeviceAdded(IMMNotificationClient *iface, + const WCHAR *device_id) +{ + ok(0, "Unexpected OnDeviceAdded call\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI notif_OnDeviceRemoved(IMMNotificationClient *iface, + const WCHAR *device_id) +{ + ok(0, "Unexpected OnDeviceRemoved call\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI notif_OnDefaultDeviceChanged(IMMNotificationClient *iface, + EDataFlow flow, ERole role, const WCHAR *device_id) +{ + ok(0, "Unexpected OnDefaultDeviceChanged call\n"); + return E_NOTIMPL; +} + +static HRESULT WINAPI notif_OnPropertyValueChanged(IMMNotificationClient *iface, + const WCHAR *device_id, const PROPERTYKEY key) +{ + ok(0, "Unexpected OnPropertyValueChanged call\n"); + return E_NOTIMPL; +} + +static IMMNotificationClientVtbl notif_vtbl = { + notif_QueryInterface, + notif_AddRef, + notif_Release, + notif_OnDeviceStateChanged, + notif_OnDeviceAdded, + notif_OnDeviceRemoved, + notif_OnDefaultDeviceChanged, + notif_OnPropertyValueChanged +}; + +static IMMNotificationClient notif = { ¬if_vtbl }; + +/* Only do parameter tests here, the actual MMDevice testing should be a separate test */ +START_TEST(mmdevenum) +{ + HRESULT hr; + IUnknown *unk = NULL; + IMMDeviceEnumerator *mme, *mme2; + ULONG ref; + IMMDeviceCollection *col; + IMMDevice *dev; + + CoInitializeEx(NULL, COINIT_MULTITHREADED); + hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, (void**)&mme); + if (FAILED(hr)) + { + skip("mmdevapi not available: 0x%08lx\n", hr); + return; + } + + /* Odd behavior.. bug? */ + ref = IMMDeviceEnumerator_AddRef(mme); + ok(ref == 3, "Invalid reference count after incrementing: %lu\n", ref); + IMMDeviceEnumerator_Release(mme); + + hr = IMMDeviceEnumerator_QueryInterface(mme, &IID_IUnknown, (void**)&unk); + ok(hr == S_OK, "returned 0x%08lx\n", hr); + if (hr != S_OK) return; + + ok( (LONG_PTR)mme == (LONG_PTR)unk, "Pointers are unequal %p/%p\n", unk, mme); + IUnknown_Release(unk); + + /* Proving that it is static.. */ + hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, (void**)&mme2); + ok(hr == S_OK, "CoCreateInstance failed: 0x%08lx\n", hr); + IMMDeviceEnumerator_Release(mme2); + ok(mme == mme2, "Pointers are not equal!\n"); + + hr = IMMDeviceEnumerator_QueryInterface(mme, &IID_IUnknown, NULL); + ok(hr == E_POINTER, "Null pointer on QueryInterface returned %08lx\n", hr); + + hr = IMMDeviceEnumerator_QueryInterface(mme, &GUID_NULL, (void**)&unk); + ok(!unk, "Unk not reset to null after invalid QI\n"); + ok(hr == E_NOINTERFACE, "Invalid hr %08lx returned on IID_NULL\n", hr); + + hr = IMMDeviceEnumerator_GetDevice(mme, L"notadevice", NULL); + ok(hr == E_POINTER, "GetDevice gave wrong error: %08lx\n", hr); + + hr = IMMDeviceEnumerator_GetDevice(mme, NULL, &dev); + ok(hr == E_POINTER, "GetDevice gave wrong error: %08lx\n", hr); + + hr = IMMDeviceEnumerator_GetDevice(mme, L"notadevice", &dev); + ok(hr == E_INVALIDARG, "GetDevice gave wrong error: %08lx\n", hr); + + col = (void*)(LONG_PTR)0x12345678; + hr = IMMDeviceEnumerator_EnumAudioEndpoints(mme, 0xffff, DEVICE_STATEMASK_ALL, &col); + ok(hr == E_INVALIDARG, "Setting invalid data flow returned 0x%08lx\n", hr); + ok(col == NULL, "Collection pointer non-null on failure\n"); + + hr = IMMDeviceEnumerator_EnumAudioEndpoints(mme, eAll, DEVICE_STATEMASK_ALL+1, &col); + ok(hr == E_INVALIDARG, "Setting invalid mask returned 0x%08lx\n", hr); + + hr = IMMDeviceEnumerator_EnumAudioEndpoints(mme, eAll, DEVICE_STATEMASK_ALL, NULL); + ok(hr == E_POINTER, "Invalid pointer returned: 0x%08lx\n", hr); + + hr = IMMDeviceEnumerator_EnumAudioEndpoints(mme, eAll, DEVICE_STATEMASK_ALL, &col); + ok(hr == S_OK, "Valid EnumAudioEndpoints returned 0x%08lx\n", hr); + if (hr == S_OK) + { + ok(!!col, "Returned null pointer\n"); + if (col) + test_collection(mme, col); + } + + hr = IMMDeviceEnumerator_RegisterEndpointNotificationCallback(mme, NULL); + ok(hr == E_POINTER, "RegisterEndpointNotificationCallback failed: %08lx\n", hr); + + hr = IMMDeviceEnumerator_RegisterEndpointNotificationCallback(mme, ¬if); + ok(hr == S_OK, "RegisterEndpointNotificationCallback failed: %08lx\n", hr); + + hr = IMMDeviceEnumerator_RegisterEndpointNotificationCallback(mme, ¬if); + ok(hr == S_OK, "RegisterEndpointNotificationCallback failed: %08lx\n", hr); + + hr = IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(mme, NULL); + ok(hr == E_POINTER, "UnregisterEndpointNotificationCallback failed: %08lx\n", hr); + + hr = IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(mme, (IMMNotificationClient*)0xdeadbeef); + ok(hr == E_NOTFOUND, "UnregisterEndpointNotificationCallback failed: %08lx\n", hr); + + hr = IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(mme, ¬if); + ok(hr == S_OK, "UnregisterEndpointNotificationCallback failed: %08lx\n", hr); + + hr = IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(mme, ¬if); + ok(hr == S_OK, "UnregisterEndpointNotificationCallback failed: %08lx\n", hr); + + hr = IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(mme, ¬if); + ok(hr == E_NOTFOUND, "UnregisterEndpointNotificationCallback failed: %08lx\n", hr); + + IMMDeviceEnumerator_Release(mme); + + test_ActivateAudioInterfaceAsync(); +} diff --git a/pkgs/osu-wine/audio-revert/mmdevapi/tests/propstore.c b/pkgs/osu-wine/audio-revert/mmdevapi/tests/propstore.c new file mode 100644 index 0000000..89eaada --- /dev/null +++ b/pkgs/osu-wine/audio-revert/mmdevapi/tests/propstore.c @@ -0,0 +1,254 @@ +/* + * Copyright 2010 Maarten Lankhorst for CodeWeavers + * + * 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 + */ + +#define NONAMELESSUNION +#include "wine/test.h" + +#define COBJMACROS + +#ifdef STANDALONE +#include "initguid.h" +#endif + +#include "unknwn.h" +#include "uuids.h" +#include "mmdeviceapi.h" +#include "devpkey.h" + +static BOOL (WINAPI *pIsWow64Process)(HANDLE, BOOL *); + +static const WCHAR software_renderW[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\MMDevices\\Audio\\Render"; + + +static void test_propertystore(IPropertyStore *store) +{ + HRESULT hr; + PROPVARIANT pv; + char temp[128]; + temp[sizeof(temp)-1] = 0; + + pv.vt = VT_EMPTY; + hr = IPropertyStore_GetValue(store, &PKEY_AudioEndpoint_GUID, &pv); + ok(hr == S_OK, "Failed with %08lx\n", hr); + ok(pv.vt == VT_LPWSTR, "Value should be %i, is %i\n", VT_LPWSTR, pv.vt); + if (hr == S_OK && pv.vt == VT_LPWSTR) + { + WideCharToMultiByte(CP_ACP, 0, pv.pwszVal, -1, temp, sizeof(temp)-1, NULL, NULL); + trace("guid: %s\n", temp); + PropVariantClear(&pv); + } + + pv.vt = VT_EMPTY; + hr = IPropertyStore_GetValue(store, (const PROPERTYKEY*)&DEVPKEY_DeviceInterface_FriendlyName, &pv); + ok(hr == S_OK, "Failed with %08lx\n", hr); + ok(pv.vt == VT_LPWSTR && pv.pwszVal, "FriendlyName value had wrong type: 0x%x or was NULL\n", pv.vt); + PropVariantClear(&pv); + + pv.vt = VT_EMPTY; + hr = IPropertyStore_GetValue(store, (const PROPERTYKEY*)&DEVPKEY_DeviceInterface_Enabled, &pv); + ok(hr == S_OK, "Failed with %08lx\n", hr); + ok(pv.vt == VT_EMPTY, "Key should not be found\n"); + PropVariantClear(&pv); + + pv.vt = VT_EMPTY; + hr = IPropertyStore_GetValue(store, (const PROPERTYKEY*)&DEVPKEY_DeviceInterface_ClassGuid, &pv); + ok(hr == S_OK, "Failed with %08lx\n", hr); + ok(pv.vt == VT_EMPTY, "Key should not be found\n"); + PropVariantClear(&pv); +} + +static void test_deviceinterface(IPropertyStore *store) +{ + HRESULT hr; + PROPVARIANT pv; + + static const PROPERTYKEY deviceinterface_key = { + {0x233164c8, 0x1b2c, 0x4c7d, {0xbc, 0x68, 0xb6, 0x71, 0x68, 0x7a, 0x25, 0x67}}, 1 + }; + + pv.vt = VT_EMPTY; + hr = IPropertyStore_GetValue(store, &deviceinterface_key, &pv); + ok(hr == S_OK, "GetValue failed: %08lx\n", hr); + ok(pv.vt == VT_LPWSTR, "Got wrong variant type: 0x%x\n", pv.vt); + trace("device interface: %s\n", wine_dbgstr_w(pv.pwszVal)); + PropVariantClear(&pv); +} + +static void test_getat(IPropertyStore *store) +{ + HRESULT hr; + DWORD propcount; + DWORD prop; + PROPERTYKEY pkey; + BOOL found_name = FALSE; + BOOL found_desc = FALSE; + char temp[128]; + temp[sizeof(temp)-1] = 0; + + hr = IPropertyStore_GetCount(store, &propcount); + + ok(hr == S_OK, "Failed with %08lx\n", hr); + ok(propcount > 0, "Propcount %ld should be greater than zero\n", propcount); + + for (prop = 0; prop < propcount; prop++) { + hr = IPropertyStore_GetAt(store, prop, &pkey); + ok(hr == S_OK, "Failed with %08lx\n", hr); + if (IsEqualPropertyKey(pkey, DEVPKEY_Device_FriendlyName)) + found_name = TRUE; + if (IsEqualPropertyKey(pkey, DEVPKEY_Device_DeviceDesc)) + found_desc = TRUE; + } + ok(found_name || + broken(!found_name) /* vista */, "DEVPKEY_Device_FriendlyName not found\n"); + ok(found_desc, "DEVPKEY_Device_DeviceDesc not found\n"); +} + +static void test_setvalue_on_wow64(IPropertyStore *store) +{ + PROPVARIANT pv; + HRESULT hr; + LONG ret; + WCHAR *guidW; + HKEY root, props, devkey; + DWORD type, regval, size; + + static const PROPERTYKEY PKEY_Bogus = { + {0x1da5d803, 0xd492, 0x4edd, {0x8c, 0x23, 0xe0, 0xc0, 0xff, 0xee, 0x7f, 0x00}}, 0x7f + }; + static const WCHAR bogusW[] = L"{1DA5D803-D492-4EDD-8C23-E0C0FFEE7F00},127"; + + PropVariantInit(&pv); + + pv.vt = VT_EMPTY; + hr = IPropertyStore_GetValue(store, &PKEY_AudioEndpoint_GUID, &pv); + ok(hr == S_OK, "Failed to get Endpoint GUID: %08lx\n", hr); + + guidW = pv.pwszVal; + + pv.vt = VT_UI4; + pv.ulVal = 0xAB; + + hr = IPropertyStore_SetValue(store, &PKEY_Bogus, &pv); + ok(hr == S_OK || hr == E_ACCESSDENIED, "SetValue failed: %08lx\n", hr); + if (hr != S_OK) + { + win_skip("Missing permission to write to registry\n"); + return; + } + + pv.ulVal = 0x00; + + hr = IPropertyStore_GetValue(store, &PKEY_Bogus, &pv); + ok(hr == S_OK, "GetValue failed: %08lx\n", hr); + ok(pv.ulVal == 0xAB, "Got wrong value: 0x%lx\n", pv.ulVal); + + /* should find the key in 64-bit view */ + ret = RegOpenKeyExW(HKEY_LOCAL_MACHINE, software_renderW, 0, KEY_READ|KEY_WOW64_64KEY, &root); + ok(ret == ERROR_SUCCESS, "Couldn't open mmdevices Render key: %lu\n", ret); + + ret = RegOpenKeyExW(root, guidW, 0, KEY_READ|KEY_WOW64_64KEY, &devkey); + ok(ret == ERROR_SUCCESS, "Couldn't open mmdevice guid key: %lu\n", ret); + + ret = RegOpenKeyExW(devkey, L"Properties", 0, KEY_READ|KEY_WOW64_64KEY, &props); + ok(ret == ERROR_SUCCESS, "Couldn't open mmdevice property key: %lu\n", ret); + + /* Note: the registry key exists even without calling IPropStore::Commit */ + size = sizeof(regval); + ret = RegQueryValueExW(props, bogusW, NULL, &type, (LPBYTE)®val, &size); + ok(ret == ERROR_SUCCESS, "Couldn't get bogus propertykey value: %lu\n", ret); + ok(type == REG_DWORD, "Got wrong value type: %lu\n", type); + ok(regval == 0xAB, "Got wrong value: 0x%lx\n", regval); + + RegCloseKey(props); + RegCloseKey(devkey); + RegCloseKey(root); + + CoTaskMemFree(guidW); + + /* should NOT find the key in 32-bit view */ + ret = RegOpenKeyExW(HKEY_LOCAL_MACHINE, software_renderW, 0, KEY_READ, &root); + ok(ret == ERROR_FILE_NOT_FOUND, "Wrong error when opening mmdevices Render key: %lu\n", ret); +} + +START_TEST(propstore) +{ + HRESULT hr; + IMMDeviceEnumerator *mme = NULL; + IMMDevice *dev = NULL; + IPropertyStore *store; + BOOL is_wow64 = FALSE; + HMODULE hk32 = GetModuleHandleA("kernel32.dll"); + + pIsWow64Process = (void *)GetProcAddress(hk32, "IsWow64Process"); + + if (pIsWow64Process) + pIsWow64Process(GetCurrentProcess(), &is_wow64); + + CoInitializeEx(NULL, COINIT_MULTITHREADED); + hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, (void**)&mme); + if (FAILED(hr)) + { + skip("mmdevapi not available: 0x%08lx\n", hr); + goto cleanup; + } + + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(mme, eRender, eMultimedia, &dev); + ok(hr == S_OK || hr == E_NOTFOUND, "GetDefaultAudioEndpoint failed: 0x%08lx\n", hr); + if (hr != S_OK) + { + if (hr == E_NOTFOUND) + skip("No sound card available\n"); + else + skip("GetDefaultAudioEndpoint returns 0x%08lx\n", hr); + goto cleanup; + } + store = NULL; + hr = IMMDevice_OpenPropertyStore(dev, 3, &store); + ok(hr == E_INVALIDARG, "Wrong hr returned: %08lx\n", hr); + if (hr != S_OK) + /* It seems on windows returning with E_INVALIDARG doesn't + * set store to NULL, so just don't set store to non-null + * before calling this function + */ + ok(!store, "Store set to non-NULL on failure: %p/%08lx\n", store, hr); + else if (store) + IPropertyStore_Release(store); + hr = IMMDevice_OpenPropertyStore(dev, STGM_READ, NULL); + ok(hr == E_POINTER, "Wrong hr returned: %08lx\n", hr); + + store = NULL; + hr = IMMDevice_OpenPropertyStore(dev, STGM_READWRITE, &store); + if(hr == E_ACCESSDENIED) + hr = IMMDevice_OpenPropertyStore(dev, STGM_READ, &store); + ok(hr == S_OK, "Opening valid store returned %08lx\n", hr); + if (store) + { + test_propertystore(store); + test_deviceinterface(store); + test_getat(store); + if (is_wow64) + test_setvalue_on_wow64(store); + IPropertyStore_Release(store); + } + IMMDevice_Release(dev); +cleanup: + if (mme) + IMMDeviceEnumerator_Release(mme); + CoUninitialize(); +} diff --git a/pkgs/osu-wine/audio-revert/mmdevapi/tests/render.c b/pkgs/osu-wine/audio-revert/mmdevapi/tests/render.c new file mode 100644 index 0000000..8e000f0 --- /dev/null +++ b/pkgs/osu-wine/audio-revert/mmdevapi/tests/render.c @@ -0,0 +1,2425 @@ +/* + * Copyright 2010 Maarten Lankhorst for CodeWeavers + * 2011-2012 Jörg Höhle + * + * 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 + */ + +/* This test is for audio playback specific mechanisms + * Tests: + * - IAudioClient with eRender and IAudioRenderClient + */ + +#include +#include + +#include "wine/test.h" + +#define COBJMACROS + +#ifdef STANDALONE +#include "initguid.h" +#endif + +#include "unknwn.h" +#include "uuids.h" +#include "mmdeviceapi.h" +#include "mmsystem.h" +#include "audioclient.h" +#include "audiopolicy.h" +#include "endpointvolume.h" + +static const unsigned int win_formats[][4] = { + { 8000, 8, 1}, { 8000, 8, 2}, { 8000, 16, 1}, { 8000, 16, 2}, + {11025, 8, 1}, {11025, 8, 2}, {11025, 16, 1}, {11025, 16, 2}, + {12000, 8, 1}, {12000, 8, 2}, {12000, 16, 1}, {12000, 16, 2}, + {16000, 8, 1}, {16000, 8, 2}, {16000, 16, 1}, {16000, 16, 2}, + {22050, 8, 1}, {22050, 8, 2}, {22050, 16, 1}, {22050, 16, 2}, + {44100, 8, 1}, {44100, 8, 2}, {44100, 16, 1}, {44100, 16, 2}, + {48000, 8, 1}, {48000, 8, 2}, {48000, 16, 1}, {48000, 16, 2}, + {96000, 8, 1}, {96000, 8, 2}, {96000, 16, 1}, {96000, 16, 2} +}; + +#define NULL_PTR_ERR MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, RPC_X_NULL_REF_POINTER) + +/* undocumented error code */ +#define D3D11_ERROR_4E MAKE_HRESULT(SEVERITY_ERROR, FACILITY_DIRECT3D11, 0x4e) + +static IMMDeviceEnumerator *mme = NULL; +static IMMDevice *dev = NULL; +static HRESULT hexcl = S_OK; /* or AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED */ +static BOOL win10 = FALSE; + +static const LARGE_INTEGER ullZero; + +#define PI 3.14159265358979323846L +static DWORD wave_generate_tone(PWAVEFORMATEX pwfx, BYTE* data, UINT32 frames) +{ + static double phase = 0.; /* normalized to unit, not 2*PI */ + PWAVEFORMATEXTENSIBLE wfxe = (PWAVEFORMATEXTENSIBLE)pwfx; + DWORD cn, i; + double delta, y; + + if(!winetest_interactive) + return AUDCLNT_BUFFERFLAGS_SILENT; + if(wfxe->Format.wBitsPerSample != ((wfxe->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE && + IsEqualGUID(&wfxe->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) ? 8 * sizeof(float) : 16)) + return AUDCLNT_BUFFERFLAGS_SILENT; + + for(delta = phase, cn = 0; cn < wfxe->Format.nChannels; + delta += .5/wfxe->Format.nChannels, cn++){ + for(i = 0; i < frames; i++){ + y = sin(2*PI*(440.* i / wfxe->Format.nSamplesPerSec + delta)); + /* assume alignment is granted */ + if(wfxe->Format.wBitsPerSample == 16) + ((short*)data)[cn+i*wfxe->Format.nChannels] = y * 32767.9; + else + ((float*)data)[cn+i*wfxe->Format.nChannels] = y; + } + } + phase += 440.* frames / wfxe->Format.nSamplesPerSec; + phase -= floor(phase); + return 0; +} + +static void test_uninitialized(IAudioClient *ac) +{ + HRESULT hr; + UINT32 num; + REFERENCE_TIME t1; + + HANDLE handle = CreateEventW(NULL, FALSE, FALSE, NULL); + IUnknown *unk; + + hr = IAudioClient_GetBufferSize(ac, &num); + ok(hr == AUDCLNT_E_NOT_INITIALIZED, "Uninitialized GetBufferSize call returns %08lx\n", hr); + + hr = IAudioClient_GetStreamLatency(ac, &t1); + ok(hr == AUDCLNT_E_NOT_INITIALIZED, "Uninitialized GetStreamLatency call returns %08lx\n", hr); + + hr = IAudioClient_GetCurrentPadding(ac, &num); + ok(hr == AUDCLNT_E_NOT_INITIALIZED, "Uninitialized GetCurrentPadding call returns %08lx\n", hr); + + hr = IAudioClient_Start(ac); + ok(hr == AUDCLNT_E_NOT_INITIALIZED, "Uninitialized Start call returns %08lx\n", hr); + + hr = IAudioClient_Stop(ac); + ok(hr == AUDCLNT_E_NOT_INITIALIZED, "Uninitialized Stop call returns %08lx\n", hr); + + hr = IAudioClient_Reset(ac); + ok(hr == AUDCLNT_E_NOT_INITIALIZED, "Uninitialized Reset call returns %08lx\n", hr); + + hr = IAudioClient_SetEventHandle(ac, handle); + ok(hr == AUDCLNT_E_NOT_INITIALIZED, "Uninitialized SetEventHandle call returns %08lx\n", hr); + + hr = IAudioClient_GetService(ac, &IID_IAudioStreamVolume, (void**)&unk); + ok(hr == AUDCLNT_E_NOT_INITIALIZED, "Uninitialized GetService call returns %08lx\n", hr); + + CloseHandle(handle); +} + +static void test_audioclient(void) +{ + IAudioClient *ac; + IAudioClient2 *ac2; + IAudioClient3 *ac3; + IUnknown *unk; + HRESULT hr; + ULONG ref; + WAVEFORMATEX *pwfx, *pwfx2; + REFERENCE_TIME t1, t2; + HANDLE handle; + BOOL offload_capable; + AudioClientProperties client_props; + + hr = IMMDevice_Activate(dev, &IID_IAudioClient3, CLSCTX_INPROC_SERVER, + NULL, (void**)&ac3); + ok(hr == S_OK || + broken(hr == E_NOINTERFACE) /* win8 */, + "IAudioClient3 Activation failed with %08lx\n", hr); + if(hr == S_OK) + IAudioClient3_Release(ac3); + + hr = IMMDevice_Activate(dev, &IID_IAudioClient2, CLSCTX_INPROC_SERVER, + NULL, (void**)&ac2); + ok(hr == S_OK || + broken(hr == E_NOINTERFACE) /* win7 */, + "IAudioClient2 Activation failed with %08lx\n", hr); + if(hr == S_OK) + IAudioClient2_Release(ac2); + + hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, + NULL, (void**)&ac); + ok(hr == S_OK, "Activation failed with %08lx\n", hr); + if(hr != S_OK) + return; + + handle = CreateEventW(NULL, FALSE, FALSE, NULL); + + hr = IAudioClient_QueryInterface(ac, &IID_IUnknown, NULL); + ok(hr == E_POINTER, "QueryInterface(NULL) returned %08lx\n", hr); + + unk = (void*)(LONG_PTR)0x12345678; + hr = IAudioClient_QueryInterface(ac, &IID_NULL, (void**)&unk); + ok(hr == E_NOINTERFACE, "QueryInterface(IID_NULL) returned %08lx\n", hr); + ok(!unk, "QueryInterface(IID_NULL) returned non-null pointer %p\n", unk); + + hr = IAudioClient_QueryInterface(ac, &IID_IUnknown, (void**)&unk); + ok(hr == S_OK, "QueryInterface(IID_IUnknown) returned %08lx\n", hr); + if (unk) + { + ref = IUnknown_Release(unk); + ok(ref == 1, "Released count is %lu\n", ref); + } + + hr = IAudioClient_QueryInterface(ac, &IID_IAudioClient, (void**)&unk); + ok(hr == S_OK, "QueryInterface(IID_IAudioClient) returned %08lx\n", hr); + if (unk) + { + ref = IUnknown_Release(unk); + ok(ref == 1, "Released count is %lu\n", ref); + } + + hr = IAudioClient_GetDevicePeriod(ac, NULL, NULL); + ok(hr == E_POINTER, "Invalid GetDevicePeriod call returns %08lx\n", hr); + + hr = IAudioClient_GetDevicePeriod(ac, &t1, NULL); + ok(hr == S_OK, "Valid GetDevicePeriod call returns %08lx\n", hr); + + hr = IAudioClient_GetDevicePeriod(ac, NULL, &t2); + ok(hr == S_OK, "Valid GetDevicePeriod call returns %08lx\n", hr); + + hr = IAudioClient_GetDevicePeriod(ac, &t1, &t2); + ok(hr == S_OK, "Valid GetDevicePeriod call returns %08lx\n", hr); + trace("Returned periods: %u.%04u ms %u.%04u ms\n", + (UINT)(t1/10000), (UINT)(t1 % 10000), + (UINT)(t2/10000), (UINT)(t2 % 10000)); + + hr = IAudioClient_GetMixFormat(ac, NULL); + ok(hr == E_POINTER, "GetMixFormat returns %08lx\n", hr); + + hr = IAudioClient_GetMixFormat(ac, &pwfx); + ok(hr == S_OK, "Valid GetMixFormat returns %08lx\n", hr); + + if (hr == S_OK) + { + trace("pwfx: %p\n", pwfx); + trace("Tag: %04x\n", pwfx->wFormatTag); + trace("bits: %u\n", pwfx->wBitsPerSample); + trace("chan: %u\n", pwfx->nChannels); + trace("rate: %lu\n", pwfx->nSamplesPerSec); + trace("align: %u\n", pwfx->nBlockAlign); + trace("extra: %u\n", pwfx->cbSize); + ok(pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE, "wFormatTag is %x\n", pwfx->wFormatTag); + if (pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE) + { + WAVEFORMATEXTENSIBLE *pwfxe = (void*)pwfx; + trace("Res: %u\n", pwfxe->Samples.wReserved); + trace("Mask: %lx\n", pwfxe->dwChannelMask); + trace("Alg: %s\n", + IsEqualGUID(&pwfxe->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM)?"PCM": + (IsEqualGUID(&pwfxe->SubFormat, + &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)?"FLOAT":"Other")); + ok(IsEqualGUID(&pwfxe->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT), + "got format %s\n", debugstr_guid(&pwfxe->SubFormat)); + + pwfxe->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + hr = IAudioClient_IsFormatSupported(ac, AUDCLNT_SHAREMODE_SHARED, pwfx, &pwfx2); + ok(hr == S_OK, "Valid IsFormatSupported(Shared) call returns %08lx\n", hr); + ok(pwfx2 == NULL, "pwfx2 is non-null\n"); + CoTaskMemFree(pwfx2); + } + + hr = IAudioClient_IsFormatSupported(ac, AUDCLNT_SHAREMODE_SHARED, pwfx, &pwfx2); + ok(hr == S_OK, "Valid IsFormatSupported(Shared) call returns %08lx\n", hr); + ok(pwfx2 == NULL, "pwfx2 is non-null\n"); + CoTaskMemFree(pwfx2); + + hr = IAudioClient_IsFormatSupported(ac, AUDCLNT_SHAREMODE_SHARED, NULL, NULL); + ok(hr == E_POINTER, "IsFormatSupported(NULL) call returns %08lx\n", hr); + + hr = IAudioClient_IsFormatSupported(ac, AUDCLNT_SHAREMODE_SHARED, pwfx, NULL); + ok(hr == E_POINTER, "IsFormatSupported(Shared,NULL) call returns %08lx\n", hr); + + hr = IAudioClient_IsFormatSupported(ac, AUDCLNT_SHAREMODE_EXCLUSIVE, pwfx, NULL); + ok(hr == S_OK || hr == AUDCLNT_E_UNSUPPORTED_FORMAT || hr == AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED, + "IsFormatSupported(Exclusive) call returns %08lx\n", hr); + hexcl = hr; + + pwfx2 = (WAVEFORMATEX*)0xDEADF00D; + hr = IAudioClient_IsFormatSupported(ac, AUDCLNT_SHAREMODE_EXCLUSIVE, pwfx, &pwfx2); + ok(hr == hexcl, "IsFormatSupported(Exclusive) call returns %08lx\n", hr); + ok(pwfx2 == NULL, "pwfx2 non-null on exclusive IsFormatSupported\n"); + + if (hexcl != AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED) + hexcl = S_OK; + + hr = IAudioClient_IsFormatSupported(ac, 0xffffffff, pwfx, NULL); + ok(hr == E_INVALIDARG/*w32*/ || + broken(hr == AUDCLNT_E_UNSUPPORTED_FORMAT/*w64 response from exclusive mode driver */), + "IsFormatSupported(0xffffffff) call returns %08lx\n", hr); + } + + hr = IAudioClient_QueryInterface(ac, &IID_IAudioClient2, (void**)&ac2); + if (hr == S_OK) + { + hr = IAudioClient2_IsOffloadCapable(ac2, AudioCategory_BackgroundCapableMedia, NULL); + ok(hr == E_INVALIDARG, "IsOffloadCapable gave wrong error: %08lx\n", hr); + + hr = IAudioClient2_IsOffloadCapable(ac2, AudioCategory_BackgroundCapableMedia, &offload_capable); + ok(hr == S_OK, "IsOffloadCapable failed: %08lx\n", hr); + + hr = IAudioClient2_SetClientProperties(ac2, NULL); + ok(hr == E_POINTER, "SetClientProperties with NULL props gave wrong error: %08lx\n", hr); + + /* invalid cbSize */ + client_props.cbSize = 0; + client_props.bIsOffload = FALSE; + client_props.eCategory = AudioCategory_BackgroundCapableMedia; + client_props.Options = 0; + + hr = IAudioClient2_SetClientProperties(ac2, &client_props); + ok(hr == E_INVALIDARG, "SetClientProperties with invalid cbSize gave wrong error: %08lx\n", hr); + + /* offload consistency */ + client_props.cbSize = sizeof(client_props) - sizeof(client_props.Options); + client_props.bIsOffload = TRUE; + + hr = IAudioClient2_SetClientProperties(ac2, &client_props); + if(!offload_capable) + ok(hr == AUDCLNT_E_ENDPOINT_OFFLOAD_NOT_CAPABLE, "SetClientProperties(offload) gave wrong error: %08lx\n", hr); + else + ok(hr == S_OK, "SetClientProperties(offload) failed: %08lx\n", hr); + + /* disable offload */ + client_props.bIsOffload = FALSE; + hr = IAudioClient2_SetClientProperties(ac2, &client_props); + ok(hr == S_OK, "SetClientProperties failed: %08lx\n", hr); + + /* Options field added in Win 8.1 */ + client_props.cbSize = sizeof(client_props); + hr = IAudioClient2_SetClientProperties(ac2, &client_props); + ok(hr == S_OK || + broken(hr == E_INVALIDARG) /* <= win8 */, + "SetClientProperties failed: %08lx\n", hr); + + IAudioClient2_Release(ac2); + } + else + win_skip("IAudioClient2 is not present on Win <= 7\n"); + + hr = IAudioClient_QueryInterface(ac, &IID_IAudioClient3, (void**)&ac3); + ok(hr == S_OK || + broken(hr == E_NOINTERFACE) /* win8 */, + "Failed to query IAudioClient3 interface: %08lx\n", hr); + + if(hr == S_OK) + IAudioClient3_Release(ac3); + + test_uninitialized(ac); + + hr = IAudioClient_Initialize(ac, 3, 0, 5000000, 0, pwfx, NULL); + ok(broken(hr == AUDCLNT_E_NOT_INITIALIZED) || /* <= win8 */ + hr == E_INVALIDARG, "Initialize with invalid sharemode returns %08lx\n", hr); + + hr = IAudioClient_Initialize(ac, AUDCLNT_SHAREMODE_SHARED, 0xffffffff, 5000000, 0, pwfx, NULL); + ok(hr == E_INVALIDARG || + hr == AUDCLNT_E_INVALID_STREAM_FLAG, "Initialize with invalid flags returns %08lx\n", hr); + + /* A period != 0 is ignored and the call succeeds. + * Since we can only initialize successfully once, skip those tests. + */ + hr = IAudioClient_Initialize(ac, AUDCLNT_SHAREMODE_SHARED, 0, 5000000, 0, NULL, NULL); + ok(hr == E_POINTER, "Initialize with null format returns %08lx\n", hr); + + hr = IAudioClient_Initialize(ac, AUDCLNT_SHAREMODE_SHARED, 0, 0, 0, pwfx, NULL); + ok(hr == S_OK, "Initialize with 0 buffer size returns %08lx\n", hr); + if(hr == S_OK){ + UINT32 num; + + hr = IAudioClient_GetBufferSize(ac, &num); + ok(hr == S_OK, "GetBufferSize from duration 0 returns %08lx\n", hr); + if(hr == S_OK) + trace("Initialize(duration=0) GetBufferSize is %u\n", num); + } + + hr = IAudioClient_Initialize(ac, AUDCLNT_SHAREMODE_SHARED, 0, 5000000, 0, pwfx, NULL); + ok(hr == AUDCLNT_E_ALREADY_INITIALIZED, "Calling Initialize twice returns %08lx\n", hr); + + hr = IAudioClient_Start(ac); + ok(hr == S_OK || + broken(hr == AUDCLNT_E_DEVICE_INVALIDATED), /* Win10 >= 1607 */ + "Start on a doubly initialized stream returns %08lx\n", hr); + + IAudioClient_Release(ac); + + hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, + NULL, (void**)&ac); + ok(hr == S_OK, "Activation failed with %08lx\n", hr); + + if(pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE){ + WAVEFORMATEXTENSIBLE *fmtex = (WAVEFORMATEXTENSIBLE*)pwfx; + WAVEFORMATEX *fmt2 = NULL; + + ok(fmtex->dwChannelMask != 0, "Got empty dwChannelMask\n"); + + fmtex->dwChannelMask = 0xffff; + + hr = IAudioClient_Initialize(ac, AUDCLNT_SHAREMODE_SHARED, 0, 5000000, 0, pwfx, NULL); + ok(hr == S_OK || + hr == AUDCLNT_E_UNSUPPORTED_FORMAT /* win10 */, "Initialize(dwChannelMask = 0xffff) returns %08lx\n", hr); + + IAudioClient_Release(ac); + + hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, + NULL, (void**)&ac); + ok(hr == S_OK, "Activation failed with %08lx\n", hr); + + fmtex->dwChannelMask = 0; + + hr = IAudioClient_IsFormatSupported(ac, AUDCLNT_SHAREMODE_SHARED, pwfx, &fmt2); + ok(hr == S_OK || broken(hr == S_FALSE /* w7 Realtek HDA */), + "IsFormatSupported(dwChannelMask = 0) call returns %08lx\n", hr); + ok(fmtex->dwChannelMask == 0, "Passed format was modified\n"); + + CoTaskMemFree(fmt2); + + hr = IAudioClient_Initialize(ac, AUDCLNT_SHAREMODE_SHARED, 0, 5000000, 0, pwfx, NULL); + ok(hr == S_OK, "Initialize(dwChannelMask = 0) returns %08lx\n", hr); + + IAudioClient_Release(ac); + + hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, + NULL, (void**)&ac); + ok(hr == S_OK, "Activation failed with %08lx\n", hr); + + CoTaskMemFree(pwfx); + + hr = IAudioClient_GetMixFormat(ac, &pwfx); + ok(hr == S_OK, "Valid GetMixFormat returns %08lx\n", hr); + }else + skip("Skipping dwChannelMask tests\n"); + + hr = IAudioClient_Initialize(ac, AUDCLNT_SHAREMODE_SHARED, 0, 5000000, 0, pwfx, NULL); + ok(hr == S_OK, "Valid Initialize returns %08lx\n", hr); + if (hr != S_OK) + goto cleanup; + + hr = IAudioClient_GetStreamLatency(ac, NULL); + ok(hr == E_POINTER, "GetStreamLatency(NULL) call returns %08lx\n", hr); + + hr = IAudioClient_GetStreamLatency(ac, &t2); + ok(hr == S_OK, "Valid GetStreamLatency call returns %08lx\n", hr); + trace("Returned latency: %u.%04u ms\n", + (UINT)(t2/10000), (UINT)(t2 % 10000)); + ok(t2 >= t1 || broken(t2 >= t1/2 && pwfx->nSamplesPerSec > 48000) || + broken(t2 == 0) /* (!) win10 */, + "Latency < default period, delta %ldus (%s vs %s)\n", + (LONG)((t2-t1)/10), wine_dbgstr_longlong(t2), wine_dbgstr_longlong(t1)); + /* Native appears to add the engine period to the HW latency in shared mode */ + if(t2 == 0) + win10 = TRUE; + + hr = IAudioClient_SetEventHandle(ac, NULL); + ok(hr == E_INVALIDARG, "SetEventHandle(NULL) returns %08lx\n", hr); + + hr = IAudioClient_SetEventHandle(ac, handle); + ok(hr == AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED || + broken(hr == HRESULT_FROM_WIN32(ERROR_INVALID_NAME)) || + broken(hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) /* Some 2k8 */ || + broken(hr == HRESULT_FROM_WIN32(ERROR_BAD_PATHNAME)) /* Some Vista */ + , "SetEventHandle returns %08lx\n", hr); + + hr = IAudioClient_Reset(ac); + ok(hr == S_OK, "Reset on an initialized stream returns %08lx\n", hr); + + hr = IAudioClient_Reset(ac); + ok(hr == S_OK, "Reset on an already reset stream returns %08lx\n", hr); + + hr = IAudioClient_Stop(ac); + ok(hr == S_FALSE, "Stop on a stopped stream returns %08lx\n", hr); + + hr = IAudioClient_Start(ac); + ok(hr == S_OK, "Start on a stopped stream returns %08lx\n", hr); + + hr = IAudioClient_Start(ac); + ok(hr == AUDCLNT_E_NOT_STOPPED, "Start twice returns %08lx\n", hr); + +cleanup: + IAudioClient_Release(ac); + CloseHandle(handle); + CoTaskMemFree(pwfx); +} + +static void test_formats(AUDCLNT_SHAREMODE mode) +{ + IAudioClient *ac; + HRESULT hr, hrs; + WAVEFORMATEX fmt, *pwfx, *pwfx2; + int i; + + fmt.wFormatTag = WAVE_FORMAT_PCM; + fmt.cbSize = 0; + + for(i = 0; i < ARRAY_SIZE(win_formats); i++) { + hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, + NULL, (void**)&ac); + ok(hr == S_OK, "Activation failed with %08lx\n", hr); + if(hr != S_OK) + continue; + + hr = IAudioClient_GetMixFormat(ac, &pwfx); + ok(hr == S_OK, "GetMixFormat failed: %08lx\n", hr); + + fmt.nSamplesPerSec = win_formats[i][0]; + fmt.wBitsPerSample = win_formats[i][1]; + fmt.nChannels = win_formats[i][2]; + fmt.nBlockAlign = fmt.nChannels * fmt.wBitsPerSample / 8; + fmt.nAvgBytesPerSec= fmt.nBlockAlign * fmt.nSamplesPerSec; + + pwfx2 = (WAVEFORMATEX*)0xDEADF00D; + hr = IAudioClient_IsFormatSupported(ac, mode, &fmt, &pwfx2); + hrs = hr; + /* Only shared mode suggests something ... GetMixFormat! */ + ok(hr == S_OK || (mode == AUDCLNT_SHAREMODE_SHARED + ? hr == S_FALSE || broken(hr == AUDCLNT_E_UNSUPPORTED_FORMAT && + /* 5:1 card exception when asked for 1 channel at mixer rate */ + pwfx->nChannels > 2 && fmt.nSamplesPerSec == pwfx->nSamplesPerSec) + : (hr == AUDCLNT_E_UNSUPPORTED_FORMAT || hr == hexcl)), + "IsFormatSupported(%d, %lux%2ux%u) returns %08lx\n", mode, + fmt.nSamplesPerSec, fmt.wBitsPerSample, fmt.nChannels, hr); + if (hr == S_OK) + trace("IsSupported(%s, %lux%2ux%u)\n", + mode == AUDCLNT_SHAREMODE_SHARED ? "shared " : "exclus.", + fmt.nSamplesPerSec, fmt.wBitsPerSample, fmt.nChannels); + + /* Change GetMixFormat wBitsPerSample only => S_OK */ + if (mode == AUDCLNT_SHAREMODE_SHARED + && fmt.nSamplesPerSec == pwfx->nSamplesPerSec + && fmt.nChannels == pwfx->nChannels) + ok(hr == S_OK, "Varying BitsPerSample %u\n", fmt.wBitsPerSample); + + ok((hr == S_FALSE)^(pwfx2 == NULL), "hr %lx<->suggest %p\n", hr, pwfx2); + if (pwfx2 == (WAVEFORMATEX*)0xDEADF00D) + pwfx2 = NULL; /* broken in Wine < 1.3.28 */ + if (pwfx2) { + ok(pwfx2->nSamplesPerSec == pwfx->nSamplesPerSec && + pwfx2->nChannels == pwfx->nChannels && + pwfx2->wBitsPerSample == pwfx->wBitsPerSample, + "Suggestion %lux%2ux%u differs from GetMixFormat\n", + pwfx2->nSamplesPerSec, pwfx2->wBitsPerSample, pwfx2->nChannels); + } + + /* Vista returns E_INVALIDARG upon AUDCLNT_STREAMFLAGS_RATEADJUST */ + hr = IAudioClient_Initialize(ac, mode, 0, 5000000, 0, &fmt, NULL); + if ((hrs == S_OK) ^ (hr == S_OK)) + trace("Initialize (%s, %lux%2ux%u) returns %08lx unlike IsFormatSupported\n", + mode == AUDCLNT_SHAREMODE_SHARED ? "shared " : "exclus.", + fmt.nSamplesPerSec, fmt.wBitsPerSample, fmt.nChannels, hr); + if (mode == AUDCLNT_SHAREMODE_SHARED) + ok(hrs == S_OK ? hr == S_OK : hr == AUDCLNT_E_UNSUPPORTED_FORMAT, + "Initialize(shared, %lux%2ux%u) returns %08lx\n", + fmt.nSamplesPerSec, fmt.wBitsPerSample, fmt.nChannels, hr); + else if (hrs == AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED) + /* Unsupported format implies "create failed" and shadows "not allowed" */ + ok(hrs == hexcl && (hr == AUDCLNT_E_ENDPOINT_CREATE_FAILED || hr == hrs), + "Initialize(noexcl., %lux%2ux%u) returns %08lx(%08lx)\n", + fmt.nSamplesPerSec, fmt.wBitsPerSample, fmt.nChannels, hr, hrs); + else + /* On testbot 48000x16x1 claims support, but does not Initialize. + * Some cards Initialize 44100|48000x16x1 yet claim no support; + * F. Gouget's w7 bots do that for 12000|96000x8|16x1|2 */ + ok(hrs == S_OK ? hr == S_OK || broken(hr == AUDCLNT_E_ENDPOINT_CREATE_FAILED) + : hr == AUDCLNT_E_ENDPOINT_CREATE_FAILED || hr == AUDCLNT_E_UNSUPPORTED_FORMAT || + broken(hr == S_OK && + ((fmt.nChannels == 1 && fmt.wBitsPerSample == 16) || + (fmt.nSamplesPerSec == 12000 || fmt.nSamplesPerSec == 96000))), + "Initialize(exclus., %lux%2ux%u) returns %08lx\n", + fmt.nSamplesPerSec, fmt.wBitsPerSample, fmt.nChannels, hr); + + /* Bug in native (Vista/w2k8/w7): after Initialize failed, better + * Release this ac and Activate a new one. + * A second call (with a known working format) would yield + * ALREADY_INITIALIZED in shared mode yet be unusable, and in exclusive + * mode some entity keeps a lock on the device, causing DEVICE_IN_USE to + * all subsequent calls until the audio engine service is restarted. */ + + CoTaskMemFree(pwfx2); + CoTaskMemFree(pwfx); + IAudioClient_Release(ac); + } +} + +static void test_references(void) +{ + IAudioClient *ac; + IAudioRenderClient *rc; + ISimpleAudioVolume *sav; + IAudioStreamVolume *asv; + IAudioClock *acl; + WAVEFORMATEX *pwfx; + HRESULT hr; + ULONG ref; + + /* IAudioRenderClient */ + hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, + NULL, (void**)&ac); + ok(hr == S_OK, "Activation failed with %08lx\n", hr); + if(hr != S_OK) + return; + + hr = IAudioClient_GetMixFormat(ac, &pwfx); + ok(hr == S_OK, "GetMixFormat failed: %08lx\n", hr); + + hr = IAudioClient_Initialize(ac, AUDCLNT_SHAREMODE_SHARED, 0, 5000000, + 0, pwfx, NULL); + ok(hr == S_OK, "Initialize failed: %08lx\n", hr); + + CoTaskMemFree(pwfx); + + hr = IAudioClient_GetService(ac, &IID_IAudioRenderClient, (void**)&rc); + ok(hr == S_OK, "GetService failed: %08lx\n", hr); + if(hr != S_OK) { + IAudioClient_Release(ac); + return; + } + + IAudioRenderClient_AddRef(rc); + ref = IAudioRenderClient_Release(rc); + ok(ref != 0, "RenderClient_Release gave wrong refcount: %lu\n", ref); + + ref = IAudioClient_Release(ac); + ok(ref != 0, "Client_Release gave wrong refcount: %lu\n", ref); + + ref = IAudioRenderClient_Release(rc); + ok(ref == 0, "RenderClient_Release gave wrong refcount: %lu\n", ref); + + /* ISimpleAudioVolume */ + hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, + NULL, (void**)&ac); + ok(hr == S_OK, "Activation failed with %08lx\n", hr); + if(hr != S_OK) + return; + + hr = IAudioClient_GetMixFormat(ac, &pwfx); + ok(hr == S_OK, "GetMixFormat failed: %08lx\n", hr); + + hr = IAudioClient_Initialize(ac, AUDCLNT_SHAREMODE_SHARED, 0, 5000000, + 0, pwfx, NULL); + ok(hr == S_OK, "Initialize failed: %08lx\n", hr); + + CoTaskMemFree(pwfx); + + hr = IAudioClient_GetService(ac, &IID_ISimpleAudioVolume, (void**)&sav); + ok(hr == S_OK, "GetService failed: %08lx\n", hr); + + ISimpleAudioVolume_AddRef(sav); + ref = ISimpleAudioVolume_Release(sav); + ok(ref != 0, "SimpleAudioVolume_Release gave wrong refcount: %lu\n", ref); + + ref = IAudioClient_Release(ac); + ok(ref != 0, "Client_Release gave wrong refcount: %lu\n", ref); + + ref = ISimpleAudioVolume_Release(sav); + ok(ref == 0, "SimpleAudioVolume_Release gave wrong refcount: %lu\n", ref); + + /* IAudioClock */ + hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, + NULL, (void**)&ac); + ok(hr == S_OK, "Activation failed with %08lx\n", hr); + if(hr != S_OK) + return; + + hr = IAudioClient_GetMixFormat(ac, &pwfx); + ok(hr == S_OK, "GetMixFormat failed: %08lx\n", hr); + + hr = IAudioClient_Initialize(ac, AUDCLNT_SHAREMODE_SHARED, 0, 5000000, + 0, pwfx, NULL); + ok(hr == S_OK, "Initialize failed: %08lx\n", hr); + + CoTaskMemFree(pwfx); + + hr = IAudioClient_GetService(ac, &IID_IAudioClock, (void**)&acl); + ok(hr == S_OK, "GetService failed: %08lx\n", hr); + + IAudioClock_AddRef(acl); + ref = IAudioClock_Release(acl); + ok(ref != 0, "AudioClock_Release gave wrong refcount: %lu\n", ref); + + ref = IAudioClient_Release(ac); + ok(ref != 0, "Client_Release gave wrong refcount: %lu\n", ref); + + ref = IAudioClock_Release(acl); + ok(ref == 0, "AudioClock_Release gave wrong refcount: %lu\n", ref); + + /* IAudioStreamVolume */ + hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, + NULL, (void**)&ac); + ok(hr == S_OK, "Activation failed with %08lx\n", hr); + if(hr != S_OK) + return; + + hr = IAudioClient_GetMixFormat(ac, &pwfx); + ok(hr == S_OK, "GetMixFormat failed: %08lx\n", hr); + + hr = IAudioClient_Initialize(ac, AUDCLNT_SHAREMODE_SHARED, 0, 5000000, + 0, pwfx, NULL); + ok(hr == S_OK, "Initialize failed: %08lx\n", hr); + + CoTaskMemFree(pwfx); + + hr = IAudioClient_GetService(ac, &IID_IAudioStreamVolume, (void**)&asv); + ok(hr == S_OK, "GetService failed: %08lx\n", hr); + + IAudioStreamVolume_AddRef(asv); + ref = IAudioStreamVolume_Release(asv); + ok(ref != 0, "AudioStreamVolume_Release gave wrong refcount: %lu\n", ref); + + ref = IAudioClient_Release(ac); + ok(ref != 0, "Client_Release gave wrong refcount: %lu\n", ref); + + ref = IAudioStreamVolume_Release(asv); + ok(ref == 0, "AudioStreamVolume_Release gave wrong refcount: %lu\n", ref); +} + +static void test_event(void) +{ + HANDLE event; + HRESULT hr; + DWORD r; + IAudioClient *ac; + WAVEFORMATEX *pwfx; + + hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, + NULL, (void**)&ac); + ok(hr == S_OK, "Activation failed with %08lx\n", hr); + if(hr != S_OK) + return; + + hr = IAudioClient_GetMixFormat(ac, &pwfx); + ok(hr == S_OK, "GetMixFormat failed: %08lx\n", hr); + + hr = IAudioClient_Initialize(ac, AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK, 5000000, + 0, pwfx, NULL); + ok(hr == S_OK, "Initialize failed: %08lx\n", hr); + + CoTaskMemFree(pwfx); + + event = CreateEventW(NULL, FALSE, FALSE, NULL); + ok(event != NULL, "CreateEvent failed\n"); + + hr = IAudioClient_Start(ac); + ok(hr == AUDCLNT_E_EVENTHANDLE_NOT_SET || + hr == D3D11_ERROR_4E /* win10 */, "Start failed: %08lx\n", hr); + + hr = IAudioClient_SetEventHandle(ac, event); + ok(hr == S_OK, "SetEventHandle failed: %08lx\n", hr); + + hr = IAudioClient_SetEventHandle(ac, event); + ok(hr == HRESULT_FROM_WIN32(ERROR_INVALID_NAME) || + hr == E_UNEXPECTED /* win10 */, "SetEventHandle returns %08lx\n", hr); + + r = WaitForSingleObject(event, 40); + ok(r == WAIT_TIMEOUT, "Wait(event) before Start gave %lx\n", r); + + hr = IAudioClient_Start(ac); + ok(hr == S_OK, "Start failed: %08lx\n", hr); + + r = WaitForSingleObject(event, 20); + ok(r == WAIT_OBJECT_0, "Wait(event) after Start gave %lx\n", r); + + hr = IAudioClient_Stop(ac); + ok(hr == S_OK, "Stop failed: %08lx\n", hr); + + ok(ResetEvent(event), "ResetEvent\n"); + + /* Still receiving events! */ + r = WaitForSingleObject(event, 20); + ok(r == WAIT_OBJECT_0, "Wait(event) after Stop gave %lx\n", r); + + hr = IAudioClient_Reset(ac); + ok(hr == S_OK, "Reset failed: %08lx\n", hr); + + ok(ResetEvent(event), "ResetEvent\n"); + + r = WaitForSingleObject(event, 120); + ok(r == WAIT_OBJECT_0, "Wait(event) after Reset gave %lx\n", r); + + hr = IAudioClient_SetEventHandle(ac, NULL); + ok(hr == E_INVALIDARG, "SetEventHandle(NULL) returns %08lx\n", hr); + + r = WaitForSingleObject(event, 70); + ok(r == WAIT_OBJECT_0, "Wait(NULL event) gave %lx\n", r); + + /* test releasing a playing stream */ + hr = IAudioClient_Start(ac); + ok(hr == S_OK, "Start failed: %08lx\n", hr); + IAudioClient_Release(ac); + + CloseHandle(event); +} + +static void test_padding(void) +{ + HRESULT hr; + IAudioClient *ac; + IAudioRenderClient *arc; + WAVEFORMATEX *pwfx; + REFERENCE_TIME minp, defp; + BYTE *buf, silence; + UINT32 psize, pad, written, i; + + hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, + NULL, (void**)&ac); + ok(hr == S_OK, "Activation failed with %08lx\n", hr); + if(hr != S_OK) + return; + + hr = IAudioClient_GetMixFormat(ac, &pwfx); + ok(hr == S_OK, "GetMixFormat failed: %08lx\n", hr); + if(hr != S_OK) + return; + + hr = IAudioClient_Initialize(ac, AUDCLNT_SHAREMODE_SHARED, + 0, 5000000, 0, pwfx, NULL); + ok(hr == S_OK, "Initialize failed: %08lx\n", hr); + if(hr != S_OK) + return; + + if(pwfx->wBitsPerSample == 8) + silence = 128; + else + silence = 0; + + /** GetDevicePeriod + * Default (= shared) device period is 10ms (e.g. 441 frames at 44100), + * except when the HW/OS forces a particular alignment, + * e.g. 10.1587ms is 28 * 16 = 448 frames at 44100 with HDA. + * 441 observed with Vista, 448 with w7 on the same HW! */ + hr = IAudioClient_GetDevicePeriod(ac, &defp, &minp); + ok(hr == S_OK, "GetDevicePeriod failed: %08lx\n", hr); + /* some wineXYZ.drv use 20ms, not seen on native */ + ok(defp == 100000 || broken(defp == 101587) || defp == 200000, + "Expected 10ms default period: %lu\n", (ULONG)defp); + ok(minp != 0, "Minimum period is 0\n"); + ok(minp <= defp, "Minimum period is greater than default period\n"); + + hr = IAudioClient_GetService(ac, &IID_IAudioRenderClient, (void**)&arc); + ok(hr == S_OK, "GetService failed: %08lx\n", hr); + + psize = MulDiv(defp, pwfx->nSamplesPerSec, 10000000) * 10; + + written = 0; + hr = IAudioClient_GetCurrentPadding(ac, &pad); + ok(hr == S_OK, "GetCurrentPadding failed: %08lx\n", hr); + ok(pad == written, "GetCurrentPadding returned %u, should be %u\n", pad, written); + + hr = IAudioRenderClient_GetBuffer(arc, psize, &buf); + ok(hr == S_OK, "GetBuffer failed: %08lx\n", hr); + ok(buf != NULL, "NULL buffer returned\n"); + if(!win10){ + /* win10 appears not to clear the buffer */ + for(i = 0; i < psize * pwfx->nBlockAlign; ++i){ + if(buf[i] != silence){ + ok(0, "buffer has data in it already, i: %u, value: %f\n", i, *((float*)buf)); + break; + } + } + } + + hr = IAudioRenderClient_GetBuffer(arc, 0, &buf); + ok(hr == AUDCLNT_E_OUT_OF_ORDER, "GetBuffer 0 size failed: %08lx\n", hr); + ok(buf == NULL, "GetBuffer 0 gave %p\n", buf); + /* MSDN instead documents buf remains untouched */ + + hr = IAudioClient_Reset(ac); + ok(hr == AUDCLNT_E_BUFFER_OPERATION_PENDING, "Reset failed: %08lx\n", hr); + + hr = IAudioRenderClient_ReleaseBuffer(arc, psize, + AUDCLNT_BUFFERFLAGS_SILENT); + ok(hr == S_OK, "ReleaseBuffer failed: %08lx\n", hr); + if(hr == S_OK) written += psize; + + hr = IAudioClient_GetCurrentPadding(ac, &pad); + ok(hr == S_OK, "GetCurrentPadding failed: %08lx\n", hr); + ok(pad == written, "GetCurrentPadding returned %u, should be %u\n", pad, written); + + psize = MulDiv(minp, pwfx->nSamplesPerSec, 10000000) * 10; + + hr = IAudioRenderClient_GetBuffer(arc, psize, &buf); + ok(hr == S_OK, "GetBuffer failed: %08lx\n", hr); + ok(buf != NULL, "NULL buffer returned\n"); + + hr = IAudioRenderClient_ReleaseBuffer(arc, psize, + AUDCLNT_BUFFERFLAGS_SILENT); + ok(hr == S_OK, "ReleaseBuffer failed: %08lx\n", hr); + written += psize; + + hr = IAudioClient_GetCurrentPadding(ac, &pad); + ok(hr == S_OK, "GetCurrentPadding failed: %08lx\n", hr); + ok(pad == written, "GetCurrentPadding returned %u, should be %u\n", pad, written); + + /* overfull buffer. requested 1/2s buffer size, so try + * to get a 1/2s buffer, which should fail */ + psize = pwfx->nSamplesPerSec / 2; + buf = (void*)0xDEADF00D; + hr = IAudioRenderClient_GetBuffer(arc, psize, &buf); + ok(hr == AUDCLNT_E_BUFFER_TOO_LARGE, "GetBuffer gave wrong error: %08lx\n", hr); + ok(buf == NULL, "NULL expected %p\n", buf); + + hr = IAudioRenderClient_ReleaseBuffer(arc, psize, 0); + ok(hr == AUDCLNT_E_OUT_OF_ORDER, "ReleaseBuffer gave wrong error: %08lx\n", hr); + + psize = MulDiv(minp, pwfx->nSamplesPerSec, 10000000) * 2; + + hr = IAudioRenderClient_GetBuffer(arc, psize, &buf); + ok(hr == S_OK, "GetBuffer failed: %08lx\n", hr); + ok(buf != NULL, "NULL buffer returned\n"); + + hr = IAudioRenderClient_ReleaseBuffer(arc, 0, 0); + ok(hr == S_OK, "ReleaseBuffer 0 gave wrong error: %08lx\n", hr); + + buf = (void*)0xDEADF00D; + hr = IAudioRenderClient_GetBuffer(arc, 0, &buf); + ok(hr == S_OK, "GetBuffer 0 size failed: %08lx\n", hr); + ok(buf == NULL, "GetBuffer 0 gave %p\n", buf); + /* MSDN instead documents buf remains untouched */ + + buf = (void*)0xDEADF00D; + hr = IAudioRenderClient_GetBuffer(arc, 0, &buf); + ok(hr == S_OK, "GetBuffer 0 size #2 failed: %08lx\n", hr); + ok(buf == NULL, "GetBuffer 0 #2 gave %p\n", buf); + + hr = IAudioRenderClient_ReleaseBuffer(arc, psize, 0); + ok(hr == AUDCLNT_E_OUT_OF_ORDER, "ReleaseBuffer not size 0 gave %08lx\n", hr); + + hr = IAudioRenderClient_GetBuffer(arc, psize, &buf); + ok(hr == S_OK, "GetBuffer failed: %08lx\n", hr); + ok(buf != NULL, "NULL buffer returned\n"); + + hr = IAudioRenderClient_ReleaseBuffer(arc, 0, 0); + ok(hr == S_OK, "ReleaseBuffer 0 gave wrong error: %08lx\n", hr); + + hr = IAudioClient_GetCurrentPadding(ac, &pad); + ok(hr == S_OK, "GetCurrentPadding failed: %08lx\n", hr); + ok(pad == written, "GetCurrentPadding returned %u, should be %u\n", pad, written); + + hr = IAudioRenderClient_GetBuffer(arc, psize, &buf); + ok(hr == S_OK, "GetBuffer failed: %08lx\n", hr); + ok(buf != NULL, "NULL buffer returned\n"); + + hr = IAudioRenderClient_ReleaseBuffer(arc, psize+1, AUDCLNT_BUFFERFLAGS_SILENT); + ok(hr == AUDCLNT_E_INVALID_SIZE, "ReleaseBuffer too large error: %08lx\n", hr); + /* todo_wine means Wine may overwrite memory */ + if(hr == S_OK) written += psize+1; + + /* Buffer still hold */ + hr = IAudioRenderClient_ReleaseBuffer(arc, psize/2, AUDCLNT_BUFFERFLAGS_SILENT); + ok(hr == S_OK, "ReleaseBuffer after error: %08lx\n", hr); + if(hr == S_OK) written += psize/2; + + hr = IAudioRenderClient_ReleaseBuffer(arc, 0, 0); + ok(hr == S_OK, "ReleaseBuffer 0 gave wrong error: %08lx\n", hr); + + hr = IAudioClient_GetCurrentPadding(ac, &pad); + ok(hr == S_OK, "GetCurrentPadding failed: %08lx\n", hr); + ok(pad == written, "GetCurrentPadding returned %u, should be %u\n", pad, written); + + CoTaskMemFree(pwfx); + + IAudioRenderClient_Release(arc); + IAudioClient_Release(ac); +} + +static void test_clock(int share) +{ + HRESULT hr; + IAudioClient *ac; + IAudioClock *acl; + IAudioRenderClient *arc; + UINT64 freq, pos, pcpos0, pcpos, last; + UINT32 pad, gbsize, bufsize, fragment, parts, avail, slept = 0, sum = 0; + BYTE *data; + WAVEFORMATEX *pwfx; + LARGE_INTEGER hpctime, hpctime0, hpcfreq; + REFERENCE_TIME minp, defp, t1, t2; + REFERENCE_TIME duration = 5000000, period = 150000; + int i; + + ok(QueryPerformanceFrequency(&hpcfreq), "PerfFrequency failed\n"); + + hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, + NULL, (void**)&ac); + ok(hr == S_OK, "Activation failed with %08lx\n", hr); + if(hr != S_OK) + return; + + hr = IAudioClient_GetMixFormat(ac, &pwfx); + ok(hr == S_OK, "GetMixFormat failed: %08lx\n", hr); + if(hr != S_OK) + return; + + hr = IAudioClient_GetDevicePeriod(ac, &defp, &minp); + ok(hr == S_OK, "GetDevicePeriod failed: %08lx\n", hr); + ok(minp <= period, "desired period %lu too small for %lu\n", (ULONG)period, (ULONG)minp); + + if (share) { + trace("Testing shared mode\n"); + /* period is ignored */ + hr = IAudioClient_Initialize(ac, AUDCLNT_SHAREMODE_SHARED, + 0, duration, period, pwfx, NULL); + period = defp; + } else { + pwfx->wFormatTag = WAVE_FORMAT_PCM; + pwfx->nChannels = 2; + pwfx->cbSize = 0; + pwfx->wBitsPerSample = 16; /* no floating point */ + pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8; + pwfx->nAvgBytesPerSec = pwfx->nSamplesPerSec * pwfx->nBlockAlign; + trace("Testing exclusive mode at %lu\n", pwfx->nSamplesPerSec); + + hr = IAudioClient_Initialize(ac, AUDCLNT_SHAREMODE_EXCLUSIVE, + 0, duration, period, pwfx, NULL); + } + ok(share ? hr == S_OK : hr == hexcl || hr == AUDCLNT_E_DEVICE_IN_USE, "Initialize failed: %08lx\n", hr); + if (hr != S_OK) { + CoTaskMemFree(pwfx); + IAudioClient_Release(ac); + if(hr == AUDCLNT_E_DEVICE_IN_USE) + skip("Device in use, no %s access\n", share ? "shared" : "exclusive"); + return; + } + + /** GetStreamLatency + * Shared mode: 1x period + a little, but some 192000 devices return 5.3334ms. + * Exclusive mode: testbot returns 2x period + a little, but + * some HDA drivers return 1x period, some + a little. */ + hr = IAudioClient_GetStreamLatency(ac, &t2); + ok(hr == S_OK, "GetStreamLatency failed: %08lx\n", hr); + trace("Latency: %u.%04u ms\n", (UINT)(t2/10000), (UINT)(t2 % 10000)); + ok(t2 >= period || broken(t2 >= period/2 && share && pwfx->nSamplesPerSec > 48000) || + broken(t2 == 0) /* win10 */, + "Latency < default period, delta %ldus\n", (long)((t2-period)/10)); + + /** GetBufferSize + * BufferSize must be rounded up, maximum 2s says MSDN. + * Both is wrong. Rounding may lead to size a little smaller than duration; + * duration > 2s is accepted in shared mode. + * Shared mode: round solely w.r.t. mixer rate, + * duration is no multiple of period. + * Exclusive mode: size appears as a multiple of some fragment that + * is either the rounded period or a fixed constant like 1024, + * whatever the driver implements. */ + hr = IAudioClient_GetBufferSize(ac, &gbsize); + ok(hr == S_OK, "GetBufferSize failed: %08lx\n", hr); + + bufsize = MulDiv(duration, pwfx->nSamplesPerSec, 10000000); + fragment = MulDiv(period, pwfx->nSamplesPerSec, 10000000); + parts = MulDiv(bufsize, 1, fragment); /* instead of (duration, 1, period) */ + trace("BufferSize %u estimated fragment %u x %u = %u\n", gbsize, fragment, parts, fragment * parts); + /* fragment size (= period in frames) is rounded up. + * BufferSize must be rounded up, maximum 2s says MSDN + * but it is rounded down modulo fragment ! */ + if (share) + ok(gbsize == bufsize, + "BufferSize %u at rate %lu\n", gbsize, pwfx->nSamplesPerSec); + else + flaky + ok(gbsize == parts * fragment || gbsize == MulDiv(bufsize, 1, 1024) * 1024, + "BufferSize %u misfits fragment size %u at rate %lu\n", gbsize, fragment, pwfx->nSamplesPerSec); + + /* In shared mode, GetCurrentPadding decreases in multiples of + * fragment size (i.e. updated only at period ticks), whereas + * GetPosition appears to be reporting continuous positions. + * In exclusive mode, testbot behaves likewise, but native's Intel + * HDA driver shows no such deltas, GetCurrentPadding closely + * matches GetPosition, as in + * GetCurrentPadding = GetPosition - frames held in mmdevapi */ + + hr = IAudioClient_GetService(ac, &IID_IAudioClock, (void**)&acl); + ok(hr == S_OK, "GetService(IAudioClock) failed: %08lx\n", hr); + + hr = IAudioClock_GetFrequency(acl, &freq); + ok(hr == S_OK, "GetFrequency failed: %08lx\n", hr); + trace("Clock Frequency %u\n", (UINT)freq); + + /* MSDN says it's arbitrary units, but shared mode is unlikely to change */ + if (share) + ok(freq == pwfx->nSamplesPerSec * pwfx->nBlockAlign, + "Clock Frequency %u\n", (UINT)freq); + else + ok(freq == pwfx->nSamplesPerSec, + "Clock Frequency %u\n", (UINT)freq); + + hr = IAudioClock_GetPosition(acl, NULL, NULL); + ok(hr == E_POINTER, "GetPosition wrong error: %08lx\n", hr); + + pcpos0 = 0; + hr = IAudioClock_GetPosition(acl, &pos, &pcpos0); + ok(hr == S_OK, "GetPosition failed: %08lx\n", hr); + ok(pos == 0, "GetPosition returned non-zero pos before being started\n"); + ok(pcpos0 != 0, "GetPosition returned zero pcpos\n"); + + hr = IAudioClient_GetService(ac, &IID_IAudioRenderClient, (void**)&arc); + ok(hr == S_OK, "GetService(IAudioRenderClient) failed: %08lx\n", hr); + + hr = IAudioRenderClient_GetBuffer(arc, gbsize+1, &data); + ok(hr == AUDCLNT_E_BUFFER_TOO_LARGE, "GetBuffer too large failed: %08lx\n", hr); + + avail = gbsize; + data = NULL; + hr = IAudioRenderClient_GetBuffer(arc, avail, &data); + ok(hr == S_OK, "GetBuffer failed: %08lx\n", hr); + trace("data at %p\n", data); + + hr = IAudioRenderClient_ReleaseBuffer(arc, avail, winetest_debug>2 ? + wave_generate_tone(pwfx, data, avail) : AUDCLNT_BUFFERFLAGS_SILENT); + ok(hr == S_OK, "ReleaseBuffer failed: %08lx\n", hr); + if(hr == S_OK) sum += avail; + + hr = IAudioClient_GetCurrentPadding(ac, &pad); + ok(hr == S_OK, "GetCurrentPadding failed: %08lx\n", hr); + ok(pad == sum, "padding %u prior to start\n", pad); + + hr = IAudioClock_GetPosition(acl, &pos, NULL); + ok(hr == S_OK, "GetPosition failed: %08lx\n", hr); + ok(pos == 0, "GetPosition returned non-zero pos before being started\n"); + + hr = IAudioClient_Start(ac); /* #1 */ + ok(hr == S_OK, "Start failed: %08lx\n", hr); + + Sleep(100); + slept += 100; + + hr = IAudioClient_GetStreamLatency(ac, &t1); + ok(hr == S_OK, "GetStreamLatency failed: %08lx\n", hr); + ok(t1 == t2, "Latency not constant, delta %ld\n", (long)(t1-t2)); + + hr = IAudioClock_GetPosition(acl, &pos, NULL); + ok(hr == S_OK, "GetPosition failed: %08lx\n", hr); + ok(pos > 0, "Position %u vs. last %u\n", (UINT)pos,0); + /* in rare cases is slept*1.1 not enough with dmix */ + flaky + ok(pos*1000/freq <= slept*1.4, "Position %u too far after playing %ums\n", (UINT)pos, slept); + last = pos; + + hr = IAudioClient_Stop(ac); + ok(hr == S_OK, "Stop failed: %08lx\n", hr); + + hr = IAudioClock_GetPosition(acl, &pos, NULL); + ok(hr == S_OK, "GetPosition failed: %08lx\n", hr); + ok(pos >= last, "Position %u vs. last %u\n", (UINT)pos,(UINT)last); + last = pos; + if(/*share &&*/ winetest_debug>1) + ok(pos*1000/freq <= slept*1.1, "Position %u too far after stop %ums\n", (UINT)pos, slept); + + hr = IAudioClient_Start(ac); /* #2 */ + ok(hr == S_OK, "Start failed: %08lx\n", hr); + + Sleep(100); + slept += 100; + + hr = IAudioClient_GetCurrentPadding(ac, &pad); + ok(hr == S_OK, "GetCurrentPadding failed: %08lx\n", hr); + trace("padding %u past sleep #2\n", pad); + + /** IAudioClient_Stop + * Exclusive mode: the audio engine appears to drop frames, + * bumping GetPosition to a higher value than time allows, even + * allowing GetPosition > sum Released - GetCurrentPadding (testbot) + * Shared mode: no drop observed (or too small to be visible). + * GetPosition = sum Released - GetCurrentPadding + * Bugs: Some USB headset system drained the whole buffer, leaving + * padding 0 and bumping pos to sum minus 17 frames! */ + + hr = IAudioClient_Stop(ac); + ok(hr == S_OK, "Stop failed: %08lx\n", hr); + + hr = IAudioClient_GetCurrentPadding(ac, &pad); + ok(hr == S_OK, "GetCurrentPadding failed: %08lx\n", hr); + + hr = IAudioClock_GetPosition(acl, &pos, NULL); + ok(hr == S_OK, "GetPosition failed: %08lx\n", hr); + trace("padding %u position %u past stop #2\n", pad, (UINT)pos); + ok(pos * pwfx->nSamplesPerSec <= sum * freq, "Position %u > written %u\n", (UINT)pos, sum); + /* Prove that Stop must not drop frames (in shared mode). */ + ok(pad ? pos > last : pos >= last, "Position %u vs. last %u\n", (UINT)pos,(UINT)last); + if (share && pad > 0 && winetest_debug>1) + ok(pos*1000/freq <= slept*1.1, "Position %u too far after playing %ums\n", (UINT)pos, slept); + /* in exclusive mode, testbot's w7 machines yield pos > sum-pad */ + if(/*share &&*/ winetest_debug>1) + ok(pos * pwfx->nSamplesPerSec == (sum-pad) * freq, + "Position %u after stop vs. %u padding\n", (UINT)pos, pad); + last = pos; + + Sleep(100); + + hr = IAudioClock_GetPosition(acl, &pos, NULL); + ok(hr == S_OK, "GetPosition failed: %08lx\n", hr); + ok(pos == last, "Position %u should stop.\n", (UINT)pos); + + /* Restart from 0 */ + hr = IAudioClient_Reset(ac); + ok(hr == S_OK, "Reset failed: %08lx\n", hr); + slept = sum = 0; + + hr = IAudioClient_Reset(ac); + ok(hr == S_OK, "Reset on an already reset stream returns %08lx\n", hr); + + hr = IAudioClock_GetPosition(acl, &pos, &pcpos); + ok(hr == S_OK, "GetPosition failed: %08lx\n", hr); + ok(pos == 0, "GetPosition returned non-zero pos after Reset\n"); + ok(pcpos > pcpos0, "pcpos should increase\n"); + + avail = gbsize; /* implies GetCurrentPadding == 0 */ + hr = IAudioRenderClient_GetBuffer(arc, avail, &data); + ok(hr == S_OK, "GetBuffer failed: %08lx\n", hr); + trace("data at %p\n", data); + + hr = IAudioRenderClient_ReleaseBuffer(arc, avail, winetest_debug>2 ? + wave_generate_tone(pwfx, data, avail) : AUDCLNT_BUFFERFLAGS_SILENT); + ok(hr == S_OK, "ReleaseBuffer failed: %08lx\n", hr); + if(hr == S_OK) sum += avail; + + hr = IAudioClient_GetCurrentPadding(ac, &pad); + ok(hr == S_OK, "GetCurrentPadding failed: %08lx\n", hr); + ok(pad == sum, "padding %u prior to start\n", pad); + + hr = IAudioClock_GetPosition(acl, &pos, NULL); + ok(hr == S_OK, "GetPosition failed: %08lx\n", hr); + ok(pos == 0, "GetPosition returned non-zero pos after Reset\n"); + last = pos; + + hr = IAudioClient_Start(ac); /* #3 */ + ok(hr == S_OK, "Start failed: %08lx\n", hr); + + Sleep(100); + slept += 100; + + hr = IAudioClock_GetPosition(acl, &pos, NULL); + ok(hr == S_OK, "GetPosition failed: %08lx\n", hr); + trace("position %u past %ums sleep #3\n", (UINT)pos, slept); + ok(pos > last, "Position %u vs. last %u\n", (UINT)pos,(UINT)last); + ok(pos * pwfx->nSamplesPerSec <= sum * freq, "Position %u > written %u\n", (UINT)pos, sum); + if (winetest_debug>1) + ok(pos*1000/freq <= slept*1.1, "Position %u too far after playing %ums\n", (UINT)pos, slept); + else + skip("Rerun with WINETEST_DEBUG=2 for GetPosition tests.\n"); + last = pos; + + hr = IAudioClient_Reset(ac); + ok(hr == AUDCLNT_E_NOT_STOPPED, "Reset while playing: %08lx\n", hr); + + hr = IAudioClient_Stop(ac); + ok(hr == S_OK, "Stop failed: %08lx\n", hr); + + hr = IAudioClient_GetCurrentPadding(ac, &pad); + ok(hr == S_OK, "GetCurrentPadding failed: %08lx\n", hr); + + hr = IAudioClock_GetPosition(acl, &pos, &pcpos); + ok(hr == S_OK, "GetPosition failed: %08lx\n", hr); + trace("padding %u position %u past stop #3\n", pad, (UINT)pos); + ok(pos >= last, "Position %u vs. last %u\n", (UINT)pos,(UINT)last); + ok(pcpos > pcpos0, "pcpos should increase\n"); + ok(pos * pwfx->nSamplesPerSec <= sum * freq, "Position %u > written %u\n", (UINT)pos, sum); + if (pad > 0 && winetest_debug>1) + ok(pos*1000/freq <= slept*1.1, "Position %u too far after stop %ums\n", (UINT)pos, slept); + if(winetest_debug>1) + ok(pos * pwfx->nSamplesPerSec == (sum-pad) * freq, + "Position %u after stop vs. %u padding\n", (UINT)pos, pad); + last = pos; + + /* Begin the big loop */ + hr = IAudioClient_Reset(ac); + ok(hr == S_OK, "Reset failed: %08lx\n", hr); + slept = last = sum = 0; + pcpos0 = pcpos; + + ok(QueryPerformanceCounter(&hpctime0), "PerfCounter unavailable\n"); + + hr = IAudioClient_Reset(ac); + ok(hr == S_OK, "Reset on an already reset stream returns %08lx\n", hr); + + hr = IAudioClient_Start(ac); + ok(hr == S_OK, "Start failed: %08lx\n", hr); + + avail = pwfx->nSamplesPerSec * 15 / 16 / 2; + data = NULL; + hr = IAudioRenderClient_GetBuffer(arc, avail, &data); + ok(hr == S_OK, "GetBuffer failed: %08lx\n", hr); + trace("data at %p for prefill %u\n", data, avail); + + if (winetest_debug>2) { + hr = IAudioClient_Stop(ac); + ok(hr == S_OK, "Stop failed: %08lx\n", hr); + + Sleep(20); + slept += 20; + + hr = IAudioClient_Reset(ac); + ok(hr == AUDCLNT_E_BUFFER_OPERATION_PENDING, "Reset failed: %08lx\n", hr); + + hr = IAudioClient_Start(ac); + ok(hr == S_OK, "Start failed: %08lx\n", hr); + } + + /* Despite passed time, data must still point to valid memory... */ + hr = IAudioRenderClient_ReleaseBuffer(arc, avail, + wave_generate_tone(pwfx, data, avail)); + ok(hr == S_OK, "ReleaseBuffer after stop+start failed: %08lx\n", hr); + if(hr == S_OK) sum += avail; + + /* GetCurrentPadding(GCP) == 0 does not mean an underrun happened, as the + * mixer may still have a little data. We believe an underrun will occur + * when the mixer finds GCP smaller than a period size at the *end* of a + * period cycle, i.e. shortly before calling SetEvent to signal the app + * that it has ~10ms to supply data for the next cycle. IOW, a zero GCP + * with no data written for over a period causes an underrun. */ + + Sleep(350); + slept += 350; + ok(QueryPerformanceCounter(&hpctime), "PerfCounter failed\n"); + trace("hpctime %lu after %ums\n", + (ULONG)((hpctime.QuadPart-hpctime0.QuadPart)*1000/hpcfreq.QuadPart), slept); + + hr = IAudioClock_GetPosition(acl, &pos, &pcpos); + ok(hr == S_OK, "GetPosition failed: %08lx\n", hr); + ok(pos > last, "Position %u vs. last %u\n", (UINT)pos,(UINT)last); + last = pos; + + for(i=0; i < 9; i++) { + Sleep(100); + slept += 100; + + hr = IAudioClock_GetPosition(acl, &pos, &pcpos); + ok(hr == S_OK, "GetPosition failed: %08lx\n", hr); + + hr = IAudioClient_GetCurrentPadding(ac, &pad); + ok(hr == S_OK, "GetCurrentPadding failed: %08lx\n", hr); + + ok(QueryPerformanceCounter(&hpctime), "PerfCounter failed\n"); + trace("hpctime %lu pcpos %lu\n", + (ULONG)((hpctime.QuadPart-hpctime0.QuadPart)*1000/hpcfreq.QuadPart), + (ULONG)((pcpos-pcpos0)/10000)); + + /* Use sum-pad to see whether position is ahead padding or not. */ + trace("padding %u position %u/%u slept %ums iteration %d\n", pad, (UINT)pos, sum-pad, slept, i); + ok(pad ? pos > last : pos >= last, "No position increase at iteration %d\n", i); + ok(pos * pwfx->nSamplesPerSec <= sum * freq, "Position %u > written %u\n", (UINT)pos, sum); + if (winetest_debug>1) { + /* Padding does not lag behind by much */ + ok(pos * pwfx->nSamplesPerSec <= (sum-pad+fragment) * freq, "Position %u > written %u\n", (UINT)pos, sum); + ok(pos*1000/freq <= slept*1.1, "Position %u too far after %ums\n", (UINT)pos, slept); + if (pad) /* not in case of underrun */ + ok((pos-last)*1000/freq >= 90 && 110 >= (pos-last)*1000/freq, + "Position delta %ld not regular: %ld ms\n", (long)(pos-last), (long)((pos-last)*1000/freq)); + } + last = pos; + + hr = IAudioClient_GetStreamLatency(ac, &t1); + ok(hr == S_OK, "GetStreamLatency failed: %08lx\n", hr); + ok(t1 == t2, "Latency not constant, delta %ld\n", (long)(t1-t2)); + + avail = pwfx->nSamplesPerSec * 15 / 16 / 2; + data = NULL; + hr = IAudioRenderClient_GetBuffer(arc, avail, &data); + /* ok(hr == AUDCLNT_E_BUFFER_TOO_LARGE || (hr == S_OK && i==0) without todo_wine */ + ok(hr == S_OK || hr == AUDCLNT_E_BUFFER_TOO_LARGE, + "GetBuffer large (%u) failed: %08lx\n", avail, hr); + if(hr == S_OK && i) ok(FALSE, "GetBuffer large (%u) at iteration %d\n", avail, i); + /* Only the first iteration should allow that large a buffer + * as prefill was drained during the first 350+100ms sleep. + * Afterwards, only 100ms of data should find room per iteration. */ + + if(hr == S_OK) { + trace("data at %p\n", data); + } else { + avail = gbsize - pad; + hr = IAudioRenderClient_GetBuffer(arc, avail, &data); + ok(hr == S_OK, "GetBuffer small %u failed: %08lx\n", avail, hr); + trace("data at %p (small %u)\n", data, avail); + } + ok(data != NULL, "NULL buffer returned\n"); + if(i % 3 && !winetest_interactive) { + memset(data, 0, avail * pwfx->nBlockAlign); + hr = IAudioRenderClient_ReleaseBuffer(arc, avail, 0); + } else { + hr = IAudioRenderClient_ReleaseBuffer(arc, avail, + wave_generate_tone(pwfx, data, avail)); + } + ok(hr == S_OK, "ReleaseBuffer failed: %08lx\n", hr); + if(hr == S_OK) sum += avail; + } + + hr = IAudioClock_GetPosition(acl, &pos, NULL); + ok(hr == S_OK, "GetPosition failed: %08lx\n", hr); + trace("position %u\n", (UINT)pos); + + Sleep(1000); /* 500ms buffer underrun past full buffer */ + + hr = IAudioClient_GetCurrentPadding(ac, &pad); + ok(hr == S_OK, "GetCurrentPadding failed: %08lx\n", hr); + + hr = IAudioClock_GetPosition(acl, &pos, NULL); + ok(hr == S_OK, "GetPosition failed: %08lx\n", hr); + trace("position %u past underrun, %u padding left, %u frames written\n", (UINT)pos, pad, sum); + + if (share) { + /* Following underrun, all samples were played */ + ok(pad == 0, "GetCurrentPadding returned %u, should be 0\n", pad); + ok(pos * pwfx->nSamplesPerSec == sum * freq, + "Position %u at end vs. %u submitted frames\n", (UINT)pos, sum); + } else { + /* Vista and w2k8 leave partial fragments behind */ + ok(pad == 0 /* w7, w2k8R2 */|| + pos * pwfx->nSamplesPerSec == (sum-pad) * freq, "GetCurrentPadding returned %u, should be 0\n", pad); + /* expect at most 5 fragments (75ms) away */ + ok(pos * pwfx->nSamplesPerSec <= sum * freq && + pos * pwfx->nSamplesPerSec + 5 * fragment * freq >= sum * freq, + "Position %u at end vs. %u submitted frames\n", (UINT)pos, sum); + } + + hr = IAudioClient_GetStreamLatency(ac, &t1); + ok(hr == S_OK, "GetStreamLatency failed: %08lx\n", hr); + ok(t1 == t2, "Latency not constant, delta %ld\n", (long)(t1-t2)); + + ok(QueryPerformanceCounter(&hpctime), "PerfCounter failed\n"); + trace("hpctime %lu after underrun\n", (ULONG)((hpctime.QuadPart-hpctime0.QuadPart)*1000/hpcfreq.QuadPart)); + + hr = IAudioClient_Stop(ac); + ok(hr == S_OK, "Stop failed: %08lx\n", hr); + + CoTaskMemFree(pwfx); + + IAudioClock_Release(acl); + IAudioRenderClient_Release(arc); + IAudioClient_Release(ac); +} + +static void test_session(void) +{ + IAudioClient *ses1_ac1, *ses1_ac2, *cap_ac; + IAudioSessionControl2 *ses1_ctl, *ses1_ctl2, *cap_ctl = NULL; + IMMDevice *cap_dev; + GUID ses1_guid; + AudioSessionState state; + WAVEFORMATEX *pwfx; + ULONG ref; + HRESULT hr; + + hr = CoCreateGuid(&ses1_guid); + ok(hr == S_OK, "CoCreateGuid failed: %08lx\n", hr); + + hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, + NULL, (void**)&ses1_ac1); + ok(hr == S_OK, "Activation failed with %08lx\n", hr); + if (FAILED(hr)) return; + + hr = IAudioClient_GetMixFormat(ses1_ac1, &pwfx); + ok(hr == S_OK, "GetMixFormat failed: %08lx\n", hr); + + hr = IAudioClient_Initialize(ses1_ac1, AUDCLNT_SHAREMODE_SHARED, + 0, 5000000, 0, pwfx, &ses1_guid); + ok(hr == S_OK, "Initialize failed: %08lx\n", hr); + + if(hr == S_OK){ + hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, + NULL, (void**)&ses1_ac2); + ok(hr == S_OK, "Activation failed with %08lx\n", hr); + } + if(hr != S_OK){ + skip("Unable to open the same device twice. Skipping session tests\n"); + + ref = IAudioClient_Release(ses1_ac1); + ok(ref == 0, "AudioClient wasn't released: %lu\n", ref); + CoTaskMemFree(pwfx); + return; + } + + hr = IAudioClient_Initialize(ses1_ac2, AUDCLNT_SHAREMODE_SHARED, + 0, 5000000, 0, pwfx, &ses1_guid); + ok(hr == S_OK, "Initialize failed: %08lx\n", hr); + + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(mme, eCapture, + eMultimedia, &cap_dev); + if(hr == S_OK){ + hr = IMMDevice_Activate(cap_dev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, + NULL, (void**)&cap_ac); + ok((hr == S_OK)^(cap_ac == NULL), "Activate %08lx &out pointer\n", hr); + ok(hr == S_OK, "Activate failed: %08lx\n", hr); + IMMDevice_Release(cap_dev); + } + if(hr == S_OK){ + WAVEFORMATEX *cap_pwfx; + + hr = IAudioClient_GetMixFormat(cap_ac, &cap_pwfx); + ok(hr == S_OK, "GetMixFormat failed: %08lx\n", hr); + + hr = IAudioClient_Initialize(cap_ac, AUDCLNT_SHAREMODE_SHARED, + 0, 5000000, 0, cap_pwfx, &ses1_guid); + ok(hr == S_OK, "Initialize failed for capture in rendering session: %08lx\n", hr); + CoTaskMemFree(cap_pwfx); + } + if(hr == S_OK){ + hr = IAudioClient_GetService(cap_ac, &IID_IAudioSessionControl, (void**)&cap_ctl); + ok(hr == S_OK, "GetService failed: %08lx\n", hr); + if(FAILED(hr)) + cap_ctl = NULL; + }else + skip("No capture session: %08lx; skipping capture device in render session tests\n", hr); + + hr = IAudioClient_GetService(ses1_ac1, &IID_IAudioSessionControl2, (void**)&ses1_ctl); + ok(hr == E_NOINTERFACE, "GetService gave wrong error: %08lx\n", hr); + + hr = IAudioClient_GetService(ses1_ac1, &IID_IAudioSessionControl, (void**)&ses1_ctl); + ok(hr == S_OK, "GetService failed: %08lx\n", hr); + + hr = IAudioClient_GetService(ses1_ac1, &IID_IAudioSessionControl, (void**)&ses1_ctl2); + ok(hr == S_OK, "GetService failed: %08lx\n", hr); + ok(ses1_ctl == ses1_ctl2, "Got different controls: %p %p\n", ses1_ctl, ses1_ctl2); + ref = IAudioSessionControl2_Release(ses1_ctl2); + ok(ref != 0, "AudioSessionControl was destroyed\n"); + + hr = IAudioClient_GetService(ses1_ac2, &IID_IAudioSessionControl, (void**)&ses1_ctl2); + ok(hr == S_OK, "GetService failed: %08lx\n", hr); + + hr = IAudioSessionControl2_GetState(ses1_ctl, NULL); + ok(hr == NULL_PTR_ERR, "GetState gave wrong error: %08lx\n", hr); + + hr = IAudioSessionControl2_GetState(ses1_ctl, &state); + ok(hr == S_OK, "GetState failed: %08lx\n", hr); + ok(state == AudioSessionStateInactive, "Got wrong state: %d\n", state); + + hr = IAudioSessionControl2_GetState(ses1_ctl2, &state); + ok(hr == S_OK, "GetState failed: %08lx\n", hr); + ok(state == AudioSessionStateInactive, "Got wrong state: %d\n", state); + + if(cap_ctl){ + hr = IAudioSessionControl2_GetState(cap_ctl, &state); + ok(hr == S_OK, "GetState failed: %08lx\n", hr); + ok(state == AudioSessionStateInactive, "Got wrong state: %d\n", state); + } + + hr = IAudioClient_Start(ses1_ac1); + ok(hr == S_OK, "Start failed: %08lx\n", hr); + + hr = IAudioSessionControl2_GetState(ses1_ctl, &state); + ok(hr == S_OK, "GetState failed: %08lx\n", hr); + ok(state == AudioSessionStateActive, "Got wrong state: %d\n", state); + + hr = IAudioSessionControl2_GetState(ses1_ctl2, &state); + ok(hr == S_OK, "GetState failed: %08lx\n", hr); + ok(state == AudioSessionStateActive, "Got wrong state: %d\n", state); + + if(cap_ctl){ + hr = IAudioSessionControl2_GetState(cap_ctl, &state); + ok(hr == S_OK, "GetState failed: %08lx\n", hr); + ok(state == AudioSessionStateInactive, "Got wrong state: %d\n", state); + } + + hr = IAudioClient_Stop(ses1_ac1); + ok(hr == S_OK, "Stop failed: %08lx\n", hr); + + hr = IAudioSessionControl2_GetState(ses1_ctl, &state); + ok(hr == S_OK, "GetState failed: %08lx\n", hr); + ok(state == AudioSessionStateInactive, "Got wrong state: %d\n", state); + + hr = IAudioSessionControl2_GetState(ses1_ctl2, &state); + ok(hr == S_OK, "GetState failed: %08lx\n", hr); + ok(state == AudioSessionStateInactive, "Got wrong state: %d\n", state); + + if(cap_ctl){ + hr = IAudioSessionControl2_GetState(cap_ctl, &state); + ok(hr == S_OK, "GetState failed: %08lx\n", hr); + ok(state == AudioSessionStateInactive, "Got wrong state: %d\n", state); + + hr = IAudioClient_Start(cap_ac); + ok(hr == S_OK, "Start failed: %08lx\n", hr); + + hr = IAudioSessionControl2_GetState(ses1_ctl, &state); + ok(hr == S_OK, "GetState failed: %08lx\n", hr); + ok(state == AudioSessionStateInactive, "Got wrong state: %d\n", state); + + hr = IAudioSessionControl2_GetState(ses1_ctl2, &state); + ok(hr == S_OK, "GetState failed: %08lx\n", hr); + ok(state == AudioSessionStateInactive, "Got wrong state: %d\n", state); + + hr = IAudioSessionControl2_GetState(cap_ctl, &state); + ok(hr == S_OK, "GetState failed: %08lx\n", hr); + ok(state == AudioSessionStateActive, "Got wrong state: %d\n", state); + + hr = IAudioClient_Stop(cap_ac); + ok(hr == S_OK, "Stop failed: %08lx\n", hr); + + hr = IAudioSessionControl2_GetState(ses1_ctl, &state); + ok(hr == S_OK, "GetState failed: %08lx\n", hr); + ok(state == AudioSessionStateInactive, "Got wrong state: %d\n", state); + + hr = IAudioSessionControl2_GetState(ses1_ctl2, &state); + ok(hr == S_OK, "GetState failed: %08lx\n", hr); + ok(state == AudioSessionStateInactive, "Got wrong state: %d\n", state); + + hr = IAudioSessionControl2_GetState(cap_ctl, &state); + ok(hr == S_OK, "GetState failed: %08lx\n", hr); + ok(state == AudioSessionStateInactive, "Got wrong state: %d\n", state); + + ref = IAudioSessionControl2_Release(cap_ctl); + ok(ref == 0, "AudioSessionControl wasn't released: %lu\n", ref); + + ref = IAudioClient_Release(cap_ac); + ok(ref == 0, "AudioClient wasn't released: %lu\n", ref); + } + + ref = IAudioSessionControl2_Release(ses1_ctl); + ok(ref == 0, "AudioSessionControl wasn't released: %lu\n", ref); + + ref = IAudioClient_Release(ses1_ac1); + ok(ref == 0, "AudioClient wasn't released: %lu\n", ref); + + ref = IAudioClient_Release(ses1_ac2); + ok(ref == 1, "AudioClient had wrong refcount: %lu\n", ref); + + /* we've released all of our IAudioClient references, so check GetState */ + hr = IAudioSessionControl2_GetState(ses1_ctl2, &state); + ok(hr == S_OK, "GetState failed: %08lx\n", hr); + ok(state == AudioSessionStateInactive, "Got wrong state: %d\n", state); + + ref = IAudioSessionControl2_Release(ses1_ctl2); + ok(ref == 0, "AudioSessionControl wasn't released: %lu\n", ref); + + CoTaskMemFree(pwfx); +} + +static void test_streamvolume(void) +{ + IAudioClient *ac; + IAudioStreamVolume *asv; + WAVEFORMATEX *fmt; + UINT32 chans, i; + HRESULT hr; + float vol, *vols; + + hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, + NULL, (void**)&ac); + ok(hr == S_OK, "Activation failed with %08lx\n", hr); + if(hr != S_OK) + return; + + hr = IAudioClient_GetMixFormat(ac, &fmt); + ok(hr == S_OK, "GetMixFormat failed: %08lx\n", hr); + + hr = IAudioClient_Initialize(ac, AUDCLNT_SHAREMODE_SHARED, 0, 5000000, + 0, fmt, NULL); + ok(hr == S_OK, "Initialize failed: %08lx\n", hr); + + if(hr == S_OK){ + hr = IAudioClient_GetService(ac, &IID_IAudioStreamVolume, (void**)&asv); + ok(hr == S_OK, "GetService failed: %08lx\n", hr); + } + if(hr != S_OK){ + IAudioClient_Release(ac); + CoTaskMemFree(fmt); + return; + } + + hr = IAudioStreamVolume_GetChannelCount(asv, NULL); + ok(hr == E_POINTER, "GetChannelCount gave wrong error: %08lx\n", hr); + + hr = IAudioStreamVolume_GetChannelCount(asv, &chans); + ok(hr == S_OK, "GetChannelCount failed: %08lx\n", hr); + ok(chans == fmt->nChannels, "GetChannelCount gave wrong number of channels: %d\n", chans); + + hr = IAudioStreamVolume_GetChannelVolume(asv, fmt->nChannels, NULL); + ok(hr == E_POINTER, "GetChannelVolume gave wrong error: %08lx\n", hr); + + hr = IAudioStreamVolume_GetChannelVolume(asv, fmt->nChannels, &vol); + ok(hr == E_INVALIDARG, "GetChannelVolume gave wrong error: %08lx\n", hr); + + hr = IAudioStreamVolume_GetChannelVolume(asv, 0, NULL); + ok(hr == E_POINTER, "GetChannelVolume gave wrong error: %08lx\n", hr); + + hr = IAudioStreamVolume_GetChannelVolume(asv, 0, &vol); + ok(hr == S_OK, "GetChannelVolume failed: %08lx\n", hr); + ok(vol == 1.f, "Channel volume was not 1: %f\n", vol); + + hr = IAudioStreamVolume_SetChannelVolume(asv, fmt->nChannels, -1.f); + ok(hr == E_INVALIDARG, "SetChannelVolume gave wrong error: %08lx\n", hr); + + hr = IAudioStreamVolume_SetChannelVolume(asv, 0, -1.f); + ok(hr == E_INVALIDARG, "SetChannelVolume gave wrong error: %08lx\n", hr); + + hr = IAudioStreamVolume_SetChannelVolume(asv, 0, 2.f); + ok(hr == E_INVALIDARG, "SetChannelVolume gave wrong error: %08lx\n", hr); + + hr = IAudioStreamVolume_SetChannelVolume(asv, 0, 0.2f); + ok(hr == S_OK, "SetChannelVolume failed: %08lx\n", hr); + + hr = IAudioStreamVolume_GetChannelVolume(asv, 0, &vol); + ok(hr == S_OK, "GetChannelVolume failed: %08lx\n", hr); + ok(fabsf(vol - 0.2f) < 0.05f, "Channel volume wasn't 0.2: %f\n", vol); + + hr = IAudioStreamVolume_GetAllVolumes(asv, 0, NULL); + ok(hr == E_POINTER, "GetAllVolumes gave wrong error: %08lx\n", hr); + + hr = IAudioStreamVolume_GetAllVolumes(asv, fmt->nChannels, NULL); + ok(hr == E_POINTER, "GetAllVolumes gave wrong error: %08lx\n", hr); + + vols = HeapAlloc(GetProcessHeap(), 0, fmt->nChannels * sizeof(float)); + ok(vols != NULL, "HeapAlloc failed\n"); + + hr = IAudioStreamVolume_GetAllVolumes(asv, fmt->nChannels - 1, vols); + ok(hr == E_INVALIDARG, "GetAllVolumes gave wrong error: %08lx\n", hr); + + hr = IAudioStreamVolume_GetAllVolumes(asv, fmt->nChannels, vols); + ok(hr == S_OK, "GetAllVolumes failed: %08lx\n", hr); + ok(fabsf(vols[0] - 0.2f) < 0.05f, "Channel 0 volume wasn't 0.2: %f\n", vol); + for(i = 1; i < fmt->nChannels; ++i) + ok(vols[i] == 1.f, "Channel %d volume is not 1: %f\n", i, vols[i]); + + hr = IAudioStreamVolume_SetAllVolumes(asv, 0, NULL); + ok(hr == E_POINTER, "SetAllVolumes gave wrong error: %08lx\n", hr); + + hr = IAudioStreamVolume_SetAllVolumes(asv, fmt->nChannels, NULL); + ok(hr == E_POINTER, "SetAllVolumes gave wrong error: %08lx\n", hr); + + hr = IAudioStreamVolume_SetAllVolumes(asv, fmt->nChannels - 1, vols); + ok(hr == E_INVALIDARG, "SetAllVolumes gave wrong error: %08lx\n", hr); + + hr = IAudioStreamVolume_SetAllVolumes(asv, fmt->nChannels, vols); + ok(hr == S_OK, "SetAllVolumes failed: %08lx\n", hr); + + HeapFree(GetProcessHeap(), 0, vols); + IAudioStreamVolume_Release(asv); + IAudioClient_Release(ac); + CoTaskMemFree(fmt); +} + +static void test_channelvolume(void) +{ + IAudioClient *ac; + IChannelAudioVolume *acv; + WAVEFORMATEX *fmt; + UINT32 chans, i; + HRESULT hr; + float vol, *vols; + + hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, + NULL, (void**)&ac); + ok(hr == S_OK, "Activation failed with %08lx\n", hr); + if(hr != S_OK) + return; + + hr = IAudioClient_GetMixFormat(ac, &fmt); + ok(hr == S_OK, "GetMixFormat failed: %08lx\n", hr); + + hr = IAudioClient_Initialize(ac, AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_NOPERSIST, 5000000, 0, fmt, NULL); + ok(hr == S_OK, "Initialize failed: %08lx\n", hr); + + if(hr == S_OK){ + hr = IAudioClient_GetService(ac, &IID_IChannelAudioVolume, (void**)&acv); + ok(hr == S_OK, "GetService failed: %08lx\n", hr); + } + if(hr != S_OK){ + IAudioClient_Release(ac); + CoTaskMemFree(fmt); + return; + } + + hr = IChannelAudioVolume_GetChannelCount(acv, NULL); + ok(hr == NULL_PTR_ERR, "GetChannelCount gave wrong error: %08lx\n", hr); + + hr = IChannelAudioVolume_GetChannelCount(acv, &chans); + ok(hr == S_OK, "GetChannelCount failed: %08lx\n", hr); + ok(chans == fmt->nChannels, "GetChannelCount gave wrong number of channels: %d\n", chans); + + hr = IChannelAudioVolume_GetChannelVolume(acv, fmt->nChannels, NULL); + ok(hr == NULL_PTR_ERR, "GetChannelVolume gave wrong error: %08lx\n", hr); + + hr = IChannelAudioVolume_GetChannelVolume(acv, fmt->nChannels, &vol); + ok(hr == E_INVALIDARG, "GetChannelVolume gave wrong error: %08lx\n", hr); + + hr = IChannelAudioVolume_GetChannelVolume(acv, 0, NULL); + ok(hr == NULL_PTR_ERR, "GetChannelVolume gave wrong error: %08lx\n", hr); + + hr = IChannelAudioVolume_GetChannelVolume(acv, 0, &vol); + ok(hr == S_OK, "GetChannelVolume failed: %08lx\n", hr); + ok(vol == 1.f, "Channel volume was not 1: %f\n", vol); + + hr = IChannelAudioVolume_SetChannelVolume(acv, fmt->nChannels, -1.f, NULL); + ok(hr == E_INVALIDARG, "SetChannelVolume gave wrong error: %08lx\n", hr); + + hr = IChannelAudioVolume_SetChannelVolume(acv, 0, -1.f, NULL); + ok(hr == E_INVALIDARG, "SetChannelVolume gave wrong error: %08lx\n", hr); + + hr = IChannelAudioVolume_SetChannelVolume(acv, 0, 2.f, NULL); + ok(hr == E_INVALIDARG, "SetChannelVolume gave wrong error: %08lx\n", hr); + + hr = IChannelAudioVolume_SetChannelVolume(acv, 0, 0.2f, NULL); + ok(hr == S_OK, "SetChannelVolume failed: %08lx\n", hr); + + hr = IChannelAudioVolume_GetChannelVolume(acv, 0, &vol); + ok(hr == S_OK, "GetChannelVolume failed: %08lx\n", hr); + ok(fabsf(vol - 0.2f) < 0.05f, "Channel volume wasn't 0.2: %f\n", vol); + + hr = IChannelAudioVolume_GetAllVolumes(acv, 0, NULL); + ok(hr == NULL_PTR_ERR, "GetAllVolumes gave wrong error: %08lx\n", hr); + + hr = IChannelAudioVolume_GetAllVolumes(acv, fmt->nChannels, NULL); + ok(hr == NULL_PTR_ERR, "GetAllVolumes gave wrong error: %08lx\n", hr); + + vols = HeapAlloc(GetProcessHeap(), 0, fmt->nChannels * sizeof(float)); + ok(vols != NULL, "HeapAlloc failed\n"); + + hr = IChannelAudioVolume_GetAllVolumes(acv, fmt->nChannels - 1, vols); + ok(hr == E_INVALIDARG, "GetAllVolumes gave wrong error: %08lx\n", hr); + + hr = IChannelAudioVolume_GetAllVolumes(acv, fmt->nChannels, vols); + ok(hr == S_OK, "GetAllVolumes failed: %08lx\n", hr); + ok(fabsf(vols[0] - 0.2f) < 0.05f, "Channel 0 volume wasn't 0.2: %f\n", vol); + for(i = 1; i < fmt->nChannels; ++i) + ok(vols[i] == 1.f, "Channel %d volume is not 1: %f\n", i, vols[i]); + + hr = IChannelAudioVolume_SetAllVolumes(acv, 0, NULL, NULL); + ok(hr == NULL_PTR_ERR, "SetAllVolumes gave wrong error: %08lx\n", hr); + + hr = IChannelAudioVolume_SetAllVolumes(acv, fmt->nChannels, NULL, NULL); + ok(hr == NULL_PTR_ERR, "SetAllVolumes gave wrong error: %08lx\n", hr); + + hr = IChannelAudioVolume_SetAllVolumes(acv, fmt->nChannels - 1, vols, NULL); + ok(hr == E_INVALIDARG, "SetAllVolumes gave wrong error: %08lx\n", hr); + + hr = IChannelAudioVolume_SetAllVolumes(acv, fmt->nChannels, vols, NULL); + ok(hr == S_OK, "SetAllVolumes failed: %08lx\n", hr); + + hr = IChannelAudioVolume_SetChannelVolume(acv, 0, 1.0f, NULL); + ok(hr == S_OK, "SetChannelVolume failed: %08lx\n", hr); + + HeapFree(GetProcessHeap(), 0, vols); + IChannelAudioVolume_Release(acv); + IAudioClient_Release(ac); + CoTaskMemFree(fmt); +} + +static void test_simplevolume(void) +{ + IAudioClient *ac; + ISimpleAudioVolume *sav; + WAVEFORMATEX *fmt; + HRESULT hr; + float vol; + BOOL mute; + + hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, + NULL, (void**)&ac); + ok(hr == S_OK, "Activation failed with %08lx\n", hr); + if(hr != S_OK) + return; + + hr = IAudioClient_GetMixFormat(ac, &fmt); + ok(hr == S_OK, "GetMixFormat failed: %08lx\n", hr); + + hr = IAudioClient_Initialize(ac, AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_NOPERSIST, 5000000, 0, fmt, NULL); + ok(hr == S_OK, "Initialize failed: %08lx\n", hr); + + if(hr == S_OK){ + hr = IAudioClient_GetService(ac, &IID_ISimpleAudioVolume, (void**)&sav); + ok(hr == S_OK, "GetService failed: %08lx\n", hr); + } + if(hr != S_OK){ + IAudioClient_Release(ac); + CoTaskMemFree(fmt); + return; + } + + hr = ISimpleAudioVolume_GetMasterVolume(sav, NULL); + ok(hr == NULL_PTR_ERR, "GetMasterVolume gave wrong error: %08lx\n", hr); + + hr = ISimpleAudioVolume_GetMasterVolume(sav, &vol); + ok(hr == S_OK, "GetMasterVolume failed: %08lx\n", hr); + ok(vol == 1.f, "Master volume wasn't 1: %f\n", vol); + + hr = ISimpleAudioVolume_SetMasterVolume(sav, -1.f, NULL); + ok(hr == E_INVALIDARG, "SetMasterVolume gave wrong error: %08lx\n", hr); + + hr = ISimpleAudioVolume_SetMasterVolume(sav, 2.f, NULL); + ok(hr == E_INVALIDARG, "SetMasterVolume gave wrong error: %08lx\n", hr); + + hr = ISimpleAudioVolume_SetMasterVolume(sav, 0.2f, NULL); + ok(hr == S_OK, "SetMasterVolume failed: %08lx\n", hr); + + hr = ISimpleAudioVolume_GetMasterVolume(sav, &vol); + ok(hr == S_OK, "GetMasterVolume failed: %08lx\n", hr); + ok(fabsf(vol - 0.2f) < 0.05f, "Master volume wasn't 0.2: %f\n", vol); + + hr = ISimpleAudioVolume_GetMute(sav, NULL); + ok(hr == NULL_PTR_ERR, "GetMute gave wrong error: %08lx\n", hr); + + mute = TRUE; + hr = ISimpleAudioVolume_GetMute(sav, &mute); + ok(hr == S_OK, "GetMute failed: %08lx\n", hr); + ok(mute == FALSE, "Session is already muted\n"); + + hr = ISimpleAudioVolume_SetMute(sav, TRUE, NULL); + ok(hr == S_OK, "SetMute failed: %08lx\n", hr); + + mute = FALSE; + hr = ISimpleAudioVolume_GetMute(sav, &mute); + ok(hr == S_OK, "GetMute failed: %08lx\n", hr); + ok(mute == TRUE, "Session should have been muted\n"); + + hr = ISimpleAudioVolume_GetMasterVolume(sav, &vol); + ok(hr == S_OK, "GetMasterVolume failed: %08lx\n", hr); + ok(fabsf(vol - 0.2f) < 0.05f, "Master volume wasn't 0.2: %f\n", vol); + + hr = ISimpleAudioVolume_SetMasterVolume(sav, 1.f, NULL); + ok(hr == S_OK, "SetMasterVolume failed: %08lx\n", hr); + + mute = FALSE; + hr = ISimpleAudioVolume_GetMute(sav, &mute); + ok(hr == S_OK, "GetMute failed: %08lx\n", hr); + ok(mute == TRUE, "Session should have been muted\n"); + + hr = ISimpleAudioVolume_SetMute(sav, FALSE, NULL); + ok(hr == S_OK, "SetMute failed: %08lx\n", hr); + + ISimpleAudioVolume_Release(sav); + IAudioClient_Release(ac); + CoTaskMemFree(fmt); +} + +static void test_volume_dependence(void) +{ + IAudioClient *ac, *ac2; + ISimpleAudioVolume *sav; + IChannelAudioVolume *cav; + IAudioStreamVolume *asv; + WAVEFORMATEX *fmt; + HRESULT hr; + float vol; + GUID session; + UINT32 nch; + + hr = CoCreateGuid(&session); + ok(hr == S_OK, "CoCreateGuid failed: %08lx\n", hr); + + hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, + NULL, (void**)&ac); + ok(hr == S_OK, "Activation failed with %08lx\n", hr); + if(hr != S_OK) + return; + + hr = IAudioClient_GetMixFormat(ac, &fmt); + ok(hr == S_OK, "GetMixFormat failed: %08lx\n", hr); + + hr = IAudioClient_Initialize(ac, AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_NOPERSIST, 5000000, 0, fmt, &session); + ok(hr == S_OK, "Initialize failed: %08lx\n", hr); + + if(hr == S_OK){ + hr = IAudioClient_GetService(ac, &IID_ISimpleAudioVolume, (void**)&sav); + ok(hr == S_OK, "GetService (SimpleAudioVolume) failed: %08lx\n", hr); + } + if(hr != S_OK){ + IAudioClient_Release(ac); + CoTaskMemFree(fmt); + return; + } + + hr = IAudioClient_GetService(ac, &IID_IChannelAudioVolume, (void**)&cav); + ok(hr == S_OK, "GetService (ChannelAudioVolume) failed: %08lx\n", hr); + + hr = IAudioClient_GetService(ac, &IID_IAudioStreamVolume, (void**)&asv); + ok(hr == S_OK, "GetService (AudioStreamVolume) failed: %08lx\n", hr); + + hr = IAudioStreamVolume_SetChannelVolume(asv, 0, 0.2f); + ok(hr == S_OK, "ASV_SetChannelVolume failed: %08lx\n", hr); + + hr = IChannelAudioVolume_SetChannelVolume(cav, 0, 0.4f, NULL); + ok(hr == S_OK, "CAV_SetChannelVolume failed: %08lx\n", hr); + + hr = ISimpleAudioVolume_SetMasterVolume(sav, 0.6f, NULL); + ok(hr == S_OK, "SAV_SetMasterVolume failed: %08lx\n", hr); + + hr = IAudioStreamVolume_GetChannelVolume(asv, 0, &vol); + ok(hr == S_OK, "ASV_GetChannelVolume failed: %08lx\n", hr); + ok(fabsf(vol - 0.2f) < 0.05f, "ASV_GetChannelVolume gave wrong volume: %f\n", vol); + + hr = IChannelAudioVolume_GetChannelVolume(cav, 0, &vol); + ok(hr == S_OK, "CAV_GetChannelVolume failed: %08lx\n", hr); + ok(fabsf(vol - 0.4f) < 0.05f, "CAV_GetChannelVolume gave wrong volume: %f\n", vol); + + hr = ISimpleAudioVolume_GetMasterVolume(sav, &vol); + ok(hr == S_OK, "SAV_GetMasterVolume failed: %08lx\n", hr); + ok(fabsf(vol - 0.6f) < 0.05f, "SAV_GetMasterVolume gave wrong volume: %f\n", vol); + + hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, + NULL, (void**)&ac2); + ok(hr == S_OK, "Activation failed with %08lx\n", hr); + + if(hr == S_OK){ + hr = IAudioClient_Initialize(ac2, AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_NOPERSIST, 5000000, 0, fmt, &session); + ok(hr == S_OK, "Initialize failed: %08lx\n", hr); + if(hr != S_OK) + IAudioClient_Release(ac2); + } + + if(hr == S_OK){ + IChannelAudioVolume *cav2; + IAudioStreamVolume *asv2; + + hr = IAudioClient_GetService(ac2, &IID_IChannelAudioVolume, (void**)&cav2); + ok(hr == S_OK, "GetService failed: %08lx\n", hr); + + hr = IAudioClient_GetService(ac2, &IID_IAudioStreamVolume, (void**)&asv2); + ok(hr == S_OK, "GetService failed: %08lx\n", hr); + + hr = IChannelAudioVolume_GetChannelVolume(cav2, 0, &vol); + ok(hr == S_OK, "CAV_GetChannelVolume failed: %08lx\n", hr); + ok(fabsf(vol - 0.4f) < 0.05f, "CAV_GetChannelVolume gave wrong volume: %f\n", vol); + + hr = IAudioStreamVolume_GetChannelVolume(asv2, 0, &vol); + ok(hr == S_OK, "ASV_GetChannelVolume failed: %08lx\n", hr); + ok(vol == 1.f, "ASV_GetChannelVolume gave wrong volume: %f\n", vol); + + hr = IChannelAudioVolume_GetChannelCount(cav2, &nch); + ok(hr == S_OK, "CAV_GetChannelCount failed: %08lx\n", hr); + ok(nch == fmt->nChannels, "Got wrong channel count, expected %u: %u\n", fmt->nChannels, nch); + + hr = IAudioStreamVolume_GetChannelCount(asv2, &nch); + ok(hr == S_OK, "ASV_GetChannelCount failed: %08lx\n", hr); + ok(nch == fmt->nChannels, "Got wrong channel count, expected %u: %u\n", fmt->nChannels, nch); + + IAudioStreamVolume_Release(asv2); + IChannelAudioVolume_Release(cav2); + IAudioClient_Release(ac2); + }else + skip("Unable to open the same device twice. Skipping session volume control tests\n"); + + hr = IChannelAudioVolume_SetChannelVolume(cav, 0, 1.f, NULL); + ok(hr == S_OK, "CAV_SetChannelVolume failed: %08lx\n", hr); + + hr = ISimpleAudioVolume_SetMasterVolume(sav, 1.f, NULL); + ok(hr == S_OK, "SAV_SetMasterVolume failed: %08lx\n", hr); + + CoTaskMemFree(fmt); + ISimpleAudioVolume_Release(sav); + IChannelAudioVolume_Release(cav); + IAudioStreamVolume_Release(asv); + IAudioClient_Release(ac); +} + +static void test_session_creation(void) +{ + IMMDevice *cap_dev; + IAudioClient *ac; + IAudioSessionManager *sesm; + ISimpleAudioVolume *sav; + GUID session_guid; + float vol; + HRESULT hr; + WAVEFORMATEX *fmt; + + CoCreateGuid(&session_guid); + + hr = IMMDevice_Activate(dev, &IID_IAudioSessionManager, + CLSCTX_INPROC_SERVER, NULL, (void**)&sesm); + ok((hr == S_OK)^(sesm == NULL), "Activate %08lx &out pointer\n", hr); + ok(hr == S_OK, "Activate failed: %08lx\n", hr); + + hr = IAudioSessionManager_GetSimpleAudioVolume(sesm, &session_guid, + FALSE, &sav); + ok(hr == S_OK, "GetSimpleAudioVolume failed: %08lx\n", hr); + + hr = ISimpleAudioVolume_SetMasterVolume(sav, 0.6f, NULL); + ok(hr == S_OK, "SetMasterVolume failed: %08lx\n", hr); + + /* Release completely to show session persistence */ + ISimpleAudioVolume_Release(sav); + IAudioSessionManager_Release(sesm); + + /* test if we can create a capture audioclient in the session we just + * created from a SessionManager derived from a render device */ + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(mme, eCapture, + eMultimedia, &cap_dev); + if(hr == S_OK){ + WAVEFORMATEX *cap_pwfx; + IAudioClient *cap_ac; + ISimpleAudioVolume *cap_sav; + IAudioSessionManager *cap_sesm; + + hr = IMMDevice_Activate(cap_dev, &IID_IAudioSessionManager, + CLSCTX_INPROC_SERVER, NULL, (void**)&cap_sesm); + ok((hr == S_OK)^(cap_sesm == NULL), "Activate %08lx &out pointer\n", hr); + ok(hr == S_OK, "Activate failed: %08lx\n", hr); + + hr = IAudioSessionManager_GetSimpleAudioVolume(cap_sesm, &session_guid, + FALSE, &cap_sav); + ok(hr == S_OK, "GetSimpleAudioVolume failed: %08lx\n", hr); + + vol = 0.5f; + hr = ISimpleAudioVolume_GetMasterVolume(cap_sav, &vol); + ok(hr == S_OK, "GetMasterVolume failed: %08lx\n", hr); + + ISimpleAudioVolume_Release(cap_sav); + IAudioSessionManager_Release(cap_sesm); + + hr = IMMDevice_Activate(cap_dev, &IID_IAudioClient, + CLSCTX_INPROC_SERVER, NULL, (void**)&cap_ac); + ok(hr == S_OK, "Activate failed: %08lx\n", hr); + + IMMDevice_Release(cap_dev); + + hr = IAudioClient_GetMixFormat(cap_ac, &cap_pwfx); + ok(hr == S_OK, "GetMixFormat failed: %08lx\n", hr); + + hr = IAudioClient_Initialize(cap_ac, AUDCLNT_SHAREMODE_SHARED, + 0, 5000000, 0, cap_pwfx, &session_guid); + ok(hr == S_OK, "Initialize failed: %08lx\n", hr); + + CoTaskMemFree(cap_pwfx); + + if(hr == S_OK){ + hr = IAudioClient_GetService(cap_ac, &IID_ISimpleAudioVolume, + (void**)&cap_sav); + ok(hr == S_OK, "GetService failed: %08lx\n", hr); + } + if(hr == S_OK){ + vol = 0.5f; + hr = ISimpleAudioVolume_GetMasterVolume(cap_sav, &vol); + ok(hr == S_OK, "GetMasterVolume failed: %08lx\n", hr); + + ISimpleAudioVolume_Release(cap_sav); + } + + IAudioClient_Release(cap_ac); + } + + hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, + NULL, (void**)&ac); + ok((hr == S_OK)^(ac == NULL), "Activate %08lx &out pointer\n", hr); + ok(hr == S_OK, "Activation failed with %08lx\n", hr); + if(hr != S_OK) + return; + + hr = IAudioClient_GetMixFormat(ac, &fmt); + ok(hr == S_OK, "GetMixFormat failed: %08lx\n", hr); + + hr = IAudioClient_Initialize(ac, AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_NOPERSIST, 5000000, 0, fmt, &session_guid); + ok(hr == S_OK, "Initialize failed: %08lx\n", hr); + + hr = IAudioClient_GetService(ac, &IID_ISimpleAudioVolume, (void**)&sav); + ok(hr == S_OK, "GetService failed: %08lx\n", hr); + if(hr == S_OK){ + vol = 0.5f; + hr = ISimpleAudioVolume_GetMasterVolume(sav, &vol); + ok(hr == S_OK, "GetMasterVolume failed: %08lx\n", hr); + ok(fabs(vol - 0.6f) < 0.05f, "Got wrong volume: %f\n", vol); + + ISimpleAudioVolume_Release(sav); + } + + CoTaskMemFree(fmt); + IAudioClient_Release(ac); +} + +static void test_worst_case(void) +{ + HANDLE event; + HRESULT hr; + IAudioClient *ac; + IAudioRenderClient *arc; + IAudioClock *acl; + WAVEFORMATEX *pwfx; + REFERENCE_TIME defp; + UINT64 freq, pos, pcpos0, pcpos; + BYTE *data; + DWORD r; + UINT32 pad, fragment, sum; + int i,j; + + hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, + NULL, (void**)&ac); + ok(hr == S_OK, "Activation failed with %08lx\n", hr); + if(hr != S_OK) + return; + + hr = IAudioClient_GetMixFormat(ac, &pwfx); + ok(hr == S_OK, "GetMixFormat failed: %08lx\n", hr); + + hr = IAudioClient_Initialize(ac, AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK, 500000, 0, pwfx, NULL); + ok(hr == S_OK, "Initialize failed: %08lx\n", hr); + if(hr != S_OK) + return; + + hr = IAudioClient_GetDevicePeriod(ac, &defp, NULL); + ok(hr == S_OK, "GetDevicePeriod failed: %08lx\n", hr); + + fragment = MulDiv(defp, pwfx->nSamplesPerSec, 10000000); + + event = CreateEventW(NULL, FALSE, FALSE, NULL); + ok(event != NULL, "CreateEvent failed\n"); + + hr = IAudioClient_SetEventHandle(ac, event); + ok(hr == S_OK, "SetEventHandle failed: %08lx\n", hr); + + hr = IAudioClient_GetService(ac, &IID_IAudioRenderClient, (void**)&arc); + ok(hr == S_OK, "GetService(IAudioRenderClient) failed: %08lx\n", hr); + + hr = IAudioClient_GetService(ac, &IID_IAudioClock, (void**)&acl); + ok(hr == S_OK, "GetService(IAudioClock) failed: %08lx\n", hr); + + hr = IAudioClock_GetFrequency(acl, &freq); + ok(hr == S_OK, "GetFrequency failed: %08lx\n", hr); + + for(j = 0; j <= (winetest_interactive ? 9 : 2); j++){ + sum = 0; + trace("Should play %lums continuous tone with fragment size %u.\n", + (ULONG)(defp/100), fragment); + + hr = IAudioClock_GetPosition(acl, &pos, &pcpos0); + ok(hr == S_OK, "GetPosition failed: %08lx\n", hr); + + /* XAudio2 prefills one period, play without it */ + if(winetest_debug>2){ + hr = IAudioRenderClient_GetBuffer(arc, fragment, &data); + ok(hr == S_OK, "GetBuffer failed: %08lx\n", hr); + + hr = IAudioRenderClient_ReleaseBuffer(arc, fragment, AUDCLNT_BUFFERFLAGS_SILENT); + ok(hr == S_OK, "ReleaseBuffer failed: %08lx\n", hr); + if(hr == S_OK) + sum += fragment; + } + + hr = IAudioClient_Start(ac); + ok(hr == S_OK, "Start failed: %08lx\n", hr); + + for(i = 0; i <= 99; i++){ /* 100 x 10ms = 1 second */ + r = WaitForSingleObject(event, 60 + defp / 10000); + flaky_wine + ok(r == WAIT_OBJECT_0, "Wait iteration %d gave %lx\n", i, r); + + /* the app has nearly one period time to feed data */ + Sleep((i % 10) * defp / 120000); + + hr = IAudioClient_GetCurrentPadding(ac, &pad); + ok(hr == S_OK, "GetCurrentPadding failed: %08lx\n", hr); + + /* XAudio2 writes only when there's little data left */ + if(pad <= fragment){ + hr = IAudioRenderClient_GetBuffer(arc, fragment, &data); + ok(hr == S_OK, "GetBuffer failed: %08lx\n", hr); + + hr = IAudioRenderClient_ReleaseBuffer(arc, fragment, + wave_generate_tone(pwfx, data, fragment)); + ok(hr == S_OK, "ReleaseBuffer failed: %08lx\n", hr); + if(hr == S_OK) + sum += fragment; + } + } + + hr = IAudioClient_Stop(ac); + ok(hr == S_OK, "Stop failed: %08lx\n", hr); + + hr = IAudioClient_GetCurrentPadding(ac, &pad); + ok(hr == S_OK, "GetCurrentPadding failed: %08lx\n", hr); + + hr = IAudioClock_GetPosition(acl, &pos, &pcpos); + ok(hr == S_OK, "GetPosition failed: %08lx\n", hr); + + Sleep(100); + + trace("Released %u=%ux%u -%u frames at %lu worth %ums in %lums\n", + sum, sum/fragment, fragment, pad, + pwfx->nSamplesPerSec, MulDiv(sum-pad, 1000, pwfx->nSamplesPerSec), + (ULONG)((pcpos-pcpos0)/10000)); + + ok(pos * pwfx->nSamplesPerSec == (sum-pad) * freq, + "Position %u at end vs. %u-%u submitted frames\n", (UINT)pos, sum, pad); + + hr = IAudioClient_Reset(ac); + ok(hr == S_OK, "Reset failed: %08lx\n", hr); + + Sleep(250); + } + + CoTaskMemFree(pwfx); + IAudioClient_Release(ac); + IAudioClock_Release(acl); + IAudioRenderClient_Release(arc); +} + +static void test_marshal(void) +{ + IStream *pStream; + IAudioClient *ac, *acDest; + IAudioRenderClient *rc, *rcDest; + WAVEFORMATEX *pwfx; + HRESULT hr; + + /* IAudioRenderClient */ + hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, + NULL, (void**)&ac); + ok(hr == S_OK, "Activation failed with %08lx\n", hr); + if(hr != S_OK) + return; + + hr = IAudioClient_GetMixFormat(ac, &pwfx); + ok(hr == S_OK, "GetMixFormat failed: %08lx\n", hr); + + hr = IAudioClient_Initialize(ac, AUDCLNT_SHAREMODE_SHARED, 0, 5000000, + 0, pwfx, NULL); + ok(hr == S_OK, "Initialize failed: %08lx\n", hr); + + CoTaskMemFree(pwfx); + + hr = IAudioClient_GetService(ac, &IID_IAudioRenderClient, (void**)&rc); + ok(hr == S_OK, "GetService failed: %08lx\n", hr); + if(hr != S_OK) { + IAudioClient_Release(ac); + return; + } + + hr = CreateStreamOnHGlobal(NULL, TRUE, &pStream); + ok(hr == S_OK, "CreateStreamOnHGlobal failed 0x%08lx\n", hr); + + /* marshal IAudioClient */ + + hr = CoMarshalInterface(pStream, &IID_IAudioClient, (IUnknown*)ac, MSHCTX_INPROC, NULL, MSHLFLAGS_NORMAL); + ok(hr == S_OK, "CoMarshalInterface IAudioClient failed 0x%08lx\n", hr); + + IStream_Seek(pStream, ullZero, STREAM_SEEK_SET, NULL); + hr = CoUnmarshalInterface(pStream, &IID_IAudioClient, (void **)&acDest); + ok(hr == S_OK, "CoUnmarshalInterface IAudioClient failed 0x%08lx\n", hr); + if (hr == S_OK) + IAudioClient_Release(acDest); + + IStream_Seek(pStream, ullZero, STREAM_SEEK_SET, NULL); + /* marshal IAudioRenderClient */ + + hr = CoMarshalInterface(pStream, &IID_IAudioRenderClient, (IUnknown*)rc, MSHCTX_INPROC, NULL, MSHLFLAGS_NORMAL); + ok(hr == S_OK, "CoMarshalInterface IAudioRenderClient failed 0x%08lx\n", hr); + + IStream_Seek(pStream, ullZero, STREAM_SEEK_SET, NULL); + hr = CoUnmarshalInterface(pStream, &IID_IAudioRenderClient, (void **)&rcDest); + ok(hr == S_OK, "CoUnmarshalInterface IAudioRenderClient failed 0x%08lx\n", hr); + if (hr == S_OK) + IAudioRenderClient_Release(rcDest); + + + IStream_Release(pStream); + + IAudioClient_Release(ac); + IAudioRenderClient_Release(rc); + +} + +static void test_endpointvolume(void) +{ + HRESULT hr; + IAudioEndpointVolume *aev; + float mindb, maxdb, increment, volume; + BOOL mute; + + hr = IMMDevice_Activate(dev, &IID_IAudioEndpointVolume, + CLSCTX_INPROC_SERVER, NULL, (void**)&aev); + ok(hr == S_OK, "Activation failed with %08lx\n", hr); + if(hr != S_OK) + return; + + hr = IAudioEndpointVolume_GetVolumeRange(aev, &mindb, NULL, NULL); + ok(hr == E_POINTER, "GetVolumeRange should have failed with E_POINTER: 0x%08lx\n", hr); + + hr = IAudioEndpointVolume_GetVolumeRange(aev, &mindb, &maxdb, &increment); + ok(hr == S_OK, "GetVolumeRange failed: 0x%08lx\n", hr); + trace("got range: [%f,%f]/%f\n", mindb, maxdb, increment); + + hr = IAudioEndpointVolume_SetMasterVolumeLevel(aev, mindb - increment, NULL); + ok(hr == E_INVALIDARG, "SetMasterVolumeLevel failed: 0x%08lx\n", hr); + + hr = IAudioEndpointVolume_GetMasterVolumeLevel(aev, &volume); + ok(hr == S_OK, "GetMasterVolumeLevel failed: 0x%08lx\n", hr); + + hr = IAudioEndpointVolume_SetMasterVolumeLevel(aev, volume, NULL); + ok(hr == S_OK, "SetMasterVolumeLevel failed: 0x%08lx\n", hr); + + hr = IAudioEndpointVolume_GetMute(aev, &mute); + ok(hr == S_OK, "GetMute failed: %08lx\n", hr); + + hr = IAudioEndpointVolume_SetMute(aev, mute, NULL); + ok(hr == S_OK || hr == S_FALSE, "SetMute failed: %08lx\n", hr); + + IAudioEndpointVolume_Release(aev); +} + +START_TEST(render) +{ + HRESULT hr; + DWORD mode; + + CoInitializeEx(NULL, COINIT_MULTITHREADED); + hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, (void**)&mme); + if (FAILED(hr)) + { + skip("mmdevapi not available: 0x%08lx\n", hr); + goto cleanup; + } + + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(mme, eRender, eMultimedia, &dev); + ok(hr == S_OK || hr == E_NOTFOUND, "GetDefaultAudioEndpoint failed: 0x%08lx\n", hr); + if (hr != S_OK || !dev) + { + if (hr == E_NOTFOUND) + skip("No sound card available\n"); + else + skip("GetDefaultAudioEndpoint returns 0x%08lx\n", hr); + goto cleanup; + } + + test_audioclient(); + test_formats(AUDCLNT_SHAREMODE_EXCLUSIVE); + test_formats(AUDCLNT_SHAREMODE_SHARED); + test_references(); + test_marshal(); + if (GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), &mode)) + { + trace("Output to a MS-DOS console is particularly slow and disturbs timing.\n"); + trace("Please redirect output to a file.\n"); + } + test_event(); + test_padding(); + test_clock(1); + test_clock(0); + test_session(); + test_streamvolume(); + test_channelvolume(); + test_simplevolume(); + test_volume_dependence(); + test_session_creation(); + test_worst_case(); + test_endpointvolume(); + + IMMDevice_Release(dev); + +cleanup: + if (mme) + IMMDeviceEnumerator_Release(mme); + CoUninitialize(); +} diff --git a/pkgs/osu-wine/audio-revert/mmdevapi/tests/spatialaudio.c b/pkgs/osu-wine/audio-revert/mmdevapi/tests/spatialaudio.c new file mode 100644 index 0000000..a382b57 --- /dev/null +++ b/pkgs/osu-wine/audio-revert/mmdevapi/tests/spatialaudio.c @@ -0,0 +1,506 @@ +/* + * Copyright 2021 Arkadiusz Hiler for CodeWeavers + * + * 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 +#include + +#include "wine/test.h" + +#define COBJMACROS + +#ifdef STANDALONE +#include "initguid.h" +#endif + +#include "mmdeviceapi.h" +#include "spatialaudioclient.h" +#include "mmsystem.h" + +static IMMDeviceEnumerator *mme = NULL; +static IMMDevice *dev = NULL; +static ISpatialAudioClient *sac = NULL; +static UINT32 max_dyn_count; +static HANDLE event; +static WAVEFORMATEX format; + +static void test_formats(void) +{ + HRESULT hr; + IAudioFormatEnumerator *afe; + UINT32 format_count = 0; + WAVEFORMATEX *fmt = NULL; + + hr = ISpatialAudioClient_GetSupportedAudioObjectFormatEnumerator(sac, &afe); + ok(hr == S_OK, "Getting format enumerator failed: 0x%08lx\n", hr); + + hr = IAudioFormatEnumerator_GetCount(afe, &format_count); + ok(hr == S_OK, "Getting format count failed: 0x%08lx\n", hr); + ok(format_count == 1, "Got wrong format count, expected 1 got %u\n", format_count); + + hr = IAudioFormatEnumerator_GetFormat(afe, 0, &fmt); + ok(hr == S_OK, "Getting format failed: 0x%08lx\n", hr); + ok(fmt != NULL, "Expected to get non-NULL format\n"); + + ok(fmt->wFormatTag == WAVE_FORMAT_IEEE_FLOAT, "Wrong format, expected WAVE_FORMAT_IEEE_FLOAT got %hx\n", fmt->wFormatTag); + ok(fmt->nChannels == 1, "Wrong number of channels, expected 1 got %hu\n", fmt->nChannels); + ok(fmt->nSamplesPerSec == 48000, "Wrong sample ret, expected 48000 got %lu\n", fmt->nSamplesPerSec); + ok(fmt->wBitsPerSample == 32, "Wrong bits per sample, expected 32 got %hu\n", fmt->wBitsPerSample); + ok(fmt->nBlockAlign == 4, "Wrong block align, expected 4 got %hu\n", fmt->nBlockAlign); + ok(fmt->nAvgBytesPerSec == 192000, "Wrong avg bytes per sec, expected 192000 got %lu\n", fmt->nAvgBytesPerSec); + ok(fmt->cbSize == 0, "Wrong cbSize for simple format, expected 0, got %hu\n", fmt->cbSize); + + memcpy(&format, fmt, sizeof(format)); + + IAudioFormatEnumerator_Release(afe); +} + +static void fill_activation_params(SpatialAudioObjectRenderStreamActivationParams *activation_params) +{ + activation_params->StaticObjectTypeMask = \ + AudioObjectType_FrontLeft | + AudioObjectType_FrontRight | + AudioObjectType_FrontCenter | + AudioObjectType_LowFrequency | + AudioObjectType_SideLeft | + AudioObjectType_SideRight | + AudioObjectType_BackLeft | + AudioObjectType_BackRight | + AudioObjectType_TopFrontLeft | + AudioObjectType_TopFrontRight | + AudioObjectType_TopBackLeft | + AudioObjectType_TopBackRight; + + activation_params->MinDynamicObjectCount = 0; + activation_params->MaxDynamicObjectCount = 0; + activation_params->Category = AudioCategory_GameEffects; + activation_params->EventHandle = event; + activation_params->NotifyObject = NULL; + + activation_params->ObjectFormat = &format; +} + +typedef struct NotifyObject +{ + ISpatialAudioObjectRenderStreamNotify ISpatialAudioObjectRenderStreamNotify_iface; + LONG ref; +} NotifyObject; + +static WINAPI HRESULT notifyobj_QueryInterface( + ISpatialAudioObjectRenderStreamNotify *This, + REFIID riid, + void **ppvObject) +{ + return S_OK; +} + +static WINAPI ULONG notifyobj_AddRef( + ISpatialAudioObjectRenderStreamNotify *This) +{ + NotifyObject *obj = CONTAINING_RECORD(This, NotifyObject, ISpatialAudioObjectRenderStreamNotify_iface); + ULONG ref = InterlockedIncrement(&obj->ref); + return ref; +} + +static WINAPI ULONG notifyobj_Release( + ISpatialAudioObjectRenderStreamNotify *This) +{ + NotifyObject *obj = CONTAINING_RECORD(This, NotifyObject, ISpatialAudioObjectRenderStreamNotify_iface); + ULONG ref = InterlockedDecrement(&obj->ref); + return ref; +} + +static WINAPI HRESULT notifyobj_OnAvailableDynamicObjectCountChange( + ISpatialAudioObjectRenderStreamNotify *This, + ISpatialAudioObjectRenderStreamBase *stream, + LONGLONG deadline, + UINT32 object_count) +{ + ok(FALSE, "Expected to never be notified of dynamic object count change\n"); + return S_OK; +} + +static const ISpatialAudioObjectRenderStreamNotifyVtbl notifyobjvtbl = +{ + notifyobj_QueryInterface, + notifyobj_AddRef, + notifyobj_Release, + notifyobj_OnAvailableDynamicObjectCountChange +}; + +static void test_stream_activation(void) +{ + HRESULT hr; + WAVEFORMATEX wrong_format; + ISpatialAudioObjectRenderStream *sas = NULL; + + SpatialAudioObjectRenderStreamActivationParams activation_params; + PROPVARIANT activation_params_prop; + NotifyObject notify_object; + + PropVariantInit(&activation_params_prop); + activation_params_prop.vt = VT_BLOB; + activation_params_prop.blob.cbSize = sizeof(activation_params); + activation_params_prop.blob.pBlobData = (BYTE*) &activation_params; + + /* correct params */ + fill_activation_params(&activation_params); + hr = ISpatialAudioClient_ActivateSpatialAudioStream(sac, &activation_params_prop, &IID_ISpatialAudioObjectRenderStream, (void**)&sas); + ok(hr == S_OK, "Failed to activate spatial audio stream: 0x%08lx\n", hr); + ok(ISpatialAudioObjectRenderStream_Release(sas) == 0, "Expected to release the last reference\n"); + + /* event handle */ + fill_activation_params(&activation_params); + activation_params.EventHandle = NULL; + hr = ISpatialAudioClient_ActivateSpatialAudioStream(sac, &activation_params_prop, &IID_ISpatialAudioObjectRenderStream, (void**)&sas); + ok(hr == E_INVALIDARG, "Expected lack of no EventHandle to be invalid: 0x%08lx\n", hr); + ok(sas == NULL, "Expected spatial audio stream to be set to NULL upon failed activation\n"); + + activation_params.EventHandle = INVALID_HANDLE_VALUE; + hr = ISpatialAudioClient_ActivateSpatialAudioStream(sac, &activation_params_prop, &IID_ISpatialAudioObjectRenderStream, (void**)&sas); + ok(hr == E_INVALIDARG, "Expected INVALID_HANDLE_VALUE to be invalid: 0x%08lx\n", hr); + ok(sas == NULL, "Expected spatial audio stream to be set to NULL upon failed activation\n"); + + /* must use only queried sample rate */ + fill_activation_params(&activation_params); + memcpy(&wrong_format, &format, sizeof(format)); + activation_params.ObjectFormat = &wrong_format; + wrong_format.nSamplesPerSec = 44100; + wrong_format.nAvgBytesPerSec = wrong_format.nSamplesPerSec * wrong_format.nBlockAlign; + hr = ISpatialAudioClient_ActivateSpatialAudioStream(sac, &activation_params_prop, &IID_ISpatialAudioObjectRenderStream, (void**)&sas); + ok(hr == AUDCLNT_E_UNSUPPORTED_FORMAT, "Expected format to be unsupported: 0x%08lx\n", hr); + ok(sas == NULL, "Expected spatial audio stream to be set to NULL upon failed activation\n"); + + /* dynamic objects are not supported */ + if (max_dyn_count == 0) + { + fill_activation_params(&activation_params); + activation_params.StaticObjectTypeMask |= AudioObjectType_Dynamic; + hr = ISpatialAudioClient_ActivateSpatialAudioStream(sac, &activation_params_prop, &IID_ISpatialAudioObjectRenderStream, (void**)&sas); + ok(hr == E_INVALIDARG, "Expected dynamic objects type be invalid: 0x%08lx\n", hr); + ok(sas == NULL, "Expected spatial audio stream to be set to NULL upon failed activation\n"); + } + + activation_params.MinDynamicObjectCount = max_dyn_count + 1; + activation_params.MaxDynamicObjectCount = max_dyn_count + 1; + hr = ISpatialAudioClient_ActivateSpatialAudioStream(sac, &activation_params_prop, &IID_ISpatialAudioObjectRenderStream, (void**)&sas); + if (max_dyn_count) + ok(hr == AUDCLNT_E_UNSUPPORTED_FORMAT, "Expected dynamic object count exceeding max to be unsupported: 0x%08lx\n", hr); + else + ok(hr == E_INVALIDARG, "Expected setting dynamic object count to be invalid: 0x%08lx\n", hr); + + /* ISpatialAudioObjectRenderStreamNotify */ + fill_activation_params(&activation_params); + notify_object.ISpatialAudioObjectRenderStreamNotify_iface.lpVtbl = ¬ifyobjvtbl; + notify_object.ref = 0; + activation_params.NotifyObject = ¬ify_object.ISpatialAudioObjectRenderStreamNotify_iface; + hr = ISpatialAudioClient_ActivateSpatialAudioStream(sac, &activation_params_prop, &IID_ISpatialAudioObjectRenderStream, (void**)&sas); + ok(hr == S_OK, "Failed to activate spatial audio stream: 0x%08lx\n", hr); + ok(notify_object.ref == 1, "Expected to get increased NotifyObject's ref count\n"); + ok(ISpatialAudioObjectRenderStream_Release(sas) == 0, "Expected to release the last reference\n"); + ok(notify_object.ref == 0, "Expected to get lowered NotifyObject's ref count\n"); +} + +static void test_audio_object_activation(void) +{ + HRESULT hr; + BOOL is_active; + ISpatialAudioObjectRenderStream *sas = NULL; + ISpatialAudioObject *sao1, *sao2; + + SpatialAudioObjectRenderStreamActivationParams activation_params; + PROPVARIANT activation_params_prop; + + PropVariantInit(&activation_params_prop); + activation_params_prop.vt = VT_BLOB; + activation_params_prop.blob.cbSize = sizeof(activation_params); + activation_params_prop.blob.pBlobData = (BYTE*) &activation_params; + + fill_activation_params(&activation_params); + activation_params.StaticObjectTypeMask &= ~AudioObjectType_FrontRight; + hr = ISpatialAudioClient_ActivateSpatialAudioStream(sac, &activation_params_prop, &IID_ISpatialAudioObjectRenderStream, (void**)&sas); + ok(hr == S_OK, "Failed to activate spatial audio stream: 0x%08lx\n", hr); + + hr = ISpatialAudioObjectRenderStream_ActivateSpatialAudioObject(sas, AudioObjectType_FrontLeft, &sao1); + ok(hr == S_OK, "Failed to activate spatial audio object: 0x%08lx\n", hr); + hr = ISpatialAudioObject_IsActive(sao1, &is_active); + todo_wine ok(hr == S_OK, "Failed to check if spatial audio object is active: 0x%08lx\n", hr); + if (hr == S_OK) + ok(is_active, "Expected spatial audio object to be active\n"); + + hr = ISpatialAudioObjectRenderStream_ActivateSpatialAudioObject(sas, AudioObjectType_FrontLeft, &sao2); + ok(hr == SPTLAUDCLNT_E_OBJECT_ALREADY_ACTIVE, "Expected audio object to be already active: 0x%08lx\n", hr); + + hr = ISpatialAudioObjectRenderStream_ActivateSpatialAudioObject(sas, AudioObjectType_FrontRight, &sao2); + ok(hr == SPTLAUDCLNT_E_STATIC_OBJECT_NOT_AVAILABLE, "Expected static object to be not available: 0x%08lx\n", hr); + + hr = ISpatialAudioObjectRenderStream_ActivateSpatialAudioObject(sas, AudioObjectType_Dynamic, &sao2); + ok(hr == SPTLAUDCLNT_E_NO_MORE_OBJECTS, "Expected to not have no more dynamic objects: 0x%08lx\n", hr); + + ISpatialAudioObject_Release(sao1); + ISpatialAudioObjectRenderStream_Release(sas); +} + +static BOOL is_buffer_zeroed(const BYTE *buffer, UINT32 buffer_length) +{ + UINT32 i; + + for (i = 0; i < buffer_length; i++) + { + if (buffer[i] != 0) + return FALSE; + } + + return TRUE; +} + +static void test_audio_object_buffers(void) +{ + UINT32 dyn_object_count, frame_count, max_frame_count, buffer_length; + SpatialAudioObjectRenderStreamActivationParams activation_params; + ISpatialAudioObjectRenderStream *sas = NULL; + PROPVARIANT activation_params_prop; + ISpatialAudioObject *sao[4]; + BYTE *buffer; + INT i, j, k; + HRESULT hr; + + PropVariantInit(&activation_params_prop); + activation_params_prop.vt = VT_BLOB; + activation_params_prop.blob.cbSize = sizeof(activation_params); + activation_params_prop.blob.pBlobData = (BYTE*) &activation_params; + + fill_activation_params(&activation_params); + hr = ISpatialAudioClient_ActivateSpatialAudioStream(sac, &activation_params_prop, &IID_ISpatialAudioObjectRenderStream, (void**)&sas); + ok(hr == S_OK, "Failed to activate spatial audio stream: 0x%08lx\n", hr); + + hr = ISpatialAudioClient_GetMaxFrameCount(sac, &format, &max_frame_count); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + frame_count = format.nSamplesPerSec / 100; /* 10ms */ + /* Most of the time the frame count matches the 10ms interval exactly. + * However (seen on some Testbot machines) it might be a bit higher for some reason. */ + ok(max_frame_count <= frame_count + frame_count / 4, "Got unexpected frame count %u.\n", frame_count); + + /* The tests below which check frame count from _BeginUpdatingAudioObjects fail on some Testbot machines + * with max_frame_count from _GetMaxFrameCount(). */ + max_frame_count = frame_count + frame_count / 4; + + hr = ISpatialAudioObjectRenderStream_ActivateSpatialAudioObject(sas, AudioObjectType_FrontLeft, &sao[0]); + ok(hr == S_OK, "Failed to activate spatial audio object: 0x%08lx\n", hr); + + hr = ISpatialAudioObjectRenderStream_Start(sas); + ok(hr == S_OK, "Failed to activate spatial audio render stream: 0x%08lx\n", hr); + + hr = ISpatialAudioObjectRenderStream_ActivateSpatialAudioObject(sas, AudioObjectType_FrontRight, &sao[1]); + ok(hr == S_OK, "Failed to activate spatial audio object: 0x%08lx\n", hr); + + hr = WaitForSingleObject(event, 200); + ok(hr == WAIT_OBJECT_0, "Expected event to be flagged: 0x%08lx\n", hr); + + hr = ISpatialAudioObjectRenderStream_ActivateSpatialAudioObject(sas, AudioObjectType_SideLeft, &sao[2]); + ok(hr == S_OK, "Failed to activate spatial audio object: 0x%08lx\n", hr); + + hr = ISpatialAudioObjectRenderStream_BeginUpdatingAudioObjects(sas, &dyn_object_count, &frame_count); + ok(hr == S_OK, "Failed to begin updating audio objects: 0x%08lx\n", hr); + ok(dyn_object_count == 0, "Unexpected dynamic objects\n"); + ok(frame_count <= max_frame_count, "Got unexpected frame count %u.\n", frame_count); + + hr = ISpatialAudioObjectRenderStream_ActivateSpatialAudioObject(sas, AudioObjectType_SideRight, &sao[3]); + ok(hr == S_OK, "Failed to activate spatial audio object: 0x%08lx\n", hr); + + for (i = 0; i < ARRAYSIZE(sao); i++) + { + hr = ISpatialAudioObject_GetBuffer(sao[i], &buffer, &buffer_length); + ok(hr == S_OK, "Expected to be able to get buffers for audio object: 0x%08lx\n", hr); + ok(buffer != NULL, "Expected to get a non-NULL buffer\n"); + ok(buffer_length == frame_count * format.wBitsPerSample / 8, "Expected buffer length to be sample_size * frame_count = %hu but got %u\n", + frame_count * format.wBitsPerSample / 8, buffer_length); + ok(is_buffer_zeroed(buffer, buffer_length), "Expected audio object's buffer to be zeroed\n"); + } + + hr = ISpatialAudioObjectRenderStream_EndUpdatingAudioObjects(sas); + ok(hr == S_OK, "Failed to end updating audio objects: 0x%08lx\n", hr); + + /* Emulate underrun and test frame count approximate limit. */ + + /* Force 1ms Sleep() timer resolution. */ + timeBeginPeriod(1); + for (j = 0; j < 20; ++j) + { + hr = WaitForSingleObject(event, 200); + ok(hr == WAIT_OBJECT_0, "Expected event to be flagged: 0x%08lx, j %u.\n", hr, j); + + hr = ISpatialAudioObjectRenderStream_BeginUpdatingAudioObjects(sas, &dyn_object_count, &frame_count); + ok(hr == S_OK, "Failed to begin updating audio objects: 0x%08lx\n", hr); + ok(dyn_object_count == 0, "Unexpected dynamic objects\n"); + ok(frame_count <= max_frame_count, "Got unexpected frame_count %u.\n", frame_count); + + /* Audio starts crackling with delays 10ms and above. However, setting such delay (that is, the delay + * which skips the whole quantum) breaks SA on some Testbot machines: _BeginUpdatingAudioObjects fails + * with SPTLAUDCLNT_E_INTERNAL starting from some iteration or WaitForSingleObject timeouts. That seems + * to work on the real hardware though. */ + Sleep(5); + + for (i = 0; i < ARRAYSIZE(sao); i++) + { + hr = ISpatialAudioObject_GetBuffer(sao[i], &buffer, &buffer_length); + ok(hr == S_OK, "Expected to be able to get buffers for audio object: 0x%08lx, i %d\n", hr, i); + ok(buffer != NULL, "Expected to get a non-NULL buffer\n"); + ok(buffer_length == frame_count * format.wBitsPerSample / 8, + "Expected buffer length to be sample_size * frame_count = %hu but got %u\n", + frame_count * format.wBitsPerSample / 8, buffer_length); + + /* Enable to hear the test sound. */ + if (0) + { + if (format.wFormatTag == WAVE_FORMAT_IEEE_FLOAT) + { + for (k = 0; k < frame_count; ++k) + { + float time_sec = 10.0f / 1000.0f * (j + (float)k / frame_count); + + /* 440Hz tone. */ + ((float *)buffer)[k] = sinf(2.0f * M_PI * time_sec * 440.0f); + } + } + } + } + hr = ISpatialAudioObjectRenderStream_EndUpdatingAudioObjects(sas); + ok(hr == S_OK, "Failed to end updating audio objects: 0x%08lx\n", hr); + } + timeEndPeriod(1); + + hr = WaitForSingleObject(event, 200); + ok(hr == WAIT_OBJECT_0, "Expected event to be flagged: 0x%08lx\n", hr); + + hr = ISpatialAudioObjectRenderStream_BeginUpdatingAudioObjects(sas, &dyn_object_count, &frame_count); + ok(hr == S_OK, "Failed to begin updating audio objects: 0x%08lx\n", hr); + ok(dyn_object_count == 0, "Unexpected dynamic objects\n"); + + /* one more iteration but not with every object */ + for (i = 0; i < ARRAYSIZE(sao) - 1; i++) + { + hr = ISpatialAudioObject_GetBuffer(sao[i], &buffer, &buffer_length); + ok(hr == S_OK, "Expected to be able to get buffers for audio object: 0x%08lx\n", hr); + ok(buffer != NULL, "Expected to get a non-NULL buffer\n"); + ok(buffer_length == frame_count * format.wBitsPerSample / 8, "Expected buffer length to be sample_size * frame_count = %hu but got %u\n", + frame_count * format.wBitsPerSample / 8, buffer_length); + ok(is_buffer_zeroed(buffer, buffer_length), "Expected audio object's buffer to be zeroed\n"); + } + + hr = ISpatialAudioObjectRenderStream_EndUpdatingAudioObjects(sas); + ok(hr == S_OK, "Failed to end updating audio objects: 0x%08lx\n", hr); + + /* ending the stream */ + hr = ISpatialAudioObject_SetEndOfStream(sao[0], 0); + todo_wine ok(hr == SPTLAUDCLNT_E_OUT_OF_ORDER, "Expected that ending the stream at this point won't be allowed: 0x%08lx\n", hr); + + hr = WaitForSingleObject(event, 200); + ok(hr == WAIT_OBJECT_0, "Expected event to be flagged: 0x%08lx\n", hr); + + hr = ISpatialAudioObject_SetEndOfStream(sao[0], 0); + todo_wine ok(hr == SPTLAUDCLNT_E_OUT_OF_ORDER, "Expected that ending the stream at this point won't be allowed: 0x%08lx\n", hr); + + hr = ISpatialAudioObjectRenderStream_BeginUpdatingAudioObjects(sas, &dyn_object_count, &frame_count); + ok(hr == S_OK, "Failed to begin updating audio objects: 0x%08lx\n", hr); + ok(dyn_object_count == 0, "Unexpected dynamic objects\n"); + + /* expect the object that was not updated last cycle to be invalidated */ + hr = ISpatialAudioObject_GetBuffer(sao[ARRAYSIZE(sao) - 1], &buffer, &buffer_length); + todo_wine ok(hr == SPTLAUDCLNT_E_RESOURCES_INVALIDATED, "Expected audio object to be invalidated: 0x%08lx\n", hr); + + for (i = 0; i < ARRAYSIZE(sao) - 1; i++) + { + hr = ISpatialAudioObject_GetBuffer(sao[i], &buffer, &buffer_length); + ok(hr == S_OK, "Expected to be able to get buffers for audio object: 0x%08lx\n", hr); + + hr = ISpatialAudioObject_SetEndOfStream(sao[i], 0); + todo_wine ok(hr == S_OK, "Failed to end the stream: 0x%08lx\n", hr); + + hr = ISpatialAudioObject_GetBuffer(sao[i], &buffer, &buffer_length); + todo_wine ok(hr == SPTLAUDCLNT_E_RESOURCES_INVALIDATED, "Expected audio object to be invalidated: 0x%08lx\n", hr); + } + + hr = ISpatialAudioObjectRenderStream_EndUpdatingAudioObjects(sas); + ok(hr == S_OK, "Failed to end updating audio objects: 0x%08lx\n", hr); + + for (i = 0; i < ARRAYSIZE(sao); i++) + { + ISpatialAudioObject_Release(sao[i]); + } + + ISpatialAudioObjectRenderStream_Release(sas); +} + +START_TEST(spatialaudio) +{ + HRESULT hr; + + event = CreateEventA(NULL, FALSE, FALSE, "spatial-audio-test-prog-event"); + ok(event != NULL, "Failed to create event, last error: 0x%08lx\n", GetLastError()); + + CoInitializeEx(NULL, COINIT_MULTITHREADED); + hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, (void**)&mme); + if (FAILED(hr)) + { + skip("mmdevapi not available: 0x%08lx\n", hr); + goto cleanup; + } + + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(mme, eRender, eMultimedia, &dev); + ok(hr == S_OK || hr == E_NOTFOUND, "GetDefaultAudioEndpoint failed: 0x%08lx\n", hr); + if (hr != S_OK || !dev) + { + if (hr == E_NOTFOUND) + skip("No sound card available\n"); + else + skip("GetDefaultAudioEndpoint returns 0x%08lx\n", hr); + goto cleanup; + } + + hr = IMMDevice_Activate(dev, &IID_ISpatialAudioClient, CLSCTX_INPROC_SERVER, NULL, (void**)&sac); + ok(hr == S_OK || hr == E_NOINTERFACE, "ISpatialAudioClient Activation failed: 0x%08lx\n", hr); + if (hr != S_OK || !dev) + { + if (hr == E_NOINTERFACE) + skip("ISpatialAudioClient interface not found\n"); + else + skip("ISpatialAudioClient Activation returns 0x%08lx\n", hr); + goto cleanup; + } + + hr = ISpatialAudioClient_GetMaxDynamicObjectCount(sac, &max_dyn_count); + ok(hr == S_OK, "Failed to get max dynamic object count: 0x%08lx\n", hr); + + /* that's the default, after manually enabling Windows Sonic it's possible to have max_dyn_count > 0 */ + /* ok(max_dyn_count == 0, "expected max dynamic object count to be 0 got %u\n", max_dyn_count); */ + + test_formats(); + test_stream_activation(); + test_audio_object_activation(); + test_audio_object_buffers(); + + ISpatialAudioClient_Release(sac); + +cleanup: + if (dev) + IMMDevice_Release(dev); + if (mme) + IMMDeviceEnumerator_Release(mme); + CoUninitialize(); + CloseHandle(event); +} diff --git a/pkgs/osu-wine/audio-revert/mmdevapi/unixlib.h b/pkgs/osu-wine/audio-revert/mmdevapi/unixlib.h new file mode 100644 index 0000000..ea0d2f9 --- /dev/null +++ b/pkgs/osu-wine/audio-revert/mmdevapi/unixlib.h @@ -0,0 +1,336 @@ +/* + * Copyright 2021 Jacek Caban for CodeWeavers + * Copyright 2021-2022 Huw Davies + * + * 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 "audioclient.h" +#include "mmdeviceapi.h" + +typedef UINT64 stream_handle; + +enum driver_priority +{ + Priority_Unavailable = 0, /* driver won't work */ + Priority_Low, /* driver may work, but unlikely */ + Priority_Neutral, /* driver makes no judgment */ + Priority_Preferred /* driver thinks it's correct */ +}; + +struct endpoint +{ + unsigned int name; + unsigned int device; +}; + +struct main_loop_params +{ + HANDLE event; +}; + +struct get_endpoint_ids_params +{ + EDataFlow flow; + struct endpoint *endpoints; + unsigned int size; + HRESULT result; + unsigned int num; + unsigned int default_idx; +}; + +struct create_stream_params +{ + const char *name; + const char *device; + EDataFlow flow; + AUDCLNT_SHAREMODE share; + DWORD flags; + REFERENCE_TIME duration; + REFERENCE_TIME period; + const WAVEFORMATEX *fmt; + HRESULT result; + UINT32 *channel_count; + stream_handle *stream; +}; + +struct release_stream_params +{ + stream_handle stream; + HANDLE timer_thread; + HRESULT result; +}; + +struct start_params +{ + stream_handle stream; + HRESULT result; +}; + +struct stop_params +{ + stream_handle stream; + HRESULT result; +}; + +struct reset_params +{ + stream_handle stream; + HRESULT result; +}; + +struct timer_loop_params +{ + stream_handle stream; +}; + +struct get_render_buffer_params +{ + stream_handle stream; + UINT32 frames; + HRESULT result; + BYTE **data; +}; + +struct release_render_buffer_params +{ + stream_handle stream; + UINT32 written_frames; + UINT flags; + HRESULT result; +}; + +struct get_capture_buffer_params +{ + stream_handle stream; + HRESULT result; + BYTE **data; + UINT32 *frames; + UINT *flags; + UINT64 *devpos; + UINT64 *qpcpos; +}; + +struct release_capture_buffer_params +{ + stream_handle stream; + UINT32 done; + HRESULT result; +}; + +struct is_format_supported_params +{ + const char *device; + EDataFlow flow; + AUDCLNT_SHAREMODE share; + const WAVEFORMATEX *fmt_in; + WAVEFORMATEXTENSIBLE *fmt_out; + HRESULT result; +}; + +struct get_mix_format_params +{ + const char *device; + EDataFlow flow; + WAVEFORMATEXTENSIBLE *fmt; + HRESULT result; +}; + +struct get_device_period_params +{ + const char *device; + EDataFlow flow; + HRESULT result; + REFERENCE_TIME *def_period; + REFERENCE_TIME *min_period; +}; + +struct get_buffer_size_params +{ + stream_handle stream; + HRESULT result; + UINT32 *frames; +}; + +struct get_latency_params +{ + stream_handle stream; + HRESULT result; + REFERENCE_TIME *latency; +}; + +struct get_current_padding_params +{ + stream_handle stream; + HRESULT result; + UINT32 *padding; +}; + +struct get_next_packet_size_params +{ + stream_handle stream; + HRESULT result; + UINT32 *frames; +}; + +struct get_frequency_params +{ + stream_handle stream; + HRESULT result; + UINT64 *freq; +}; + +struct get_position_params +{ + stream_handle stream; + BOOL device; + HRESULT result; + UINT64 *pos; + UINT64 *qpctime; +}; + +struct set_volumes_params +{ + stream_handle stream; + float master_volume; + const float *volumes; + const float *session_volumes; + int channel; +}; + +struct set_event_handle_params +{ + stream_handle stream; + HANDLE event; + HRESULT result; +}; + +struct test_connect_params +{ + const char *name; + enum driver_priority priority; +}; + +struct is_started_params +{ + stream_handle stream; + HRESULT result; +}; + +struct get_prop_value_params +{ + const char *device; + EDataFlow flow; + const GUID *guid; + const PROPERTYKEY *prop; + HRESULT result; + PROPVARIANT *value; + void *buffer; /* caller allocated buffer to hold value's strings */ + unsigned int *buffer_size; +}; + +struct midi_init_params +{ + UINT *err; +}; + +struct notify_context +{ + BOOL send_notify; + WORD dev_id; + WORD msg; + UINT_PTR param_1; + UINT_PTR param_2; + UINT_PTR callback; + UINT flags; + HANDLE device; + UINT_PTR instance; +}; + +struct midi_out_message_params +{ + UINT dev_id; + UINT msg; + UINT_PTR user; + UINT_PTR param_1; + UINT_PTR param_2; + UINT *err; + struct notify_context *notify; +}; + +struct midi_in_message_params +{ + UINT dev_id; + UINT msg; + UINT_PTR user; + UINT_PTR param_1; + UINT_PTR param_2; + UINT *err; + struct notify_context *notify; +}; + +struct midi_notify_wait_params +{ + BOOL *quit; + struct notify_context *notify; +}; + +struct aux_message_params +{ + UINT dev_id; + UINT msg; + UINT_PTR user; + UINT_PTR param_1; + UINT_PTR param_2; + UINT *err; +}; + +enum unix_funcs +{ + process_attach, + process_detach, + main_loop, + get_endpoint_ids, + create_stream, + release_stream, + start, + stop, + reset, + timer_loop, + get_render_buffer, + release_render_buffer, + get_capture_buffer, + release_capture_buffer, + is_format_supported, + get_mix_format, + get_device_period, + get_buffer_size, + get_latency, + get_current_padding, + get_next_packet_size, + get_frequency, + get_position, + set_volumes, + set_event_handle, + test_connect, + is_started, + get_prop_value, + midi_init, + midi_release, + midi_out_message, + midi_in_message, + midi_notify_wait, + aux_message, +}; diff --git a/pkgs/osu-wine/audio-revert/winealsa.drv/Makefile.in b/pkgs/osu-wine/audio-revert/winealsa.drv/Makefile.in new file mode 100644 index 0000000..7c84f69 --- /dev/null +++ b/pkgs/osu-wine/audio-revert/winealsa.drv/Makefile.in @@ -0,0 +1,11 @@ +MODULE = winealsa.drv +UNIXLIB = winealsa.so +IMPORTS = uuid ole32 advapi32 +DELAYIMPORTS = winmm +UNIX_LIBS = $(ALSA_LIBS) $(PTHREAD_LIBS) + +SOURCES = \ + alsa.c \ + alsamidi.c \ + midi.c \ + mmdevdrv.c diff --git a/pkgs/osu-wine/audio-revert/winealsa.drv/alsa.c b/pkgs/osu-wine/audio-revert/winealsa.drv/alsa.c new file mode 100644 index 0000000..ddc5b37 --- /dev/null +++ b/pkgs/osu-wine/audio-revert/winealsa.drv/alsa.c @@ -0,0 +1,2899 @@ +/* + * Copyright 2010 Maarten Lankhorst for CodeWeavers + * Copyright 2011 Andrew Eikum for CodeWeavers + * Copyright 2022 Huw Davies + * + * 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 + */ +#if 0 +#pragma makedep unix +#endif + +#include "config.h" + +#include +#include +#include + +#include + +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "windef.h" +#include "winbase.h" +#include "winternl.h" +#include "initguid.h" +#include "mmdeviceapi.h" + +#include "wine/debug.h" +#include "wine/list.h" +#include "wine/unixlib.h" + +#include "unixlib.h" + +WINE_DEFAULT_DEBUG_CHANNEL(alsa); + +struct alsa_stream +{ + snd_pcm_t *pcm_handle; + snd_pcm_uframes_t alsa_bufsize_frames, alsa_period_frames, safe_rewind_frames; + snd_pcm_hw_params_t *hw_params; /* does not hold state between calls */ + snd_pcm_format_t alsa_format; + + LARGE_INTEGER last_period_time; + + WAVEFORMATEX *fmt; + DWORD flags; + AUDCLNT_SHAREMODE share; + EDataFlow flow; + HANDLE event; + + BOOL need_remapping; + int alsa_channels; + int alsa_channel_map[32]; + + BOOL started, please_quit; + REFERENCE_TIME mmdev_period_rt; + UINT64 written_frames, last_pos_frames; + UINT32 bufsize_frames, held_frames, tmp_buffer_frames, mmdev_period_frames; + snd_pcm_uframes_t remapping_buf_frames; + UINT32 lcl_offs_frames; /* offs into local_buffer where valid data starts */ + UINT32 wri_offs_frames; /* where to write fresh data in local_buffer */ + UINT32 hidden_frames; /* ALSA reserve to ensure continuous rendering */ + UINT32 vol_adjusted_frames; /* Frames we've already adjusted the volume of but didn't write yet */ + UINT32 data_in_alsa_frames; + + BYTE *local_buffer, *tmp_buffer, *remapping_buf, *silence_buf; + LONG32 getbuf_last; /* <0 when using tmp_buffer */ + float *vols; + + pthread_mutex_t lock; +}; + +#define EXTRA_SAFE_RT 40000 + +int GetAudioEnv(char const* env, int def) { + char* val = getenv(env); + if (val) { + return atoi(val); + } + return def; +} + +static const WCHAR drv_keyW[] = {'S','o','f','t','w','a','r','e','\\', + 'W','i','n','e','\\','D','r','i','v','e','r','s','\\', + 'w','i','n','e','a','l','s','a','.','d','r','v'}; + +static inline void ascii_to_unicode( WCHAR *dst, const char *src, size_t len ) +{ + while (len--) *dst++ = (unsigned char)*src++; +} + +static HKEY reg_open_key( HKEY root, const WCHAR *name, ULONG name_len ) +{ + UNICODE_STRING nameW = { name_len, name_len, (WCHAR *)name }; + OBJECT_ATTRIBUTES attr; + HANDLE ret; + + attr.Length = sizeof(attr); + attr.RootDirectory = root; + attr.ObjectName = &nameW; + attr.Attributes = 0; + attr.SecurityDescriptor = NULL; + attr.SecurityQualityOfService = NULL; + + if (NtOpenKeyEx( &ret, MAXIMUM_ALLOWED, &attr, 0 )) return 0; + return ret; +} + +static HKEY open_hkcu(void) +{ + char buffer[256]; + WCHAR bufferW[256]; + DWORD_PTR sid_data[(sizeof(TOKEN_USER) + SECURITY_MAX_SID_SIZE) / sizeof(DWORD_PTR)]; + DWORD i, len = sizeof(sid_data); + SID *sid; + + if (NtQueryInformationToken( GetCurrentThreadEffectiveToken(), TokenUser, sid_data, len, &len )) + return 0; + + sid = ((TOKEN_USER *)sid_data)->User.Sid; + len = sprintf( buffer, "\\Registry\\User\\S-%u-%u", sid->Revision, + (unsigned)MAKELONG( MAKEWORD( sid->IdentifierAuthority.Value[5], sid->IdentifierAuthority.Value[4] ), + MAKEWORD( sid->IdentifierAuthority.Value[3], sid->IdentifierAuthority.Value[2] ))); + for (i = 0; i < sid->SubAuthorityCount; i++) + len += sprintf( buffer + len, "-%u", (unsigned)sid->SubAuthority[i] ); + ascii_to_unicode( bufferW, buffer, len + 1 ); + + return reg_open_key( NULL, bufferW, len * sizeof(WCHAR) ); +} + +static HKEY reg_open_hkcu_key( const WCHAR *name, ULONG name_len ) +{ + HKEY hkcu = open_hkcu(), key; + + key = reg_open_key( hkcu, name, name_len ); + NtClose( hkcu ); + + return key; +} + +static ULONG reg_query_value( HKEY hkey, const WCHAR *name, + KEY_VALUE_PARTIAL_INFORMATION *info, ULONG size ) +{ + unsigned int name_size = name ? wcslen( name ) * sizeof(WCHAR) : 0; + UNICODE_STRING nameW = { name_size, name_size, (WCHAR *)name }; + + if (NtQueryValueKey( hkey, &nameW, KeyValuePartialInformation, + info, size, &size )) + return 0; + + return size - FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data); +} + +static snd_pcm_stream_t alsa_get_direction(EDataFlow flow) +{ + return (flow == eRender) ? SND_PCM_STREAM_PLAYBACK : SND_PCM_STREAM_CAPTURE; +} + +static WCHAR *strdupAtoW(const char *str) +{ + unsigned int len; + WCHAR *ret; + + if(!str) return NULL; + + len = strlen(str) + 1; + ret = malloc(len * sizeof(WCHAR)); + if(ret) ntdll_umbstowcs(str, len, ret, len); + return ret; +} + +/* copied from kernelbase */ +static int muldiv( int a, int b, int c ) +{ + LONGLONG ret; + + if (!c) return -1; + + /* We want to deal with a positive divisor to simplify the logic. */ + if (c < 0) + { + a = -a; + c = -c; + } + + /* If the result is positive, we "add" to round. else, we subtract to round. */ + if ((a < 0 && b < 0) || (a >= 0 && b >= 0)) + ret = (((LONGLONG)a * b) + (c / 2)) / c; + else + ret = (((LONGLONG)a * b) - (c / 2)) / c; + + if (ret > 2147483647 || ret < -2147483647) return -1; + return ret; +} + +static void alsa_lock(struct alsa_stream *stream) +{ + pthread_mutex_lock(&stream->lock); +} + +static void alsa_unlock(struct alsa_stream *stream) +{ + pthread_mutex_unlock(&stream->lock); +} + +static NTSTATUS alsa_unlock_result(struct alsa_stream *stream, + HRESULT *result, HRESULT value) +{ + *result = value; + alsa_unlock(stream); + return STATUS_SUCCESS; +} + +static struct alsa_stream *handle_get_stream(stream_handle h) +{ + return (struct alsa_stream *)(UINT_PTR)h; +} + +static BOOL alsa_try_open(const char *devnode, EDataFlow flow) +{ + snd_pcm_t *handle; + int err; + + TRACE("devnode: %s, flow: %d\n", devnode, flow); + + if((err = snd_pcm_open(&handle, devnode, alsa_get_direction(flow), SND_PCM_NONBLOCK)) < 0){ + WARN("The device \"%s\" failed to open: %d (%s).\n", devnode, err, snd_strerror(err)); + return FALSE; + } + + snd_pcm_close(handle); + return TRUE; +} + +static WCHAR *construct_device_id(EDataFlow flow, const WCHAR *chunk1, const WCHAR *chunk2) +{ + WCHAR *ret; + const WCHAR *prefix; + size_t len_wchars = 0, chunk1_len = 0, chunk2_len = 0, copied = 0, prefix_len; + + static const WCHAR dashW[] = {' ','-',' ',0}; + static const size_t dashW_len = ARRAY_SIZE(dashW) - 1; + static const WCHAR outW[] = {'O','u','t',':',' ',0}; + static const WCHAR inW[] = {'I','n',':',' ',0}; + + if(flow == eRender){ + prefix = outW; + prefix_len = ARRAY_SIZE(outW) - 1; + len_wchars += prefix_len; + }else{ + prefix = inW; + prefix_len = ARRAY_SIZE(inW) - 1; + len_wchars += prefix_len; + } + if(chunk1){ + chunk1_len = wcslen(chunk1); + len_wchars += chunk1_len; + } + if(chunk1 && chunk2) + len_wchars += dashW_len; + if(chunk2){ + chunk2_len = wcslen(chunk2); + len_wchars += chunk2_len; + } + len_wchars += 1; /* NULL byte */ + + ret = malloc(len_wchars * sizeof(WCHAR)); + + memcpy(ret, prefix, prefix_len * sizeof(WCHAR)); + copied += prefix_len; + if(chunk1){ + memcpy(ret + copied, chunk1, chunk1_len * sizeof(WCHAR)); + copied += chunk1_len; + } + if(chunk1 && chunk2){ + memcpy(ret + copied, dashW, dashW_len * sizeof(WCHAR)); + copied += dashW_len; + } + if(chunk2){ + memcpy(ret + copied, chunk2, chunk2_len * sizeof(WCHAR)); + copied += chunk2_len; + } + ret[copied] = 0; + + TRACE("Enumerated device: %s\n", wine_dbgstr_w(ret)); + + return ret; +} + +struct endpt +{ + WCHAR *name; + char *device; +}; + +struct endpoints_info +{ + unsigned int num, size; + struct endpt *endpoints; +}; + +static void endpoints_add(struct endpoints_info *endpoints, WCHAR *name, char *device) +{ + if(endpoints->num >= endpoints->size){ + if (!endpoints->size) endpoints->size = 16; + else endpoints->size *= 2; + endpoints->endpoints = realloc(endpoints->endpoints, endpoints->size * sizeof(*endpoints->endpoints)); + } + + endpoints->endpoints[endpoints->num].name = name; + endpoints->endpoints[endpoints->num++].device = device; +} + +static HRESULT alsa_get_card_devices(EDataFlow flow, struct endpoints_info *endpoints_info, + snd_ctl_t *ctl, int card, const WCHAR *cardname) +{ + int err, device; + snd_pcm_info_t *info; + + info = calloc(1, snd_pcm_info_sizeof()); + if(!info) + return E_OUTOFMEMORY; + + snd_pcm_info_set_subdevice(info, 0); + snd_pcm_info_set_stream(info, alsa_get_direction(flow)); + + device = -1; + for(err = snd_ctl_pcm_next_device(ctl, &device); device != -1 && err >= 0; + err = snd_ctl_pcm_next_device(ctl, &device)){ + char devnode[32]; + WCHAR *devname; + + snd_pcm_info_set_device(info, device); + + if((err = snd_ctl_pcm_info(ctl, info)) < 0){ + if(err == -ENOENT) + /* This device doesn't have the right stream direction */ + continue; + + WARN("Failed to get info for card %d, device %d: %d (%s)\n", + card, device, err, snd_strerror(err)); + continue; + } + + sprintf(devnode, "plughw:%d,%d", card, device); + if(!alsa_try_open(devnode, flow)) + continue; + + devname = strdupAtoW(snd_pcm_info_get_name(info)); + if(!devname){ + WARN("Unable to get device name for card %d, device %d\n", card, device); + continue; + } + + endpoints_add(endpoints_info, construct_device_id(flow, cardname, devname), strdup(devnode)); + free(devname); + } + + free(info); + + if(err != 0) + WARN("Got a failure during device enumeration on card %d: %d (%s)\n", + card, err, snd_strerror(err)); + + return S_OK; +} + +static void get_reg_devices(EDataFlow flow, struct endpoints_info *endpoints_info) +{ + static const WCHAR ALSAOutputDevices[] = {'A','L','S','A','O','u','t','p','u','t','D','e','v','i','c','e','s',0}; + static const WCHAR ALSAInputDevices[] = {'A','L','S','A','I','n','p','u','t','D','e','v','i','c','e','s',0}; + char buffer[4096]; + KEY_VALUE_PARTIAL_INFORMATION *key_info = (void *)buffer; + HKEY key; + DWORD size; + const WCHAR *value_name = (flow == eRender) ? ALSAOutputDevices : ALSAInputDevices; + + /* @@ Wine registry key: HKCU\Software\Wine\Drivers\winealsa.drv */ + if((key = reg_open_hkcu_key(drv_keyW, sizeof(drv_keyW)))){ + if((size = reg_query_value(key, value_name, key_info, sizeof(buffer)))){ + WCHAR *p = (WCHAR *)key_info->Data; + + if(key_info->Type != REG_MULTI_SZ){ + ERR("Registry ALSA device list value type must be REG_MULTI_SZ\n"); + NtClose(key); + return; + } + + while(*p){ + int len = wcslen(p); + char *devname = malloc(len * 3 + 1); + + ntdll_wcstoumbs(p, len + 1, devname, len * 3 + 1, FALSE); + + if(alsa_try_open(devname, flow)) + endpoints_add(endpoints_info, construct_device_id(flow, p, NULL), strdup(devname)); + + free(devname); + p += len + 1; + } + } + + NtClose(key); + } +} + +struct card_type { + struct list entry; + int first_card_number; + char string[1]; +}; + +static struct list card_types = LIST_INIT(card_types); + +static BOOL need_card_number(int card, const char *string) +{ + struct card_type *cptr; + + LIST_FOR_EACH_ENTRY(cptr, &card_types, struct card_type, entry) + { + if(!strcmp(string, cptr->string)) + return card != cptr->first_card_number; + } + + /* this is the first instance of string */ + cptr = malloc(sizeof(struct card_type) + strlen(string)); + if(!cptr) + /* Default to displaying card number if we can't track cards */ + return TRUE; + + cptr->first_card_number = card; + strcpy(cptr->string, string); + list_add_head(&card_types, &cptr->entry); + return FALSE; +} + +static WCHAR *alsa_get_card_name(int card) +{ + char *cardname; + WCHAR *ret; + int err; + + if((err = snd_card_get_name(card, &cardname)) < 0){ + /* FIXME: Should be localized */ + WARN("Unable to get card name for ALSA device %d: %d (%s)\n", card, err, snd_strerror(err)); + cardname = strdup("Unknown soundcard"); + } + + if(need_card_number(card, cardname)){ + char *cardnameN; + /* + * For identical card names, second and subsequent instances get + * card number prefix to distinguish them (like Windows). + */ + if(asprintf(&cardnameN, "%u-%s", card, cardname) > 0){ + free(cardname); + cardname = cardnameN; + } + } + + ret = strdupAtoW(cardname); + free(cardname); + + return ret; +} + +static NTSTATUS alsa_get_endpoint_ids(void *args) +{ + static const WCHAR defaultW[] = {'d','e','f','a','u','l','t',0}; + struct get_endpoint_ids_params *params = args; + struct endpoints_info endpoints_info; + unsigned int i, needed, name_len, device_len, offset; + struct endpoint *endpoint; + int err, card; + + card = -1; + + endpoints_info.num = endpoints_info.size = 0; + endpoints_info.endpoints = NULL; + + if(alsa_try_open("default", params->flow)) + endpoints_add(&endpoints_info, construct_device_id(params->flow, defaultW, NULL), strdup("default")); + + get_reg_devices(params->flow, &endpoints_info); + + for(err = snd_card_next(&card); card != -1 && err >= 0; err = snd_card_next(&card)){ + char cardpath[64]; + WCHAR *cardname; + snd_ctl_t *ctl; + + sprintf(cardpath, "hw:%u", card); + + if((err = snd_ctl_open(&ctl, cardpath, 0)) < 0){ + WARN("Unable to open ctl for ALSA device %s: %d (%s)\n", cardpath, + err, snd_strerror(err)); + continue; + } + + cardname = alsa_get_card_name(card); + alsa_get_card_devices(params->flow, &endpoints_info, ctl, card, cardname); + free(cardname); + + snd_ctl_close(ctl); + } + + if(err != 0) + WARN("Got a failure during card enumeration: %d (%s)\n", err, snd_strerror(err)); + + offset = needed = endpoints_info.num * sizeof(*params->endpoints); + endpoint = params->endpoints; + + for(i = 0; i < endpoints_info.num; i++){ + name_len = wcslen(endpoints_info.endpoints[i].name) + 1; + device_len = strlen(endpoints_info.endpoints[i].device) + 1; + needed += name_len * sizeof(WCHAR) + ((device_len + 1) & ~1); + + if(needed <= params->size){ + endpoint->name = offset; + memcpy((char *)params->endpoints + offset, endpoints_info.endpoints[i].name, name_len * sizeof(WCHAR)); + offset += name_len * sizeof(WCHAR); + endpoint->device = offset; + memcpy((char *)params->endpoints + offset, endpoints_info.endpoints[i].device, device_len); + offset += (device_len + 1) & ~1; + endpoint++; + } + free(endpoints_info.endpoints[i].name); + free(endpoints_info.endpoints[i].device); + } + free(endpoints_info.endpoints); + + params->num = endpoints_info.num; + params->default_idx = 0; + + if(needed > params->size){ + params->size = needed; + params->result = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } else + params->result = S_OK; + + return STATUS_SUCCESS; +} + +static WAVEFORMATEXTENSIBLE *clone_format(const WAVEFORMATEX *fmt) +{ + WAVEFORMATEXTENSIBLE *ret; + size_t size; + + if(fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE) + size = sizeof(WAVEFORMATEXTENSIBLE); + else + size = sizeof(WAVEFORMATEX); + + ret = malloc(size); + if(!ret) + return NULL; + + memcpy(ret, fmt, size); + + ret->Format.cbSize = size - sizeof(WAVEFORMATEX); + + return ret; +} + +static HRESULT alsa_open_device(const char *alsa_name, EDataFlow flow, snd_pcm_t **pcm_handle, + snd_pcm_hw_params_t **hw_params) +{ + snd_pcm_stream_t pcm_stream; + int err; + + if(flow == eRender) + pcm_stream = SND_PCM_STREAM_PLAYBACK; + else if(flow == eCapture) + pcm_stream = SND_PCM_STREAM_CAPTURE; + else + return E_UNEXPECTED; + + err = snd_pcm_open(pcm_handle, alsa_name, pcm_stream, SND_PCM_NONBLOCK); + if(err < 0){ + WARN("Unable to open PCM \"%s\": %d (%s)\n", alsa_name, err, snd_strerror(err)); + switch(err){ + case -EBUSY: + return AUDCLNT_E_DEVICE_IN_USE; + default: + return AUDCLNT_E_ENDPOINT_CREATE_FAILED; + } + } + + *hw_params = malloc(snd_pcm_hw_params_sizeof()); + if(!*hw_params){ + snd_pcm_close(*pcm_handle); + return E_OUTOFMEMORY; + } + + return S_OK; +} + +static snd_pcm_format_t alsa_format(const WAVEFORMATEX *fmt) +{ + snd_pcm_format_t format = SND_PCM_FORMAT_UNKNOWN; + const WAVEFORMATEXTENSIBLE *fmtex = (const WAVEFORMATEXTENSIBLE *)fmt; + + if(fmt->wFormatTag == WAVE_FORMAT_PCM || + (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + IsEqualGUID(&fmtex->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM))){ + if(fmt->wBitsPerSample == 8) + format = SND_PCM_FORMAT_U8; + else if(fmt->wBitsPerSample == 16) + format = SND_PCM_FORMAT_S16_LE; + else if(fmt->wBitsPerSample == 24) + format = SND_PCM_FORMAT_S24_3LE; + else if(fmt->wBitsPerSample == 32) + format = SND_PCM_FORMAT_S32_LE; + else + WARN("Unsupported bit depth: %u\n", fmt->wBitsPerSample); + if(fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + fmt->wBitsPerSample != fmtex->Samples.wValidBitsPerSample){ + if(fmtex->Samples.wValidBitsPerSample == 20 && fmt->wBitsPerSample == 24) + format = SND_PCM_FORMAT_S20_3LE; + else + WARN("Unsupported ValidBits: %u\n", fmtex->Samples.wValidBitsPerSample); + } + }else if(fmt->wFormatTag == WAVE_FORMAT_IEEE_FLOAT || + (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + IsEqualGUID(&fmtex->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))){ + if(fmt->wBitsPerSample == 32) + format = SND_PCM_FORMAT_FLOAT_LE; + else if(fmt->wBitsPerSample == 64) + format = SND_PCM_FORMAT_FLOAT64_LE; + else + WARN("Unsupported float size: %u\n", fmt->wBitsPerSample); + }else + WARN("Unknown wave format: %04x\n", fmt->wFormatTag); + return format; +} + +static int alsa_channel_index(UINT flag) +{ + switch(flag){ + case SPEAKER_FRONT_LEFT: + return 0; + case SPEAKER_FRONT_RIGHT: + return 1; + case SPEAKER_BACK_LEFT: + return 2; + case SPEAKER_BACK_RIGHT: + return 3; + case SPEAKER_FRONT_CENTER: + return 4; + case SPEAKER_LOW_FREQUENCY: + return 5; + case SPEAKER_SIDE_LEFT: + return 6; + case SPEAKER_SIDE_RIGHT: + return 7; + } + return -1; +} + +static BOOL need_remapping(const WAVEFORMATEX *fmt, int *map) +{ + unsigned int i; + for(i = 0; i < fmt->nChannels; ++i){ + if(map[i] != i) + return TRUE; + } + return FALSE; +} + +static DWORD get_channel_mask(unsigned int channels) +{ + switch(channels){ + case 0: + return 0; + case 1: + return KSAUDIO_SPEAKER_MONO; + case 2: + return KSAUDIO_SPEAKER_STEREO; + case 3: + return KSAUDIO_SPEAKER_STEREO | SPEAKER_LOW_FREQUENCY; + case 4: + return KSAUDIO_SPEAKER_QUAD; /* not _SURROUND */ + case 5: + return KSAUDIO_SPEAKER_QUAD | SPEAKER_LOW_FREQUENCY; + case 6: + return KSAUDIO_SPEAKER_5POINT1; /* not 5POINT1_SURROUND */ + case 7: + return KSAUDIO_SPEAKER_5POINT1 | SPEAKER_BACK_CENTER; + case 8: + return KSAUDIO_SPEAKER_7POINT1_SURROUND; /* Vista deprecates 7POINT1 */ + } + FIXME("Unknown speaker configuration: %u\n", channels); + return 0; +} + +static HRESULT map_channels(EDataFlow flow, const WAVEFORMATEX *fmt, int *alsa_channels, int *map) +{ + BOOL need_remap; + + if(flow != eCapture && (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE || fmt->nChannels > 2) ){ + WAVEFORMATEXTENSIBLE *fmtex = (void*)fmt; + UINT mask, flag = SPEAKER_FRONT_LEFT; + UINT i = 0; + + if(fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + fmtex->dwChannelMask != 0) + mask = fmtex->dwChannelMask; + else + mask = get_channel_mask(fmt->nChannels); + + *alsa_channels = 0; + + while(i < fmt->nChannels && !(flag & SPEAKER_RESERVED)){ + if(mask & flag){ + map[i] = alsa_channel_index(flag); + TRACE("Mapping mmdevapi channel %u (0x%x) to ALSA channel %d\n", + i, flag, map[i]); + if(map[i] >= *alsa_channels) + *alsa_channels = map[i] + 1; + ++i; + } + flag <<= 1; + } + + while(i < fmt->nChannels){ + map[i] = *alsa_channels; + TRACE("Mapping mmdevapi channel %u to ALSA channel %d\n", + i, map[i]); + ++*alsa_channels; + ++i; + } + + for(i = 0; i < fmt->nChannels; ++i){ + if(map[i] == -1){ + map[i] = *alsa_channels; + ++*alsa_channels; + TRACE("Remapping mmdevapi channel %u to ALSA channel %d\n", + i, map[i]); + } + } + + need_remap = need_remapping(fmt, map); + }else{ + *alsa_channels = fmt->nChannels; + + need_remap = FALSE; + } + + TRACE("need_remapping: %u, alsa_channels: %d\n", need_remap, *alsa_channels); + + return need_remap ? S_OK : S_FALSE; +} + +static void silence_buffer(struct alsa_stream *stream, BYTE *buffer, UINT32 frames) +{ + WAVEFORMATEXTENSIBLE *fmtex = (WAVEFORMATEXTENSIBLE*)stream->fmt; + if((stream->fmt->wFormatTag == WAVE_FORMAT_PCM || + (stream->fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + IsEqualGUID(&fmtex->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM))) && + stream->fmt->wBitsPerSample == 8) + memset(buffer, 128, frames * stream->fmt->nBlockAlign); + else + memset(buffer, 0, frames * stream->fmt->nBlockAlign); +} + +static ULONG_PTR zero_bits(void) +{ +#ifdef _WIN64 + return !NtCurrentTeb()->WowTebOffset ? 0 : 0x7fffffff; +#else + return 0; +#endif +} + +static NTSTATUS alsa_create_stream(void *args) +{ + struct create_stream_params *params = args; + struct alsa_stream *stream; + snd_pcm_sw_params_t *sw_params = NULL; + snd_pcm_format_t format; + unsigned int rate, alsa_period_us, i; + WAVEFORMATEXTENSIBLE *fmtex; + int err; + SIZE_T size; + + stream = calloc(1, sizeof(*stream)); + if(!stream){ + params->result = E_OUTOFMEMORY; + return STATUS_SUCCESS; + } + + params->result = alsa_open_device(params->device, params->flow, &stream->pcm_handle, &stream->hw_params); + if(FAILED(params->result)){ + free(stream); + return STATUS_SUCCESS; + } + + stream->need_remapping = map_channels(params->flow, params->fmt, &stream->alsa_channels, stream->alsa_channel_map) == S_OK; + + if((err = snd_pcm_hw_params_any(stream->pcm_handle, stream->hw_params)) < 0){ + WARN("Unable to get hw_params: %d (%s)\n", err, snd_strerror(err)); + params->result = AUDCLNT_E_ENDPOINT_CREATE_FAILED; + goto exit; + } + + if((err = snd_pcm_hw_params_set_access(stream->pcm_handle, stream->hw_params, + SND_PCM_ACCESS_RW_INTERLEAVED)) < 0){ + WARN("Unable to set access: %d (%s)\n", err, snd_strerror(err)); + params->result = AUDCLNT_E_ENDPOINT_CREATE_FAILED; + goto exit; + } + + format = alsa_format(params->fmt); + if (format == SND_PCM_FORMAT_UNKNOWN){ + params->result = AUDCLNT_E_UNSUPPORTED_FORMAT; + goto exit; + } + + if((err = snd_pcm_hw_params_set_format(stream->pcm_handle, stream->hw_params, + format)) < 0){ + WARN("Unable to set ALSA format to %u: %d (%s)\n", format, err, + snd_strerror(err)); + params->result = AUDCLNT_E_UNSUPPORTED_FORMAT; + goto exit; + } + + stream->alsa_format = format; + stream->flow = params->flow; + + rate = params->fmt->nSamplesPerSec; + if((err = snd_pcm_hw_params_set_rate_near(stream->pcm_handle, stream->hw_params, + &rate, NULL)) < 0){ + WARN("Unable to set rate to %u: %d (%s)\n", rate, err, + snd_strerror(err)); + params->result = AUDCLNT_E_UNSUPPORTED_FORMAT; + goto exit; + } + + if((err = snd_pcm_hw_params_set_channels(stream->pcm_handle, stream->hw_params, + stream->alsa_channels)) < 0){ + WARN("Unable to set channels to %u: %d (%s)\n", params->fmt->nChannels, err, + snd_strerror(err)); + params->result = AUDCLNT_E_UNSUPPORTED_FORMAT; + goto exit; + } + + stream->mmdev_period_rt = params->period; + alsa_period_us = stream->mmdev_period_rt / 10; + if((err = snd_pcm_hw_params_set_period_time_near(stream->pcm_handle, + stream->hw_params, &alsa_period_us, NULL)) < 0) + WARN("Unable to set period time near %u: %d (%s)\n", alsa_period_us, + err, snd_strerror(err)); + /* ALSA updates the output variable alsa_period_us */ + + stream->mmdev_period_frames = muldiv(params->fmt->nSamplesPerSec, + stream->mmdev_period_rt, 10000000); + + /* Buffer 4 ALSA periods if large enough, else 4 mmdevapi periods */ + stream->alsa_bufsize_frames = stream->mmdev_period_frames * 4; + if(err < 0 || alsa_period_us < params->period / 10) + err = snd_pcm_hw_params_set_buffer_size_near(stream->pcm_handle, + stream->hw_params, &stream->alsa_bufsize_frames); + else{ + unsigned int periods = 4; + err = snd_pcm_hw_params_set_periods_near(stream->pcm_handle, stream->hw_params, &periods, NULL); + } + if(err < 0) + WARN("Unable to set buffer size: %d (%s)\n", err, snd_strerror(err)); + + if((err = snd_pcm_hw_params(stream->pcm_handle, stream->hw_params)) < 0){ + WARN("Unable to set hw params: %d (%s)\n", err, snd_strerror(err)); + params->result = AUDCLNT_E_ENDPOINT_CREATE_FAILED; + goto exit; + } + + if((err = snd_pcm_hw_params_get_period_size(stream->hw_params, + &stream->alsa_period_frames, NULL)) < 0){ + WARN("Unable to get period size: %d (%s)\n", err, snd_strerror(err)); + params->result = AUDCLNT_E_ENDPOINT_CREATE_FAILED; + goto exit; + } + + if((err = snd_pcm_hw_params_get_buffer_size(stream->hw_params, + &stream->alsa_bufsize_frames)) < 0){ + WARN("Unable to get buffer size: %d (%s)\n", err, snd_strerror(err)); + params->result = AUDCLNT_E_ENDPOINT_CREATE_FAILED; + goto exit; + } + + sw_params = calloc(1, snd_pcm_sw_params_sizeof()); + if(!sw_params){ + params->result = E_OUTOFMEMORY; + goto exit; + } + + if((err = snd_pcm_sw_params_current(stream->pcm_handle, sw_params)) < 0){ + WARN("Unable to get sw_params: %d (%s)\n", err, snd_strerror(err)); + params->result = AUDCLNT_E_ENDPOINT_CREATE_FAILED; + goto exit; + } + + if((err = snd_pcm_sw_params_set_start_threshold(stream->pcm_handle, + sw_params, 1)) < 0){ + WARN("Unable set start threshold to 1: %d (%s)\n", err, snd_strerror(err)); + params->result = AUDCLNT_E_ENDPOINT_CREATE_FAILED; + goto exit; + } + + if((err = snd_pcm_sw_params_set_stop_threshold(stream->pcm_handle, + sw_params, stream->alsa_bufsize_frames)) < 0){ + WARN("Unable set stop threshold to %lu: %d (%s)\n", + stream->alsa_bufsize_frames, err, snd_strerror(err)); + params->result = AUDCLNT_E_ENDPOINT_CREATE_FAILED; + goto exit; + } + + if((err = snd_pcm_sw_params(stream->pcm_handle, sw_params)) < 0){ + WARN("Unable to set sw params: %d (%s)\n", err, snd_strerror(err)); + params->result = AUDCLNT_E_ENDPOINT_CREATE_FAILED; + goto exit; + } + + if((err = snd_pcm_prepare(stream->pcm_handle)) < 0){ + WARN("Unable to prepare device: %d (%s)\n", err, snd_strerror(err)); + params->result = AUDCLNT_E_ENDPOINT_CREATE_FAILED; + goto exit; + } + + /* Bear in mind weird situations where + * ALSA period (50ms) > mmdevapi buffer (3x10ms) + * or surprising rounding as seen with 22050x8x1 with Pulse: + * ALSA period 220 vs. 221 frames in mmdevapi and + * buffer 883 vs. 2205 frames in mmdevapi! */ + stream->bufsize_frames = muldiv(params->duration, params->fmt->nSamplesPerSec, 10000000); + if(params->share == AUDCLNT_SHAREMODE_EXCLUSIVE) + stream->bufsize_frames -= stream->bufsize_frames % stream->mmdev_period_frames; + stream->hidden_frames = stream->alsa_period_frames + stream->mmdev_period_frames + + muldiv(params->fmt->nSamplesPerSec, EXTRA_SAFE_RT, 10000000); + /* leave no less than about 1.33ms or 256 bytes of data after a rewind */ + stream->safe_rewind_frames = max(256 / params->fmt->nBlockAlign, muldiv(133, params->fmt->nSamplesPerSec, 100000)); + + /* Check if the ALSA buffer is so small that it will run out before + * the next MMDevAPI period tick occurs. Allow a little wiggle room + * with 120% of the period time. */ + if(stream->alsa_bufsize_frames < 1.2 * stream->mmdev_period_frames) + FIXME("ALSA buffer time is too small. Expect underruns. (%lu < %u * 1.2)\n", + stream->alsa_bufsize_frames, stream->mmdev_period_frames); + + fmtex = clone_format(params->fmt); + if(!fmtex){ + params->result = E_OUTOFMEMORY; + goto exit; + } + stream->fmt = &fmtex->Format; + + size = stream->bufsize_frames * params->fmt->nBlockAlign; + if(NtAllocateVirtualMemory(GetCurrentProcess(), (void **)&stream->local_buffer, zero_bits(), &size, + MEM_COMMIT, PAGE_READWRITE)){ + params->result = E_OUTOFMEMORY; + goto exit; + } + silence_buffer(stream, stream->local_buffer, stream->bufsize_frames); + + stream->silence_buf = malloc(stream->alsa_period_frames * stream->fmt->nBlockAlign); + if(!stream->silence_buf){ + params->result = E_OUTOFMEMORY; + goto exit; + } + silence_buffer(stream, stream->silence_buf, stream->alsa_period_frames); + + stream->vols = malloc(params->fmt->nChannels * sizeof(float)); + if(!stream->vols){ + params->result = E_OUTOFMEMORY; + goto exit; + } + for(i = 0; i < params->fmt->nChannels; ++i) + stream->vols[i] = 1.f; + + stream->share = params->share; + stream->flags = params->flags; + + pthread_mutex_init(&stream->lock, NULL); + + TRACE("ALSA period: %lu frames\n", stream->alsa_period_frames); + TRACE("ALSA buffer: %lu frames\n", stream->alsa_bufsize_frames); + TRACE("MMDevice period: %u frames\n", stream->mmdev_period_frames); + TRACE("MMDevice buffer: %u frames\n", stream->bufsize_frames); + +exit: + free(sw_params); + if(FAILED(params->result)){ + snd_pcm_close(stream->pcm_handle); + if(stream->local_buffer){ + size = 0; + NtFreeVirtualMemory(GetCurrentProcess(), (void **)&stream->local_buffer, &size, MEM_RELEASE); + } + free(stream->silence_buf); + free(stream->hw_params); + free(stream->fmt); + free(stream->vols); + free(stream); + }else{ + *params->stream = (stream_handle)(UINT_PTR)stream; + } + + return STATUS_SUCCESS; +} + +static NTSTATUS alsa_release_stream(void *args) +{ + struct release_stream_params *params = args; + struct alsa_stream *stream = handle_get_stream(params->stream); + SIZE_T size; + + if(params->timer_thread){ + stream->please_quit = TRUE; + NtWaitForSingleObject(params->timer_thread, FALSE, NULL); + NtClose(params->timer_thread); + } + + snd_pcm_drop(stream->pcm_handle); + snd_pcm_close(stream->pcm_handle); + if(stream->local_buffer){ + size = 0; + NtFreeVirtualMemory(GetCurrentProcess(), (void **)&stream->local_buffer, &size, MEM_RELEASE); + } + if(stream->tmp_buffer){ + size = 0; + NtFreeVirtualMemory(GetCurrentProcess(), (void **)&stream->tmp_buffer, &size, MEM_RELEASE); + } + free(stream->remapping_buf); + free(stream->silence_buf); + free(stream->hw_params); + free(stream->fmt); + free(stream->vols); + pthread_mutex_destroy(&stream->lock); + free(stream); + + params->result = S_OK; + return STATUS_SUCCESS; +} + +static BYTE *remap_channels(struct alsa_stream *stream, BYTE *buf, snd_pcm_uframes_t frames) +{ + snd_pcm_uframes_t i; + UINT c; + UINT bytes_per_sample = stream->fmt->wBitsPerSample / 8; + + if(!stream->need_remapping) + return buf; + + if(stream->remapping_buf_frames < frames){ + stream->remapping_buf = realloc(stream->remapping_buf, + bytes_per_sample * stream->alsa_channels * frames); + stream->remapping_buf_frames = frames; + } + + snd_pcm_format_set_silence(stream->alsa_format, stream->remapping_buf, + frames * stream->alsa_channels); + + switch(stream->fmt->wBitsPerSample){ + case 8: { + UINT8 *tgt_buf, *src_buf; + tgt_buf = stream->remapping_buf; + src_buf = buf; + for(i = 0; i < frames; ++i){ + for(c = 0; c < stream->fmt->nChannels; ++c) + tgt_buf[stream->alsa_channel_map[c]] = src_buf[c]; + tgt_buf += stream->alsa_channels; + src_buf += stream->fmt->nChannels; + } + break; + } + case 16: { + UINT16 *tgt_buf, *src_buf; + tgt_buf = (UINT16*)stream->remapping_buf; + src_buf = (UINT16*)buf; + for(i = 0; i < frames; ++i){ + for(c = 0; c < stream->fmt->nChannels; ++c) + tgt_buf[stream->alsa_channel_map[c]] = src_buf[c]; + tgt_buf += stream->alsa_channels; + src_buf += stream->fmt->nChannels; + } + } + break; + case 32: { + UINT32 *tgt_buf, *src_buf; + tgt_buf = (UINT32*)stream->remapping_buf; + src_buf = (UINT32*)buf; + for(i = 0; i < frames; ++i){ + for(c = 0; c < stream->fmt->nChannels; ++c) + tgt_buf[stream->alsa_channel_map[c]] = src_buf[c]; + tgt_buf += stream->alsa_channels; + src_buf += stream->fmt->nChannels; + } + } + break; + default: { + BYTE *tgt_buf, *src_buf; + tgt_buf = stream->remapping_buf; + src_buf = buf; + for(i = 0; i < frames; ++i){ + for(c = 0; c < stream->fmt->nChannels; ++c) + memcpy(&tgt_buf[stream->alsa_channel_map[c] * bytes_per_sample], + &src_buf[c * bytes_per_sample], bytes_per_sample); + tgt_buf += stream->alsa_channels * bytes_per_sample; + src_buf += stream->fmt->nChannels * bytes_per_sample; + } + } + break; + } + + return stream->remapping_buf; +} + +static void adjust_buffer_volume(const struct alsa_stream *stream, BYTE *buf, snd_pcm_uframes_t frames) +{ + BOOL adjust = FALSE; + UINT32 i, channels, mute = 0; + BYTE *end; + + if (stream->vol_adjusted_frames >= frames) + return; + channels = stream->fmt->nChannels; + + /* Adjust the buffer based on the volume for each channel */ + for (i = 0; i < channels; i++) + { + adjust |= stream->vols[i] != 1.0f; + if (stream->vols[i] == 0.0f) + mute++; + } + + if (mute == channels) + { + int err = snd_pcm_format_set_silence(stream->alsa_format, buf, frames * channels); + if (err < 0) + WARN("Setting buffer to silence failed: %d (%s)\n", err, snd_strerror(err)); + return; + } + if (!adjust) return; + + /* Skip the frames we've already adjusted before */ + end = buf + frames * stream->fmt->nBlockAlign; + buf += stream->vol_adjusted_frames * stream->fmt->nBlockAlign; + + switch (stream->alsa_format) + { +#ifndef WORDS_BIGENDIAN +#define PROCESS_BUFFER(type) do \ +{ \ + type *p = (type*)buf; \ + do \ + { \ + for (i = 0; i < channels; i++) \ + p[i] = p[i] * stream->vols[i]; \ + p += i; \ + } while ((BYTE*)p != end); \ +} while (0) + case SND_PCM_FORMAT_S16_LE: + PROCESS_BUFFER(INT16); + break; + case SND_PCM_FORMAT_S32_LE: + PROCESS_BUFFER(INT32); + break; + case SND_PCM_FORMAT_FLOAT_LE: + PROCESS_BUFFER(float); + break; + case SND_PCM_FORMAT_FLOAT64_LE: + PROCESS_BUFFER(double); + break; +#undef PROCESS_BUFFER + case SND_PCM_FORMAT_S20_3LE: + case SND_PCM_FORMAT_S24_3LE: + { + /* Do it 12 bytes at a time until it is no longer possible */ + UINT32 *q = (UINT32*)buf, mask = ~0xff; + BYTE *p; + + /* After we adjust the volume, we need to mask out low bits */ + if (stream->alsa_format == SND_PCM_FORMAT_S20_3LE) + mask = ~0x0fff; + + i = 0; + while (end - (BYTE*)q >= 12) + { + UINT32 v[4], k; + v[0] = q[0] << 8; + v[1] = q[1] << 16 | (q[0] >> 16 & ~0xff); + v[2] = q[2] << 24 | (q[1] >> 8 & ~0xff); + v[3] = q[2] & ~0xff; + for (k = 0; k < 4; k++) + { + v[k] = (INT32)((INT32)v[k] * stream->vols[i]); + v[k] &= mask; + if (++i == channels) i = 0; + } + *q++ = v[0] >> 8 | v[1] << 16; + *q++ = v[1] >> 16 | v[2] << 8; + *q++ = v[2] >> 24 | v[3]; + } + p = (BYTE*)q; + while (p != end) + { + UINT32 v = (INT32)((INT32)(p[0] << 8 | p[1] << 16 | p[2] << 24) * stream->vols[i]); + v &= mask; + *p++ = v >> 8 & 0xff; + *p++ = v >> 16 & 0xff; + *p++ = v >> 24; + if (++i == channels) i = 0; + } + break; + } +#endif + case SND_PCM_FORMAT_U8: + { + UINT8 *p = (UINT8*)buf; + do + { + for (i = 0; i < channels; i++) + p[i] = (int)((p[i] - 128) * stream->vols[i]) + 128; + p += i; + } while ((BYTE*)p != end); + break; + } + default: + TRACE("Unhandled format %i, not adjusting volume.\n", stream->alsa_format); + break; + } +} + +static snd_pcm_sframes_t alsa_write_best_effort(struct alsa_stream *stream, BYTE *buf, snd_pcm_uframes_t frames) +{ + snd_pcm_sframes_t written; + + adjust_buffer_volume(stream, buf, frames); + + /* Mark the frames we've already adjusted */ + if (stream->vol_adjusted_frames < frames) + stream->vol_adjusted_frames = frames; + + buf = remap_channels(stream, buf, frames); + + written = snd_pcm_writei(stream->pcm_handle, buf, frames); + if(written < 0){ + int ret; + + if(written == -EAGAIN) + /* buffer full */ + return 0; + + WARN("writei failed, recovering: %ld (%s)\n", written, + snd_strerror(written)); + + ret = snd_pcm_recover(stream->pcm_handle, written, 0); + if(ret < 0){ + WARN("Could not recover: %d (%s)\n", ret, snd_strerror(ret)); + return ret; + } + + written = snd_pcm_writei(stream->pcm_handle, buf, frames); + } + + if (written > 0) + stream->vol_adjusted_frames -= written; + return written; +} + +static snd_pcm_sframes_t alsa_write_buffer_wrap(struct alsa_stream *stream, BYTE *buf, + snd_pcm_uframes_t buflen, snd_pcm_uframes_t offs, + snd_pcm_uframes_t to_write) +{ + snd_pcm_sframes_t ret = 0; + + while(to_write){ + snd_pcm_uframes_t chunk; + snd_pcm_sframes_t tmp; + + if(offs + to_write > buflen) + chunk = buflen - offs; + else + chunk = to_write; + + tmp = alsa_write_best_effort(stream, buf + offs * stream->fmt->nBlockAlign, chunk); + if(tmp < 0) + return ret; + if(!tmp) + break; + + ret += tmp; + to_write -= tmp; + offs += tmp; + offs %= buflen; + } + + return ret; +} + +static UINT buf_ptr_diff(UINT left, UINT right, UINT bufsize) +{ + if(left <= right) + return right - left; + return bufsize - (left - right); +} + +static UINT data_not_in_alsa(struct alsa_stream *stream) +{ + UINT32 diff; + + diff = buf_ptr_diff(stream->lcl_offs_frames, stream->wri_offs_frames, stream->bufsize_frames); + if(diff) + return diff; + + return stream->held_frames - stream->data_in_alsa_frames; +} + +/* Here's the buffer setup: + * + * vvvvvvvv sent to HW already + * vvvvvvvv in ALSA buffer but rewindable + * [dddddddddddddddd] ALSA buffer + * [dddddddddddddddd--------] mmdevapi buffer + * ^^^^^^^^ data_in_alsa_frames + * ^^^^^^^^^^^^^^^^ held_frames + * ^ lcl_offs_frames + * ^ wri_offs_frames + * + * GetCurrentPadding is held_frames + * + * During period callback, we decrement held_frames, fill ALSA buffer, and move + * lcl_offs forward + * + * During Stop, we rewind the ALSA buffer + */ +static void alsa_write_data(struct alsa_stream *stream) +{ + snd_pcm_sframes_t written; + snd_pcm_uframes_t avail, max_copy_frames, data_frames_played; + int err; + + /* this call seems to be required to get an accurate snd_pcm_state() */ + avail = snd_pcm_avail_update(stream->pcm_handle); + + if(snd_pcm_state(stream->pcm_handle) == SND_PCM_STATE_XRUN){ + TRACE("XRun state, recovering\n"); + + avail = stream->alsa_bufsize_frames; + + if((err = snd_pcm_recover(stream->pcm_handle, -EPIPE, 1)) < 0) + WARN("snd_pcm_recover failed: %d (%s)\n", err, snd_strerror(err)); + + if((err = snd_pcm_reset(stream->pcm_handle)) < 0) + WARN("snd_pcm_reset failed: %d (%s)\n", err, snd_strerror(err)); + + if((err = snd_pcm_prepare(stream->pcm_handle)) < 0) + WARN("snd_pcm_prepare failed: %d (%s)\n", err, snd_strerror(err)); + } + + TRACE("avail: %ld\n", avail); + + /* Add a lead-in when starting with too few frames to ensure + * continuous rendering. Additional benefit: Force ALSA to start. */ + if(stream->data_in_alsa_frames == 0 && stream->held_frames < stream->alsa_period_frames) + { + alsa_write_best_effort(stream, stream->silence_buf, + stream->alsa_period_frames - stream->held_frames); + stream->vol_adjusted_frames = 0; + } + + if(stream->started) + max_copy_frames = data_not_in_alsa(stream); + else + max_copy_frames = 0; + + data_frames_played = min(stream->data_in_alsa_frames, avail); + stream->data_in_alsa_frames -= data_frames_played; + + if(stream->held_frames > data_frames_played){ + if(stream->started) + stream->held_frames -= data_frames_played; + }else + stream->held_frames = 0; + + while(avail && max_copy_frames){ + snd_pcm_uframes_t to_write; + + to_write = min(avail, max_copy_frames); + + written = alsa_write_buffer_wrap(stream, stream->local_buffer, + stream->bufsize_frames, stream->lcl_offs_frames, to_write); + if(written <= 0) + break; + + avail -= written; + stream->lcl_offs_frames += written; + stream->lcl_offs_frames %= stream->bufsize_frames; + stream->data_in_alsa_frames += written; + max_copy_frames -= written; + } + + if(stream->event) + NtSetEvent(stream->event, NULL); +} + +static void alsa_read_data(struct alsa_stream *stream) +{ + snd_pcm_sframes_t nread; + UINT32 pos = stream->wri_offs_frames, limit = stream->held_frames; + unsigned int i; + + if(!stream->started) + goto exit; + + /* FIXME: Detect overrun and signal DATA_DISCONTINUITY + * How to count overrun frames and report them as position increase? */ + limit = stream->bufsize_frames - max(limit, pos); + + nread = snd_pcm_readi(stream->pcm_handle, + stream->local_buffer + pos * stream->fmt->nBlockAlign, limit); + TRACE("read %ld from %u limit %u\n", nread, pos, limit); + if(nread < 0){ + int ret; + + if(nread == -EAGAIN) /* no data yet */ + return; + + WARN("read failed, recovering: %ld (%s)\n", nread, snd_strerror(nread)); + + ret = snd_pcm_recover(stream->pcm_handle, nread, 0); + if(ret < 0){ + WARN("Recover failed: %d (%s)\n", ret, snd_strerror(ret)); + return; + } + + nread = snd_pcm_readi(stream->pcm_handle, + stream->local_buffer + pos * stream->fmt->nBlockAlign, limit); + if(nread < 0){ + WARN("read failed: %ld (%s)\n", nread, snd_strerror(nread)); + return; + } + } + + for(i = 0; i < stream->fmt->nChannels; i++) + if(stream->vols[i] != 0.0f) + break; + if(i == stream->fmt->nChannels){ /* mute */ + int err; + if((err = snd_pcm_format_set_silence(stream->alsa_format, + stream->local_buffer + pos * stream->fmt->nBlockAlign, + nread)) < 0) + WARN("Setting buffer to silence failed: %d (%s)\n", err, + snd_strerror(err)); + } + + stream->wri_offs_frames += nread; + stream->wri_offs_frames %= stream->bufsize_frames; + stream->held_frames += nread; + +exit: + if(stream->event) + NtSetEvent(stream->event, NULL); +} + +static snd_pcm_uframes_t interp_elapsed_frames(struct alsa_stream *stream) +{ + LARGE_INTEGER time_freq, current_time, time_diff; + + NtQueryPerformanceCounter(¤t_time, &time_freq); + time_diff.QuadPart = current_time.QuadPart - stream->last_period_time.QuadPart; + return muldiv(time_diff.QuadPart, stream->fmt->nSamplesPerSec, time_freq.QuadPart); +} + +static int alsa_rewind_best_effort(struct alsa_stream *stream) +{ + snd_pcm_uframes_t len, leave; + + /* we can't use snd_pcm_rewindable, some PCM devices crash. so follow + * PulseAudio's example and rewind as much data as we believe is in the + * buffer, minus 1.33ms for safety. */ + + /* amount of data to leave in ALSA buffer */ + leave = interp_elapsed_frames(stream) + stream->safe_rewind_frames; + + if(stream->held_frames < leave) + stream->held_frames = 0; + else + stream->held_frames -= leave; + + if(stream->data_in_alsa_frames < leave) + len = 0; + else + len = stream->data_in_alsa_frames - leave; + + TRACE("rewinding %lu frames, now held %u\n", len, stream->held_frames); + + if(len) + /* snd_pcm_rewind return value is often broken, assume it succeeded */ + snd_pcm_rewind(stream->pcm_handle, len); + + stream->data_in_alsa_frames = 0; + + return len; +} + +static NTSTATUS alsa_start(void *args) +{ + struct start_params *params = args; + struct alsa_stream *stream = handle_get_stream(params->stream); + + alsa_lock(stream); + + if((stream->flags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK) && !stream->event) + return alsa_unlock_result(stream, ¶ms->result, AUDCLNT_E_EVENTHANDLE_NOT_SET); + + if(stream->started) + return alsa_unlock_result(stream, ¶ms->result, AUDCLNT_E_NOT_STOPPED); + + if(stream->flow == eCapture){ + /* dump any data that might be leftover in the ALSA capture buffer */ + snd_pcm_readi(stream->pcm_handle, stream->local_buffer, + stream->bufsize_frames); + }else{ + snd_pcm_sframes_t avail, written; + snd_pcm_uframes_t offs; + + avail = snd_pcm_avail_update(stream->pcm_handle); + avail = min(avail, stream->held_frames); + + if(stream->wri_offs_frames < stream->held_frames) + offs = stream->bufsize_frames - stream->held_frames + stream->wri_offs_frames; + else + offs = stream->wri_offs_frames - stream->held_frames; + + /* fill it with data */ + written = alsa_write_buffer_wrap(stream, stream->local_buffer, + stream->bufsize_frames, offs, avail); + + if(written > 0){ + stream->lcl_offs_frames = (offs + written) % stream->bufsize_frames; + stream->data_in_alsa_frames = written; + }else{ + stream->lcl_offs_frames = offs; + stream->data_in_alsa_frames = 0; + } + } + stream->started = TRUE; + + return alsa_unlock_result(stream, ¶ms->result, S_OK); +} + +static NTSTATUS alsa_stop(void *args) +{ + struct stop_params *params = args; + struct alsa_stream *stream = handle_get_stream(params->stream); + + alsa_lock(stream); + + if(!stream->started) + return alsa_unlock_result(stream, ¶ms->result, S_FALSE); + + if(stream->flow == eRender) + alsa_rewind_best_effort(stream); + + stream->started = FALSE; + + return alsa_unlock_result(stream, ¶ms->result, S_OK); +} + +static NTSTATUS alsa_reset(void *args) +{ + struct reset_params *params = args; + struct alsa_stream *stream = handle_get_stream(params->stream); + + alsa_lock(stream); + + if(stream->started) + return alsa_unlock_result(stream, ¶ms->result, AUDCLNT_E_NOT_STOPPED); + + if(stream->getbuf_last) + return alsa_unlock_result(stream, ¶ms->result, AUDCLNT_E_BUFFER_OPERATION_PENDING); + + if(snd_pcm_drop(stream->pcm_handle) < 0) + WARN("snd_pcm_drop failed\n"); + + if(snd_pcm_reset(stream->pcm_handle) < 0) + WARN("snd_pcm_reset failed\n"); + + if(snd_pcm_prepare(stream->pcm_handle) < 0) + WARN("snd_pcm_prepare failed\n"); + + if(stream->flow == eRender){ + stream->written_frames = 0; + stream->last_pos_frames = 0; + }else{ + stream->written_frames += stream->held_frames; + } + stream->held_frames = 0; + stream->lcl_offs_frames = 0; + stream->wri_offs_frames = 0; + + return alsa_unlock_result(stream, ¶ms->result, S_OK); +} + +static NTSTATUS alsa_timer_loop(void *args) +{ + struct timer_loop_params *params = args; + struct alsa_stream *stream = handle_get_stream(params->stream); + LARGE_INTEGER delay, next; + int adjust; + + alsa_lock(stream); + + delay.QuadPart = -stream->mmdev_period_rt; + NtQueryPerformanceCounter(&stream->last_period_time, NULL); + next.QuadPart = stream->last_period_time.QuadPart + stream->mmdev_period_rt; + + while(!stream->please_quit){ + if(stream->flow == eRender) + alsa_write_data(stream); + else if(stream->flow == eCapture) + alsa_read_data(stream); + alsa_unlock(stream); + + NtDelayExecution(FALSE, &delay); + + alsa_lock(stream); + NtQueryPerformanceCounter(&stream->last_period_time, NULL); + adjust = next.QuadPart - stream->last_period_time.QuadPart; + if(adjust > stream->mmdev_period_rt / 2) + adjust = stream->mmdev_period_rt / 2; + else if(adjust < -stream->mmdev_period_rt / 2) + adjust = -stream->mmdev_period_rt / 2; + delay.QuadPart = -(stream->mmdev_period_rt + adjust); + next.QuadPart += stream->mmdev_period_rt; + } + + alsa_unlock(stream); + + return STATUS_SUCCESS; +} + +static NTSTATUS alsa_get_render_buffer(void *args) +{ + struct get_render_buffer_params *params = args; + struct alsa_stream *stream = handle_get_stream(params->stream); + UINT32 write_pos, frames = params->frames; + SIZE_T size; + + alsa_lock(stream); + + if(stream->getbuf_last) + return alsa_unlock_result(stream, ¶ms->result, AUDCLNT_E_OUT_OF_ORDER); + + if(!frames) + return alsa_unlock_result(stream, ¶ms->result, S_OK); + + /* held_frames == GetCurrentPadding_nolock(); */ + if(stream->held_frames + frames > stream->bufsize_frames) + return alsa_unlock_result(stream, ¶ms->result, AUDCLNT_E_BUFFER_TOO_LARGE); + + write_pos = stream->wri_offs_frames; + if(write_pos + frames > stream->bufsize_frames){ + if(stream->tmp_buffer_frames < frames){ + if(stream->tmp_buffer){ + size = 0; + NtFreeVirtualMemory(GetCurrentProcess(), (void **)&stream->tmp_buffer, &size, MEM_RELEASE); + stream->tmp_buffer = NULL; + } + size = frames * stream->fmt->nBlockAlign; + if(NtAllocateVirtualMemory(GetCurrentProcess(), (void **)&stream->tmp_buffer, zero_bits(), &size, + MEM_COMMIT, PAGE_READWRITE)){ + stream->tmp_buffer_frames = 0; + return alsa_unlock_result(stream, ¶ms->result, E_OUTOFMEMORY); + } + stream->tmp_buffer_frames = frames; + } + *params->data = stream->tmp_buffer; + stream->getbuf_last = -frames; + }else{ + *params->data = stream->local_buffer + write_pos * stream->fmt->nBlockAlign; + stream->getbuf_last = frames; + } + + silence_buffer(stream, *params->data, frames); + + return alsa_unlock_result(stream, ¶ms->result, S_OK); +} + +static void alsa_wrap_buffer(struct alsa_stream *stream, BYTE *buffer, UINT32 written_frames) +{ + snd_pcm_uframes_t write_offs_frames = stream->wri_offs_frames; + UINT32 write_offs_bytes = write_offs_frames * stream->fmt->nBlockAlign; + snd_pcm_uframes_t chunk_frames = stream->bufsize_frames - write_offs_frames; + UINT32 chunk_bytes = chunk_frames * stream->fmt->nBlockAlign; + UINT32 written_bytes = written_frames * stream->fmt->nBlockAlign; + + if(written_bytes <= chunk_bytes){ + memcpy(stream->local_buffer + write_offs_bytes, buffer, written_bytes); + }else{ + memcpy(stream->local_buffer + write_offs_bytes, buffer, chunk_bytes); + memcpy(stream->local_buffer, buffer + chunk_bytes, + written_bytes - chunk_bytes); + } +} + +static NTSTATUS alsa_release_render_buffer(void *args) +{ + struct release_render_buffer_params *params = args; + struct alsa_stream *stream = handle_get_stream(params->stream); + UINT32 written_frames = params->written_frames; + BYTE *buffer; + + alsa_lock(stream); + + if(!written_frames){ + stream->getbuf_last = 0; + return alsa_unlock_result(stream, ¶ms->result, S_OK); + } + + if(!stream->getbuf_last) + return alsa_unlock_result(stream, ¶ms->result, AUDCLNT_E_OUT_OF_ORDER); + + if(written_frames > (stream->getbuf_last >= 0 ? stream->getbuf_last : -stream->getbuf_last)) + return alsa_unlock_result(stream, ¶ms->result, AUDCLNT_E_INVALID_SIZE); + + if(stream->getbuf_last >= 0) + buffer = stream->local_buffer + stream->wri_offs_frames * stream->fmt->nBlockAlign; + else + buffer = stream->tmp_buffer; + + if(params->flags & AUDCLNT_BUFFERFLAGS_SILENT) + silence_buffer(stream, buffer, written_frames); + + if(stream->getbuf_last < 0) + alsa_wrap_buffer(stream, buffer, written_frames); + + stream->wri_offs_frames += written_frames; + stream->wri_offs_frames %= stream->bufsize_frames; + stream->held_frames += written_frames; + stream->written_frames += written_frames; + stream->getbuf_last = 0; + + return alsa_unlock_result(stream, ¶ms->result, S_OK); +} + +static NTSTATUS alsa_get_capture_buffer(void *args) +{ + struct get_capture_buffer_params *params = args; + struct alsa_stream *stream = handle_get_stream(params->stream); + UINT32 *frames = params->frames; + SIZE_T size; + + alsa_lock(stream); + + if(stream->getbuf_last) + return alsa_unlock_result(stream, ¶ms->result, AUDCLNT_E_OUT_OF_ORDER); + + if(stream->held_frames < stream->mmdev_period_frames){ + *frames = 0; + return alsa_unlock_result(stream, ¶ms->result, AUDCLNT_S_BUFFER_EMPTY); + } + *frames = stream->mmdev_period_frames; + + if(stream->lcl_offs_frames + *frames > stream->bufsize_frames){ + UINT32 chunk_bytes, offs_bytes, frames_bytes; + if(stream->tmp_buffer_frames < *frames){ + if(stream->tmp_buffer){ + size = 0; + NtFreeVirtualMemory(GetCurrentProcess(), (void **)&stream->tmp_buffer, &size, MEM_RELEASE); + stream->tmp_buffer = NULL; + } + size = *frames * stream->fmt->nBlockAlign; + if(NtAllocateVirtualMemory(GetCurrentProcess(), (void **)&stream->tmp_buffer, zero_bits(), &size, + MEM_COMMIT, PAGE_READWRITE)){ + stream->tmp_buffer_frames = 0; + return alsa_unlock_result(stream, ¶ms->result, E_OUTOFMEMORY); + } + stream->tmp_buffer_frames = *frames; + } + + *params->data = stream->tmp_buffer; + chunk_bytes = (stream->bufsize_frames - stream->lcl_offs_frames) * + stream->fmt->nBlockAlign; + offs_bytes = stream->lcl_offs_frames * stream->fmt->nBlockAlign; + frames_bytes = *frames * stream->fmt->nBlockAlign; + memcpy(stream->tmp_buffer, stream->local_buffer + offs_bytes, chunk_bytes); + memcpy(stream->tmp_buffer + chunk_bytes, stream->local_buffer, + frames_bytes - chunk_bytes); + }else + *params->data = stream->local_buffer + + stream->lcl_offs_frames * stream->fmt->nBlockAlign; + + stream->getbuf_last = *frames; + *params->flags = 0; + + if(params->devpos) + *params->devpos = stream->written_frames; + if(params->qpcpos){ /* fixme: qpc of recording time */ + LARGE_INTEGER stamp, freq; + NtQueryPerformanceCounter(&stamp, &freq); + *params->qpcpos = (stamp.QuadPart * (INT64)10000000) / freq.QuadPart; + } + + return alsa_unlock_result(stream, ¶ms->result, *frames ? S_OK : AUDCLNT_S_BUFFER_EMPTY); +} + +static NTSTATUS alsa_release_capture_buffer(void *args) +{ + struct release_capture_buffer_params *params = args; + struct alsa_stream *stream = handle_get_stream(params->stream); + UINT32 done = params->done; + + alsa_lock(stream); + + if(!done){ + stream->getbuf_last = 0; + return alsa_unlock_result(stream, ¶ms->result, S_OK); + } + + if(!stream->getbuf_last) + return alsa_unlock_result(stream, ¶ms->result, AUDCLNT_E_OUT_OF_ORDER); + + if(stream->getbuf_last != done) + return alsa_unlock_result(stream, ¶ms->result, AUDCLNT_E_INVALID_SIZE); + + stream->written_frames += done; + stream->held_frames -= done; + stream->lcl_offs_frames += done; + stream->lcl_offs_frames %= stream->bufsize_frames; + stream->getbuf_last = 0; + + return alsa_unlock_result(stream, ¶ms->result, S_OK); +} + +static NTSTATUS alsa_is_format_supported(void *args) +{ + struct is_format_supported_params *params = args; + const WAVEFORMATEXTENSIBLE *fmtex = (const WAVEFORMATEXTENSIBLE *)params->fmt_in; + snd_pcm_t *pcm_handle; + snd_pcm_hw_params_t *hw_params; + snd_pcm_format_mask_t *formats = NULL; + snd_pcm_format_t format; + WAVEFORMATEXTENSIBLE *closest = NULL; + unsigned int max = 0, min = 0; + int err; + int alsa_channels, alsa_channel_map[32]; + + params->result = S_OK; + + if(!params->fmt_in || (params->share == AUDCLNT_SHAREMODE_SHARED && !params->fmt_out)) + params->result = E_POINTER; + else if(params->share != AUDCLNT_SHAREMODE_SHARED && params->share != AUDCLNT_SHAREMODE_EXCLUSIVE) + params->result = E_INVALIDARG; + else if(params->fmt_in->wFormatTag == WAVE_FORMAT_EXTENSIBLE){ + if(params->fmt_in->cbSize < sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX)) + params->result = E_INVALIDARG; + else if(params->fmt_in->nAvgBytesPerSec == 0 || params->fmt_in->nBlockAlign == 0 || + (fmtex->Samples.wValidBitsPerSample > params->fmt_in->wBitsPerSample)) + params->result = E_INVALIDARG; + } + if(FAILED(params->result)) + return STATUS_SUCCESS; + + if(params->fmt_in->nChannels == 0){ + params->result = AUDCLNT_E_UNSUPPORTED_FORMAT; + return STATUS_SUCCESS; + } + + params->result = alsa_open_device(params->device, params->flow, &pcm_handle, &hw_params); + if(FAILED(params->result)) + return STATUS_SUCCESS; + + if((err = snd_pcm_hw_params_any(pcm_handle, hw_params)) < 0){ + params->result = AUDCLNT_E_DEVICE_INVALIDATED; + goto exit; + } + + formats = calloc(1, snd_pcm_format_mask_sizeof()); + if(!formats){ + params->result = E_OUTOFMEMORY; + goto exit; + } + + snd_pcm_hw_params_get_format_mask(hw_params, formats); + format = alsa_format(params->fmt_in); + if (format == SND_PCM_FORMAT_UNKNOWN || + !snd_pcm_format_mask_test(formats, format)){ + params->result = AUDCLNT_E_UNSUPPORTED_FORMAT; + goto exit; + } + + closest = clone_format(params->fmt_in); + if(!closest){ + params->result = E_OUTOFMEMORY; + goto exit; + } + + if((err = snd_pcm_hw_params_get_rate_min(hw_params, &min, NULL)) < 0){ + params->result = AUDCLNT_E_DEVICE_INVALIDATED; + WARN("Unable to get min rate: %d (%s)\n", err, snd_strerror(err)); + goto exit; + } + + if((err = snd_pcm_hw_params_get_rate_max(hw_params, &max, NULL)) < 0){ + params->result = AUDCLNT_E_DEVICE_INVALIDATED; + WARN("Unable to get max rate: %d (%s)\n", err, snd_strerror(err)); + goto exit; + } + + if(params->fmt_in->nSamplesPerSec < min || params->fmt_in->nSamplesPerSec > max){ + params->result = AUDCLNT_E_UNSUPPORTED_FORMAT; + goto exit; + } + + if((err = snd_pcm_hw_params_get_channels_min(hw_params, &min)) < 0){ + params->result = AUDCLNT_E_DEVICE_INVALIDATED; + WARN("Unable to get min channels: %d (%s)\n", err, snd_strerror(err)); + goto exit; + } + + if((err = snd_pcm_hw_params_get_channels_max(hw_params, &max)) < 0){ + params->result = AUDCLNT_E_DEVICE_INVALIDATED; + WARN("Unable to get max channels: %d (%s)\n", err, snd_strerror(err)); + goto exit; + } + if(params->fmt_in->nChannels > max){ + params->result = S_FALSE; + closest->Format.nChannels = max; + }else if(params->fmt_in->nChannels < min){ + params->result = S_FALSE; + closest->Format.nChannels = min; + } + + map_channels(params->flow, params->fmt_in, &alsa_channels, alsa_channel_map); + + if(alsa_channels > max){ + params->result = S_FALSE; + closest->Format.nChannels = max; + } + + if(closest->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) + closest->dwChannelMask = get_channel_mask(closest->Format.nChannels); + + if(params->fmt_in->nBlockAlign != params->fmt_in->nChannels * params->fmt_in->wBitsPerSample / 8 || + params->fmt_in->nAvgBytesPerSec != params->fmt_in->nBlockAlign * params->fmt_in->nSamplesPerSec || + (params->fmt_in->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + fmtex->Samples.wValidBitsPerSample < params->fmt_in->wBitsPerSample)) + params->result = S_FALSE; + + if(params->share == AUDCLNT_SHAREMODE_EXCLUSIVE && params->fmt_in->wFormatTag == WAVE_FORMAT_EXTENSIBLE){ + if(fmtex->dwChannelMask == 0 || fmtex->dwChannelMask & SPEAKER_RESERVED) + params->result = S_FALSE; + } + +exit: + if(params->result == S_FALSE && !params->fmt_out) + params->result = AUDCLNT_E_UNSUPPORTED_FORMAT; + + if(params->result == S_FALSE && params->fmt_out) { + closest->Format.nBlockAlign = closest->Format.nChannels * closest->Format.wBitsPerSample / 8; + closest->Format.nAvgBytesPerSec = closest->Format.nBlockAlign * closest->Format.nSamplesPerSec; + if(closest->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) + closest->Samples.wValidBitsPerSample = closest->Format.wBitsPerSample; + memcpy(params->fmt_out, closest, closest->Format.cbSize); + } + free(closest); + free(formats); + free(hw_params); + snd_pcm_close(pcm_handle); + + return STATUS_SUCCESS; +} + +static NTSTATUS alsa_get_mix_format(void *args) +{ + struct get_mix_format_params *params = args; + WAVEFORMATEXTENSIBLE *fmt = params->fmt; + snd_pcm_t *pcm_handle; + snd_pcm_hw_params_t *hw_params; + snd_pcm_format_mask_t *formats; + unsigned int max_rate, max_channels; + int err; + + params->result = alsa_open_device(params->device, params->flow, &pcm_handle, &hw_params); + if(FAILED(params->result)) + return STATUS_SUCCESS; + + formats = calloc(1, snd_pcm_format_mask_sizeof()); + if(!formats){ + free(hw_params); + snd_pcm_close(pcm_handle); + params->result = E_OUTOFMEMORY; + return STATUS_SUCCESS; + } + + if((err = snd_pcm_hw_params_any(pcm_handle, hw_params)) < 0){ + WARN("Unable to get hw_params: %d (%s)\n", err, snd_strerror(err)); + params->result = AUDCLNT_E_DEVICE_INVALIDATED; + goto exit; + } + + snd_pcm_hw_params_get_format_mask(hw_params, formats); + + fmt->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + if(snd_pcm_format_mask_test(formats, SND_PCM_FORMAT_FLOAT_LE)){ + fmt->Format.wBitsPerSample = 32; + fmt->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + }else if(snd_pcm_format_mask_test(formats, SND_PCM_FORMAT_S16_LE)){ + fmt->Format.wBitsPerSample = 16; + fmt->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + }else if(snd_pcm_format_mask_test(formats, SND_PCM_FORMAT_U8)){ + fmt->Format.wBitsPerSample = 8; + fmt->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + }else if(snd_pcm_format_mask_test(formats, SND_PCM_FORMAT_S32_LE)){ + fmt->Format.wBitsPerSample = 32; + fmt->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + }else if(snd_pcm_format_mask_test(formats, SND_PCM_FORMAT_S24_3LE)){ + fmt->Format.wBitsPerSample = 24; + fmt->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + }else{ + ERR("Didn't recognize any available ALSA formats\n"); + params->result = AUDCLNT_E_DEVICE_INVALIDATED; + goto exit; + } + + if((err = snd_pcm_hw_params_get_channels_max(hw_params, &max_channels)) < 0){ + WARN("Unable to get max channels: %d (%s)\n", err, snd_strerror(err)); + params->result = AUDCLNT_E_DEVICE_INVALIDATED; + goto exit; + } + + if(max_channels > 6) + fmt->Format.nChannels = 2; + else + fmt->Format.nChannels = max_channels; + + if(fmt->Format.nChannels > 1 && (fmt->Format.nChannels & 0x1)){ + /* For most hardware on Windows, users must choose a configuration with an even + * number of channels (stereo, quad, 5.1, 7.1). Users can then disable + * channels, but those channels are still reported to applications from + * GetMixFormat! Some applications behave badly if given an odd number of + * channels (e.g. 2.1). */ + + if(fmt->Format.nChannels < max_channels) + fmt->Format.nChannels += 1; + else + /* We could "fake" more channels and downmix the emulated channels, + * but at that point you really ought to tweak your ALSA setup or + * just use PulseAudio. */ + WARN("Some Windows applications behave badly with an odd number of channels (%u)!\n", fmt->Format.nChannels); + } + + fmt->dwChannelMask = get_channel_mask(fmt->Format.nChannels); + + if((err = snd_pcm_hw_params_get_rate_max(hw_params, &max_rate, NULL)) < 0){ + WARN("Unable to get max rate: %d (%s)\n", err, snd_strerror(err)); + params->result = AUDCLNT_E_DEVICE_INVALIDATED; + goto exit; + } + + if(max_rate >= 48000) + fmt->Format.nSamplesPerSec = 48000; + else if(max_rate >= 44100) + fmt->Format.nSamplesPerSec = 44100; + else if(max_rate >= 22050) + fmt->Format.nSamplesPerSec = 22050; + else if(max_rate >= 11025) + fmt->Format.nSamplesPerSec = 11025; + else if(max_rate >= 8000) + fmt->Format.nSamplesPerSec = 8000; + else{ + ERR("Unknown max rate: %u\n", max_rate); + params->result = AUDCLNT_E_DEVICE_INVALIDATED; + goto exit; + } + + fmt->Format.nBlockAlign = (fmt->Format.wBitsPerSample * fmt->Format.nChannels) / 8; + fmt->Format.nAvgBytesPerSec = fmt->Format.nSamplesPerSec * fmt->Format.nBlockAlign; + + fmt->Samples.wValidBitsPerSample = fmt->Format.wBitsPerSample; + fmt->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); + +exit: + free(formats); + free(hw_params); + snd_pcm_close(pcm_handle); + + return STATUS_SUCCESS; +} + +static NTSTATUS alsa_get_buffer_size(void *args) +{ + struct get_buffer_size_params *params = args; + struct alsa_stream *stream = handle_get_stream(params->stream); + + alsa_lock(stream); + + *params->frames = stream->bufsize_frames; + + return alsa_unlock_result(stream, ¶ms->result, S_OK); +} + +static NTSTATUS alsa_get_latency(void *args) +{ + struct get_latency_params *params = args; + struct alsa_stream *stream = handle_get_stream(params->stream); + + alsa_lock(stream); + + /* Hide some frames in the ALSA buffer. Allows us to return GetCurrentPadding=0 + * yet have enough data left to play (as if it were in native's mixer). Add: + * + mmdevapi_period such that at the end of it, ALSA still has data; + * + EXTRA_SAFE (~4ms) to allow for late callback invocation / fluctuation; + * + alsa_period such that ALSA always has at least one period to play. */ + if(stream->flow == eRender) + *params->latency = muldiv(stream->hidden_frames, 10000000, stream->fmt->nSamplesPerSec); + else + *params->latency = muldiv(stream->alsa_period_frames, 10000000, stream->fmt->nSamplesPerSec) + + stream->mmdev_period_rt; + + return alsa_unlock_result(stream, ¶ms->result, S_OK); +} + +static NTSTATUS alsa_get_current_padding(void *args) +{ + struct get_current_padding_params *params = args; + struct alsa_stream *stream = handle_get_stream(params->stream); + + alsa_lock(stream); + + /* padding is solely updated at callback time in shared mode */ + *params->padding = stream->held_frames; + + return alsa_unlock_result(stream, ¶ms->result, S_OK); +} + +static NTSTATUS alsa_get_next_packet_size(void *args) +{ + struct get_next_packet_size_params *params = args; + struct alsa_stream *stream = handle_get_stream(params->stream); + + alsa_lock(stream); + + *params->frames = stream->held_frames < stream->mmdev_period_frames ? 0 : stream->mmdev_period_frames; + + return alsa_unlock_result(stream, ¶ms->result, S_OK); +} + +static NTSTATUS alsa_get_frequency(void *args) +{ + struct get_frequency_params *params = args; + struct alsa_stream *stream = handle_get_stream(params->stream); + UINT64 *freq = params->freq; + + alsa_lock(stream); + + if(stream->share == AUDCLNT_SHAREMODE_SHARED) + *freq = (UINT64)stream->fmt->nSamplesPerSec * stream->fmt->nBlockAlign; + else + *freq = stream->fmt->nSamplesPerSec; + + return alsa_unlock_result(stream, ¶ms->result, S_OK); +} + +static NTSTATUS alsa_get_position(void *args) +{ + struct get_position_params *params = args; + struct alsa_stream *stream = handle_get_stream(params->stream); + UINT64 position; + snd_pcm_state_t alsa_state; + + alsa_lock(stream); + + /* avail_update required to get accurate snd_pcm_state() */ + snd_pcm_avail_update(stream->pcm_handle); + alsa_state = snd_pcm_state(stream->pcm_handle); + + if(stream->flow == eRender){ + position = stream->written_frames - stream->held_frames; + + if(stream->started && alsa_state == SND_PCM_STATE_RUNNING && stream->held_frames) + /* we should be using snd_pcm_delay here, but it is broken + * especially during ALSA device underrun. instead, let's just + * interpolate between periods with the system timer. */ + position += interp_elapsed_frames(stream); + + position = min(position, stream->written_frames - stream->held_frames + stream->mmdev_period_frames); + + position = min(position, stream->written_frames); + }else + position = stream->written_frames + stream->held_frames; + + /* ensure monotic growth */ + if(position < stream->last_pos_frames) + position = stream->last_pos_frames; + else + stream->last_pos_frames = position; + + TRACE("frames written: %u, held: %u, state: 0x%x, position: %u\n", + (UINT32)(stream->written_frames%1000000000), stream->held_frames, + alsa_state, (UINT32)(position%1000000000)); + + if(stream->share == AUDCLNT_SHAREMODE_SHARED) + *params->pos = position * stream->fmt->nBlockAlign; + else + *params->pos = position; + + if(params->qpctime){ + LARGE_INTEGER stamp, freq; + NtQueryPerformanceCounter(&stamp, &freq); + *params->qpctime = (stamp.QuadPart * (INT64)10000000) / freq.QuadPart; + } + + return alsa_unlock_result(stream, ¶ms->result, S_OK); +} + +static NTSTATUS alsa_set_volumes(void *args) +{ + struct set_volumes_params *params = args; + struct alsa_stream *stream = handle_get_stream(params->stream); + unsigned int i; + + for(i = 0; i < stream->fmt->nChannels; i++) + stream->vols[i] = params->volumes[i] * params->session_volumes[i] * params->master_volume; + + return STATUS_SUCCESS; +} + +static NTSTATUS alsa_set_event_handle(void *args) +{ + struct set_event_handle_params *params = args; + struct alsa_stream *stream = handle_get_stream(params->stream); + + alsa_lock(stream); + + if(!(stream->flags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK)) + return alsa_unlock_result(stream, ¶ms->result, AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED); + + if (stream->event){ + FIXME("called twice\n"); + return alsa_unlock_result(stream, ¶ms->result, HRESULT_FROM_WIN32(ERROR_INVALID_NAME)); + } + + stream->event = params->event; + + return alsa_unlock_result(stream, ¶ms->result, S_OK); +} + +static NTSTATUS alsa_is_started(void *args) +{ + struct is_started_params *params = args; + struct alsa_stream *stream = handle_get_stream(params->stream); + + alsa_lock(stream); + + return alsa_unlock_result(stream, ¶ms->result, stream->started ? S_OK : S_FALSE); +} + +static unsigned int alsa_probe_num_speakers(char *name) +{ + snd_pcm_t *handle; + snd_pcm_hw_params_t *params; + int err; + unsigned int max_channels = 0; + + if ((err = snd_pcm_open(&handle, name, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + WARN("The device \"%s\" failed to open: %d (%s).\n", + name, err, snd_strerror(err)); + return 0; + } + + params = malloc(snd_pcm_hw_params_sizeof()); + if (!params) { + WARN("Out of memory.\n"); + snd_pcm_close(handle); + return 0; + } + + if ((err = snd_pcm_hw_params_any(handle, params)) < 0) { + WARN("snd_pcm_hw_params_any failed for \"%s\": %d (%s).\n", + name, err, snd_strerror(err)); + goto exit; + } + + if ((err = snd_pcm_hw_params_get_channels_max(params, + &max_channels)) < 0){ + WARN("Unable to get max channels: %d (%s)\n", err, snd_strerror(err)); + goto exit; + } + +exit: + free(params); + snd_pcm_close(handle); + + return max_channels; +} + +enum AudioDeviceConnectionType { + AudioDeviceConnectionType_Unknown = 0, + AudioDeviceConnectionType_PCI, + AudioDeviceConnectionType_USB +}; + +static NTSTATUS alsa_get_prop_value(void *args) +{ + struct get_prop_value_params *params = args; + const char *name = params->device; + EDataFlow flow = params->flow; + const GUID *guid = params->guid; + const PROPERTYKEY *prop = params->prop; + PROPVARIANT *out = params->value; + static const PROPERTYKEY devicepath_key = { /* undocumented? - {b3f8fa53-0004-438e-9003-51a46e139bfc},2 */ + {0xb3f8fa53, 0x0004, 0x438e, {0x90, 0x03, 0x51, 0xa4, 0x6e, 0x13, 0x9b, 0xfc}}, 2 + }; + + if(IsEqualPropertyKey(*prop, devicepath_key)) + { + enum AudioDeviceConnectionType connection = AudioDeviceConnectionType_Unknown; + USHORT vendor_id = 0, product_id = 0; + char uevent[MAX_PATH]; + FILE *fuevent = NULL; + int card, device; + UINT serial_number; + char buf[128]; + int len; + + if(sscanf(name, "plughw:%u,%u", &card, &device)){ + sprintf(uevent, "/sys/class/sound/card%u/device/uevent", card); + fuevent = fopen(uevent, "r"); + } + + if(fuevent){ + char line[256]; + + while (fgets(line, sizeof(line), fuevent)) { + char *val; + size_t val_len; + + if((val = strchr(line, '='))) { + val[0] = 0; + val++; + + val_len = strlen(val); + if(val_len > 0 && val[val_len - 1] == '\n') { val[val_len - 1] = 0; } + + if(!strcmp(line, "PCI_ID")){ + connection = AudioDeviceConnectionType_PCI; + if(sscanf(val, "%hX:%hX", &vendor_id, &product_id)<2){ + WARN("Unexpected input when reading PCI_ID in uevent file.\n"); + connection = AudioDeviceConnectionType_Unknown; + break; + } + }else if(!strcmp(line, "DEVTYPE") && !strcmp(val,"usb_interface")) + connection = AudioDeviceConnectionType_USB; + else if(!strcmp(line, "PRODUCT")) + if(sscanf(val, "%hx/%hx/", &vendor_id, &product_id)<2){ + WARN("Unexpected input when reading PRODUCT in uevent file.\n"); + connection = AudioDeviceConnectionType_Unknown; + break; + } + } + } + + fclose(fuevent); + } + + /* As hardly any audio devices have serial numbers, Windows instead + appears to use a persistent random number. We emulate this here + by instead using the last 8 hex digits of the GUID. */ + serial_number = (guid->Data4[4] << 24) | (guid->Data4[5] << 16) | (guid->Data4[6] << 8) | guid->Data4[7]; + + if(connection == AudioDeviceConnectionType_USB) + sprintf(buf, "{1}.USB\\VID_%04X&PID_%04X\\%u&%08X", + vendor_id, product_id, device, serial_number); + else if (connection == AudioDeviceConnectionType_PCI) + sprintf(buf, "{1}.HDAUDIO\\FUNC_01&VEN_%04X&DEV_%04X\\%u&%08X", + vendor_id, product_id, device, serial_number); + else + sprintf(buf, "{1}.ROOT\\MEDIA\\%04u", serial_number & 0x1FF); + + len = strlen(buf) + 1; + if(*params->buffer_size < len * sizeof(WCHAR)){ + params->result = E_NOT_SUFFICIENT_BUFFER; + *params->buffer_size = len * sizeof(WCHAR); + return STATUS_SUCCESS; + } + out->vt = VT_LPWSTR; + out->pwszVal = params->buffer; + ntdll_umbstowcs(buf, len, out->pwszVal, len); + params->result = S_OK; + return STATUS_SUCCESS; + } else if (flow != eCapture && IsEqualPropertyKey(*prop, PKEY_AudioEndpoint_PhysicalSpeakers)) { + unsigned int num_speakers, card, device; + char hwname[255]; + + if (sscanf(name, "plughw:%u,%u", &card, &device)) + sprintf(hwname, "hw:%u,%u", card, device); /* must be hw rather than plughw to work */ + else + strcpy(hwname, name); + + num_speakers = alsa_probe_num_speakers(hwname); + if (num_speakers == 0){ + params->result = E_FAIL; + return STATUS_SUCCESS; + } + out->vt = VT_UI4; + + if (num_speakers > 6) + out->ulVal = KSAUDIO_SPEAKER_STEREO; + else if (num_speakers == 6) + out->ulVal = KSAUDIO_SPEAKER_5POINT1; + else if (num_speakers >= 4) + out->ulVal = KSAUDIO_SPEAKER_QUAD; + else if (num_speakers >= 2) + out->ulVal = KSAUDIO_SPEAKER_STEREO; + else if (num_speakers == 1) + out->ulVal = KSAUDIO_SPEAKER_MONO; + + params->result = S_OK; + return STATUS_SUCCESS; + } + + TRACE("Unimplemented property %s,%u\n", wine_dbgstr_guid(&prop->fmtid), (unsigned)prop->pid); + + params->result = E_NOTIMPL; + return STATUS_SUCCESS; +} + +const unixlib_entry_t __wine_unix_call_funcs[] = +{ + NULL, + NULL, + NULL, + alsa_get_endpoint_ids, + alsa_create_stream, + alsa_release_stream, + alsa_start, + alsa_stop, + alsa_reset, + alsa_timer_loop, + alsa_get_render_buffer, + alsa_release_render_buffer, + alsa_get_capture_buffer, + alsa_release_capture_buffer, + alsa_is_format_supported, + alsa_get_mix_format, + NULL, + alsa_get_buffer_size, + alsa_get_latency, + alsa_get_current_padding, + alsa_get_next_packet_size, + alsa_get_frequency, + alsa_get_position, + alsa_set_volumes, + alsa_set_event_handle, + NULL, + alsa_is_started, + alsa_get_prop_value, + NULL, + alsa_midi_release, + alsa_midi_out_message, + alsa_midi_in_message, + alsa_midi_notify_wait, + NULL, +}; + +#ifdef _WIN64 + +typedef UINT PTR32; + +static NTSTATUS alsa_wow64_get_endpoint_ids(void *args) +{ + struct + { + EDataFlow flow; + PTR32 endpoints; + unsigned int size; + HRESULT result; + unsigned int num; + unsigned int default_idx; + } *params32 = args; + struct get_endpoint_ids_params params = + { + .flow = params32->flow, + .endpoints = ULongToPtr(params32->endpoints), + .size = params32->size + }; + alsa_get_endpoint_ids(¶ms); + params32->size = params.size; + params32->result = params.result; + params32->num = params.num; + params32->default_idx = params.default_idx; + return STATUS_SUCCESS; +} + +static NTSTATUS alsa_wow64_create_stream(void *args) +{ + struct + { + PTR32 name; + PTR32 device; + EDataFlow flow; + AUDCLNT_SHAREMODE share; + DWORD flags; + REFERENCE_TIME duration; + REFERENCE_TIME period; + PTR32 fmt; + HRESULT result; + PTR32 channel_count; + PTR32 stream; + } *params32 = args; + struct create_stream_params params = + { + .name = ULongToPtr(params32->name), + .device = ULongToPtr(params32->device), + .flow = params32->flow, + .share = params32->share, + .flags = params32->flags, + .duration = params32->duration, + .period = params32->period, + .fmt = ULongToPtr(params32->fmt), + .channel_count = ULongToPtr(params32->channel_count), + .stream = ULongToPtr(params32->stream) + }; + alsa_create_stream(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + +static NTSTATUS alsa_wow64_release_stream(void *args) +{ + struct + { + stream_handle stream; + PTR32 timer_thread; + HRESULT result; + } *params32 = args; + struct release_stream_params params = + { + .stream = params32->stream, + .timer_thread = ULongToHandle(params32->timer_thread) + }; + alsa_release_stream(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + +static NTSTATUS alsa_wow64_get_render_buffer(void *args) +{ + struct + { + stream_handle stream; + UINT32 frames; + HRESULT result; + PTR32 data; + } *params32 = args; + BYTE *data = NULL; + struct get_render_buffer_params params = + { + .stream = params32->stream, + .frames = params32->frames, + .data = &data + }; + alsa_get_render_buffer(¶ms); + params32->result = params.result; + *(unsigned int *)ULongToPtr(params32->data) = PtrToUlong(data); + return STATUS_SUCCESS; +} + +static NTSTATUS alsa_wow64_get_capture_buffer(void *args) +{ + struct + { + stream_handle stream; + HRESULT result; + PTR32 data; + PTR32 frames; + PTR32 flags; + PTR32 devpos; + PTR32 qpcpos; + } *params32 = args; + BYTE *data = NULL; + struct get_capture_buffer_params params = + { + .stream = params32->stream, + .data = &data, + .frames = ULongToPtr(params32->frames), + .flags = ULongToPtr(params32->flags), + .devpos = ULongToPtr(params32->devpos), + .qpcpos = ULongToPtr(params32->qpcpos) + }; + alsa_get_capture_buffer(¶ms); + params32->result = params.result; + *(unsigned int *)ULongToPtr(params32->data) = PtrToUlong(data); + return STATUS_SUCCESS; +}; + +static NTSTATUS alsa_wow64_is_format_supported(void *args) +{ + struct + { + PTR32 device; + EDataFlow flow; + AUDCLNT_SHAREMODE share; + PTR32 fmt_in; + PTR32 fmt_out; + HRESULT result; + } *params32 = args; + struct is_format_supported_params params = + { + .device = ULongToPtr(params32->device), + .flow = params32->flow, + .share = params32->share, + .fmt_in = ULongToPtr(params32->fmt_in), + .fmt_out = ULongToPtr(params32->fmt_out) + }; + alsa_is_format_supported(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + +static NTSTATUS alsa_wow64_get_mix_format(void *args) +{ + struct + { + PTR32 device; + EDataFlow flow; + PTR32 fmt; + HRESULT result; + } *params32 = args; + struct get_mix_format_params params = + { + .device = ULongToPtr(params32->device), + .flow = params32->flow, + .fmt = ULongToPtr(params32->fmt) + }; + alsa_get_mix_format(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + +static NTSTATUS alsa_wow64_get_buffer_size(void *args) +{ + struct + { + stream_handle stream; + HRESULT result; + PTR32 frames; + } *params32 = args; + struct get_buffer_size_params params = + { + .stream = params32->stream, + .frames = ULongToPtr(params32->frames) + }; + alsa_get_buffer_size(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + +static NTSTATUS alsa_wow64_get_latency(void *args) +{ + struct + { + stream_handle stream; + HRESULT result; + PTR32 latency; + } *params32 = args; + struct get_latency_params params = + { + .stream = params32->stream, + .latency = ULongToPtr(params32->latency) + }; + alsa_get_latency(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + +static NTSTATUS alsa_wow64_get_current_padding(void *args) +{ + struct + { + stream_handle stream; + HRESULT result; + PTR32 padding; + } *params32 = args; + struct get_current_padding_params params = + { + .stream = params32->stream, + .padding = ULongToPtr(params32->padding) + }; + alsa_get_current_padding(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + +static NTSTATUS alsa_wow64_get_next_packet_size(void *args) +{ + struct + { + stream_handle stream; + HRESULT result; + PTR32 frames; + } *params32 = args; + struct get_next_packet_size_params params = + { + .stream = params32->stream, + .frames = ULongToPtr(params32->frames) + }; + alsa_get_next_packet_size(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + +static NTSTATUS alsa_wow64_get_frequency(void *args) +{ + struct + { + stream_handle stream; + HRESULT result; + PTR32 freq; + } *params32 = args; + struct get_frequency_params params = + { + .stream = params32->stream, + .freq = ULongToPtr(params32->freq) + }; + alsa_get_frequency(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + +static NTSTATUS alsa_wow64_get_position(void *args) +{ + struct + { + stream_handle stream; + BOOL device; + HRESULT result; + PTR32 pos; + PTR32 qpctime; + } *params32 = args; + struct get_position_params params = + { + .stream = params32->stream, + .device = params32->device, + .pos = ULongToPtr(params32->pos), + .qpctime = ULongToPtr(params32->qpctime) + }; + alsa_get_position(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + +static NTSTATUS alsa_wow64_set_volumes(void *args) +{ + struct + { + stream_handle stream; + float master_volume; + PTR32 volumes; + PTR32 session_volumes; + int channel; + } *params32 = args; + struct set_volumes_params params = + { + .stream = params32->stream, + .master_volume = params32->master_volume, + .volumes = ULongToPtr(params32->volumes), + .session_volumes = ULongToPtr(params32->session_volumes), + .channel = params32->channel + }; + return alsa_set_volumes(¶ms); +} + +static NTSTATUS alsa_wow64_set_event_handle(void *args) +{ + struct + { + stream_handle stream; + PTR32 event; + HRESULT result; + } *params32 = args; + struct set_event_handle_params params = + { + .stream = params32->stream, + .event = ULongToHandle(params32->event) + }; + + alsa_set_event_handle(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + +static NTSTATUS alsa_wow64_get_prop_value(void *args) +{ + struct propvariant32 + { + WORD vt; + WORD pad1, pad2, pad3; + union + { + ULONG ulVal; + PTR32 ptr; + ULARGE_INTEGER uhVal; + }; + } *value32; + struct + { + PTR32 device; + EDataFlow flow; + PTR32 guid; + PTR32 prop; + HRESULT result; + PTR32 value; + PTR32 buffer; /* caller allocated buffer to hold value's strings */ + PTR32 buffer_size; + } *params32 = args; + PROPVARIANT value; + struct get_prop_value_params params = + { + .device = ULongToPtr(params32->device), + .flow = params32->flow, + .guid = ULongToPtr(params32->guid), + .prop = ULongToPtr(params32->prop), + .value = &value, + .buffer = ULongToPtr(params32->buffer), + .buffer_size = ULongToPtr(params32->buffer_size) + }; + alsa_get_prop_value(¶ms); + params32->result = params.result; + if (SUCCEEDED(params.result)) + { + value32 = UlongToPtr(params32->value); + value32->vt = value.vt; + switch (value.vt) + { + case VT_UI4: + value32->ulVal = value.ulVal; + break; + case VT_LPWSTR: + value32->ptr = params32->buffer; + break; + default: + FIXME("Unhandled vt %04x\n", value.vt); + } + } + return STATUS_SUCCESS; +} + +const unixlib_entry_t __wine_unix_call_wow64_funcs[] = +{ + NULL, + NULL, + NULL, + alsa_wow64_get_endpoint_ids, + alsa_wow64_create_stream, + alsa_wow64_release_stream, + alsa_start, + alsa_stop, + alsa_reset, + alsa_timer_loop, + alsa_wow64_get_render_buffer, + alsa_release_render_buffer, + alsa_wow64_get_capture_buffer, + alsa_release_capture_buffer, + alsa_wow64_is_format_supported, + alsa_wow64_get_mix_format, + NULL, + alsa_wow64_get_buffer_size, + alsa_wow64_get_latency, + alsa_wow64_get_current_padding, + alsa_wow64_get_next_packet_size, + alsa_wow64_get_frequency, + alsa_wow64_get_position, + alsa_wow64_set_volumes, + alsa_wow64_set_event_handle, + NULL, + alsa_is_started, + alsa_wow64_get_prop_value, + NULL, + alsa_midi_release, + alsa_wow64_midi_out_message, + alsa_wow64_midi_in_message, + alsa_wow64_midi_notify_wait, + NULL, +}; + +#endif /* _WIN64 */ diff --git a/pkgs/osu-wine/audio-revert/winealsa.drv/alsamidi.c b/pkgs/osu-wine/audio-revert/winealsa.drv/alsamidi.c new file mode 100644 index 0000000..8b4207c --- /dev/null +++ b/pkgs/osu-wine/audio-revert/winealsa.drv/alsamidi.c @@ -0,0 +1,1877 @@ +/* + * MIDI driver for ALSA (unixlib) + * + * Copyright 1994 Martin Ayotte + * Copyright 1998 Luiz Otavio L. Zorzella + * Copyright 1998, 1999 Eric POUECH + * Copyright 2003 Christian Costa + * Copyright 2022 Huw Davies + * + * 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 + */ + +#if 0 +#pragma makedep unix +#endif + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "windef.h" +#include "winbase.h" +#include "winternl.h" +#include "mmdeviceapi.h" +#include "mmddk.h" + +#include "wine/debug.h" +#include "wine/unixlib.h" + +#include "unixlib.h" + +WINE_DEFAULT_DEBUG_CHANNEL(midi); + +struct midi_dest +{ + BOOL bEnabled; + MIDIOPENDESC midiDesc; + BYTE runningStatus; + WORD wFlags; + MIDIOUTCAPSW caps; + snd_seq_t *seq; + snd_seq_addr_t addr; + int port_out; +}; + +struct midi_src +{ + int state; /* -1 disabled, 0 is no recording started, 1 in recording, bit 2 set if in sys exclusive recording */ + MIDIOPENDESC midiDesc; + WORD wFlags; + MIDIHDR *lpQueueHdr; + UINT startTime; + MIDIINCAPSW caps; + snd_seq_t *seq; + snd_seq_addr_t addr; + int port_in; +}; + +static pthread_mutex_t seq_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t in_buffer_mutex = PTHREAD_MUTEX_INITIALIZER; + +static unsigned int num_dests, num_srcs; +static struct midi_dest dests[MAX_MIDIOUTDRV]; +static struct midi_src srcs[MAX_MIDIINDRV]; +static snd_seq_t *midi_seq; +static unsigned int seq_refs; +static int port_in = -1; + +static unsigned int num_midi_in_started; +static int rec_cancel_pipe[2]; +static pthread_t rec_thread_id; + +static pthread_mutex_t notify_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t notify_read_cond = PTHREAD_COND_INITIALIZER; +static pthread_cond_t notify_write_cond = PTHREAD_COND_INITIALIZER; +static BOOL notify_quit; +#define NOTIFY_BUFFER_SIZE 64 + 1 /* + 1 for the sentinel */ +static struct notify_context notify_buffer[NOTIFY_BUFFER_SIZE]; +static struct notify_context *notify_read = notify_buffer, *notify_write = notify_buffer; + +static void seq_lock(void) +{ + pthread_mutex_lock(&seq_mutex); +} + +static void seq_unlock(void) +{ + pthread_mutex_unlock(&seq_mutex); +} + +static void in_buffer_lock(void) +{ + pthread_mutex_lock(&in_buffer_mutex); +} + +static void in_buffer_unlock(void) +{ + pthread_mutex_unlock(&in_buffer_mutex); +} + +static uint64_t get_time_msec(void) +{ + struct timespec now = {0, 0}; + +#ifdef CLOCK_MONOTONIC_RAW + if (!clock_gettime(CLOCK_MONOTONIC_RAW, &now)) + return (uint64_t)now.tv_sec * 1000 + now.tv_nsec / 1000000; +#endif + clock_gettime(CLOCK_MONOTONIC, &now); + return (uint64_t)now.tv_sec * 1000 + now.tv_nsec / 1000000; +} + +static void set_in_notify(struct notify_context *notify, struct midi_src *src, WORD dev_id, WORD msg, + UINT_PTR param_1, UINT_PTR param_2) +{ + notify->send_notify = TRUE; + notify->dev_id = dev_id; + notify->msg = msg; + notify->param_1 = param_1; + notify->param_2 = param_2; + notify->callback = src->midiDesc.dwCallback; + notify->flags = src->wFlags; + notify->device = src->midiDesc.hMidi; + notify->instance = src->midiDesc.dwInstance; +} + +/* + * notify buffer: The notification ring buffer is implemented so that + * there is always at least one unused sentinel before the current + * read position in order to allow detection of the full vs empty + * state. + */ +static struct notify_context *notify_buffer_next(struct notify_context *notify) +{ + if (++notify >= notify_buffer + ARRAY_SIZE(notify_buffer)) + notify = notify_buffer; + + return notify; +} + +static BOOL notify_buffer_empty(void) +{ + return notify_read == notify_write; +} + +static BOOL notify_buffer_full(void) +{ + return notify_buffer_next(notify_write) == notify_read; +} + +static BOOL notify_buffer_add(struct notify_context *notify) +{ + if (notify_buffer_full()) return FALSE; + + *notify_write = *notify; + notify_write = notify_buffer_next(notify_write); + return TRUE; +} + +static BOOL notify_buffer_remove(struct notify_context *notify) +{ + if (notify_buffer_empty()) return FALSE; + + *notify = *notify_read; + notify_read = notify_buffer_next(notify_read); + return TRUE; +} + +static void notify_post(struct notify_context *notify) +{ + pthread_mutex_lock(¬ify_mutex); + + if (notify) + { + while (notify_buffer_full()) + pthread_cond_wait(¬ify_write_cond, ¬ify_mutex); + + notify_buffer_add(notify); + } + else notify_quit = TRUE; + pthread_cond_signal(¬ify_read_cond); + + pthread_mutex_unlock(¬ify_mutex); +} + +static snd_seq_t *seq_open(int *port_in_ret) +{ + static int midi_warn; + + seq_lock(); + if (seq_refs == 0) + { + if (snd_seq_open(&midi_seq, "default", SND_SEQ_OPEN_DUPLEX, 0) < 0) + { + if (!midi_warn) + WARN("Error opening ALSA sequencer.\n"); + midi_warn = 1; + seq_unlock(); + return NULL; + } + snd_seq_set_client_name(midi_seq, "WINE midi driver"); + } + seq_refs++; + + if (port_in_ret) + { + if (port_in < 0) + { + port_in = snd_seq_create_simple_port(midi_seq, "WINE ALSA Input", + SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_READ | SND_SEQ_PORT_CAP_SUBS_WRITE, + SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION); + if (port_in < 0) + TRACE("Unable to create input port\n"); + else + TRACE("Input port %d created successfully\n", port_in); + } + *port_in_ret = port_in; + } + seq_unlock(); + return midi_seq; +} + +static void seq_close(void) +{ + seq_lock(); + if (--seq_refs == 0) + { + if (port_in >= 0) + { + snd_seq_delete_simple_port(midi_seq, port_in); + port_in = -1; + } + snd_seq_close(midi_seq); + midi_seq = NULL; + } + seq_unlock(); +} + +static int alsa_to_win_device_type(unsigned int type) +{ + /* MOD_MIDIPORT output port + * MOD_SYNTH generic internal synth + * MOD_SQSYNTH square wave internal synth + * MOD_FMSYNTH FM internal synth + * MOD_MAPPER MIDI mapper + * MOD_WAVETABLE hardware wavetable internal synth + * MOD_SWSYNTH software internal synth + */ + + /* FIXME Is this really the correct equivalence from ALSA to + Windows Sound type? */ + + if (type & SND_SEQ_PORT_TYPE_SYNTH) + return MOD_FMSYNTH; + + if (type & (SND_SEQ_PORT_TYPE_DIRECT_SAMPLE|SND_SEQ_PORT_TYPE_SAMPLE)) + return MOD_SYNTH; + + if (type & (SND_SEQ_PORT_TYPE_MIDI_GENERIC|SND_SEQ_PORT_TYPE_APPLICATION)) + return MOD_MIDIPORT; + + ERR("Cannot determine the type (alsa type is %x) of this midi device. Assuming FM Synth\n", type); + return MOD_FMSYNTH; +} + +static void port_add(snd_seq_client_info_t* cinfo, snd_seq_port_info_t* pinfo, unsigned int cap, unsigned int type) +{ + char name[MAXPNAMELEN]; + unsigned int len; + struct midi_dest *dest; + struct midi_src *src; + + if (cap & SND_SEQ_PORT_CAP_WRITE) { + TRACE("OUT (%d:%s:%s:%d:%s:%x)\n",snd_seq_client_info_get_client(cinfo), + snd_seq_client_info_get_name(cinfo), + snd_seq_client_info_get_type(cinfo) == SND_SEQ_USER_CLIENT ? "user" : "kernel", + snd_seq_port_info_get_port(pinfo), + snd_seq_port_info_get_name(pinfo), + type); + + if (num_dests >= MAX_MIDIOUTDRV) + return; + if (!type) + return; + + dest = dests + num_dests; + dest->addr = *snd_seq_port_info_get_addr(pinfo); + + /* Manufac ID. We do not have access to this with soundcard.h + * Does not seem to be a problem, because in mmsystem.h only + * Microsoft's ID is listed. + */ + dest->caps.wMid = 0x00FF; + dest->caps.wPid = 0x0001; /* FIXME Product ID */ + /* Product Version. We simply say "1" */ + dest->caps.vDriverVersion = 0x001; + /* The following are mandatory for MOD_MIDIPORT */ + dest->caps.wChannelMask = 0xFFFF; + dest->caps.wVoices = 0; + dest->caps.wNotes = 0; + dest->caps.dwSupport = 0; + + /* Try to use both client and port names, if this is too long take the port name only. + In the second case the port name should be explicit enough due to its big size. + */ + len = strlen(snd_seq_port_info_get_name(pinfo)); + if ( (strlen(snd_seq_client_info_get_name(cinfo)) + len + 3) < sizeof(name) ) { + sprintf(name, "%s - %s", snd_seq_client_info_get_name(cinfo), snd_seq_port_info_get_name(pinfo)); + len = strlen(name); + } else { + len = min(len, sizeof(name) - 1); + memcpy(name, snd_seq_port_info_get_name(pinfo), len); + name[len] = '\0'; + } + ntdll_umbstowcs( name, len + 1, dest->caps.szPname, ARRAY_SIZE(dest->caps.szPname)); + + dest->caps.wTechnology = alsa_to_win_device_type(type); + + if (MOD_MIDIPORT != dest->caps.wTechnology) { + /* FIXME Do we have this information? + * Assuming the soundcards can handle + * MIDICAPS_VOLUME and MIDICAPS_LRVOLUME but + * not MIDICAPS_CACHE. + */ + dest->caps.dwSupport = MIDICAPS_VOLUME|MIDICAPS_LRVOLUME; + dest->caps.wVoices = 16; + + /* FIXME Is it possible to know the maximum + * number of simultaneous notes of a soundcard ? + * I believe we don't have this information, but + * it's probably equal or more than wVoices + */ + dest->caps.wNotes = 16; + } + dest->bEnabled = TRUE; + dest->port_out = -1; + + TRACE("MidiOut[%d]\tname='%s' techn=%d voices=%d notes=%d chnMsk=%04x support=%d\n" + "\tALSA info: midi dev-type=%x, capa=0\n", + num_dests, wine_dbgstr_w(dest->caps.szPname), + dest->caps.wTechnology, + dest->caps.wVoices, dest->caps.wNotes, + dest->caps.wChannelMask, (unsigned)dest->caps.dwSupport, + type); + + num_dests++; + } + if (cap & SND_SEQ_PORT_CAP_READ) { + TRACE("IN (%d:%s:%s:%d:%s:%x)\n",snd_seq_client_info_get_client(cinfo), + snd_seq_client_info_get_name(cinfo), + snd_seq_client_info_get_type(cinfo) == SND_SEQ_USER_CLIENT ? "user" : "kernel", + snd_seq_port_info_get_port(pinfo), + snd_seq_port_info_get_name(pinfo), + type); + + if (num_srcs >= MAX_MIDIINDRV) + return; + if (!type) + return; + + src = srcs + num_srcs; + src->addr = *snd_seq_port_info_get_addr(pinfo); + + /* Manufac ID. We do not have access to this with soundcard.h + * Does not seem to be a problem, because in mmsystem.h only + * Microsoft's ID is listed. + */ + src->caps.wMid = 0x00FF; + src->caps.wPid = 0x0001; /* FIXME Product ID */ + /* Product Version. We simply say "1" */ + src->caps.vDriverVersion = 0x001; + src->caps.dwSupport = 0; /* mandatory with MIDIINCAPS */ + + /* Try to use both client and port names, if this is too long take the port name only. + In the second case the port name should be explicit enough due to its big size. + */ + len = strlen(snd_seq_port_info_get_name(pinfo)); + if ( (strlen(snd_seq_client_info_get_name(cinfo)) + len + 3) < sizeof(name) ) { + sprintf(name, "%s - %s", snd_seq_client_info_get_name(cinfo), snd_seq_port_info_get_name(pinfo)); + len = strlen(name); + } else { + len = min(len, sizeof(name) - 1); + memcpy(name, snd_seq_port_info_get_name(pinfo), len); + name[len] = '\0'; + } + ntdll_umbstowcs( name, len + 1, src->caps.szPname, ARRAY_SIZE(src->caps.szPname)); + src->state = 0; + + TRACE("MidiIn [%d]\tname='%s' support=%d\n" + "\tALSA info: midi dev-type=%x, capa=0\n", + num_srcs, wine_dbgstr_w(src->caps.szPname), (unsigned)src->caps.dwSupport, type); + + num_srcs++; + } +} + +static UINT alsa_midi_init(void) +{ + static BOOL init_done; + snd_seq_client_info_t *cinfo; + snd_seq_port_info_t *pinfo; + snd_seq_t *seq; + + if (init_done) + return ERROR_ALREADY_INITIALIZED; + + TRACE("Initializing the MIDI variables.\n"); + init_done = TRUE; + + /* try to open device */ + if (!(seq = seq_open(NULL))) + return ERROR_OPEN_FAILED; + + cinfo = calloc( 1, snd_seq_client_info_sizeof() ); + pinfo = calloc( 1, snd_seq_port_info_sizeof() ); + + /* First, search for all internal midi devices */ + snd_seq_client_info_set_client(cinfo, -1); + while (snd_seq_query_next_client(seq, cinfo) >= 0) { + snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo)); + snd_seq_port_info_set_port(pinfo, -1); + while (snd_seq_query_next_port(seq, pinfo) >= 0) { + unsigned int cap = snd_seq_port_info_get_capability(pinfo); + unsigned int type = snd_seq_port_info_get_type(pinfo); + if (!(type & SND_SEQ_PORT_TYPE_PORT)) + port_add(cinfo, pinfo, cap, type); + } + } + + /* Second, search for all external ports */ + snd_seq_client_info_set_client(cinfo, -1); + while (snd_seq_query_next_client(seq, cinfo) >= 0) { + snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo)); + snd_seq_port_info_set_port(pinfo, -1); + while (snd_seq_query_next_port(seq, pinfo) >= 0) { + unsigned int cap = snd_seq_port_info_get_capability(pinfo); + unsigned int type = snd_seq_port_info_get_type(pinfo); + if (type & SND_SEQ_PORT_TYPE_PORT) + port_add(cinfo, pinfo, cap, type); + } + } + seq_close(); + free( cinfo ); + free( pinfo ); + + TRACE("End\n"); + + return NOERROR; +} + +NTSTATUS alsa_midi_release(void *args) +{ + /* stop the notify_wait thread */ + notify_post(NULL); + + return STATUS_SUCCESS; +} + +static void set_out_notify(struct notify_context *notify, struct midi_dest *dest, WORD dev_id, WORD msg, + UINT_PTR param_1, UINT_PTR param_2) +{ + notify->send_notify = TRUE; + notify->dev_id = dev_id; + notify->msg = msg; + notify->param_1 = param_1; + notify->param_2 = param_2; + notify->callback = dest->midiDesc.dwCallback; + notify->flags = dest->wFlags; + notify->device = dest->midiDesc.hMidi; + notify->instance = dest->midiDesc.dwInstance; +} + +static UINT midi_out_open(WORD dev_id, MIDIOPENDESC *midi_desc, UINT flags, struct notify_context *notify) +{ + int ret; + int port_out; + char port_out_name[32]; + snd_seq_t *midi_seq; + struct midi_dest *dest; + + TRACE("(%04X, %p, %08X);\n", dev_id, midi_desc, flags); + + if (midi_desc == NULL) + { + WARN("Invalid Parameter !\n"); + return MMSYSERR_INVALPARAM; + } + if (dev_id >= num_dests) + { + TRACE("MAX_MIDIOUTDRV reached !\n"); + return MMSYSERR_BADDEVICEID; + } + dest = dests + dev_id; + if (dest->midiDesc.hMidi != 0) + { + WARN("device already open !\n"); + return MMSYSERR_ALLOCATED; + } + if (!dest->bEnabled) + { + WARN("device disabled !\n"); + return MIDIERR_NODEVICE; + } + if ((flags & ~CALLBACK_TYPEMASK) != 0) + { + WARN("bad dwFlags\n"); + return MMSYSERR_INVALFLAG; + } + + switch (dest->caps.wTechnology) + { + case MOD_FMSYNTH: + case MOD_MIDIPORT: + case MOD_SYNTH: + if (!(midi_seq = seq_open(NULL))) + return MMSYSERR_ALLOCATED; + break; + default: + WARN("Technology not supported (yet) %d !\n", dest->caps.wTechnology); + return MMSYSERR_NOTENABLED; + } + + dest->runningStatus = 0; + dest->wFlags = HIWORD(flags & CALLBACK_TYPEMASK); + dest->midiDesc = *midi_desc; + dest->seq = midi_seq; + + seq_lock(); + /* Create a port dedicated to a specific device */ + /* Keep the old name without a number for the first port */ + if (dev_id) + sprintf(port_out_name, "WINE ALSA Output #%d", dev_id); + + port_out = snd_seq_create_simple_port(midi_seq, dev_id ? port_out_name : "WINE ALSA Output", + SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ | SND_SEQ_PORT_CAP_SUBS_WRITE, + SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION); + + if (port_out < 0) + { + TRACE("Unable to create output port\n"); + dest->port_out = -1; + } + else + { + TRACE("Output port %d created successfully\n", port_out); + dest->port_out = port_out; + + /* Connect our app port to the device port */ + ret = snd_seq_connect_to(midi_seq, port_out, dest->addr.client, dest->addr.port); + + /* usually will happen when the port is already connected */ + /* other errors should not be fatal either */ + if (ret < 0) + WARN("Could not connect port %d to %d:%d: %s\n", dev_id, dest->addr.client, + dest->addr.port, snd_strerror(ret)); + } + seq_unlock(); + + if (port_out < 0) + return MMSYSERR_NOTENABLED; + + TRACE("Output port :%d connected %d:%d\n", port_out, dest->addr.client, dest->addr.port); + + set_out_notify(notify, dest, dev_id, MOM_OPEN, 0, 0); + return MMSYSERR_NOERROR; +} + +static UINT midi_out_close(WORD dev_id, struct notify_context *notify) +{ + struct midi_dest *dest; + + TRACE("(%04X);\n", dev_id); + + if (dev_id >= num_dests) + { + WARN("bad device ID : %d\n", dev_id); + return MMSYSERR_BADDEVICEID; + } + + dest = dests + dev_id; + + if (dest->midiDesc.hMidi == 0) + { + WARN("device not opened !\n"); + return MMSYSERR_ERROR; + } + + /* FIXME: should test that no pending buffer is still in the queue for + * playing */ + + if (dest->seq == NULL) + { + WARN("can't close !\n"); + return MMSYSERR_ERROR; + } + + switch (dest->caps.wTechnology) + { + case MOD_FMSYNTH: + case MOD_MIDIPORT: + case MOD_SYNTH: + seq_lock(); + TRACE("Deleting port :%d, connected to %d:%d\n", dest->port_out, dest->addr.client, dest->addr.port); + snd_seq_delete_simple_port(dest->seq, dest->port_out); + dest->port_out = -1; + seq_unlock(); + seq_close(); + dest->seq = NULL; + break; + default: + WARN("Technology not supported (yet) %d !\n", dest->caps.wTechnology); + return MMSYSERR_NOTENABLED; + } + + set_out_notify(notify, dest, dev_id, MOM_CLOSE, 0, 0); + + dest->midiDesc.hMidi = 0; + + return MMSYSERR_NOERROR; +} + +static UINT midi_out_data(WORD dev_id, UINT data) +{ + BYTE evt = LOBYTE(LOWORD(data)), d1, d2; + struct midi_dest *dest; + + TRACE("(%04X, %08X);\n", dev_id, data); + + if (dev_id >= num_dests) return MMSYSERR_BADDEVICEID; + dest = dests + dev_id; + + if (!dest->bEnabled) return MIDIERR_NODEVICE; + + if (dest->seq == NULL) + { + WARN("can't play !\n"); + return MIDIERR_NODEVICE; + } + + if (evt & 0x80) + { + d1 = HIBYTE(LOWORD(data)); + d2 = LOBYTE(HIWORD(data)); + if (evt < 0xF0) + dest->runningStatus = evt; + else if (evt <= 0xF7) + dest->runningStatus = 0; + } + else if (dest->runningStatus) + { + evt = dest->runningStatus; + d1 = LOBYTE(LOWORD(data)); + d2 = HIBYTE(LOWORD(data)); + } + else + { + FIXME("ooch %x\n", data); + return MMSYSERR_NOERROR; + } + + switch (dest->caps.wTechnology) + { + case MOD_SYNTH: + case MOD_MIDIPORT: + { + int handled = 1; /* Assume event is handled */ + snd_seq_event_t event; + snd_seq_ev_clear(&event); + snd_seq_ev_set_direct(&event); + snd_seq_ev_set_source(&event, dest->port_out); + snd_seq_ev_set_subs(&event); + + switch (evt & 0xF0) + { + case MIDI_CMD_NOTE_OFF: + snd_seq_ev_set_noteoff(&event, evt & 0x0F, d1, d2); + break; + case MIDI_CMD_NOTE_ON: + snd_seq_ev_set_noteon(&event, evt & 0x0F, d1, d2); + break; + case MIDI_CMD_NOTE_PRESSURE: + snd_seq_ev_set_keypress(&event, evt & 0x0F, d1, d2); + break; + case MIDI_CMD_CONTROL: + snd_seq_ev_set_controller(&event, evt & 0x0F, d1, d2); + break; + case MIDI_CMD_BENDER: + snd_seq_ev_set_pitchbend(&event, evt & 0x0F, ((WORD)d2 << 7 | (WORD)d1) - 0x2000); + break; + case MIDI_CMD_PGM_CHANGE: + snd_seq_ev_set_pgmchange(&event, evt & 0x0F, d1); + break; + case MIDI_CMD_CHANNEL_PRESSURE: + snd_seq_ev_set_chanpress(&event, evt & 0x0F, d1); + break; + case MIDI_CMD_COMMON_SYSEX: + switch (evt & 0x0F) + { + case 0x00: /* System Exclusive, don't do it on modData, + * should require modLongData*/ + case 0x04: /* Undefined. */ + case 0x05: /* Undefined. */ + case 0x07: /* End of Exclusive. */ + case 0x09: /* Undefined. */ + case 0x0D: /* Undefined. */ + handled = 0; + break; + case 0x06: /* Tune Request */ + case 0x08: /* Timing Clock. */ + case 0x0A: /* Start. */ + case 0x0B: /* Continue */ + case 0x0C: /* Stop */ + case 0x0E: /* Active Sensing. */ + { + snd_midi_event_t *midi_event; + + snd_midi_event_new(1, &midi_event); + snd_midi_event_init(midi_event); + snd_midi_event_encode_byte(midi_event, evt, &event); + snd_midi_event_free(midi_event); + break; + } + case 0x0F: /* Reset */ + /* snd_seq_ev_set_sysex(&event, 1, &evt); + this other way may be better */ + { + BYTE reset_sysex_seq[] = {MIDI_CMD_COMMON_SYSEX, 0x7e, 0x7f, 0x09, 0x01, 0xf7}; + snd_seq_ev_set_sysex(&event, sizeof(reset_sysex_seq), reset_sysex_seq); + dest->runningStatus = 0; + break; + } + case 0x01: /* MTC Quarter frame */ + case 0x03: /* Song Select. */ + { + BYTE buf[2]; + buf[0] = evt; + buf[1] = d1; + snd_seq_ev_set_sysex(&event, sizeof(buf), buf); + break; + } + case 0x02: /* Song Position Pointer. */ + { + BYTE buf[3]; + buf[0] = evt; + buf[1] = d1; + buf[2] = d2; + snd_seq_ev_set_sysex(&event, sizeof(buf), buf); + break; + } + } + break; + } + if (handled) + { + seq_lock(); + snd_seq_event_output_direct(dest->seq, &event); + seq_unlock(); + } + break; + } + default: + WARN("Technology not supported (yet) %d !\n", dest->caps.wTechnology); + return MMSYSERR_NOTENABLED; + } + + return MMSYSERR_NOERROR; +} + +static UINT midi_out_long_data(WORD dev_id, MIDIHDR *hdr, UINT hdr_size, struct notify_context *notify) +{ + struct midi_dest *dest; + int len_add = 0; + BYTE *data, *new_data = NULL; + snd_seq_event_t event; + + TRACE("(%04X, %p, %08X);\n", dev_id, hdr, hdr_size); + + /* Note: MS doc does not say much about the dwBytesRecorded member of the MIDIHDR structure + * but it seems to be used only for midi input. + * Taking a look at the WAVEHDR structure (which is quite similar) confirms this assumption. + */ + + if (dev_id >= num_dests) return MMSYSERR_BADDEVICEID; + dest = dests + dev_id; + + if (!dest->bEnabled) return MIDIERR_NODEVICE; + + if (dest->seq == NULL) + { + WARN("can't play !\n"); + return MIDIERR_NODEVICE; + } + + data = (BYTE*)hdr->lpData; + + if (data == NULL) + return MIDIERR_UNPREPARED; + if (!(hdr->dwFlags & MHDR_PREPARED)) + return MIDIERR_UNPREPARED; + if (hdr->dwFlags & MHDR_INQUEUE) + return MIDIERR_STILLPLAYING; + hdr->dwFlags &= ~MHDR_DONE; + hdr->dwFlags |= MHDR_INQUEUE; + + /* FIXME: MS doc is not 100% clear. Will lpData only contain system exclusive + * data, or can it also contain raw MIDI data, to be split up and sent to + * modShortData() ? + * If the latest is true, then the following WARNing will fire up + */ + if (data[0] != 0xF0 || data[hdr->dwBufferLength - 1] != 0xF7) + { + WARN("Alleged system exclusive buffer is not correct\n\tPlease report with MIDI file\n"); + new_data = malloc(hdr->dwBufferLength + 2); + } + + TRACE("dwBufferLength=%u !\n", (unsigned)hdr->dwBufferLength); + TRACE(" %02X %02X %02X ... %02X %02X %02X\n", + data[0], data[1], data[2], data[hdr->dwBufferLength-3], + data[hdr->dwBufferLength-2], data[hdr->dwBufferLength-1]); + + switch (dest->caps.wTechnology) + { + case MOD_FMSYNTH: + /* FIXME: I don't think there is much to do here */ + free(new_data); + break; + case MOD_MIDIPORT: + if (data[0] != 0xF0) + { + /* Send start of System Exclusive */ + len_add = 1; + new_data[0] = 0xF0; + memcpy(new_data + 1, data, hdr->dwBufferLength); + WARN("Adding missing 0xF0 marker at the beginning of system exclusive byte stream\n"); + } + if (data[hdr->dwBufferLength-1] != 0xF7) + { + /* Send end of System Exclusive */ + if (!len_add) + memcpy(new_data, data, hdr->dwBufferLength); + new_data[hdr->dwBufferLength + len_add] = 0xF7; + len_add++; + WARN("Adding missing 0xF7 marker at the end of system exclusive byte stream\n"); + } + snd_seq_ev_clear(&event); + snd_seq_ev_set_direct(&event); + snd_seq_ev_set_source(&event, dest->port_out); + snd_seq_ev_set_subs(&event); + snd_seq_ev_set_sysex(&event, hdr->dwBufferLength + len_add, new_data ? new_data : data); + seq_lock(); + snd_seq_event_output_direct(dest->seq, &event); + seq_unlock(); + free(new_data); + break; + default: + WARN("Technology not supported (yet) %d !\n", dest->caps.wTechnology); + free(new_data); + return MMSYSERR_NOTENABLED; + } + + dest->runningStatus = 0; + hdr->dwFlags &= ~MHDR_INQUEUE; + hdr->dwFlags |= MHDR_DONE; + set_out_notify(notify, dest, dev_id, MOM_DONE, (DWORD_PTR)hdr, 0); + return MMSYSERR_NOERROR; +} + +static UINT midi_out_prepare(WORD dev_id, MIDIHDR *hdr, UINT hdr_size) +{ + TRACE("(%04X, %p, %d);\n", dev_id, hdr, hdr_size); + + if (hdr_size < offsetof(MIDIHDR, dwOffset) || !hdr || !hdr->lpData) + return MMSYSERR_INVALPARAM; + if (hdr->dwFlags & MHDR_PREPARED) + return MMSYSERR_NOERROR; + + hdr->lpNext = 0; + hdr->dwFlags |= MHDR_PREPARED; + hdr->dwFlags &= ~(MHDR_DONE | MHDR_INQUEUE); + return MMSYSERR_NOERROR; +} + +static UINT midi_out_unprepare(WORD dev_id, MIDIHDR *hdr, UINT hdr_size) +{ + TRACE("(%04X, %p, %d);\n", dev_id, hdr, hdr_size); + + if (hdr_size < offsetof(MIDIHDR, dwOffset) || !hdr || !hdr->lpData) + return MMSYSERR_INVALPARAM; + if (!(hdr->dwFlags & MHDR_PREPARED)) + return MMSYSERR_NOERROR; + if (hdr->dwFlags & MHDR_INQUEUE) + return MIDIERR_STILLPLAYING; + + hdr->dwFlags &= ~MHDR_PREPARED; + return MMSYSERR_NOERROR; +} + +static UINT midi_out_get_devcaps(WORD dev_id, MIDIOUTCAPSW *caps, UINT size) +{ + TRACE("(%04X, %p, %08X);\n", dev_id, caps, size); + + if (dev_id >= num_dests) return MMSYSERR_BADDEVICEID; + if (!caps) return MMSYSERR_INVALPARAM; + + memcpy(caps, &dests[dev_id].caps, min(size, sizeof(*caps))); + + return MMSYSERR_NOERROR; +} + +static UINT midi_out_get_volume(WORD dev_id, UINT* volume) +{ + if (!volume) return MMSYSERR_INVALPARAM; + if (dev_id >= num_dests) return MMSYSERR_BADDEVICEID; + + *volume = 0xFFFFFFFF; + return (dests[dev_id].caps.dwSupport & MIDICAPS_VOLUME) ? 0 : MMSYSERR_NOTSUPPORTED; +} + +static UINT midi_out_reset(WORD dev_id) +{ + unsigned chn; + + TRACE("(%04X);\n", dev_id); + + if (dev_id >= num_dests) return MMSYSERR_BADDEVICEID; + if (!dests[dev_id].bEnabled) return MIDIERR_NODEVICE; + + /* stop all notes */ + for (chn = 0; chn < 16; chn++) + { + /* turn off every note */ + midi_out_data(dev_id, (MIDI_CTL_ALL_SOUNDS_OFF << 8) | MIDI_CMD_CONTROL | chn); + /* remove sustain on all channels */ + midi_out_data(dev_id, (MIDI_CTL_SUSTAIN << 8) | MIDI_CMD_CONTROL | chn); + } + dests[dev_id].runningStatus = 0; + /* FIXME: the LongData buffers must also be returned to the app */ + return MMSYSERR_NOERROR; +} + +static void handle_sysex_event(struct midi_src *src, BYTE *data, UINT len) +{ + UINT pos = 0, copy_len, current_time = get_time_msec() - src->startTime; + struct notify_context notify; + MIDIHDR *hdr; + + in_buffer_lock(); + + while (len) + { + hdr = src->lpQueueHdr; + if (!hdr) break; + + copy_len = min(len, hdr->dwBufferLength - hdr->dwBytesRecorded); + memcpy(hdr->lpData + hdr->dwBytesRecorded, data + pos, copy_len); + hdr->dwBytesRecorded += copy_len; + len -= copy_len; + pos += copy_len; + + if ((hdr->dwBytesRecorded == hdr->dwBufferLength) || + (*(BYTE*)(hdr->lpData + hdr->dwBytesRecorded - 1) == 0xF7)) + { /* buffer full or end of sysex message */ + src->lpQueueHdr = hdr->lpNext; + hdr->dwFlags &= ~MHDR_INQUEUE; + hdr->dwFlags |= MHDR_DONE; + set_in_notify(¬ify, src, src - srcs, MIM_LONGDATA, (DWORD_PTR)hdr, current_time); + notify_post(¬ify); + } + } + + in_buffer_unlock(); +} + +static void handle_regular_event(struct midi_src *src, snd_seq_event_t *ev) +{ + UINT data = 0, value, current_time = get_time_msec() - src->startTime; + struct notify_context notify; + + switch (ev->type) + { + case SND_SEQ_EVENT_NOTEOFF: + data = (ev->data.note.velocity << 16) | (ev->data.note.note << 8) | MIDI_CMD_NOTE_OFF | ev->data.control.channel; + break; + case SND_SEQ_EVENT_NOTEON: + data = (ev->data.note.velocity << 16) | (ev->data.note.note << 8) | MIDI_CMD_NOTE_ON | ev->data.control.channel; + break; + case SND_SEQ_EVENT_KEYPRESS: + data = (ev->data.note.velocity << 16) | (ev->data.note.note << 8) | MIDI_CMD_NOTE_PRESSURE | ev->data.control.channel; + break; + case SND_SEQ_EVENT_CONTROLLER: + data = (ev->data.control.value << 16) | (ev->data.control.param << 8) | MIDI_CMD_CONTROL | ev->data.control.channel; + break; + case SND_SEQ_EVENT_PITCHBEND: + value = ev->data.control.value + 0x2000; + data = (((value >> 7) & 0x7f) << 16) | ((value & 0x7f) << 8) | MIDI_CMD_BENDER | ev->data.control.channel; + break; + case SND_SEQ_EVENT_PGMCHANGE: + data = ((ev->data.control.value & 0x7f) << 8) | MIDI_CMD_PGM_CHANGE | ev->data.control.channel; + break; + case SND_SEQ_EVENT_CHANPRESS: + data = ((ev->data.control.value & 0x7f) << 8) | MIDI_CMD_CHANNEL_PRESSURE | ev->data.control.channel; + break; + case SND_SEQ_EVENT_CLOCK: + data = 0xF8; + break; + case SND_SEQ_EVENT_START: + data = 0xFA; + break; + case SND_SEQ_EVENT_CONTINUE: + data = 0xFB; + break; + case SND_SEQ_EVENT_STOP: + data = 0xFC; + break; + case SND_SEQ_EVENT_SONGPOS: + data = (((ev->data.control.value >> 7) & 0x7f) << 16) | ((ev->data.control.value & 0x7f) << 8) | MIDI_CMD_COMMON_SONG_POS; + break; + case SND_SEQ_EVENT_SONGSEL: + data = ((ev->data.control.value & 0x7f) << 8) | MIDI_CMD_COMMON_SONG_SELECT; + break; + case SND_SEQ_EVENT_RESET: + data = 0xFF; + break; + case SND_SEQ_EVENT_QFRAME: + data = ((ev->data.control.value & 0x7f) << 8) | MIDI_CMD_COMMON_MTC_QUARTER; + break; + case SND_SEQ_EVENT_SENSING: + /* Noting to do */ + break; + } + + if (data != 0) + { + set_in_notify(¬ify, src, src - srcs, MIM_DATA, data, current_time); + notify_post(¬ify); + } +} + +static void midi_handle_event(snd_seq_event_t *ev) +{ + struct midi_src *src; + + /* Find the target device */ + for (src = srcs; src < srcs + num_srcs; src++) + if ((ev->source.client == src->addr.client) && (ev->source.port == src->addr.port)) + break; + if ((src == srcs + num_srcs) || (src->state != 1)) + return; + + if (ev->type == SND_SEQ_EVENT_SYSEX) + handle_sysex_event(src, ev->data.ext.ptr, ev->data.ext.len); + else + handle_regular_event(src, ev); +} + +static void *rec_thread_proc(void *arg) +{ + snd_seq_t *midi_seq = (snd_seq_t *)arg; + int num_fds; + struct pollfd *pollfd; + int ret; + + /* Add on one for the read end of the cancel pipe */ + num_fds = snd_seq_poll_descriptors_count(midi_seq, POLLIN) + 1; + pollfd = malloc(num_fds * sizeof(struct pollfd)); + + while(1) + { + pollfd[0].fd = rec_cancel_pipe[0]; + pollfd[0].events = POLLIN; + + seq_lock(); + snd_seq_poll_descriptors(midi_seq, pollfd + 1, num_fds - 1, POLLIN); + seq_unlock(); + + /* Check if an event is present */ + if (poll(pollfd, num_fds, -1) <= 0) + continue; + + if (pollfd[0].revents & POLLIN) /* cancelled */ + break; + + do + { + snd_seq_event_t *ev; + + seq_lock(); + snd_seq_event_input(midi_seq, &ev); + seq_unlock(); + + if (ev) + { + midi_handle_event(ev); + snd_seq_free_event(ev); + } + + seq_lock(); + ret = snd_seq_event_input_pending(midi_seq, 0); + seq_unlock(); + } while(ret > 0); + } + + free(pollfd); + return 0; +} + +static UINT midi_in_open(WORD dev_id, MIDIOPENDESC *desc, UINT flags, struct notify_context *notify) +{ + struct midi_src *src; + int ret = 0, port_in; + snd_seq_t *midi_seq; + + TRACE("(%04X, %p, %08X);\n", dev_id, desc, flags); + + if (!desc) + { + WARN("Invalid Parameter !\n"); + return MMSYSERR_INVALPARAM; + } + + /* FIXME: check that contents of desc are correct */ + + if (dev_id >= num_srcs) + { + WARN("dev_id too large (%u) !\n", dev_id); + return MMSYSERR_BADDEVICEID; + } + src = srcs + dev_id; + + if (src->state == -1) + { + WARN("device disabled\n"); + return MIDIERR_NODEVICE; + } + if (src->midiDesc.hMidi) + { + WARN("device already open !\n"); + return MMSYSERR_ALLOCATED; + } + if (flags & MIDI_IO_STATUS) + { + WARN("No support for MIDI_IO_STATUS in flags yet, ignoring it\n"); + flags &= ~MIDI_IO_STATUS; + } + if (flags & ~CALLBACK_TYPEMASK) + { + FIXME("Bad flags %08X\n", flags); + return MMSYSERR_INVALFLAG; + } + + if (!(midi_seq = seq_open(&port_in))) + return MMSYSERR_ERROR; + + src->wFlags = HIWORD(flags & CALLBACK_TYPEMASK); + + src->lpQueueHdr = NULL; + src->midiDesc = *desc; + src->state = 0; + src->startTime = 0; + src->seq = midi_seq; + src->port_in = port_in; + + /* Connect our app port to the device port */ + seq_lock(); + ret = snd_seq_connect_from(midi_seq, port_in, src->addr.client, src->addr.port); + seq_unlock(); + if (ret < 0) + return MMSYSERR_NOTENABLED; + + TRACE("Input port :%d connected %d:%d\n", port_in, src->addr.client, src->addr.port); + + if (num_midi_in_started++ == 0) + { + pipe(rec_cancel_pipe); + if (pthread_create(&rec_thread_id, NULL, rec_thread_proc, midi_seq)) + { + close(rec_cancel_pipe[0]); + close(rec_cancel_pipe[1]); + num_midi_in_started = 0; + WARN("Couldn't create thread for midi-in\n"); + seq_close(); + return MMSYSERR_ERROR; + } + } + + set_in_notify(notify, src, dev_id, MIM_OPEN, 0, 0); + return MMSYSERR_NOERROR; +} + +static UINT midi_in_close(WORD dev_id, struct notify_context *notify) +{ + struct midi_src *src; + + TRACE("(%04X);\n", dev_id); + + if (dev_id >= num_srcs) + { + WARN("dev_id too big (%u) !\n", dev_id); + return MMSYSERR_BADDEVICEID; + } + src = srcs + dev_id; + if (!src->midiDesc.hMidi) + { + WARN("device not opened !\n"); + return MMSYSERR_ERROR; + } + if (src->lpQueueHdr) + return MIDIERR_STILLPLAYING; + + if (src->seq == NULL) + { + WARN("ooops !\n"); + return MMSYSERR_ERROR; + } + if (--num_midi_in_started == 0) + { + TRACE("Stopping thread for midi-in\n"); + write(rec_cancel_pipe[1], "x", 1); + pthread_join(rec_thread_id, NULL); + close(rec_cancel_pipe[0]); + close(rec_cancel_pipe[1]); + TRACE("Stopped thread for midi-in\n"); + } + + seq_lock(); + snd_seq_disconnect_from(src->seq, src->port_in, src->addr.client, src->addr.port); + seq_unlock(); + seq_close(); + + set_in_notify(notify, src, dev_id, MIM_CLOSE, 0, 0); + src->midiDesc.hMidi = 0; + src->seq = NULL; + + return MMSYSERR_NOERROR; +} + +static UINT midi_in_add_buffer(WORD dev_id, MIDIHDR *hdr, UINT hdr_size) +{ + struct midi_src *src; + MIDIHDR **next; + + TRACE("(%04X, %p, %d);\n", dev_id, hdr, hdr_size); + + if (dev_id >= num_srcs) return MMSYSERR_BADDEVICEID; + src = srcs + dev_id; + if (src->state == -1) return MIDIERR_NODEVICE; + + if (!hdr || hdr_size < offsetof(MIDIHDR, dwOffset) || !hdr->dwBufferLength) + return MMSYSERR_INVALPARAM; + if (hdr->dwFlags & MHDR_INQUEUE) return MIDIERR_STILLPLAYING; + if (!(hdr->dwFlags & MHDR_PREPARED)) return MIDIERR_UNPREPARED; + + in_buffer_lock(); + + hdr->dwFlags &= ~WHDR_DONE; + hdr->dwFlags |= MHDR_INQUEUE; + hdr->dwBytesRecorded = 0; + hdr->lpNext = NULL; + + next = &src->lpQueueHdr; + while (*next) next = &(*next)->lpNext; + *next = hdr; + + in_buffer_unlock(); + + return MMSYSERR_NOERROR; +} + +static UINT midi_in_prepare(WORD dev_id, MIDIHDR *hdr, UINT hdr_size) +{ + TRACE("(%04X, %p, %d);\n", dev_id, hdr, hdr_size); + + if (hdr_size < offsetof(MIDIHDR, dwOffset) || !hdr || !hdr->lpData) + return MMSYSERR_INVALPARAM; + if (hdr->dwFlags & MHDR_PREPARED) + return MMSYSERR_NOERROR; + + hdr->lpNext = 0; + hdr->dwFlags |= MHDR_PREPARED; + hdr->dwFlags &= ~(MHDR_DONE | MHDR_INQUEUE); + + return MMSYSERR_NOERROR; +} + +static UINT midi_in_unprepare(WORD dev_id, MIDIHDR *hdr, UINT hdr_size) +{ + TRACE("(%04X, %p, %d);\n", dev_id, hdr, hdr_size); + + if (hdr_size < offsetof(MIDIHDR, dwOffset) || !hdr || !hdr->lpData) + return MMSYSERR_INVALPARAM; + if (!(hdr->dwFlags & MHDR_PREPARED)) + return MMSYSERR_NOERROR; + if (hdr->dwFlags & MHDR_INQUEUE) + return MIDIERR_STILLPLAYING; + + hdr->dwFlags &= ~MHDR_PREPARED; + + return MMSYSERR_NOERROR; +} + +static UINT midi_in_get_devcaps(WORD dev_id, MIDIINCAPSW *caps, UINT size) +{ + TRACE("(%04X, %p, %08X);\n", dev_id, caps, size); + + if (dev_id >= num_srcs) return MMSYSERR_BADDEVICEID; + if (!caps) return MMSYSERR_INVALPARAM; + + memcpy(caps, &srcs[dev_id].caps, min(size, sizeof(*caps))); + + return MMSYSERR_NOERROR; +} + +static UINT midi_in_start(WORD dev_id) +{ + struct midi_src *src; + + TRACE("(%04X);\n", dev_id); + + if (dev_id >= num_srcs) return MMSYSERR_BADDEVICEID; + src = srcs + dev_id; + if (src->state == -1) return MIDIERR_NODEVICE; + + src->state = 1; + src->startTime = get_time_msec(); + return MMSYSERR_NOERROR; +} + +static UINT midi_in_stop(WORD dev_id) +{ + struct midi_src *src; + + TRACE("(%04X);\n", dev_id); + + if (dev_id >= num_srcs) return MMSYSERR_BADDEVICEID; + src = srcs + dev_id; + if (src->state == -1) return MIDIERR_NODEVICE; + + src->state = 0; + return MMSYSERR_NOERROR; +} + +static DWORD midi_in_reset(WORD dev_id, struct notify_context *notify) +{ + UINT cur_time = get_time_msec(); + UINT err = MMSYSERR_NOERROR; + struct midi_src *src; + MIDIHDR *hdr; + + TRACE("(%04X);\n", dev_id); + + if (dev_id >= num_srcs) return MMSYSERR_BADDEVICEID; + src = srcs + dev_id; + if (src->state == -1) return MIDIERR_NODEVICE; + + in_buffer_lock(); + + if (src->lpQueueHdr) + { + hdr = src->lpQueueHdr; + src->lpQueueHdr = hdr->lpNext; + hdr->dwFlags &= ~MHDR_INQUEUE; + hdr->dwFlags |= MHDR_DONE; + set_in_notify(notify, src, dev_id, MIM_LONGDATA, (DWORD_PTR)hdr, cur_time - src->startTime); + if (src->lpQueueHdr) err = ERROR_RETRY; /* ask the client to call again */ + } + + in_buffer_unlock(); + + return err; +} + +NTSTATUS alsa_midi_out_message(void *args) +{ + struct midi_out_message_params *params = args; + + params->notify->send_notify = FALSE; + + switch (params->msg) + { + case DRVM_INIT: + *params->err = alsa_midi_init(); + break; + case DRVM_EXIT: + case DRVM_ENABLE: + case DRVM_DISABLE: + /* FIXME: Pretend this is supported */ + *params->err = MMSYSERR_NOERROR; + break; + case MODM_OPEN: + *params->err = midi_out_open(params->dev_id, (MIDIOPENDESC *)params->param_1, params->param_2, params->notify); + break; + case MODM_CLOSE: + *params->err = midi_out_close(params->dev_id, params->notify); + break; + case MODM_DATA: + *params->err = midi_out_data(params->dev_id, params->param_1); + break; + case MODM_LONGDATA: + *params->err = midi_out_long_data(params->dev_id, (MIDIHDR *)params->param_1, params->param_2, params->notify); + break; + case MODM_PREPARE: + *params->err = midi_out_prepare(params->dev_id, (MIDIHDR *)params->param_1, params->param_2); + break; + case MODM_UNPREPARE: + *params->err = midi_out_unprepare(params->dev_id, (MIDIHDR *)params->param_1, params->param_2); + break; + case MODM_GETDEVCAPS: + *params->err = midi_out_get_devcaps(params->dev_id, (MIDIOUTCAPSW *)params->param_1, params->param_2); + break; + case MODM_GETNUMDEVS: + *params->err = num_dests; + break; + case MODM_GETVOLUME: + *params->err = midi_out_get_volume(params->dev_id, (UINT *)params->param_1); + break; + case MODM_SETVOLUME: + *params->err = 0; + break; + case MODM_RESET: + *params->err = midi_out_reset(params->dev_id); + break; + default: + TRACE("Unsupported message\n"); + *params->err = MMSYSERR_NOTSUPPORTED; + } + + return STATUS_SUCCESS; +} + +NTSTATUS alsa_midi_in_message(void *args) +{ + struct midi_in_message_params *params = args; + + params->notify->send_notify = FALSE; + + switch (params->msg) + { + case DRVM_INIT: + *params->err = alsa_midi_init(); + break; + case DRVM_EXIT: + case DRVM_ENABLE: + case DRVM_DISABLE: + /* FIXME: Pretend this is supported */ + *params->err = MMSYSERR_NOERROR; + break; + case MIDM_OPEN: + *params->err = midi_in_open(params->dev_id, (MIDIOPENDESC *)params->param_1, params->param_2, params->notify); + break; + case MIDM_CLOSE: + *params->err = midi_in_close(params->dev_id, params->notify); + break; + case MIDM_ADDBUFFER: + *params->err = midi_in_add_buffer(params->dev_id, (MIDIHDR *)params->param_1, params->param_2); + break; + case MIDM_PREPARE: + *params->err = midi_in_prepare(params->dev_id, (MIDIHDR *)params->param_1, params->param_2); + break; + case MIDM_UNPREPARE: + *params->err = midi_in_unprepare(params->dev_id, (MIDIHDR *)params->param_1, params->param_2); + break; + case MIDM_GETDEVCAPS: + *params->err = midi_in_get_devcaps(params->dev_id, (MIDIINCAPSW *)params->param_1, params->param_2); + break; + case MIDM_GETNUMDEVS: + *params->err = num_srcs; + break; + case MIDM_START: + *params->err = midi_in_start(params->dev_id); + break; + case MIDM_STOP: + *params->err = midi_in_stop(params->dev_id); + break; + case MIDM_RESET: + *params->err = midi_in_reset(params->dev_id, params->notify); + break; + default: + TRACE("Unsupported message\n"); + *params->err = MMSYSERR_NOTSUPPORTED; + } + + return STATUS_SUCCESS; +} + +NTSTATUS alsa_midi_notify_wait(void *args) +{ + struct midi_notify_wait_params *params = args; + + pthread_mutex_lock(¬ify_mutex); + + while (!notify_quit && notify_buffer_empty()) + pthread_cond_wait(¬ify_read_cond, ¬ify_mutex); + + *params->quit = notify_quit; + if (!notify_quit) + { + notify_buffer_remove(params->notify); + pthread_cond_signal(¬ify_write_cond); + } + pthread_mutex_unlock(¬ify_mutex); + + return STATUS_SUCCESS; +} + +#ifdef _WIN64 + +typedef UINT PTR32; + +struct notify_context32 +{ + BOOL send_notify; + WORD dev_id; + WORD msg; + UINT param_1; + UINT param_2; + UINT callback; + UINT flags; + PTR32 device; + UINT instance; +}; + +static void notify_to_notify32(struct notify_context32 *notify32, + const struct notify_context *notify) +{ + notify32->send_notify = notify->send_notify; + notify32->dev_id = notify->dev_id; + notify32->msg = notify->msg; + notify32->param_1 = notify->param_1; + notify32->param_2 = notify->param_2; + notify32->callback = notify->callback; + notify32->flags = notify->flags; + notify32->device = PtrToUlong(notify->device); + notify32->instance = notify->instance; +} + +struct midi_open_desc32 +{ + PTR32 hMidi; + UINT dwCallback; + UINT dwInstance; + UINT dnDevNode; + UINT cIds; + MIDIOPENSTRMID rgIds; +}; + +struct midi_hdr32 +{ + PTR32 lpData; + UINT dwBufferLength; + UINT dwBytesRecorded; + UINT dwUser; + UINT dwFlags; + PTR32 lpNext; + UINT reserved; + UINT dwOffset; + UINT dwReserved[8]; +}; + +static UINT wow64_midi_out_prepare(WORD dev_id, struct midi_hdr32 *hdr, UINT hdr_size) +{ + TRACE("(%04X, %p, %d);\n", dev_id, hdr, hdr_size); + + if (hdr_size < offsetof(struct midi_hdr32, dwOffset) || !hdr || !hdr->lpData) + return MMSYSERR_INVALPARAM; + if (hdr->dwFlags & MHDR_PREPARED) + return MMSYSERR_NOERROR; + + hdr->lpNext = 0; + hdr->dwFlags |= MHDR_PREPARED; + hdr->dwFlags &= ~(MHDR_DONE | MHDR_INQUEUE); + return MMSYSERR_NOERROR; +} + +static UINT wow64_midi_out_unprepare(WORD dev_id, struct midi_hdr32 *hdr, UINT hdr_size) +{ + TRACE("(%04X, %p, %d);\n", dev_id, hdr, hdr_size); + + if (hdr_size < offsetof(struct midi_hdr32, dwOffset) || !hdr || !hdr->lpData) + return MMSYSERR_INVALPARAM; + if (!(hdr->dwFlags & MHDR_PREPARED)) + return MMSYSERR_NOERROR; + if (hdr->dwFlags & MHDR_INQUEUE) + return MIDIERR_STILLPLAYING; + + hdr->dwFlags &= ~MHDR_PREPARED; + return MMSYSERR_NOERROR; +} + +NTSTATUS alsa_wow64_midi_out_message(void *args) +{ + struct + { + UINT dev_id; + UINT msg; + UINT user; + UINT param_1; + UINT param_2; + PTR32 err; + PTR32 notify; + } *params32 = args; + struct notify_context32 *notify32 = ULongToPtr(params32->notify); + struct midi_open_desc32 *desc32; + struct midi_hdr32 *hdr32; + struct notify_context notify; + MIDIOPENDESC open_desc; + MIDIHDR hdr; + struct midi_out_message_params params = + { + .dev_id = params32->dev_id, + .msg = params32->msg, + .user = params32->user, + .param_1 = params32->param_1, + .param_2 = params32->param_2, + .err = ULongToPtr(params32->err), + .notify = ¬ify + }; + notify32->send_notify = FALSE; + + switch (params32->msg) + { + case MODM_OPEN: + desc32 = ULongToPtr(params32->param_1); + + open_desc.hMidi = ULongToPtr(desc32->hMidi); + open_desc.dwCallback = desc32->dwCallback; + open_desc.dwInstance = desc32->dwInstance; + open_desc.dnDevNode = desc32->dnDevNode; + open_desc.cIds = desc32->cIds; + open_desc.rgIds.dwStreamID = desc32->rgIds.dwStreamID; + open_desc.rgIds.wDeviceID = desc32->rgIds.wDeviceID; + + params.param_1 = (UINT_PTR)&open_desc; + break; + + case MODM_LONGDATA: + hdr32 = ULongToPtr(params32->param_1); + + memset(&hdr, 0, sizeof(hdr)); + hdr.lpData = ULongToPtr(hdr32->lpData); + hdr.dwBufferLength = hdr32->dwBufferLength; + hdr.dwFlags = hdr32->dwFlags; + + params.param_1 = (UINT_PTR)&hdr; + params.param_2 = sizeof(hdr); + break; + + case MODM_PREPARE: /* prepare and unprepare are easier to handle explicitly */ + hdr32 = ULongToPtr(params32->param_1); + + *params.err = wow64_midi_out_prepare(params32->dev_id, hdr32, params32->param_2); + return STATUS_SUCCESS; + + case MODM_UNPREPARE: + hdr32 = ULongToPtr(params32->param_1); + + *params.err = wow64_midi_out_unprepare(params32->dev_id, hdr32, params32->param_2); + return STATUS_SUCCESS; + } + + alsa_midi_out_message(¶ms); + + switch (params32->msg) + { + case MODM_LONGDATA: + hdr32 = ULongToPtr(params32->param_1); + + hdr32->dwFlags = hdr.dwFlags; + break; + } + + if (notify.send_notify) + { + notify_to_notify32(notify32, ¬ify); + + if (notify.msg == MOM_DONE) + notify32->param_1 = params32->param_1; /* restore the 32-bit hdr */ + } + return STATUS_SUCCESS; +} + +static UINT wow64_midi_in_prepare(WORD dev_id, struct midi_hdr32 *hdr, UINT hdr_size) +{ + TRACE("(%04X, %p, %d);\n", dev_id, hdr, hdr_size); + + if (hdr_size < offsetof(struct midi_hdr32, dwOffset) || !hdr || !hdr->lpData) + return MMSYSERR_INVALPARAM; + if (hdr->dwFlags & MHDR_PREPARED) + return MMSYSERR_NOERROR; + + hdr->lpNext = 0; + hdr->dwFlags |= MHDR_PREPARED; + hdr->dwFlags &= ~(MHDR_DONE | MHDR_INQUEUE); + + return MMSYSERR_NOERROR; +} + +static UINT wow64_midi_in_unprepare(WORD dev_id, struct midi_hdr32 *hdr, UINT hdr_size) +{ + TRACE("(%04X, %p, %d);\n", dev_id, hdr, hdr_size); + + if (hdr_size < offsetof(struct midi_hdr32, dwOffset) || !hdr || !hdr->lpData) + return MMSYSERR_INVALPARAM; + if (!(hdr->dwFlags & MHDR_PREPARED)) + return MMSYSERR_NOERROR; + if (hdr->dwFlags & MHDR_INQUEUE) + return MIDIERR_STILLPLAYING; + + hdr->dwFlags &= ~MHDR_PREPARED; + + return MMSYSERR_NOERROR; +} + +NTSTATUS alsa_wow64_midi_in_message(void *args) +{ + struct + { + UINT dev_id; + UINT msg; + UINT user; + UINT param_1; + UINT param_2; + PTR32 err; + PTR32 notify; + } *params32 = args; + struct notify_context32 *notify32 = ULongToPtr(params32->notify); + struct midi_open_desc32 *desc32; + struct midi_hdr32 *hdr32; + struct notify_context notify; + MIDIOPENDESC open_desc; + MIDIHDR *hdr = NULL; + struct midi_in_message_params params = + { + .dev_id = params32->dev_id, + .msg = params32->msg, + .user = params32->user, + .param_1 = params32->param_1, + .param_2 = params32->param_2, + .err = ULongToPtr(params32->err), + .notify = ¬ify + }; + notify32->send_notify = FALSE; + + switch (params32->msg) + { + case MIDM_OPEN: + desc32 = ULongToPtr(params32->param_1); + + open_desc.hMidi = ULongToPtr(desc32->hMidi); + open_desc.dwCallback = desc32->dwCallback; + open_desc.dwInstance = desc32->dwInstance; + open_desc.dnDevNode = desc32->dnDevNode; + open_desc.cIds = desc32->cIds; + open_desc.rgIds.dwStreamID = desc32->rgIds.dwStreamID; + open_desc.rgIds.wDeviceID = desc32->rgIds.wDeviceID; + + params.param_1 = (UINT_PTR)&open_desc; + break; + + case MIDM_ADDBUFFER: + hdr32 = ULongToPtr(params32->param_1); + + hdr = calloc(1, sizeof(*hdr)); + hdr->lpData = ULongToPtr(hdr32->lpData); + hdr->dwBufferLength = hdr32->dwBufferLength; + hdr->dwFlags = hdr32->dwFlags; + hdr->dwReserved[7] = params32->param_1; /* keep hdr32 for MIM_LONGDATA notification */ + + params.param_1 = (UINT_PTR)hdr; + params.param_2 = sizeof(*hdr); + break; + + case MIDM_PREPARE: /* prepare and unprepare are easier to handle explicitly */ + hdr32 = ULongToPtr(params32->param_1); + + *params.err = wow64_midi_in_prepare(params32->dev_id, hdr32, params32->param_2); + return STATUS_SUCCESS; + + case MIDM_UNPREPARE: + hdr32 = ULongToPtr(params32->param_1); + + *params.err = wow64_midi_in_unprepare(params32->dev_id, hdr32, params32->param_2); + return STATUS_SUCCESS; + } + + alsa_midi_in_message(¶ms); + + switch (params32->msg) + { + case MIDM_ADDBUFFER: + hdr32 = ULongToPtr(params32->param_1); + + if (!*params.err) + { + hdr32->dwFlags = hdr->dwFlags; + hdr32->dwBytesRecorded = hdr->dwBytesRecorded; + hdr32->lpNext = 0; + } + else + free(hdr); + break; + } + + if (notify.send_notify) + { + notify_to_notify32(notify32, ¬ify); + + if (notify.msg == MIM_LONGDATA) + { + hdr = (MIDIHDR *)notify.param_1; + notify32->param_1 = hdr->dwReserved[7]; + hdr32 = ULongToPtr(notify32->param_1); + hdr32->dwBytesRecorded = hdr->dwBytesRecorded; + hdr32->dwFlags = hdr->dwFlags; + free(hdr); + } + } + return STATUS_SUCCESS; +} + +NTSTATUS alsa_wow64_midi_notify_wait(void *args) +{ + struct + { + PTR32 quit; + PTR32 notify; + } *params32 = args; + struct notify_context32 *notify32 = ULongToPtr(params32->notify); + struct midi_hdr32 *hdr32; + struct notify_context notify; + MIDIHDR *hdr; + struct midi_notify_wait_params params = + { + .quit = ULongToPtr(params32->quit), + .notify = ¬ify + }; + notify32->send_notify = FALSE; + + alsa_midi_notify_wait(¶ms); + + if (!*params.quit && notify.send_notify) + { + notify_to_notify32(notify32, ¬ify); + + if (notify.msg == MIM_LONGDATA) + { + hdr = (MIDIHDR *)notify.param_1; + notify32->param_1 = hdr->dwReserved[7]; + hdr32 = ULongToPtr(notify32->param_1); + hdr32->dwBytesRecorded = hdr->dwBytesRecorded; + hdr32->dwFlags = hdr->dwFlags; + free(hdr); + } + } + return STATUS_SUCCESS; +} + +#endif /* _WIN64 */ diff --git a/pkgs/osu-wine/audio-revert/winealsa.drv/midi.c b/pkgs/osu-wine/audio-revert/winealsa.drv/midi.c new file mode 100644 index 0000000..1c44116 --- /dev/null +++ b/pkgs/osu-wine/audio-revert/winealsa.drv/midi.c @@ -0,0 +1,157 @@ +/* + * MIDI driver for ALSA (PE-side) + * + * Copyright 1994 Martin Ayotte + * Copyright 1998 Luiz Otavio L. Zorzella + * Copyright 1998, 1999 Eric POUECH + * Copyright 2003 Christian Costa + * Copyright 2022 Huw Davies + * + * 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 + +#include "windef.h" +#include "winbase.h" +#include "wingdi.h" +#include "winternl.h" +#include "mmddk.h" +#include "mmdeviceapi.h" + +#include "wine/debug.h" +#include "wine/unixlib.h" + +#include "unixlib.h" + +WINE_DEFAULT_DEBUG_CHANNEL(midi); + +static void notify_client(struct notify_context *notify) +{ + TRACE("dev_id = %d msg = %d param1 = %04IX param2 = %04IX\n", notify->dev_id, notify->msg, notify->param_1, notify->param_2); + + DriverCallback(notify->callback, notify->flags, notify->device, notify->msg, + notify->instance, notify->param_1, notify->param_2); +} + +/*======================================================================* + * MIDI entry points * + *======================================================================*/ + +/************************************************************************** + * midMessage (WINEALSA.@) + */ +DWORD WINAPI ALSA_midMessage(UINT wDevID, UINT wMsg, DWORD_PTR dwUser, + DWORD_PTR dwParam1, DWORD_PTR dwParam2) +{ + struct midi_in_message_params params; + struct notify_context notify; + UINT err; + + TRACE("(%04X, %04X, %08IX, %08IX, %08IX);\n", + wDevID, wMsg, dwUser, dwParam1, dwParam2); + + params.dev_id = wDevID; + params.msg = wMsg; + params.user = dwUser; + params.param_1 = dwParam1; + params.param_2 = dwParam2; + params.err = &err; + params.notify = ¬ify; + + do + { + ALSA_CALL(midi_in_message, ¶ms); + if ((!err || err == ERROR_RETRY) && notify.send_notify) notify_client(¬ify); + } while (err == ERROR_RETRY); + + return err; +} + +/************************************************************************** + * modMessage (WINEALSA.@) + */ +DWORD WINAPI ALSA_modMessage(UINT wDevID, UINT wMsg, DWORD_PTR dwUser, + DWORD_PTR dwParam1, DWORD_PTR dwParam2) +{ + struct midi_out_message_params params; + struct notify_context notify; + UINT err; + + TRACE("(%04X, %04X, %08IX, %08IX, %08IX);\n", + wDevID, wMsg, dwUser, dwParam1, dwParam2); + + params.dev_id = wDevID; + params.msg = wMsg; + params.user = dwUser; + params.param_1 = dwParam1; + params.param_2 = dwParam2; + params.err = &err; + params.notify = ¬ify; + + ALSA_CALL(midi_out_message, ¶ms); + + if (!err && notify.send_notify) notify_client(¬ify); + + return err; +} + +static DWORD WINAPI notify_thread(void *p) +{ + struct midi_notify_wait_params params; + struct notify_context notify; + BOOL quit; + + SetThreadDescription(GetCurrentThread(), L"winealsa_midi_notify"); + + params.notify = ¬ify; + params.quit = &quit; + + while (1) + { + ALSA_CALL(midi_notify_wait, ¶ms); + if (quit) break; + if (notify.send_notify) notify_client(¬ify); + } + return 0; +} + +/************************************************************************** + * DriverProc (WINEALSA.@) + */ +LRESULT CALLBACK ALSA_DriverProc(DWORD_PTR dwDevID, HDRVR hDriv, UINT wMsg, + LPARAM dwParam1, LPARAM dwParam2) +{ + switch(wMsg) { + case DRV_LOAD: + CloseHandle(CreateThread(NULL, 0, notify_thread, NULL, 0, NULL)); + return 1; + case DRV_FREE: + ALSA_CALL(midi_release, NULL); + return 1; + case DRV_OPEN: + case DRV_CLOSE: + case DRV_ENABLE: + case DRV_DISABLE: + case DRV_QUERYCONFIGURE: + case DRV_CONFIGURE: + return 1; + case DRV_INSTALL: + case DRV_REMOVE: + return DRV_SUCCESS; + default: + return 0; + } +} diff --git a/pkgs/osu-wine/audio-revert/winealsa.drv/mmdevdrv.c b/pkgs/osu-wine/audio-revert/winealsa.drv/mmdevdrv.c new file mode 100644 index 0000000..8367e7e --- /dev/null +++ b/pkgs/osu-wine/audio-revert/winealsa.drv/mmdevdrv.c @@ -0,0 +1,2484 @@ +/* + * Copyright 2010 Maarten Lankhorst for CodeWeavers + * Copyright 2011 Andrew Eikum for CodeWeavers + * Copyright 2022 Huw Davies + * + * 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 + */ + +#define COBJMACROS + +#include + +#include "windef.h" +#include "winbase.h" +#include "winnls.h" +#include "winreg.h" +#include "winternl.h" +#include "propsys.h" +#include "propkey.h" +#include "initguid.h" +#include "ole2.h" +#include "mmdeviceapi.h" +#include "devpkey.h" +#include "mmsystem.h" +#include "dsound.h" + +#include "endpointvolume.h" +#include "audioclient.h" +#include "audiopolicy.h" + +#include "wine/debug.h" +#include "wine/list.h" +#include "wine/unixlib.h" + +#include "unixlib.h" + +WINE_DEFAULT_DEBUG_CHANNEL(alsa); + +#define NULL_PTR_ERR MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, RPC_X_NULL_REF_POINTER) + +static const REFERENCE_TIME DefaultPeriod = 100000; +static const REFERENCE_TIME MinimumPeriod = 50000; + +int GetAudioEnv(char const* env, int def) { + char* val = getenv(env); + if (val) { + return atoi(val); + } + return def; +} + +struct ACImpl; +typedef struct ACImpl ACImpl; + +typedef struct _AudioSession { + GUID guid; + struct list clients; + + IMMDevice *device; + + float master_vol; + UINT32 channel_count; + float *channel_vols; + BOOL mute; + + struct list entry; +} AudioSession; + +typedef struct _AudioSessionWrapper { + IAudioSessionControl2 IAudioSessionControl2_iface; + IChannelAudioVolume IChannelAudioVolume_iface; + ISimpleAudioVolume ISimpleAudioVolume_iface; + + LONG ref; + + ACImpl *client; + AudioSession *session; +} AudioSessionWrapper; + +struct ACImpl { + IAudioClient3 IAudioClient3_iface; + IAudioRenderClient IAudioRenderClient_iface; + IAudioCaptureClient IAudioCaptureClient_iface; + IAudioClock IAudioClock_iface; + IAudioClock2 IAudioClock2_iface; + IAudioStreamVolume IAudioStreamVolume_iface; + + LONG ref; + + IMMDevice *parent; + IUnknown *pUnkFTMarshal; + + EDataFlow dataflow; + float *vols; + UINT32 channel_count; + stream_handle stream; + + HANDLE timer_thread; + + AudioSession *session; + AudioSessionWrapper *session_wrapper; + + struct list entry; + + /* Keep at end */ + char alsa_name[1]; +}; + +typedef struct _SessionMgr { + IAudioSessionManager2 IAudioSessionManager2_iface; + + LONG ref; + + IMMDevice *device; +} SessionMgr; + +static CRITICAL_SECTION g_sessions_lock; +static CRITICAL_SECTION_DEBUG g_sessions_lock_debug = +{ + 0, 0, &g_sessions_lock, + { &g_sessions_lock_debug.ProcessLocksList, &g_sessions_lock_debug.ProcessLocksList }, + 0, 0, { (DWORD_PTR)(__FILE__ ": g_sessions_lock") } +}; +static CRITICAL_SECTION g_sessions_lock = { &g_sessions_lock_debug, -1, 0, 0, 0, 0 }; +static struct list g_sessions = LIST_INIT(g_sessions); + +static const WCHAR drv_key_devicesW[] = {'S','o','f','t','w','a','r','e','\\', + 'W','i','n','e','\\','D','r','i','v','e','r','s','\\', + 'w','i','n','e','a','l','s','a','.','d','r','v','\\','d','e','v','i','c','e','s',0}; +static const WCHAR guidW[] = {'g','u','i','d',0}; + +static const IAudioClient3Vtbl AudioClient3_Vtbl; +static const IAudioRenderClientVtbl AudioRenderClient_Vtbl; +static const IAudioCaptureClientVtbl AudioCaptureClient_Vtbl; +static const IAudioSessionControl2Vtbl AudioSessionControl2_Vtbl; +static const ISimpleAudioVolumeVtbl SimpleAudioVolume_Vtbl; +static const IAudioClockVtbl AudioClock_Vtbl; +static const IAudioClock2Vtbl AudioClock2_Vtbl; +static const IAudioStreamVolumeVtbl AudioStreamVolume_Vtbl; +static const IChannelAudioVolumeVtbl ChannelAudioVolume_Vtbl; +static const IAudioSessionManager2Vtbl AudioSessionManager2_Vtbl; + +static AudioSessionWrapper *AudioSessionWrapper_Create(ACImpl *client); + +static inline ACImpl *impl_from_IAudioClient3(IAudioClient3 *iface) +{ + return CONTAINING_RECORD(iface, ACImpl, IAudioClient3_iface); +} + +static inline ACImpl *impl_from_IAudioRenderClient(IAudioRenderClient *iface) +{ + return CONTAINING_RECORD(iface, ACImpl, IAudioRenderClient_iface); +} + +static inline ACImpl *impl_from_IAudioCaptureClient(IAudioCaptureClient *iface) +{ + return CONTAINING_RECORD(iface, ACImpl, IAudioCaptureClient_iface); +} + +static inline AudioSessionWrapper *impl_from_IAudioSessionControl2(IAudioSessionControl2 *iface) +{ + return CONTAINING_RECORD(iface, AudioSessionWrapper, IAudioSessionControl2_iface); +} + +static inline AudioSessionWrapper *impl_from_ISimpleAudioVolume(ISimpleAudioVolume *iface) +{ + return CONTAINING_RECORD(iface, AudioSessionWrapper, ISimpleAudioVolume_iface); +} + +static inline AudioSessionWrapper *impl_from_IChannelAudioVolume(IChannelAudioVolume *iface) +{ + return CONTAINING_RECORD(iface, AudioSessionWrapper, IChannelAudioVolume_iface); +} + +static inline ACImpl *impl_from_IAudioClock(IAudioClock *iface) +{ + return CONTAINING_RECORD(iface, ACImpl, IAudioClock_iface); +} + +static inline ACImpl *impl_from_IAudioClock2(IAudioClock2 *iface) +{ + return CONTAINING_RECORD(iface, ACImpl, IAudioClock2_iface); +} + +static inline ACImpl *impl_from_IAudioStreamVolume(IAudioStreamVolume *iface) +{ + return CONTAINING_RECORD(iface, ACImpl, IAudioStreamVolume_iface); +} + +static inline SessionMgr *impl_from_IAudioSessionManager2(IAudioSessionManager2 *iface) +{ + return CONTAINING_RECORD(iface, SessionMgr, IAudioSessionManager2_iface); +} + +BOOL WINAPI DllMain(HINSTANCE dll, DWORD reason, void *reserved) +{ + switch (reason) + { + case DLL_PROCESS_ATTACH: + if(__wine_init_unix_call()) return FALSE; + break; + + case DLL_PROCESS_DETACH: + if (reserved) break; + DeleteCriticalSection(&g_sessions_lock); + break; + } + return TRUE; +} + +int WINAPI AUDDRV_GetPriority(void) +{ + return Priority_Neutral; +} + +static HRESULT alsa_stream_release(stream_handle stream, HANDLE timer_thread) +{ + struct release_stream_params params; + + params.stream = stream; + params.timer_thread = timer_thread; + + ALSA_CALL(release_stream, ¶ms); + + return params.result; +} + +static DWORD WINAPI alsa_timer_thread(void *user) +{ + struct timer_loop_params params; + struct ACImpl *This = user; + + SetThreadDescription(GetCurrentThread(), L"winealsa_timer"); + + params.stream = This->stream; + + ALSA_CALL(timer_loop, ¶ms); + + return 0; +} + +static void set_device_guid(EDataFlow flow, HKEY drv_key, const WCHAR *key_name, + GUID *guid) +{ + HKEY key; + BOOL opened = FALSE; + LONG lr; + + if(!drv_key){ + lr = RegCreateKeyExW(HKEY_CURRENT_USER, drv_key_devicesW, 0, NULL, 0, KEY_WRITE, + NULL, &drv_key, NULL); + if(lr != ERROR_SUCCESS){ + ERR("RegCreateKeyEx(drv_key) failed: %lu\n", lr); + return; + } + opened = TRUE; + } + + lr = RegCreateKeyExW(drv_key, key_name, 0, NULL, 0, KEY_WRITE, + NULL, &key, NULL); + if(lr != ERROR_SUCCESS){ + ERR("RegCreateKeyEx(%s) failed: %lu\n", wine_dbgstr_w(key_name), lr); + goto exit; + } + + lr = RegSetValueExW(key, guidW, 0, REG_BINARY, (BYTE*)guid, + sizeof(GUID)); + if(lr != ERROR_SUCCESS) + ERR("RegSetValueEx(%s\\guid) failed: %lu\n", wine_dbgstr_w(key_name), lr); + + RegCloseKey(key); +exit: + if(opened) + RegCloseKey(drv_key); +} + +static void get_device_guid(EDataFlow flow, const char *device, GUID *guid) +{ + HKEY key = NULL, dev_key; + DWORD type, size = sizeof(*guid); + WCHAR key_name[256]; + + if(flow == eCapture) + key_name[0] = '1'; + else + key_name[0] = '0'; + key_name[1] = ','; + MultiByteToWideChar(CP_UNIXCP, 0, device, -1, key_name + 2, ARRAY_SIZE(key_name) - 2); + + if(RegOpenKeyExW(HKEY_CURRENT_USER, drv_key_devicesW, 0, KEY_WRITE|KEY_READ, &key) == ERROR_SUCCESS){ + if(RegOpenKeyExW(key, key_name, 0, KEY_READ, &dev_key) == ERROR_SUCCESS){ + if(RegQueryValueExW(dev_key, guidW, 0, &type, + (BYTE*)guid, &size) == ERROR_SUCCESS){ + if(type == REG_BINARY){ + RegCloseKey(dev_key); + RegCloseKey(key); + return; + } + ERR("Invalid type for device %s GUID: %lu; ignoring and overwriting\n", + wine_dbgstr_w(key_name), type); + } + RegCloseKey(dev_key); + } + } + + CoCreateGuid(guid); + + set_device_guid(flow, key, key_name, guid); + + if(key) + RegCloseKey(key); +} + +static void set_stream_volumes(ACImpl *This) +{ + struct set_volumes_params params; + + params.stream = This->stream; + params.master_volume = (This->session->mute ? 0.0f : This->session->master_vol); + params.volumes = This->vols; + params.session_volumes = This->session->channel_vols; + params.channel = 0; + + ALSA_CALL(set_volumes, ¶ms); +} + +HRESULT WINAPI AUDDRV_GetEndpointIDs(EDataFlow flow, WCHAR ***ids_out, GUID **guids_out, + UINT *num, UINT *def_index) +{ + struct get_endpoint_ids_params params; + unsigned int i; + GUID *guids = NULL; + WCHAR **ids = NULL; + + TRACE("%d %p %p %p %p\n", flow, ids, guids, num, def_index); + + params.flow = flow; + params.size = 1000; + params.endpoints = NULL; + do{ + HeapFree(GetProcessHeap(), 0, params.endpoints); + params.endpoints = HeapAlloc(GetProcessHeap(), 0, params.size); + ALSA_CALL(get_endpoint_ids, ¶ms); + }while(params.result == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)); + + if(FAILED(params.result)) goto end; + + ids = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, params.num * sizeof(*ids)); + guids = HeapAlloc(GetProcessHeap(), 0, params.num * sizeof(*guids)); + if(!ids || !guids){ + params.result = E_OUTOFMEMORY; + goto end; + } + + for(i = 0; i < params.num; i++){ + WCHAR *name = (WCHAR *)((char *)params.endpoints + params.endpoints[i].name); + char *device = (char *)params.endpoints + params.endpoints[i].device; + unsigned int size = (wcslen(name) + 1) * sizeof(WCHAR); + + ids[i] = HeapAlloc(GetProcessHeap(), 0, size); + if(!ids[i]){ + params.result = E_OUTOFMEMORY; + goto end; + } + memcpy(ids[i], name, size); + get_device_guid(flow, device, guids + i); + } + *def_index = params.default_idx; + +end: + HeapFree(GetProcessHeap(), 0, params.endpoints); + if(FAILED(params.result)){ + HeapFree(GetProcessHeap(), 0, guids); + if(ids){ + for(i = 0; i < params.num; i++) + HeapFree(GetProcessHeap(), 0, ids[i]); + HeapFree(GetProcessHeap(), 0, ids); + } + }else{ + *ids_out = ids; + *guids_out = guids; + *num = params.num; + } + + return params.result; +} + +static BOOL get_alsa_name_by_guid(GUID *guid, char *name, DWORD name_size, EDataFlow *flow) +{ + HKEY devices_key; + UINT i = 0; + WCHAR key_name[256]; + DWORD key_name_size; + + if(RegOpenKeyExW(HKEY_CURRENT_USER, drv_key_devicesW, 0, KEY_READ, &devices_key) != ERROR_SUCCESS){ + ERR("No devices found in registry?\n"); + return FALSE; + } + + while(1){ + HKEY key; + DWORD size, type; + GUID reg_guid; + + key_name_size = ARRAY_SIZE(key_name); + if(RegEnumKeyExW(devices_key, i++, key_name, &key_name_size, NULL, + NULL, NULL, NULL) != ERROR_SUCCESS) + break; + + if(RegOpenKeyExW(devices_key, key_name, 0, KEY_READ, &key) != ERROR_SUCCESS){ + WARN("Couldn't open key: %s\n", wine_dbgstr_w(key_name)); + continue; + } + + size = sizeof(reg_guid); + if(RegQueryValueExW(key, guidW, 0, &type, + (BYTE*)®_guid, &size) == ERROR_SUCCESS){ + if(IsEqualGUID(®_guid, guid)){ + RegCloseKey(key); + RegCloseKey(devices_key); + + TRACE("Found matching device key: %s\n", wine_dbgstr_w(key_name)); + + if(key_name[0] == '0') + *flow = eRender; + else if(key_name[0] == '1') + *flow = eCapture; + else{ + ERR("Unknown device type: %c\n", key_name[0]); + return FALSE; + } + + WideCharToMultiByte(CP_UNIXCP, 0, key_name + 2, -1, name, name_size, NULL, NULL); + + return TRUE; + } + } + + RegCloseKey(key); + } + + RegCloseKey(devices_key); + + WARN("No matching device in registry for GUID %s\n", debugstr_guid(guid)); + + return FALSE; +} + +HRESULT WINAPI AUDDRV_GetAudioEndpoint(GUID *guid, IMMDevice *dev, IAudioClient **out) +{ + ACImpl *This; + char alsa_name[256]; + EDataFlow dataflow; + HRESULT hr; + int len; + + TRACE("%s %p %p\n", debugstr_guid(guid), dev, out); + + if(!get_alsa_name_by_guid(guid, alsa_name, sizeof(alsa_name), &dataflow)) + return AUDCLNT_E_DEVICE_INVALIDATED; + + if(dataflow != eRender && dataflow != eCapture) + return E_UNEXPECTED; + + len = strlen(alsa_name); + This = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, offsetof(ACImpl, alsa_name[len + 1])); + if(!This) + return E_OUTOFMEMORY; + + This->IAudioClient3_iface.lpVtbl = &AudioClient3_Vtbl; + This->IAudioRenderClient_iface.lpVtbl = &AudioRenderClient_Vtbl; + This->IAudioCaptureClient_iface.lpVtbl = &AudioCaptureClient_Vtbl; + This->IAudioClock_iface.lpVtbl = &AudioClock_Vtbl; + This->IAudioClock2_iface.lpVtbl = &AudioClock2_Vtbl; + This->IAudioStreamVolume_iface.lpVtbl = &AudioStreamVolume_Vtbl; + + hr = CoCreateFreeThreadedMarshaler((IUnknown *)&This->IAudioClient3_iface, &This->pUnkFTMarshal); + if (FAILED(hr)) { + HeapFree(GetProcessHeap(), 0, This); + return hr; + } + + This->dataflow = dataflow; + memcpy(This->alsa_name, alsa_name, len + 1); + + This->parent = dev; + IMMDevice_AddRef(This->parent); + + *out = (IAudioClient *)&This->IAudioClient3_iface; + IAudioClient3_AddRef(&This->IAudioClient3_iface); + + return S_OK; +} + +static HRESULT WINAPI AudioClient_QueryInterface(IAudioClient3 *iface, + REFIID riid, void **ppv) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + if(IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IAudioClient) || + IsEqualIID(riid, &IID_IAudioClient2) || + IsEqualIID(riid, &IID_IAudioClient3)) + *ppv = iface; + else if(IsEqualIID(riid, &IID_IMarshal)) + return IUnknown_QueryInterface(This->pUnkFTMarshal, riid, ppv); + + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioClient_AddRef(IAudioClient3 *iface) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + ULONG ref; + ref = InterlockedIncrement(&This->ref); + TRACE("(%p) Refcount now %lu\n", This, ref); + return ref; +} + +static ULONG WINAPI AudioClient_Release(IAudioClient3 *iface) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + ULONG ref; + + ref = InterlockedDecrement(&This->ref); + TRACE("(%p) Refcount now %lu\n", This, ref); + if(!ref){ + IAudioClient3_Stop(iface); + IMMDevice_Release(This->parent); + IUnknown_Release(This->pUnkFTMarshal); + if(This->session){ + EnterCriticalSection(&g_sessions_lock); + list_remove(&This->entry); + LeaveCriticalSection(&g_sessions_lock); + } + HeapFree(GetProcessHeap(), 0, This->vols); + if (This->stream) + alsa_stream_release(This->stream, This->timer_thread); + HeapFree(GetProcessHeap(), 0, This); + } + return ref; +} + +static void dump_fmt(const WAVEFORMATEX *fmt) +{ + TRACE("wFormatTag: 0x%x (", fmt->wFormatTag); + switch(fmt->wFormatTag){ + case WAVE_FORMAT_PCM: + TRACE("WAVE_FORMAT_PCM"); + break; + case WAVE_FORMAT_IEEE_FLOAT: + TRACE("WAVE_FORMAT_IEEE_FLOAT"); + break; + case WAVE_FORMAT_EXTENSIBLE: + TRACE("WAVE_FORMAT_EXTENSIBLE"); + break; + default: + TRACE("Unknown"); + break; + } + TRACE(")\n"); + + TRACE("nChannels: %u\n", fmt->nChannels); + TRACE("nSamplesPerSec: %lu\n", fmt->nSamplesPerSec); + TRACE("nAvgBytesPerSec: %lu\n", fmt->nAvgBytesPerSec); + TRACE("nBlockAlign: %u\n", fmt->nBlockAlign); + TRACE("wBitsPerSample: %u\n", fmt->wBitsPerSample); + TRACE("cbSize: %u\n", fmt->cbSize); + + if(fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE){ + WAVEFORMATEXTENSIBLE *fmtex = (void*)fmt; + TRACE("dwChannelMask: %08lx\n", fmtex->dwChannelMask); + TRACE("Samples: %04x\n", fmtex->Samples.wReserved); + TRACE("SubFormat: %s\n", wine_dbgstr_guid(&fmtex->SubFormat)); + } +} + +static void session_init_vols(AudioSession *session, UINT channels) +{ + if(session->channel_count < channels){ + UINT i; + + if(session->channel_vols) + session->channel_vols = HeapReAlloc(GetProcessHeap(), 0, + session->channel_vols, sizeof(float) * channels); + else + session->channel_vols = HeapAlloc(GetProcessHeap(), 0, + sizeof(float) * channels); + if(!session->channel_vols) + return; + + for(i = session->channel_count; i < channels; ++i) + session->channel_vols[i] = 1.f; + + session->channel_count = channels; + } +} + +static AudioSession *create_session(const GUID *guid, IMMDevice *device, + UINT num_channels) +{ + AudioSession *ret; + + ret = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(AudioSession)); + if(!ret) + return NULL; + + memcpy(&ret->guid, guid, sizeof(GUID)); + + ret->device = device; + + list_init(&ret->clients); + + list_add_head(&g_sessions, &ret->entry); + + session_init_vols(ret, num_channels); + + ret->master_vol = 1.f; + + return ret; +} + +/* if channels == 0, then this will return or create a session with + * matching dataflow and GUID. otherwise, channels must also match */ +static HRESULT get_audio_session(const GUID *sessionguid, + IMMDevice *device, UINT channels, AudioSession **out) +{ + AudioSession *session; + + if(!sessionguid || IsEqualGUID(sessionguid, &GUID_NULL)){ + *out = create_session(&GUID_NULL, device, channels); + if(!*out) + return E_OUTOFMEMORY; + + return S_OK; + } + + *out = NULL; + LIST_FOR_EACH_ENTRY(session, &g_sessions, AudioSession, entry){ + if(session->device == device && + IsEqualGUID(sessionguid, &session->guid)){ + session_init_vols(session, channels); + *out = session; + break; + } + } + + if(!*out){ + *out = create_session(sessionguid, device, channels); + if(!*out) + return E_OUTOFMEMORY; + } + + return S_OK; +} + +static HRESULT WINAPI AudioClient_Initialize(IAudioClient3 *iface, + AUDCLNT_SHAREMODE mode, DWORD flags, REFERENCE_TIME duration, + REFERENCE_TIME period, const WAVEFORMATEX *fmt, + const GUID *sessionguid) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct create_stream_params params; + stream_handle stream; + unsigned int i; + + TRACE("(%p)->(%x, %lx, %s, %s, %p, %s)\n", This, mode, flags, + wine_dbgstr_longlong(duration), wine_dbgstr_longlong(period), fmt, debugstr_guid(sessionguid)); + + if(!fmt) + return E_POINTER; + + if(mode != AUDCLNT_SHAREMODE_SHARED && mode != AUDCLNT_SHAREMODE_EXCLUSIVE) + return E_INVALIDARG; + + if(flags & ~(AUDCLNT_STREAMFLAGS_CROSSPROCESS | + AUDCLNT_STREAMFLAGS_LOOPBACK | + AUDCLNT_STREAMFLAGS_EVENTCALLBACK | + AUDCLNT_STREAMFLAGS_NOPERSIST | + AUDCLNT_STREAMFLAGS_RATEADJUST | + AUDCLNT_SESSIONFLAGS_EXPIREWHENUNOWNED | + AUDCLNT_SESSIONFLAGS_DISPLAY_HIDE | + AUDCLNT_SESSIONFLAGS_DISPLAY_HIDEWHENEXPIRED | + AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY | + AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM)){ + FIXME("Unknown flags: %08lx\n", flags); + return E_INVALIDARG; + } + + if(mode == AUDCLNT_SHAREMODE_SHARED){ + period = GetAudioEnv("STAGING_AUDIO_DEFAULT_PERIOD", DefaultPeriod); + duration = GetAudioEnv("STAGING_AUDIO_DURATION", 100000); + }else{ + if(fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE){ + if(((WAVEFORMATEXTENSIBLE*)fmt)->dwChannelMask == 0 || + ((WAVEFORMATEXTENSIBLE*)fmt)->dwChannelMask & SPEAKER_RESERVED) + return AUDCLNT_E_UNSUPPORTED_FORMAT; + } + + if(!period){ + period = GetAudioEnv("STAGING_AUDIO_DEFAULT_PERIOD", DefaultPeriod); /* not minimum */ + duration = GetAudioEnv("STAGING_AUDIO_DURATION", 100000); + } + if(period < GetAudioEnv("STAGING_AUDIO_MINIMUM_PERIOD", MinimumPeriod) || period > 5000000) + return AUDCLNT_E_INVALID_DEVICE_PERIOD; + if(duration > 20000000) /* the smaller the period, the lower this limit */ + return AUDCLNT_E_BUFFER_SIZE_ERROR; + if(flags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK){ + if(duration != period) + return AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL; + FIXME("EXCLUSIVE mode with EVENTCALLBACK\n"); + return AUDCLNT_E_DEVICE_IN_USE; + }else{ + if( duration < 8 * period) + duration = 8 * period; /* may grow above 2s */ + } + } + + EnterCriticalSection(&g_sessions_lock); + + if(This->stream){ + LeaveCriticalSection(&g_sessions_lock); + return AUDCLNT_E_ALREADY_INITIALIZED; + } + + dump_fmt(fmt); + + params.name = NULL; + params.device = This->alsa_name; + params.flow = This->dataflow; + params.share = mode; + params.flags = flags; + params.duration = duration; + params.period = period; + params.fmt = fmt; + params.channel_count = NULL; + params.stream = &stream; + + ALSA_CALL(create_stream, ¶ms); + if(FAILED(params.result)){ + LeaveCriticalSection(&g_sessions_lock); + return params.result; + } + + This->channel_count = fmt->nChannels; + This->vols = HeapAlloc(GetProcessHeap(), 0, This->channel_count * sizeof(float)); + if(!This->vols){ + params.result = E_OUTOFMEMORY; + goto exit; + } + for(i = 0; i < This->channel_count; ++i) + This->vols[i] = 1.f; + + params.result = get_audio_session(sessionguid, This->parent, This->channel_count, + &This->session); + if(FAILED(params.result)) + goto exit; + + list_add_tail(&This->session->clients, &This->entry); + +exit: + if(FAILED(params.result)){ + alsa_stream_release(stream, NULL); + HeapFree(GetProcessHeap(), 0, This->vols); + This->vols = NULL; + }else{ + This->stream = stream; + set_stream_volumes(This); + } + + LeaveCriticalSection(&g_sessions_lock); + + return params.result; +} + +static HRESULT WINAPI AudioClient_GetBufferSize(IAudioClient3 *iface, + UINT32 *out) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct get_buffer_size_params params; + + TRACE("(%p)->(%p)\n", This, out); + + if(!out) + return E_POINTER; + + if(!This->stream) + return AUDCLNT_E_NOT_INITIALIZED; + + params.stream = This->stream; + params.frames = out; + + ALSA_CALL(get_buffer_size, ¶ms); + + return params.result; +} + +static HRESULT WINAPI AudioClient_GetStreamLatency(IAudioClient3 *iface, + REFERENCE_TIME *latency) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct get_latency_params params; + + TRACE("(%p)->(%p)\n", This, latency); + + if(!latency) + return E_POINTER; + + if(!This->stream) + return AUDCLNT_E_NOT_INITIALIZED; + + params.stream = This->stream; + params.latency = latency; + + ALSA_CALL(get_latency, ¶ms); + + return params.result; +} + +static HRESULT WINAPI AudioClient_GetCurrentPadding(IAudioClient3 *iface, + UINT32 *out) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct get_current_padding_params params; + + TRACE("(%p)->(%p)\n", This, out); + + if(!out) + return E_POINTER; + + if(!This->stream) + return AUDCLNT_E_NOT_INITIALIZED; + + params.stream = This->stream; + params.padding = out; + + ALSA_CALL(get_current_padding, ¶ms); + + TRACE("pad: %u\n", *out); + + return params.result; +} + +static HRESULT WINAPI AudioClient_IsFormatSupported(IAudioClient3 *iface, + AUDCLNT_SHAREMODE mode, const WAVEFORMATEX *fmt, + WAVEFORMATEX **out) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct is_format_supported_params params; + + TRACE("(%p)->(%x, %p, %p)\n", This, mode, fmt, out); + if(fmt) dump_fmt(fmt); + + params.device = This->alsa_name; + params.flow = This->dataflow; + params.share = mode; + params.fmt_in = fmt; + params.fmt_out = NULL; + + if(out){ + *out = NULL; + if(mode == AUDCLNT_SHAREMODE_SHARED) + params.fmt_out = CoTaskMemAlloc(sizeof(*params.fmt_out)); + } + ALSA_CALL(is_format_supported, ¶ms); + + if(params.result == S_FALSE) + *out = ¶ms.fmt_out->Format; + else + CoTaskMemFree(params.fmt_out); + + return params.result; +} + +static HRESULT WINAPI AudioClient_GetMixFormat(IAudioClient3 *iface, + WAVEFORMATEX **pwfx) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct get_mix_format_params params; + + TRACE("(%p)->(%p)\n", This, pwfx); + + if(!pwfx) + return E_POINTER; + *pwfx = NULL; + + params.device = This->alsa_name; + params.flow = This->dataflow; + params.fmt = CoTaskMemAlloc(sizeof(WAVEFORMATEXTENSIBLE)); + if(!params.fmt) + return E_OUTOFMEMORY; + + ALSA_CALL(get_mix_format, ¶ms); + + if(SUCCEEDED(params.result)){ + *pwfx = ¶ms.fmt->Format; + dump_fmt(*pwfx); + } else + CoTaskMemFree(params.fmt); + + return params.result; +} + +static HRESULT WINAPI AudioClient_GetDevicePeriod(IAudioClient3 *iface, + REFERENCE_TIME *defperiod, REFERENCE_TIME *minperiod) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + + TRACE("(%p)->(%p, %p)\n", This, defperiod, minperiod); + + if(!defperiod && !minperiod) + return E_POINTER; + + if(defperiod) + *defperiod = GetAudioEnv("STAGING_AUDIO_DEFAULT_PERIOD", DefaultPeriod); + if(minperiod) + *minperiod = GetAudioEnv("STAGING_AUDIO_MINIMUM_PERIOD", MinimumPeriod); + + return S_OK; +} + +static HRESULT WINAPI AudioClient_Start(IAudioClient3 *iface) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct start_params params; + + TRACE("(%p)\n", This); + + EnterCriticalSection(&g_sessions_lock); + + if(!This->stream){ + LeaveCriticalSection(&g_sessions_lock); + return AUDCLNT_E_NOT_INITIALIZED; + } + + params.stream = This->stream; + + ALSA_CALL(start, ¶ms); + + if(SUCCEEDED(params.result) && !This->timer_thread){ + This->timer_thread = CreateThread(NULL, 0, alsa_timer_thread, This, 0, NULL); + SetThreadPriority(This->timer_thread, THREAD_PRIORITY_TIME_CRITICAL); + } + + LeaveCriticalSection(&g_sessions_lock); + + return params.result; +} + +static HRESULT WINAPI AudioClient_Stop(IAudioClient3 *iface) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct stop_params params; + + TRACE("(%p)\n", This); + + if(!This->stream) + return AUDCLNT_E_NOT_INITIALIZED; + + params.stream = This->stream; + + ALSA_CALL(stop, ¶ms); + + return params.result; +} + +static HRESULT WINAPI AudioClient_Reset(IAudioClient3 *iface) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct reset_params params; + + TRACE("(%p)\n", This); + + if(!This->stream) + return AUDCLNT_E_NOT_INITIALIZED; + + params.stream = This->stream; + + ALSA_CALL(reset, ¶ms); + + return params.result; +} + +static HRESULT WINAPI AudioClient_SetEventHandle(IAudioClient3 *iface, + HANDLE event) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct set_event_handle_params params; + + TRACE("(%p)->(%p)\n", This, event); + + if(!event) + return E_INVALIDARG; + + if(!This->stream) + return AUDCLNT_E_NOT_INITIALIZED; + + params.stream = This->stream; + params.event = event; + + ALSA_CALL(set_event_handle, ¶ms); + + return params.result; +} + +static HRESULT WINAPI AudioClient_GetService(IAudioClient3 *iface, REFIID riid, + void **ppv) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + + TRACE("(%p)->(%s, %p)\n", This, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + EnterCriticalSection(&g_sessions_lock); + + if(!This->stream){ + LeaveCriticalSection(&g_sessions_lock); + return AUDCLNT_E_NOT_INITIALIZED; + } + + if(IsEqualIID(riid, &IID_IAudioRenderClient)){ + if(This->dataflow != eRender){ + LeaveCriticalSection(&g_sessions_lock); + return AUDCLNT_E_WRONG_ENDPOINT_TYPE; + } + IAudioRenderClient_AddRef(&This->IAudioRenderClient_iface); + *ppv = &This->IAudioRenderClient_iface; + }else if(IsEqualIID(riid, &IID_IAudioCaptureClient)){ + if(This->dataflow != eCapture){ + LeaveCriticalSection(&g_sessions_lock); + return AUDCLNT_E_WRONG_ENDPOINT_TYPE; + } + IAudioCaptureClient_AddRef(&This->IAudioCaptureClient_iface); + *ppv = &This->IAudioCaptureClient_iface; + }else if(IsEqualIID(riid, &IID_IAudioClock)){ + IAudioClock_AddRef(&This->IAudioClock_iface); + *ppv = &This->IAudioClock_iface; + }else if(IsEqualIID(riid, &IID_IAudioStreamVolume)){ + IAudioStreamVolume_AddRef(&This->IAudioStreamVolume_iface); + *ppv = &This->IAudioStreamVolume_iface; + }else if(IsEqualIID(riid, &IID_IAudioSessionControl)){ + if(!This->session_wrapper){ + This->session_wrapper = AudioSessionWrapper_Create(This); + if(!This->session_wrapper){ + LeaveCriticalSection(&g_sessions_lock); + return E_OUTOFMEMORY; + } + }else + IAudioSessionControl2_AddRef(&This->session_wrapper->IAudioSessionControl2_iface); + + *ppv = &This->session_wrapper->IAudioSessionControl2_iface; + }else if(IsEqualIID(riid, &IID_IChannelAudioVolume)){ + if(!This->session_wrapper){ + This->session_wrapper = AudioSessionWrapper_Create(This); + if(!This->session_wrapper){ + LeaveCriticalSection(&g_sessions_lock); + return E_OUTOFMEMORY; + } + }else + IChannelAudioVolume_AddRef(&This->session_wrapper->IChannelAudioVolume_iface); + + *ppv = &This->session_wrapper->IChannelAudioVolume_iface; + }else if(IsEqualIID(riid, &IID_ISimpleAudioVolume)){ + if(!This->session_wrapper){ + This->session_wrapper = AudioSessionWrapper_Create(This); + if(!This->session_wrapper){ + LeaveCriticalSection(&g_sessions_lock); + return E_OUTOFMEMORY; + } + }else + ISimpleAudioVolume_AddRef(&This->session_wrapper->ISimpleAudioVolume_iface); + + *ppv = &This->session_wrapper->ISimpleAudioVolume_iface; + } + + if(*ppv){ + LeaveCriticalSection(&g_sessions_lock); + return S_OK; + } + + LeaveCriticalSection(&g_sessions_lock); + + FIXME("stub %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static HRESULT WINAPI AudioClient_IsOffloadCapable(IAudioClient3 *iface, + AUDIO_STREAM_CATEGORY category, BOOL *offload_capable) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + + TRACE("(%p)->(0x%x, %p)\n", This, category, offload_capable); + + if(!offload_capable) + return E_INVALIDARG; + + *offload_capable = FALSE; + + return S_OK; +} + +static HRESULT WINAPI AudioClient_SetClientProperties(IAudioClient3 *iface, + const AudioClientProperties *prop) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + const Win8AudioClientProperties *legacy_prop = (const Win8AudioClientProperties *)prop; + + TRACE("(%p)->(%p)\n", This, prop); + + if(!legacy_prop) + return E_POINTER; + + if(legacy_prop->cbSize == sizeof(AudioClientProperties)){ + TRACE("{ bIsOffload: %u, eCategory: 0x%x, Options: 0x%x }\n", + legacy_prop->bIsOffload, + legacy_prop->eCategory, + prop->Options); + }else if(legacy_prop->cbSize == sizeof(Win8AudioClientProperties)){ + TRACE("{ bIsOffload: %u, eCategory: 0x%x }\n", + legacy_prop->bIsOffload, + legacy_prop->eCategory); + }else{ + WARN("Unsupported Size = %d\n", legacy_prop->cbSize); + return E_INVALIDARG; + } + + + if(legacy_prop->bIsOffload) + return AUDCLNT_E_ENDPOINT_OFFLOAD_NOT_CAPABLE; + + return S_OK; +} + +static HRESULT WINAPI AudioClient_GetBufferSizeLimits(IAudioClient3 *iface, + const WAVEFORMATEX *format, BOOL event_driven, REFERENCE_TIME *min_duration, + REFERENCE_TIME *max_duration) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + + FIXME("(%p)->(%p, %u, %p, %p)\n", This, format, event_driven, min_duration, max_duration); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioClient_GetSharedModeEnginePeriod(IAudioClient3 *iface, + const WAVEFORMATEX *format, UINT32 *default_period_frames, UINT32 *unit_period_frames, + UINT32 *min_period_frames, UINT32 *max_period_frames) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + + FIXME("(%p)->(%p, %p, %p, %p, %p)\n", This, format, default_period_frames, unit_period_frames, + min_period_frames, max_period_frames); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioClient_GetCurrentSharedModeEnginePeriod(IAudioClient3 *iface, + WAVEFORMATEX **cur_format, UINT32 *cur_period_frames) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + + FIXME("(%p)->(%p, %p)\n", This, cur_format, cur_period_frames); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioClient_InitializeSharedAudioStream(IAudioClient3 *iface, + DWORD flags, UINT32 period_frames, const WAVEFORMATEX *format, + const GUID *session_guid) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + + FIXME("(%p)->(0x%lx, %u, %p, %s)\n", This, flags, period_frames, format, debugstr_guid(session_guid)); + + return E_NOTIMPL; +} + +static const IAudioClient3Vtbl AudioClient3_Vtbl = +{ + AudioClient_QueryInterface, + AudioClient_AddRef, + AudioClient_Release, + AudioClient_Initialize, + AudioClient_GetBufferSize, + AudioClient_GetStreamLatency, + AudioClient_GetCurrentPadding, + AudioClient_IsFormatSupported, + AudioClient_GetMixFormat, + AudioClient_GetDevicePeriod, + AudioClient_Start, + AudioClient_Stop, + AudioClient_Reset, + AudioClient_SetEventHandle, + AudioClient_GetService, + AudioClient_IsOffloadCapable, + AudioClient_SetClientProperties, + AudioClient_GetBufferSizeLimits, + AudioClient_GetSharedModeEnginePeriod, + AudioClient_GetCurrentSharedModeEnginePeriod, + AudioClient_InitializeSharedAudioStream, +}; + +static HRESULT WINAPI AudioRenderClient_QueryInterface( + IAudioRenderClient *iface, REFIID riid, void **ppv) +{ + ACImpl *This = impl_from_IAudioRenderClient(iface); + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + if(IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IAudioRenderClient)) + *ppv = iface; + else if(IsEqualIID(riid, &IID_IMarshal)) + return IUnknown_QueryInterface(This->pUnkFTMarshal, riid, ppv); + + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioRenderClient_AddRef(IAudioRenderClient *iface) +{ + ACImpl *This = impl_from_IAudioRenderClient(iface); + return AudioClient_AddRef(&This->IAudioClient3_iface); +} + +static ULONG WINAPI AudioRenderClient_Release(IAudioRenderClient *iface) +{ + ACImpl *This = impl_from_IAudioRenderClient(iface); + return AudioClient_Release(&This->IAudioClient3_iface); +} + +static HRESULT WINAPI AudioRenderClient_GetBuffer(IAudioRenderClient *iface, + UINT32 frames, BYTE **data) +{ + ACImpl *This = impl_from_IAudioRenderClient(iface); + struct get_render_buffer_params params; + + TRACE("(%p)->(%u, %p)\n", This, frames, data); + + if(!data) + return E_POINTER; + *data = NULL; + + params.stream = This->stream; + params.frames = frames; + params.data = data; + + ALSA_CALL(get_render_buffer, ¶ms); + + return params.result; +} + +static HRESULT WINAPI AudioRenderClient_ReleaseBuffer( + IAudioRenderClient *iface, UINT32 written_frames, DWORD flags) +{ + ACImpl *This = impl_from_IAudioRenderClient(iface); + struct release_render_buffer_params params; + + TRACE("(%p)->(%u, %lx)\n", This, written_frames, flags); + + params.stream = This->stream; + params.written_frames = written_frames; + params.flags = flags; + + ALSA_CALL(release_render_buffer, ¶ms); + + return params.result; +} + +static const IAudioRenderClientVtbl AudioRenderClient_Vtbl = { + AudioRenderClient_QueryInterface, + AudioRenderClient_AddRef, + AudioRenderClient_Release, + AudioRenderClient_GetBuffer, + AudioRenderClient_ReleaseBuffer +}; + +static HRESULT WINAPI AudioCaptureClient_QueryInterface( + IAudioCaptureClient *iface, REFIID riid, void **ppv) +{ + ACImpl *This = impl_from_IAudioCaptureClient(iface); + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + if(IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IAudioCaptureClient)) + *ppv = iface; + else if(IsEqualIID(riid, &IID_IMarshal)) + return IUnknown_QueryInterface(This->pUnkFTMarshal, riid, ppv); + + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioCaptureClient_AddRef(IAudioCaptureClient *iface) +{ + ACImpl *This = impl_from_IAudioCaptureClient(iface); + return IAudioClient3_AddRef(&This->IAudioClient3_iface); +} + +static ULONG WINAPI AudioCaptureClient_Release(IAudioCaptureClient *iface) +{ + ACImpl *This = impl_from_IAudioCaptureClient(iface); + return IAudioClient3_Release(&This->IAudioClient3_iface); +} + +static HRESULT WINAPI AudioCaptureClient_GetBuffer(IAudioCaptureClient *iface, + BYTE **data, UINT32 *frames, DWORD *flags, UINT64 *devpos, + UINT64 *qpcpos) +{ + ACImpl *This = impl_from_IAudioCaptureClient(iface); + struct get_capture_buffer_params params; + + TRACE("(%p)->(%p, %p, %p, %p, %p)\n", This, data, frames, flags, + devpos, qpcpos); + + if(!data) + return E_POINTER; + + *data = NULL; + + if(!frames || !flags) + return E_POINTER; + + params.stream = This->stream; + params.data = data; + params.frames = frames; + params.flags = (UINT*)flags; + params.devpos = devpos; + params.qpcpos = qpcpos; + + ALSA_CALL(get_capture_buffer, ¶ms); + + return params.result; +} + +static HRESULT WINAPI AudioCaptureClient_ReleaseBuffer( + IAudioCaptureClient *iface, UINT32 done) +{ + ACImpl *This = impl_from_IAudioCaptureClient(iface); + struct release_capture_buffer_params params; + + TRACE("(%p)->(%u)\n", This, done); + + params.stream = This->stream; + params.done = done; + + ALSA_CALL(release_capture_buffer, ¶ms); + + return params.result; +} + +static HRESULT WINAPI AudioCaptureClient_GetNextPacketSize( + IAudioCaptureClient *iface, UINT32 *frames) +{ + ACImpl *This = impl_from_IAudioCaptureClient(iface); + struct get_next_packet_size_params params; + + TRACE("(%p)->(%p)\n", This, frames); + + if(!frames) + return E_POINTER; + + params.stream = This->stream; + params.frames = frames; + + ALSA_CALL(get_next_packet_size, ¶ms); + + return params.result; +} + +static const IAudioCaptureClientVtbl AudioCaptureClient_Vtbl = +{ + AudioCaptureClient_QueryInterface, + AudioCaptureClient_AddRef, + AudioCaptureClient_Release, + AudioCaptureClient_GetBuffer, + AudioCaptureClient_ReleaseBuffer, + AudioCaptureClient_GetNextPacketSize +}; + +static HRESULT WINAPI AudioClock_QueryInterface(IAudioClock *iface, + REFIID riid, void **ppv) +{ + ACImpl *This = impl_from_IAudioClock(iface); + + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + if(IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IAudioClock)) + *ppv = iface; + else if(IsEqualIID(riid, &IID_IAudioClock2)) + *ppv = &This->IAudioClock2_iface; + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioClock_AddRef(IAudioClock *iface) +{ + ACImpl *This = impl_from_IAudioClock(iface); + return IAudioClient3_AddRef(&This->IAudioClient3_iface); +} + +static ULONG WINAPI AudioClock_Release(IAudioClock *iface) +{ + ACImpl *This = impl_from_IAudioClock(iface); + return IAudioClient3_Release(&This->IAudioClient3_iface); +} + +static HRESULT WINAPI AudioClock_GetFrequency(IAudioClock *iface, UINT64 *freq) +{ + ACImpl *This = impl_from_IAudioClock(iface); + struct get_frequency_params params; + + TRACE("(%p)->(%p)\n", This, freq); + + params.stream = This->stream; + params.freq = freq; + + ALSA_CALL(get_frequency, ¶ms); + + return params.result; +} + +static HRESULT WINAPI AudioClock_GetPosition(IAudioClock *iface, UINT64 *pos, + UINT64 *qpctime) +{ + ACImpl *This = impl_from_IAudioClock(iface); + struct get_position_params params; + + TRACE("(%p)->(%p, %p)\n", This, pos, qpctime); + + if(!pos) + return E_POINTER; + + params.stream = This->stream; + params.device = FALSE; + params.pos = pos; + params.qpctime = qpctime; + + ALSA_CALL(get_position, ¶ms); + + return params.result; +} + +static HRESULT WINAPI AudioClock_GetCharacteristics(IAudioClock *iface, + DWORD *chars) +{ + ACImpl *This = impl_from_IAudioClock(iface); + + TRACE("(%p)->(%p)\n", This, chars); + + if(!chars) + return E_POINTER; + + *chars = AUDIOCLOCK_CHARACTERISTIC_FIXED_FREQ; + + return S_OK; +} + +static const IAudioClockVtbl AudioClock_Vtbl = +{ + AudioClock_QueryInterface, + AudioClock_AddRef, + AudioClock_Release, + AudioClock_GetFrequency, + AudioClock_GetPosition, + AudioClock_GetCharacteristics +}; + +static HRESULT WINAPI AudioClock2_QueryInterface(IAudioClock2 *iface, + REFIID riid, void **ppv) +{ + ACImpl *This = impl_from_IAudioClock2(iface); + return IAudioClock_QueryInterface(&This->IAudioClock_iface, riid, ppv); +} + +static ULONG WINAPI AudioClock2_AddRef(IAudioClock2 *iface) +{ + ACImpl *This = impl_from_IAudioClock2(iface); + return IAudioClient3_AddRef(&This->IAudioClient3_iface); +} + +static ULONG WINAPI AudioClock2_Release(IAudioClock2 *iface) +{ + ACImpl *This = impl_from_IAudioClock2(iface); + return IAudioClient3_Release(&This->IAudioClient3_iface); +} + +static HRESULT WINAPI AudioClock2_GetDevicePosition(IAudioClock2 *iface, + UINT64 *pos, UINT64 *qpctime) +{ + ACImpl *This = impl_from_IAudioClock2(iface); + + FIXME("(%p)->(%p, %p)\n", This, pos, qpctime); + + return E_NOTIMPL; +} + +static const IAudioClock2Vtbl AudioClock2_Vtbl = +{ + AudioClock2_QueryInterface, + AudioClock2_AddRef, + AudioClock2_Release, + AudioClock2_GetDevicePosition +}; + +static AudioSessionWrapper *AudioSessionWrapper_Create(ACImpl *client) +{ + AudioSessionWrapper *ret; + + ret = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, + sizeof(AudioSessionWrapper)); + if(!ret) + return NULL; + + ret->IAudioSessionControl2_iface.lpVtbl = &AudioSessionControl2_Vtbl; + ret->ISimpleAudioVolume_iface.lpVtbl = &SimpleAudioVolume_Vtbl; + ret->IChannelAudioVolume_iface.lpVtbl = &ChannelAudioVolume_Vtbl; + + ret->ref = 1; + + ret->client = client; + if(client){ + ret->session = client->session; + AudioClient_AddRef(&client->IAudioClient3_iface); + } + + return ret; +} + +static HRESULT WINAPI AudioSessionControl_QueryInterface( + IAudioSessionControl2 *iface, REFIID riid, void **ppv) +{ + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + if(IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IAudioSessionControl) || + IsEqualIID(riid, &IID_IAudioSessionControl2)) + *ppv = iface; + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioSessionControl_AddRef(IAudioSessionControl2 *iface) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + ULONG ref; + ref = InterlockedIncrement(&This->ref); + TRACE("(%p) Refcount now %lu\n", This, ref); + return ref; +} + +static ULONG WINAPI AudioSessionControl_Release(IAudioSessionControl2 *iface) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + ULONG ref; + ref = InterlockedDecrement(&This->ref); + TRACE("(%p) Refcount now %lu\n", This, ref); + if(!ref){ + if(This->client){ + EnterCriticalSection(&g_sessions_lock); + This->client->session_wrapper = NULL; + LeaveCriticalSection(&g_sessions_lock); + AudioClient_Release(&This->client->IAudioClient3_iface); + } + HeapFree(GetProcessHeap(), 0, This); + } + return ref; +} + +static HRESULT WINAPI AudioSessionControl_GetState(IAudioSessionControl2 *iface, + AudioSessionState *state) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + struct is_started_params params; + ACImpl *client; + + TRACE("(%p)->(%p)\n", This, state); + + if(!state) + return NULL_PTR_ERR; + + EnterCriticalSection(&g_sessions_lock); + + if(list_empty(&This->session->clients)){ + *state = AudioSessionStateExpired; + LeaveCriticalSection(&g_sessions_lock); + return S_OK; + } + + LIST_FOR_EACH_ENTRY(client, &This->session->clients, ACImpl, entry){ + params.stream = client->stream; + ALSA_CALL(is_started, ¶ms); + if(params.result == S_OK){ + *state = AudioSessionStateActive; + LeaveCriticalSection(&g_sessions_lock); + return S_OK; + } + } + + LeaveCriticalSection(&g_sessions_lock); + + *state = AudioSessionStateInactive; + + return S_OK; +} + +static HRESULT WINAPI AudioSessionControl_GetDisplayName( + IAudioSessionControl2 *iface, WCHAR **name) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, name); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_SetDisplayName( + IAudioSessionControl2 *iface, const WCHAR *name, const GUID *session) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p, %s) - stub\n", This, name, debugstr_guid(session)); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_GetIconPath( + IAudioSessionControl2 *iface, WCHAR **path) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, path); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_SetIconPath( + IAudioSessionControl2 *iface, const WCHAR *path, const GUID *session) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p, %s) - stub\n", This, path, debugstr_guid(session)); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_GetGroupingParam( + IAudioSessionControl2 *iface, GUID *group) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, group); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_SetGroupingParam( + IAudioSessionControl2 *iface, const GUID *group, const GUID *session) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%s, %s) - stub\n", This, debugstr_guid(group), + debugstr_guid(session)); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_RegisterAudioSessionNotification( + IAudioSessionControl2 *iface, IAudioSessionEvents *events) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, events); + + return S_OK; +} + +static HRESULT WINAPI AudioSessionControl_UnregisterAudioSessionNotification( + IAudioSessionControl2 *iface, IAudioSessionEvents *events) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, events); + + return S_OK; +} + +static HRESULT WINAPI AudioSessionControl_GetSessionIdentifier( + IAudioSessionControl2 *iface, WCHAR **id) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, id); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_GetSessionInstanceIdentifier( + IAudioSessionControl2 *iface, WCHAR **id) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, id); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_GetProcessId( + IAudioSessionControl2 *iface, DWORD *pid) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + TRACE("(%p)->(%p)\n", This, pid); + + if(!pid) + return E_POINTER; + + *pid = GetCurrentProcessId(); + + return S_OK; +} + +static HRESULT WINAPI AudioSessionControl_IsSystemSoundsSession( + IAudioSessionControl2 *iface) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + TRACE("(%p)\n", This); + + return S_FALSE; +} + +static HRESULT WINAPI AudioSessionControl_SetDuckingPreference( + IAudioSessionControl2 *iface, BOOL optout) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + TRACE("(%p)->(%d)\n", This, optout); + + return S_OK; +} + +static const IAudioSessionControl2Vtbl AudioSessionControl2_Vtbl = +{ + AudioSessionControl_QueryInterface, + AudioSessionControl_AddRef, + AudioSessionControl_Release, + AudioSessionControl_GetState, + AudioSessionControl_GetDisplayName, + AudioSessionControl_SetDisplayName, + AudioSessionControl_GetIconPath, + AudioSessionControl_SetIconPath, + AudioSessionControl_GetGroupingParam, + AudioSessionControl_SetGroupingParam, + AudioSessionControl_RegisterAudioSessionNotification, + AudioSessionControl_UnregisterAudioSessionNotification, + AudioSessionControl_GetSessionIdentifier, + AudioSessionControl_GetSessionInstanceIdentifier, + AudioSessionControl_GetProcessId, + AudioSessionControl_IsSystemSoundsSession, + AudioSessionControl_SetDuckingPreference +}; + +static HRESULT WINAPI SimpleAudioVolume_QueryInterface( + ISimpleAudioVolume *iface, REFIID riid, void **ppv) +{ + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + if(IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_ISimpleAudioVolume)) + *ppv = iface; + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI SimpleAudioVolume_AddRef(ISimpleAudioVolume *iface) +{ + AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface); + return AudioSessionControl_AddRef(&This->IAudioSessionControl2_iface); +} + +static ULONG WINAPI SimpleAudioVolume_Release(ISimpleAudioVolume *iface) +{ + AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface); + return AudioSessionControl_Release(&This->IAudioSessionControl2_iface); +} + +static HRESULT WINAPI SimpleAudioVolume_SetMasterVolume( + ISimpleAudioVolume *iface, float level, const GUID *context) +{ + AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface); + AudioSession *session = This->session; + ACImpl *client; + + TRACE("(%p)->(%f, %s)\n", session, level, wine_dbgstr_guid(context)); + + if(level < 0.f || level > 1.f) + return E_INVALIDARG; + + if(context) + FIXME("Notifications not supported yet\n"); + + TRACE("ALSA does not support volume control\n"); + + EnterCriticalSection(&g_sessions_lock); + + session->master_vol = level; + + LIST_FOR_EACH_ENTRY(client, &session->clients, ACImpl, entry) + set_stream_volumes(client); + + LeaveCriticalSection(&g_sessions_lock); + + return S_OK; +} + +static HRESULT WINAPI SimpleAudioVolume_GetMasterVolume( + ISimpleAudioVolume *iface, float *level) +{ + AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface); + AudioSession *session = This->session; + + TRACE("(%p)->(%p)\n", session, level); + + if(!level) + return NULL_PTR_ERR; + + *level = session->master_vol; + + return S_OK; +} + +static HRESULT WINAPI SimpleAudioVolume_SetMute(ISimpleAudioVolume *iface, + BOOL mute, const GUID *context) +{ + AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface); + AudioSession *session = This->session; + ACImpl *client; + + TRACE("(%p)->(%u, %s)\n", session, mute, debugstr_guid(context)); + + if(context) + FIXME("Notifications not supported yet\n"); + + EnterCriticalSection(&g_sessions_lock); + + session->mute = mute; + LIST_FOR_EACH_ENTRY(client, &session->clients, ACImpl, entry) + set_stream_volumes(client); + + LeaveCriticalSection(&g_sessions_lock); + + return S_OK; +} + +static HRESULT WINAPI SimpleAudioVolume_GetMute(ISimpleAudioVolume *iface, + BOOL *mute) +{ + AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface); + AudioSession *session = This->session; + + TRACE("(%p)->(%p)\n", session, mute); + + if(!mute) + return NULL_PTR_ERR; + + *mute = session->mute; + + return S_OK; +} + +static const ISimpleAudioVolumeVtbl SimpleAudioVolume_Vtbl = +{ + SimpleAudioVolume_QueryInterface, + SimpleAudioVolume_AddRef, + SimpleAudioVolume_Release, + SimpleAudioVolume_SetMasterVolume, + SimpleAudioVolume_GetMasterVolume, + SimpleAudioVolume_SetMute, + SimpleAudioVolume_GetMute +}; + +static HRESULT WINAPI AudioStreamVolume_QueryInterface( + IAudioStreamVolume *iface, REFIID riid, void **ppv) +{ + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + if(IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IAudioStreamVolume)) + *ppv = iface; + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioStreamVolume_AddRef(IAudioStreamVolume *iface) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + return IAudioClient3_AddRef(&This->IAudioClient3_iface); +} + +static ULONG WINAPI AudioStreamVolume_Release(IAudioStreamVolume *iface) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + return IAudioClient3_Release(&This->IAudioClient3_iface); +} + +static HRESULT WINAPI AudioStreamVolume_GetChannelCount( + IAudioStreamVolume *iface, UINT32 *out) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + + TRACE("(%p)->(%p)\n", This, out); + + if(!out) + return E_POINTER; + + *out = This->channel_count; + + return S_OK; +} + +static HRESULT WINAPI AudioStreamVolume_SetChannelVolume( + IAudioStreamVolume *iface, UINT32 index, float level) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + + TRACE("(%p)->(%d, %f)\n", This, index, level); + + if(level < 0.f || level > 1.f) + return E_INVALIDARG; + + if(index >= This->channel_count) + return E_INVALIDARG; + + TRACE("ALSA does not support volume control\n"); + + EnterCriticalSection(&g_sessions_lock); + + This->vols[index] = level; + set_stream_volumes(This); + + LeaveCriticalSection(&g_sessions_lock); + + return S_OK; +} + +static HRESULT WINAPI AudioStreamVolume_GetChannelVolume( + IAudioStreamVolume *iface, UINT32 index, float *level) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + + TRACE("(%p)->(%d, %p)\n", This, index, level); + + if(!level) + return E_POINTER; + + if(index >= This->channel_count) + return E_INVALIDARG; + + *level = This->vols[index]; + + return S_OK; +} + +static HRESULT WINAPI AudioStreamVolume_SetAllVolumes( + IAudioStreamVolume *iface, UINT32 count, const float *levels) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + unsigned int i; + + TRACE("(%p)->(%d, %p)\n", This, count, levels); + + if(!levels) + return E_POINTER; + + if(count != This->channel_count) + return E_INVALIDARG; + + TRACE("ALSA does not support volume control\n"); + + EnterCriticalSection(&g_sessions_lock); + + for(i = 0; i < count; ++i) + This->vols[i] = levels[i]; + set_stream_volumes(This); + + LeaveCriticalSection(&g_sessions_lock); + + return S_OK; +} + +static HRESULT WINAPI AudioStreamVolume_GetAllVolumes( + IAudioStreamVolume *iface, UINT32 count, float *levels) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + unsigned int i; + + TRACE("(%p)->(%d, %p)\n", This, count, levels); + + if(!levels) + return E_POINTER; + + if(count != This->channel_count) + return E_INVALIDARG; + + EnterCriticalSection(&g_sessions_lock); + + for(i = 0; i < count; ++i) + levels[i] = This->vols[i]; + + LeaveCriticalSection(&g_sessions_lock); + + return S_OK; +} + +static const IAudioStreamVolumeVtbl AudioStreamVolume_Vtbl = +{ + AudioStreamVolume_QueryInterface, + AudioStreamVolume_AddRef, + AudioStreamVolume_Release, + AudioStreamVolume_GetChannelCount, + AudioStreamVolume_SetChannelVolume, + AudioStreamVolume_GetChannelVolume, + AudioStreamVolume_SetAllVolumes, + AudioStreamVolume_GetAllVolumes +}; + +static HRESULT WINAPI ChannelAudioVolume_QueryInterface( + IChannelAudioVolume *iface, REFIID riid, void **ppv) +{ + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + if(IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IChannelAudioVolume)) + *ppv = iface; + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI ChannelAudioVolume_AddRef(IChannelAudioVolume *iface) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + return AudioSessionControl_AddRef(&This->IAudioSessionControl2_iface); +} + +static ULONG WINAPI ChannelAudioVolume_Release(IChannelAudioVolume *iface) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + return AudioSessionControl_Release(&This->IAudioSessionControl2_iface); +} + +static HRESULT WINAPI ChannelAudioVolume_GetChannelCount( + IChannelAudioVolume *iface, UINT32 *out) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + AudioSession *session = This->session; + + TRACE("(%p)->(%p)\n", session, out); + + if(!out) + return NULL_PTR_ERR; + + *out = session->channel_count; + + return S_OK; +} + +static HRESULT WINAPI ChannelAudioVolume_SetChannelVolume( + IChannelAudioVolume *iface, UINT32 index, float level, + const GUID *context) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + AudioSession *session = This->session; + ACImpl *client; + + TRACE("(%p)->(%d, %f, %s)\n", session, index, level, + wine_dbgstr_guid(context)); + + if(level < 0.f || level > 1.f) + return E_INVALIDARG; + + if(index >= session->channel_count) + return E_INVALIDARG; + + if(context) + FIXME("Notifications not supported yet\n"); + + TRACE("ALSA does not support volume control\n"); + + EnterCriticalSection(&g_sessions_lock); + + session->channel_vols[index] = level; + + LIST_FOR_EACH_ENTRY(client, &session->clients, ACImpl, entry) + set_stream_volumes(client); + + LeaveCriticalSection(&g_sessions_lock); + + return S_OK; +} + +static HRESULT WINAPI ChannelAudioVolume_GetChannelVolume( + IChannelAudioVolume *iface, UINT32 index, float *level) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + AudioSession *session = This->session; + + TRACE("(%p)->(%d, %p)\n", session, index, level); + + if(!level) + return NULL_PTR_ERR; + + if(index >= session->channel_count) + return E_INVALIDARG; + + *level = session->channel_vols[index]; + + return S_OK; +} + +static HRESULT WINAPI ChannelAudioVolume_SetAllVolumes( + IChannelAudioVolume *iface, UINT32 count, const float *levels, + const GUID *context) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + AudioSession *session = This->session; + unsigned int i; + ACImpl *client; + + TRACE("(%p)->(%d, %p, %s)\n", session, count, levels, + wine_dbgstr_guid(context)); + + if(!levels) + return NULL_PTR_ERR; + + if(count != session->channel_count) + return E_INVALIDARG; + + if(context) + FIXME("Notifications not supported yet\n"); + + TRACE("ALSA does not support volume control\n"); + + EnterCriticalSection(&g_sessions_lock); + + for(i = 0; i < count; ++i) + session->channel_vols[i] = levels[i]; + + LIST_FOR_EACH_ENTRY(client, &session->clients, ACImpl, entry) + set_stream_volumes(client); + + LeaveCriticalSection(&g_sessions_lock); + + return S_OK; +} + +static HRESULT WINAPI ChannelAudioVolume_GetAllVolumes( + IChannelAudioVolume *iface, UINT32 count, float *levels) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + AudioSession *session = This->session; + unsigned int i; + + TRACE("(%p)->(%d, %p)\n", session, count, levels); + + if(!levels) + return NULL_PTR_ERR; + + if(count != session->channel_count) + return E_INVALIDARG; + + for(i = 0; i < count; ++i) + levels[i] = session->channel_vols[i]; + + return S_OK; +} + +static const IChannelAudioVolumeVtbl ChannelAudioVolume_Vtbl = +{ + ChannelAudioVolume_QueryInterface, + ChannelAudioVolume_AddRef, + ChannelAudioVolume_Release, + ChannelAudioVolume_GetChannelCount, + ChannelAudioVolume_SetChannelVolume, + ChannelAudioVolume_GetChannelVolume, + ChannelAudioVolume_SetAllVolumes, + ChannelAudioVolume_GetAllVolumes +}; + +static HRESULT WINAPI AudioSessionManager_QueryInterface(IAudioSessionManager2 *iface, + REFIID riid, void **ppv) +{ + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + if(IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IAudioSessionManager) || + IsEqualIID(riid, &IID_IAudioSessionManager2)) + *ppv = iface; + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioSessionManager_AddRef(IAudioSessionManager2 *iface) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + ULONG ref; + ref = InterlockedIncrement(&This->ref); + TRACE("(%p) Refcount now %lu\n", This, ref); + return ref; +} + +static ULONG WINAPI AudioSessionManager_Release(IAudioSessionManager2 *iface) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + ULONG ref; + ref = InterlockedDecrement(&This->ref); + TRACE("(%p) Refcount now %lu\n", This, ref); + if(!ref) + HeapFree(GetProcessHeap(), 0, This); + return ref; +} + +static HRESULT WINAPI AudioSessionManager_GetAudioSessionControl( + IAudioSessionManager2 *iface, const GUID *session_guid, DWORD flags, + IAudioSessionControl **out) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + AudioSession *session; + AudioSessionWrapper *wrapper; + HRESULT hr; + + TRACE("(%p)->(%s, %lx, %p)\n", This, debugstr_guid(session_guid), + flags, out); + + hr = get_audio_session(session_guid, This->device, 0, &session); + if(FAILED(hr)) + return hr; + + wrapper = AudioSessionWrapper_Create(NULL); + if(!wrapper) + return E_OUTOFMEMORY; + + wrapper->session = session; + + *out = (IAudioSessionControl*)&wrapper->IAudioSessionControl2_iface; + + return S_OK; +} + +static HRESULT WINAPI AudioSessionManager_GetSimpleAudioVolume( + IAudioSessionManager2 *iface, const GUID *session_guid, DWORD flags, + ISimpleAudioVolume **out) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + AudioSession *session; + AudioSessionWrapper *wrapper; + HRESULT hr; + + TRACE("(%p)->(%s, %lx, %p)\n", This, debugstr_guid(session_guid), + flags, out); + + hr = get_audio_session(session_guid, This->device, 0, &session); + if(FAILED(hr)) + return hr; + + wrapper = AudioSessionWrapper_Create(NULL); + if(!wrapper) + return E_OUTOFMEMORY; + + wrapper->session = session; + + *out = &wrapper->ISimpleAudioVolume_iface; + + return S_OK; +} + +static HRESULT WINAPI AudioSessionManager_GetSessionEnumerator( + IAudioSessionManager2 *iface, IAudioSessionEnumerator **out) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + FIXME("(%p)->(%p) - stub\n", This, out); + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionManager_RegisterSessionNotification( + IAudioSessionManager2 *iface, IAudioSessionNotification *notification) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + FIXME("(%p)->(%p) - stub\n", This, notification); + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionManager_UnregisterSessionNotification( + IAudioSessionManager2 *iface, IAudioSessionNotification *notification) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + FIXME("(%p)->(%p) - stub\n", This, notification); + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionManager_RegisterDuckNotification( + IAudioSessionManager2 *iface, const WCHAR *session_id, + IAudioVolumeDuckNotification *notification) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + FIXME("(%p)->(%p) - stub\n", This, notification); + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionManager_UnregisterDuckNotification( + IAudioSessionManager2 *iface, + IAudioVolumeDuckNotification *notification) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + FIXME("(%p)->(%p) - stub\n", This, notification); + return E_NOTIMPL; +} + +static const IAudioSessionManager2Vtbl AudioSessionManager2_Vtbl = +{ + AudioSessionManager_QueryInterface, + AudioSessionManager_AddRef, + AudioSessionManager_Release, + AudioSessionManager_GetAudioSessionControl, + AudioSessionManager_GetSimpleAudioVolume, + AudioSessionManager_GetSessionEnumerator, + AudioSessionManager_RegisterSessionNotification, + AudioSessionManager_UnregisterSessionNotification, + AudioSessionManager_RegisterDuckNotification, + AudioSessionManager_UnregisterDuckNotification +}; + +HRESULT WINAPI AUDDRV_GetAudioSessionManager(IMMDevice *device, + IAudioSessionManager2 **out) +{ + SessionMgr *This; + + This = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(SessionMgr)); + if(!This) + return E_OUTOFMEMORY; + + This->IAudioSessionManager2_iface.lpVtbl = &AudioSessionManager2_Vtbl; + This->device = device; + This->ref = 1; + + *out = &This->IAudioSessionManager2_iface; + + return S_OK; +} + +HRESULT WINAPI AUDDRV_GetPropValue(GUID *guid, const PROPERTYKEY *prop, PROPVARIANT *out) +{ + struct get_prop_value_params params; + char name[256]; + EDataFlow flow; + unsigned int size = 0; + + TRACE("%s, (%s,%lu), %p\n", wine_dbgstr_guid(guid), wine_dbgstr_guid(&prop->fmtid), prop->pid, out); + + if(!get_alsa_name_by_guid(guid, name, sizeof(name), &flow)) + { + WARN("Unknown interface %s\n", debugstr_guid(guid)); + return E_NOINTERFACE; + } + + params.device = name; + params.flow = flow; + params.guid = guid; + params.prop = prop; + params.value = out; + params.buffer = NULL; + params.buffer_size = &size; + + while(1) { + ALSA_CALL(get_prop_value, ¶ms); + + if(params.result != E_NOT_SUFFICIENT_BUFFER) + break; + + CoTaskMemFree(params.buffer); + params.buffer = CoTaskMemAlloc(*params.buffer_size); + if(!params.buffer) + return E_OUTOFMEMORY; + } + if(FAILED(params.result)) + CoTaskMemFree(params.buffer); + + return params.result; +} diff --git a/pkgs/osu-wine/audio-revert/winealsa.drv/mmdevdrv.c.orig b/pkgs/osu-wine/audio-revert/winealsa.drv/mmdevdrv.c.orig new file mode 100644 index 0000000..ea48143 --- /dev/null +++ b/pkgs/osu-wine/audio-revert/winealsa.drv/mmdevdrv.c.orig @@ -0,0 +1,2475 @@ +/* + * Copyright 2010 Maarten Lankhorst for CodeWeavers + * Copyright 2011 Andrew Eikum for CodeWeavers + * Copyright 2022 Huw Davies + * + * 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 + */ + +#define COBJMACROS + +#include + +#include "windef.h" +#include "winbase.h" +#include "winnls.h" +#include "winreg.h" +#include "winternl.h" +#include "propsys.h" +#include "propkey.h" +#include "initguid.h" +#include "ole2.h" +#include "mmdeviceapi.h" +#include "devpkey.h" +#include "mmsystem.h" +#include "dsound.h" + +#include "endpointvolume.h" +#include "audioclient.h" +#include "audiopolicy.h" + +#include "wine/debug.h" +#include "wine/list.h" +#include "wine/unixlib.h" + +#include "unixlib.h" + +WINE_DEFAULT_DEBUG_CHANNEL(alsa); + +#define NULL_PTR_ERR MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, RPC_X_NULL_REF_POINTER) + +static const REFERENCE_TIME DefaultPeriod = 100000; +static const REFERENCE_TIME MinimumPeriod = 50000; + +struct ACImpl; +typedef struct ACImpl ACImpl; + +typedef struct _AudioSession { + GUID guid; + struct list clients; + + IMMDevice *device; + + float master_vol; + UINT32 channel_count; + float *channel_vols; + BOOL mute; + + struct list entry; +} AudioSession; + +typedef struct _AudioSessionWrapper { + IAudioSessionControl2 IAudioSessionControl2_iface; + IChannelAudioVolume IChannelAudioVolume_iface; + ISimpleAudioVolume ISimpleAudioVolume_iface; + + LONG ref; + + ACImpl *client; + AudioSession *session; +} AudioSessionWrapper; + +struct ACImpl { + IAudioClient3 IAudioClient3_iface; + IAudioRenderClient IAudioRenderClient_iface; + IAudioCaptureClient IAudioCaptureClient_iface; + IAudioClock IAudioClock_iface; + IAudioClock2 IAudioClock2_iface; + IAudioStreamVolume IAudioStreamVolume_iface; + + LONG ref; + + IMMDevice *parent; + IUnknown *pUnkFTMarshal; + + EDataFlow dataflow; + float *vols; + UINT32 channel_count; + stream_handle stream; + + HANDLE timer_thread; + + AudioSession *session; + AudioSessionWrapper *session_wrapper; + + struct list entry; + + /* Keep at end */ + char alsa_name[1]; +}; + +typedef struct _SessionMgr { + IAudioSessionManager2 IAudioSessionManager2_iface; + + LONG ref; + + IMMDevice *device; +} SessionMgr; + +static CRITICAL_SECTION g_sessions_lock; +static CRITICAL_SECTION_DEBUG g_sessions_lock_debug = +{ + 0, 0, &g_sessions_lock, + { &g_sessions_lock_debug.ProcessLocksList, &g_sessions_lock_debug.ProcessLocksList }, + 0, 0, { (DWORD_PTR)(__FILE__ ": g_sessions_lock") } +}; +static CRITICAL_SECTION g_sessions_lock = { &g_sessions_lock_debug, -1, 0, 0, 0, 0 }; +static struct list g_sessions = LIST_INIT(g_sessions); + +static const WCHAR drv_key_devicesW[] = {'S','o','f','t','w','a','r','e','\\', + 'W','i','n','e','\\','D','r','i','v','e','r','s','\\', + 'w','i','n','e','a','l','s','a','.','d','r','v','\\','d','e','v','i','c','e','s',0}; +static const WCHAR guidW[] = {'g','u','i','d',0}; + +static const IAudioClient3Vtbl AudioClient3_Vtbl; +static const IAudioRenderClientVtbl AudioRenderClient_Vtbl; +static const IAudioCaptureClientVtbl AudioCaptureClient_Vtbl; +static const IAudioSessionControl2Vtbl AudioSessionControl2_Vtbl; +static const ISimpleAudioVolumeVtbl SimpleAudioVolume_Vtbl; +static const IAudioClockVtbl AudioClock_Vtbl; +static const IAudioClock2Vtbl AudioClock2_Vtbl; +static const IAudioStreamVolumeVtbl AudioStreamVolume_Vtbl; +static const IChannelAudioVolumeVtbl ChannelAudioVolume_Vtbl; +static const IAudioSessionManager2Vtbl AudioSessionManager2_Vtbl; + +static AudioSessionWrapper *AudioSessionWrapper_Create(ACImpl *client); + +static inline ACImpl *impl_from_IAudioClient3(IAudioClient3 *iface) +{ + return CONTAINING_RECORD(iface, ACImpl, IAudioClient3_iface); +} + +static inline ACImpl *impl_from_IAudioRenderClient(IAudioRenderClient *iface) +{ + return CONTAINING_RECORD(iface, ACImpl, IAudioRenderClient_iface); +} + +static inline ACImpl *impl_from_IAudioCaptureClient(IAudioCaptureClient *iface) +{ + return CONTAINING_RECORD(iface, ACImpl, IAudioCaptureClient_iface); +} + +static inline AudioSessionWrapper *impl_from_IAudioSessionControl2(IAudioSessionControl2 *iface) +{ + return CONTAINING_RECORD(iface, AudioSessionWrapper, IAudioSessionControl2_iface); +} + +static inline AudioSessionWrapper *impl_from_ISimpleAudioVolume(ISimpleAudioVolume *iface) +{ + return CONTAINING_RECORD(iface, AudioSessionWrapper, ISimpleAudioVolume_iface); +} + +static inline AudioSessionWrapper *impl_from_IChannelAudioVolume(IChannelAudioVolume *iface) +{ + return CONTAINING_RECORD(iface, AudioSessionWrapper, IChannelAudioVolume_iface); +} + +static inline ACImpl *impl_from_IAudioClock(IAudioClock *iface) +{ + return CONTAINING_RECORD(iface, ACImpl, IAudioClock_iface); +} + +static inline ACImpl *impl_from_IAudioClock2(IAudioClock2 *iface) +{ + return CONTAINING_RECORD(iface, ACImpl, IAudioClock2_iface); +} + +static inline ACImpl *impl_from_IAudioStreamVolume(IAudioStreamVolume *iface) +{ + return CONTAINING_RECORD(iface, ACImpl, IAudioStreamVolume_iface); +} + +static inline SessionMgr *impl_from_IAudioSessionManager2(IAudioSessionManager2 *iface) +{ + return CONTAINING_RECORD(iface, SessionMgr, IAudioSessionManager2_iface); +} + +BOOL WINAPI DllMain(HINSTANCE dll, DWORD reason, void *reserved) +{ + switch (reason) + { + case DLL_PROCESS_ATTACH: + if(__wine_init_unix_call()) return FALSE; + break; + + case DLL_PROCESS_DETACH: + if (reserved) break; + DeleteCriticalSection(&g_sessions_lock); + break; + } + return TRUE; +} + +int WINAPI AUDDRV_GetPriority(void) +{ + return Priority_Neutral; +} + +static HRESULT alsa_stream_release(stream_handle stream, HANDLE timer_thread) +{ + struct release_stream_params params; + + params.stream = stream; + params.timer_thread = timer_thread; + + ALSA_CALL(release_stream, ¶ms); + + return params.result; +} + +static DWORD WINAPI alsa_timer_thread(void *user) +{ + struct timer_loop_params params; + struct ACImpl *This = user; + + SetThreadDescription(GetCurrentThread(), L"winealsa_timer"); + + params.stream = This->stream; + + ALSA_CALL(timer_loop, ¶ms); + + return 0; +} + +static void set_device_guid(EDataFlow flow, HKEY drv_key, const WCHAR *key_name, + GUID *guid) +{ + HKEY key; + BOOL opened = FALSE; + LONG lr; + + if(!drv_key){ + lr = RegCreateKeyExW(HKEY_CURRENT_USER, drv_key_devicesW, 0, NULL, 0, KEY_WRITE, + NULL, &drv_key, NULL); + if(lr != ERROR_SUCCESS){ + ERR("RegCreateKeyEx(drv_key) failed: %lu\n", lr); + return; + } + opened = TRUE; + } + + lr = RegCreateKeyExW(drv_key, key_name, 0, NULL, 0, KEY_WRITE, + NULL, &key, NULL); + if(lr != ERROR_SUCCESS){ + ERR("RegCreateKeyEx(%s) failed: %lu\n", wine_dbgstr_w(key_name), lr); + goto exit; + } + + lr = RegSetValueExW(key, guidW, 0, REG_BINARY, (BYTE*)guid, + sizeof(GUID)); + if(lr != ERROR_SUCCESS) + ERR("RegSetValueEx(%s\\guid) failed: %lu\n", wine_dbgstr_w(key_name), lr); + + RegCloseKey(key); +exit: + if(opened) + RegCloseKey(drv_key); +} + +static void get_device_guid(EDataFlow flow, const char *device, GUID *guid) +{ + HKEY key = NULL, dev_key; + DWORD type, size = sizeof(*guid); + WCHAR key_name[256]; + + if(flow == eCapture) + key_name[0] = '1'; + else + key_name[0] = '0'; + key_name[1] = ','; + MultiByteToWideChar(CP_UNIXCP, 0, device, -1, key_name + 2, ARRAY_SIZE(key_name) - 2); + + if(RegOpenKeyExW(HKEY_CURRENT_USER, drv_key_devicesW, 0, KEY_WRITE|KEY_READ, &key) == ERROR_SUCCESS){ + if(RegOpenKeyExW(key, key_name, 0, KEY_READ, &dev_key) == ERROR_SUCCESS){ + if(RegQueryValueExW(dev_key, guidW, 0, &type, + (BYTE*)guid, &size) == ERROR_SUCCESS){ + if(type == REG_BINARY){ + RegCloseKey(dev_key); + RegCloseKey(key); + return; + } + ERR("Invalid type for device %s GUID: %lu; ignoring and overwriting\n", + wine_dbgstr_w(key_name), type); + } + RegCloseKey(dev_key); + } + } + + CoCreateGuid(guid); + + set_device_guid(flow, key, key_name, guid); + + if(key) + RegCloseKey(key); +} + +static void set_stream_volumes(ACImpl *This) +{ + struct set_volumes_params params; + + params.stream = This->stream; + params.master_volume = (This->session->mute ? 0.0f : This->session->master_vol); + params.volumes = This->vols; + params.session_volumes = This->session->channel_vols; + params.channel = 0; + + ALSA_CALL(set_volumes, ¶ms); +} + +HRESULT WINAPI AUDDRV_GetEndpointIDs(EDataFlow flow, WCHAR ***ids_out, GUID **guids_out, + UINT *num, UINT *def_index) +{ + struct get_endpoint_ids_params params; + unsigned int i; + GUID *guids = NULL; + WCHAR **ids = NULL; + + TRACE("%d %p %p %p %p\n", flow, ids, guids, num, def_index); + + params.flow = flow; + params.size = 1000; + params.endpoints = NULL; + do{ + HeapFree(GetProcessHeap(), 0, params.endpoints); + params.endpoints = HeapAlloc(GetProcessHeap(), 0, params.size); + ALSA_CALL(get_endpoint_ids, ¶ms); + }while(params.result == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)); + + if(FAILED(params.result)) goto end; + + ids = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, params.num * sizeof(*ids)); + guids = HeapAlloc(GetProcessHeap(), 0, params.num * sizeof(*guids)); + if(!ids || !guids){ + params.result = E_OUTOFMEMORY; + goto end; + } + + for(i = 0; i < params.num; i++){ + WCHAR *name = (WCHAR *)((char *)params.endpoints + params.endpoints[i].name); + char *device = (char *)params.endpoints + params.endpoints[i].device; + unsigned int size = (wcslen(name) + 1) * sizeof(WCHAR); + + ids[i] = HeapAlloc(GetProcessHeap(), 0, size); + if(!ids[i]){ + params.result = E_OUTOFMEMORY; + goto end; + } + memcpy(ids[i], name, size); + get_device_guid(flow, device, guids + i); + } + *def_index = params.default_idx; + +end: + HeapFree(GetProcessHeap(), 0, params.endpoints); + if(FAILED(params.result)){ + HeapFree(GetProcessHeap(), 0, guids); + if(ids){ + for(i = 0; i < params.num; i++) + HeapFree(GetProcessHeap(), 0, ids[i]); + HeapFree(GetProcessHeap(), 0, ids); + } + }else{ + *ids_out = ids; + *guids_out = guids; + *num = params.num; + } + + return params.result; +} + +static BOOL get_alsa_name_by_guid(GUID *guid, char *name, DWORD name_size, EDataFlow *flow) +{ + HKEY devices_key; + UINT i = 0; + WCHAR key_name[256]; + DWORD key_name_size; + + if(RegOpenKeyExW(HKEY_CURRENT_USER, drv_key_devicesW, 0, KEY_READ, &devices_key) != ERROR_SUCCESS){ + ERR("No devices found in registry?\n"); + return FALSE; + } + + while(1){ + HKEY key; + DWORD size, type; + GUID reg_guid; + + key_name_size = ARRAY_SIZE(key_name); + if(RegEnumKeyExW(devices_key, i++, key_name, &key_name_size, NULL, + NULL, NULL, NULL) != ERROR_SUCCESS) + break; + + if(RegOpenKeyExW(devices_key, key_name, 0, KEY_READ, &key) != ERROR_SUCCESS){ + WARN("Couldn't open key: %s\n", wine_dbgstr_w(key_name)); + continue; + } + + size = sizeof(reg_guid); + if(RegQueryValueExW(key, guidW, 0, &type, + (BYTE*)®_guid, &size) == ERROR_SUCCESS){ + if(IsEqualGUID(®_guid, guid)){ + RegCloseKey(key); + RegCloseKey(devices_key); + + TRACE("Found matching device key: %s\n", wine_dbgstr_w(key_name)); + + if(key_name[0] == '0') + *flow = eRender; + else if(key_name[0] == '1') + *flow = eCapture; + else{ + ERR("Unknown device type: %c\n", key_name[0]); + return FALSE; + } + + WideCharToMultiByte(CP_UNIXCP, 0, key_name + 2, -1, name, name_size, NULL, NULL); + + return TRUE; + } + } + + RegCloseKey(key); + } + + RegCloseKey(devices_key); + + WARN("No matching device in registry for GUID %s\n", debugstr_guid(guid)); + + return FALSE; +} + +HRESULT WINAPI AUDDRV_GetAudioEndpoint(GUID *guid, IMMDevice *dev, IAudioClient **out) +{ + ACImpl *This; + char alsa_name[256]; + EDataFlow dataflow; + HRESULT hr; + int len; + + TRACE("%s %p %p\n", debugstr_guid(guid), dev, out); + + if(!get_alsa_name_by_guid(guid, alsa_name, sizeof(alsa_name), &dataflow)) + return AUDCLNT_E_DEVICE_INVALIDATED; + + if(dataflow != eRender && dataflow != eCapture) + return E_UNEXPECTED; + + len = strlen(alsa_name); + This = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, offsetof(ACImpl, alsa_name[len + 1])); + if(!This) + return E_OUTOFMEMORY; + + This->IAudioClient3_iface.lpVtbl = &AudioClient3_Vtbl; + This->IAudioRenderClient_iface.lpVtbl = &AudioRenderClient_Vtbl; + This->IAudioCaptureClient_iface.lpVtbl = &AudioCaptureClient_Vtbl; + This->IAudioClock_iface.lpVtbl = &AudioClock_Vtbl; + This->IAudioClock2_iface.lpVtbl = &AudioClock2_Vtbl; + This->IAudioStreamVolume_iface.lpVtbl = &AudioStreamVolume_Vtbl; + + hr = CoCreateFreeThreadedMarshaler((IUnknown *)&This->IAudioClient3_iface, &This->pUnkFTMarshal); + if (FAILED(hr)) { + HeapFree(GetProcessHeap(), 0, This); + return hr; + } + + This->dataflow = dataflow; + memcpy(This->alsa_name, alsa_name, len + 1); + + This->parent = dev; + IMMDevice_AddRef(This->parent); + + *out = (IAudioClient *)&This->IAudioClient3_iface; + IAudioClient3_AddRef(&This->IAudioClient3_iface); + + return S_OK; +} + +static HRESULT WINAPI AudioClient_QueryInterface(IAudioClient3 *iface, + REFIID riid, void **ppv) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + if(IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IAudioClient) || + IsEqualIID(riid, &IID_IAudioClient2) || + IsEqualIID(riid, &IID_IAudioClient3)) + *ppv = iface; + else if(IsEqualIID(riid, &IID_IMarshal)) + return IUnknown_QueryInterface(This->pUnkFTMarshal, riid, ppv); + + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioClient_AddRef(IAudioClient3 *iface) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + ULONG ref; + ref = InterlockedIncrement(&This->ref); + TRACE("(%p) Refcount now %lu\n", This, ref); + return ref; +} + +static ULONG WINAPI AudioClient_Release(IAudioClient3 *iface) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + ULONG ref; + + ref = InterlockedDecrement(&This->ref); + TRACE("(%p) Refcount now %lu\n", This, ref); + if(!ref){ + IAudioClient3_Stop(iface); + IMMDevice_Release(This->parent); + IUnknown_Release(This->pUnkFTMarshal); + if(This->session){ + EnterCriticalSection(&g_sessions_lock); + list_remove(&This->entry); + LeaveCriticalSection(&g_sessions_lock); + } + HeapFree(GetProcessHeap(), 0, This->vols); + if (This->stream) + alsa_stream_release(This->stream, This->timer_thread); + HeapFree(GetProcessHeap(), 0, This); + } + return ref; +} + +static void dump_fmt(const WAVEFORMATEX *fmt) +{ + TRACE("wFormatTag: 0x%x (", fmt->wFormatTag); + switch(fmt->wFormatTag){ + case WAVE_FORMAT_PCM: + TRACE("WAVE_FORMAT_PCM"); + break; + case WAVE_FORMAT_IEEE_FLOAT: + TRACE("WAVE_FORMAT_IEEE_FLOAT"); + break; + case WAVE_FORMAT_EXTENSIBLE: + TRACE("WAVE_FORMAT_EXTENSIBLE"); + break; + default: + TRACE("Unknown"); + break; + } + TRACE(")\n"); + + TRACE("nChannels: %u\n", fmt->nChannels); + TRACE("nSamplesPerSec: %lu\n", fmt->nSamplesPerSec); + TRACE("nAvgBytesPerSec: %lu\n", fmt->nAvgBytesPerSec); + TRACE("nBlockAlign: %u\n", fmt->nBlockAlign); + TRACE("wBitsPerSample: %u\n", fmt->wBitsPerSample); + TRACE("cbSize: %u\n", fmt->cbSize); + + if(fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE){ + WAVEFORMATEXTENSIBLE *fmtex = (void*)fmt; + TRACE("dwChannelMask: %08lx\n", fmtex->dwChannelMask); + TRACE("Samples: %04x\n", fmtex->Samples.wReserved); + TRACE("SubFormat: %s\n", wine_dbgstr_guid(&fmtex->SubFormat)); + } +} + +static void session_init_vols(AudioSession *session, UINT channels) +{ + if(session->channel_count < channels){ + UINT i; + + if(session->channel_vols) + session->channel_vols = HeapReAlloc(GetProcessHeap(), 0, + session->channel_vols, sizeof(float) * channels); + else + session->channel_vols = HeapAlloc(GetProcessHeap(), 0, + sizeof(float) * channels); + if(!session->channel_vols) + return; + + for(i = session->channel_count; i < channels; ++i) + session->channel_vols[i] = 1.f; + + session->channel_count = channels; + } +} + +static AudioSession *create_session(const GUID *guid, IMMDevice *device, + UINT num_channels) +{ + AudioSession *ret; + + ret = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(AudioSession)); + if(!ret) + return NULL; + + memcpy(&ret->guid, guid, sizeof(GUID)); + + ret->device = device; + + list_init(&ret->clients); + + list_add_head(&g_sessions, &ret->entry); + + session_init_vols(ret, num_channels); + + ret->master_vol = 1.f; + + return ret; +} + +/* if channels == 0, then this will return or create a session with + * matching dataflow and GUID. otherwise, channels must also match */ +static HRESULT get_audio_session(const GUID *sessionguid, + IMMDevice *device, UINT channels, AudioSession **out) +{ + AudioSession *session; + + if(!sessionguid || IsEqualGUID(sessionguid, &GUID_NULL)){ + *out = create_session(&GUID_NULL, device, channels); + if(!*out) + return E_OUTOFMEMORY; + + return S_OK; + } + + *out = NULL; + LIST_FOR_EACH_ENTRY(session, &g_sessions, AudioSession, entry){ + if(session->device == device && + IsEqualGUID(sessionguid, &session->guid)){ + session_init_vols(session, channels); + *out = session; + break; + } + } + + if(!*out){ + *out = create_session(sessionguid, device, channels); + if(!*out) + return E_OUTOFMEMORY; + } + + return S_OK; +} + +static HRESULT WINAPI AudioClient_Initialize(IAudioClient3 *iface, + AUDCLNT_SHAREMODE mode, DWORD flags, REFERENCE_TIME duration, + REFERENCE_TIME period, const WAVEFORMATEX *fmt, + const GUID *sessionguid) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct create_stream_params params; + stream_handle stream; + unsigned int i; + + TRACE("(%p)->(%x, %lx, %s, %s, %p, %s)\n", This, mode, flags, + wine_dbgstr_longlong(duration), wine_dbgstr_longlong(period), fmt, debugstr_guid(sessionguid)); + + if(!fmt) + return E_POINTER; + + if(mode != AUDCLNT_SHAREMODE_SHARED && mode != AUDCLNT_SHAREMODE_EXCLUSIVE) + return E_INVALIDARG; + + if(flags & ~(AUDCLNT_STREAMFLAGS_CROSSPROCESS | + AUDCLNT_STREAMFLAGS_LOOPBACK | + AUDCLNT_STREAMFLAGS_EVENTCALLBACK | + AUDCLNT_STREAMFLAGS_NOPERSIST | + AUDCLNT_STREAMFLAGS_RATEADJUST | + AUDCLNT_SESSIONFLAGS_EXPIREWHENUNOWNED | + AUDCLNT_SESSIONFLAGS_DISPLAY_HIDE | + AUDCLNT_SESSIONFLAGS_DISPLAY_HIDEWHENEXPIRED | + AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY | + AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM)){ + FIXME("Unknown flags: %08lx\n", flags); + return E_INVALIDARG; + } + + if(mode == AUDCLNT_SHAREMODE_SHARED){ + period = DefaultPeriod; + if( duration < 3 * period) + duration = 3 * period; + }else{ + if(fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE){ + if(((WAVEFORMATEXTENSIBLE*)fmt)->dwChannelMask == 0 || + ((WAVEFORMATEXTENSIBLE*)fmt)->dwChannelMask & SPEAKER_RESERVED) + return AUDCLNT_E_UNSUPPORTED_FORMAT; + } + + if(!period) + period = DefaultPeriod; /* not minimum */ + if(period < MinimumPeriod || period > 5000000) + return AUDCLNT_E_INVALID_DEVICE_PERIOD; + if(duration > 20000000) /* the smaller the period, the lower this limit */ + return AUDCLNT_E_BUFFER_SIZE_ERROR; + if(flags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK){ + if(duration != period) + return AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL; + FIXME("EXCLUSIVE mode with EVENTCALLBACK\n"); + return AUDCLNT_E_DEVICE_IN_USE; + }else{ + if( duration < 8 * period) + duration = 8 * period; /* may grow above 2s */ + } + } + + EnterCriticalSection(&g_sessions_lock); + + if(This->stream){ + LeaveCriticalSection(&g_sessions_lock); + return AUDCLNT_E_ALREADY_INITIALIZED; + } + + dump_fmt(fmt); + + params.name = NULL; + params.device = This->alsa_name; + params.flow = This->dataflow; + params.share = mode; + params.flags = flags; + params.duration = duration; + params.period = period; + params.fmt = fmt; + params.channel_count = NULL; + params.stream = &stream; + + ALSA_CALL(create_stream, ¶ms); + if(FAILED(params.result)){ + LeaveCriticalSection(&g_sessions_lock); + return params.result; + } + + This->channel_count = fmt->nChannels; + This->vols = HeapAlloc(GetProcessHeap(), 0, This->channel_count * sizeof(float)); + if(!This->vols){ + params.result = E_OUTOFMEMORY; + goto exit; + } + for(i = 0; i < This->channel_count; ++i) + This->vols[i] = 1.f; + + params.result = get_audio_session(sessionguid, This->parent, This->channel_count, + &This->session); + if(FAILED(params.result)) + goto exit; + + list_add_tail(&This->session->clients, &This->entry); + +exit: + if(FAILED(params.result)){ + alsa_stream_release(stream, NULL); + HeapFree(GetProcessHeap(), 0, This->vols); + This->vols = NULL; + }else{ + This->stream = stream; + set_stream_volumes(This); + } + + LeaveCriticalSection(&g_sessions_lock); + + return params.result; +} + +static HRESULT WINAPI AudioClient_GetBufferSize(IAudioClient3 *iface, + UINT32 *out) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct get_buffer_size_params params; + + TRACE("(%p)->(%p)\n", This, out); + + if(!out) + return E_POINTER; + + if(!This->stream) + return AUDCLNT_E_NOT_INITIALIZED; + + params.stream = This->stream; + params.frames = out; + + ALSA_CALL(get_buffer_size, ¶ms); + + return params.result; +} + +static HRESULT WINAPI AudioClient_GetStreamLatency(IAudioClient3 *iface, + REFERENCE_TIME *latency) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct get_latency_params params; + + TRACE("(%p)->(%p)\n", This, latency); + + if(!latency) + return E_POINTER; + + if(!This->stream) + return AUDCLNT_E_NOT_INITIALIZED; + + params.stream = This->stream; + params.latency = latency; + + ALSA_CALL(get_latency, ¶ms); + + return params.result; +} + +static HRESULT WINAPI AudioClient_GetCurrentPadding(IAudioClient3 *iface, + UINT32 *out) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct get_current_padding_params params; + + TRACE("(%p)->(%p)\n", This, out); + + if(!out) + return E_POINTER; + + if(!This->stream) + return AUDCLNT_E_NOT_INITIALIZED; + + params.stream = This->stream; + params.padding = out; + + ALSA_CALL(get_current_padding, ¶ms); + + TRACE("pad: %u\n", *out); + + return params.result; +} + +static HRESULT WINAPI AudioClient_IsFormatSupported(IAudioClient3 *iface, + AUDCLNT_SHAREMODE mode, const WAVEFORMATEX *fmt, + WAVEFORMATEX **out) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct is_format_supported_params params; + + TRACE("(%p)->(%x, %p, %p)\n", This, mode, fmt, out); + if(fmt) dump_fmt(fmt); + + params.device = This->alsa_name; + params.flow = This->dataflow; + params.share = mode; + params.fmt_in = fmt; + params.fmt_out = NULL; + + if(out){ + *out = NULL; + if(mode == AUDCLNT_SHAREMODE_SHARED) + params.fmt_out = CoTaskMemAlloc(sizeof(*params.fmt_out)); + } + ALSA_CALL(is_format_supported, ¶ms); + + if(params.result == S_FALSE) + *out = ¶ms.fmt_out->Format; + else + CoTaskMemFree(params.fmt_out); + + return params.result; +} + +static HRESULT WINAPI AudioClient_GetMixFormat(IAudioClient3 *iface, + WAVEFORMATEX **pwfx) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct get_mix_format_params params; + + TRACE("(%p)->(%p)\n", This, pwfx); + + if(!pwfx) + return E_POINTER; + *pwfx = NULL; + + params.device = This->alsa_name; + params.flow = This->dataflow; + params.fmt = CoTaskMemAlloc(sizeof(WAVEFORMATEXTENSIBLE)); + if(!params.fmt) + return E_OUTOFMEMORY; + + ALSA_CALL(get_mix_format, ¶ms); + + if(SUCCEEDED(params.result)){ + *pwfx = ¶ms.fmt->Format; + dump_fmt(*pwfx); + } else + CoTaskMemFree(params.fmt); + + return params.result; +} + +static HRESULT WINAPI AudioClient_GetDevicePeriod(IAudioClient3 *iface, + REFERENCE_TIME *defperiod, REFERENCE_TIME *minperiod) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + + TRACE("(%p)->(%p, %p)\n", This, defperiod, minperiod); + + if(!defperiod && !minperiod) + return E_POINTER; + + if(defperiod) + *defperiod = DefaultPeriod; + if(minperiod) + *minperiod = DefaultPeriod; + + return S_OK; +} + +static HRESULT WINAPI AudioClient_Start(IAudioClient3 *iface) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct start_params params; + + TRACE("(%p)\n", This); + + EnterCriticalSection(&g_sessions_lock); + + if(!This->stream){ + LeaveCriticalSection(&g_sessions_lock); + return AUDCLNT_E_NOT_INITIALIZED; + } + + params.stream = This->stream; + + ALSA_CALL(start, ¶ms); + + if(SUCCEEDED(params.result) && !This->timer_thread){ + This->timer_thread = CreateThread(NULL, 0, alsa_timer_thread, This, 0, NULL); + SetThreadPriority(This->timer_thread, THREAD_PRIORITY_TIME_CRITICAL); + } + + LeaveCriticalSection(&g_sessions_lock); + + return params.result; +} + +static HRESULT WINAPI AudioClient_Stop(IAudioClient3 *iface) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct stop_params params; + + TRACE("(%p)\n", This); + + if(!This->stream) + return AUDCLNT_E_NOT_INITIALIZED; + + params.stream = This->stream; + + ALSA_CALL(stop, ¶ms); + + return params.result; +} + +static HRESULT WINAPI AudioClient_Reset(IAudioClient3 *iface) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct reset_params params; + + TRACE("(%p)\n", This); + + if(!This->stream) + return AUDCLNT_E_NOT_INITIALIZED; + + params.stream = This->stream; + + ALSA_CALL(reset, ¶ms); + + return params.result; +} + +static HRESULT WINAPI AudioClient_SetEventHandle(IAudioClient3 *iface, + HANDLE event) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct set_event_handle_params params; + + TRACE("(%p)->(%p)\n", This, event); + + if(!event) + return E_INVALIDARG; + + if(!This->stream) + return AUDCLNT_E_NOT_INITIALIZED; + + params.stream = This->stream; + params.event = event; + + ALSA_CALL(set_event_handle, ¶ms); + + return params.result; +} + +static HRESULT WINAPI AudioClient_GetService(IAudioClient3 *iface, REFIID riid, + void **ppv) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + + TRACE("(%p)->(%s, %p)\n", This, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + EnterCriticalSection(&g_sessions_lock); + + if(!This->stream){ + LeaveCriticalSection(&g_sessions_lock); + return AUDCLNT_E_NOT_INITIALIZED; + } + + if(IsEqualIID(riid, &IID_IAudioRenderClient)){ + if(This->dataflow != eRender){ + LeaveCriticalSection(&g_sessions_lock); + return AUDCLNT_E_WRONG_ENDPOINT_TYPE; + } + IAudioRenderClient_AddRef(&This->IAudioRenderClient_iface); + *ppv = &This->IAudioRenderClient_iface; + }else if(IsEqualIID(riid, &IID_IAudioCaptureClient)){ + if(This->dataflow != eCapture){ + LeaveCriticalSection(&g_sessions_lock); + return AUDCLNT_E_WRONG_ENDPOINT_TYPE; + } + IAudioCaptureClient_AddRef(&This->IAudioCaptureClient_iface); + *ppv = &This->IAudioCaptureClient_iface; + }else if(IsEqualIID(riid, &IID_IAudioClock)){ + IAudioClock_AddRef(&This->IAudioClock_iface); + *ppv = &This->IAudioClock_iface; + }else if(IsEqualIID(riid, &IID_IAudioStreamVolume)){ + IAudioStreamVolume_AddRef(&This->IAudioStreamVolume_iface); + *ppv = &This->IAudioStreamVolume_iface; + }else if(IsEqualIID(riid, &IID_IAudioSessionControl)){ + if(!This->session_wrapper){ + This->session_wrapper = AudioSessionWrapper_Create(This); + if(!This->session_wrapper){ + LeaveCriticalSection(&g_sessions_lock); + return E_OUTOFMEMORY; + } + }else + IAudioSessionControl2_AddRef(&This->session_wrapper->IAudioSessionControl2_iface); + + *ppv = &This->session_wrapper->IAudioSessionControl2_iface; + }else if(IsEqualIID(riid, &IID_IChannelAudioVolume)){ + if(!This->session_wrapper){ + This->session_wrapper = AudioSessionWrapper_Create(This); + if(!This->session_wrapper){ + LeaveCriticalSection(&g_sessions_lock); + return E_OUTOFMEMORY; + } + }else + IChannelAudioVolume_AddRef(&This->session_wrapper->IChannelAudioVolume_iface); + + *ppv = &This->session_wrapper->IChannelAudioVolume_iface; + }else if(IsEqualIID(riid, &IID_ISimpleAudioVolume)){ + if(!This->session_wrapper){ + This->session_wrapper = AudioSessionWrapper_Create(This); + if(!This->session_wrapper){ + LeaveCriticalSection(&g_sessions_lock); + return E_OUTOFMEMORY; + } + }else + ISimpleAudioVolume_AddRef(&This->session_wrapper->ISimpleAudioVolume_iface); + + *ppv = &This->session_wrapper->ISimpleAudioVolume_iface; + } + + if(*ppv){ + LeaveCriticalSection(&g_sessions_lock); + return S_OK; + } + + LeaveCriticalSection(&g_sessions_lock); + + FIXME("stub %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static HRESULT WINAPI AudioClient_IsOffloadCapable(IAudioClient3 *iface, + AUDIO_STREAM_CATEGORY category, BOOL *offload_capable) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + + TRACE("(%p)->(0x%x, %p)\n", This, category, offload_capable); + + if(!offload_capable) + return E_INVALIDARG; + + *offload_capable = FALSE; + + return S_OK; +} + +static HRESULT WINAPI AudioClient_SetClientProperties(IAudioClient3 *iface, + const AudioClientProperties *prop) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + const Win8AudioClientProperties *legacy_prop = (const Win8AudioClientProperties *)prop; + + TRACE("(%p)->(%p)\n", This, prop); + + if(!legacy_prop) + return E_POINTER; + + if(legacy_prop->cbSize == sizeof(AudioClientProperties)){ + TRACE("{ bIsOffload: %u, eCategory: 0x%x, Options: 0x%x }\n", + legacy_prop->bIsOffload, + legacy_prop->eCategory, + prop->Options); + }else if(legacy_prop->cbSize == sizeof(Win8AudioClientProperties)){ + TRACE("{ bIsOffload: %u, eCategory: 0x%x }\n", + legacy_prop->bIsOffload, + legacy_prop->eCategory); + }else{ + WARN("Unsupported Size = %d\n", legacy_prop->cbSize); + return E_INVALIDARG; + } + + + if(legacy_prop->bIsOffload) + return AUDCLNT_E_ENDPOINT_OFFLOAD_NOT_CAPABLE; + + return S_OK; +} + +static HRESULT WINAPI AudioClient_GetBufferSizeLimits(IAudioClient3 *iface, + const WAVEFORMATEX *format, BOOL event_driven, REFERENCE_TIME *min_duration, + REFERENCE_TIME *max_duration) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + + FIXME("(%p)->(%p, %u, %p, %p)\n", This, format, event_driven, min_duration, max_duration); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioClient_GetSharedModeEnginePeriod(IAudioClient3 *iface, + const WAVEFORMATEX *format, UINT32 *default_period_frames, UINT32 *unit_period_frames, + UINT32 *min_period_frames, UINT32 *max_period_frames) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + + FIXME("(%p)->(%p, %p, %p, %p, %p)\n", This, format, default_period_frames, unit_period_frames, + min_period_frames, max_period_frames); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioClient_GetCurrentSharedModeEnginePeriod(IAudioClient3 *iface, + WAVEFORMATEX **cur_format, UINT32 *cur_period_frames) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + + FIXME("(%p)->(%p, %p)\n", This, cur_format, cur_period_frames); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioClient_InitializeSharedAudioStream(IAudioClient3 *iface, + DWORD flags, UINT32 period_frames, const WAVEFORMATEX *format, + const GUID *session_guid) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + + FIXME("(%p)->(0x%lx, %u, %p, %s)\n", This, flags, period_frames, format, debugstr_guid(session_guid)); + + return E_NOTIMPL; +} + +static const IAudioClient3Vtbl AudioClient3_Vtbl = +{ + AudioClient_QueryInterface, + AudioClient_AddRef, + AudioClient_Release, + AudioClient_Initialize, + AudioClient_GetBufferSize, + AudioClient_GetStreamLatency, + AudioClient_GetCurrentPadding, + AudioClient_IsFormatSupported, + AudioClient_GetMixFormat, + AudioClient_GetDevicePeriod, + AudioClient_Start, + AudioClient_Stop, + AudioClient_Reset, + AudioClient_SetEventHandle, + AudioClient_GetService, + AudioClient_IsOffloadCapable, + AudioClient_SetClientProperties, + AudioClient_GetBufferSizeLimits, + AudioClient_GetSharedModeEnginePeriod, + AudioClient_GetCurrentSharedModeEnginePeriod, + AudioClient_InitializeSharedAudioStream, +}; + +static HRESULT WINAPI AudioRenderClient_QueryInterface( + IAudioRenderClient *iface, REFIID riid, void **ppv) +{ + ACImpl *This = impl_from_IAudioRenderClient(iface); + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + if(IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IAudioRenderClient)) + *ppv = iface; + else if(IsEqualIID(riid, &IID_IMarshal)) + return IUnknown_QueryInterface(This->pUnkFTMarshal, riid, ppv); + + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioRenderClient_AddRef(IAudioRenderClient *iface) +{ + ACImpl *This = impl_from_IAudioRenderClient(iface); + return AudioClient_AddRef(&This->IAudioClient3_iface); +} + +static ULONG WINAPI AudioRenderClient_Release(IAudioRenderClient *iface) +{ + ACImpl *This = impl_from_IAudioRenderClient(iface); + return AudioClient_Release(&This->IAudioClient3_iface); +} + +static HRESULT WINAPI AudioRenderClient_GetBuffer(IAudioRenderClient *iface, + UINT32 frames, BYTE **data) +{ + ACImpl *This = impl_from_IAudioRenderClient(iface); + struct get_render_buffer_params params; + + TRACE("(%p)->(%u, %p)\n", This, frames, data); + + if(!data) + return E_POINTER; + *data = NULL; + + params.stream = This->stream; + params.frames = frames; + params.data = data; + + ALSA_CALL(get_render_buffer, ¶ms); + + return params.result; +} + +static HRESULT WINAPI AudioRenderClient_ReleaseBuffer( + IAudioRenderClient *iface, UINT32 written_frames, DWORD flags) +{ + ACImpl *This = impl_from_IAudioRenderClient(iface); + struct release_render_buffer_params params; + + TRACE("(%p)->(%u, %lx)\n", This, written_frames, flags); + + params.stream = This->stream; + params.written_frames = written_frames; + params.flags = flags; + + ALSA_CALL(release_render_buffer, ¶ms); + + return params.result; +} + +static const IAudioRenderClientVtbl AudioRenderClient_Vtbl = { + AudioRenderClient_QueryInterface, + AudioRenderClient_AddRef, + AudioRenderClient_Release, + AudioRenderClient_GetBuffer, + AudioRenderClient_ReleaseBuffer +}; + +static HRESULT WINAPI AudioCaptureClient_QueryInterface( + IAudioCaptureClient *iface, REFIID riid, void **ppv) +{ + ACImpl *This = impl_from_IAudioCaptureClient(iface); + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + if(IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IAudioCaptureClient)) + *ppv = iface; + else if(IsEqualIID(riid, &IID_IMarshal)) + return IUnknown_QueryInterface(This->pUnkFTMarshal, riid, ppv); + + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioCaptureClient_AddRef(IAudioCaptureClient *iface) +{ + ACImpl *This = impl_from_IAudioCaptureClient(iface); + return IAudioClient3_AddRef(&This->IAudioClient3_iface); +} + +static ULONG WINAPI AudioCaptureClient_Release(IAudioCaptureClient *iface) +{ + ACImpl *This = impl_from_IAudioCaptureClient(iface); + return IAudioClient3_Release(&This->IAudioClient3_iface); +} + +static HRESULT WINAPI AudioCaptureClient_GetBuffer(IAudioCaptureClient *iface, + BYTE **data, UINT32 *frames, DWORD *flags, UINT64 *devpos, + UINT64 *qpcpos) +{ + ACImpl *This = impl_from_IAudioCaptureClient(iface); + struct get_capture_buffer_params params; + + TRACE("(%p)->(%p, %p, %p, %p, %p)\n", This, data, frames, flags, + devpos, qpcpos); + + if(!data) + return E_POINTER; + + *data = NULL; + + if(!frames || !flags) + return E_POINTER; + + params.stream = This->stream; + params.data = data; + params.frames = frames; + params.flags = (UINT*)flags; + params.devpos = devpos; + params.qpcpos = qpcpos; + + ALSA_CALL(get_capture_buffer, ¶ms); + + return params.result; +} + +static HRESULT WINAPI AudioCaptureClient_ReleaseBuffer( + IAudioCaptureClient *iface, UINT32 done) +{ + ACImpl *This = impl_from_IAudioCaptureClient(iface); + struct release_capture_buffer_params params; + + TRACE("(%p)->(%u)\n", This, done); + + params.stream = This->stream; + params.done = done; + + ALSA_CALL(release_capture_buffer, ¶ms); + + return params.result; +} + +static HRESULT WINAPI AudioCaptureClient_GetNextPacketSize( + IAudioCaptureClient *iface, UINT32 *frames) +{ + ACImpl *This = impl_from_IAudioCaptureClient(iface); + struct get_next_packet_size_params params; + + TRACE("(%p)->(%p)\n", This, frames); + + if(!frames) + return E_POINTER; + + params.stream = This->stream; + params.frames = frames; + + ALSA_CALL(get_next_packet_size, ¶ms); + + return params.result; +} + +static const IAudioCaptureClientVtbl AudioCaptureClient_Vtbl = +{ + AudioCaptureClient_QueryInterface, + AudioCaptureClient_AddRef, + AudioCaptureClient_Release, + AudioCaptureClient_GetBuffer, + AudioCaptureClient_ReleaseBuffer, + AudioCaptureClient_GetNextPacketSize +}; + +static HRESULT WINAPI AudioClock_QueryInterface(IAudioClock *iface, + REFIID riid, void **ppv) +{ + ACImpl *This = impl_from_IAudioClock(iface); + + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + if(IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IAudioClock)) + *ppv = iface; + else if(IsEqualIID(riid, &IID_IAudioClock2)) + *ppv = &This->IAudioClock2_iface; + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioClock_AddRef(IAudioClock *iface) +{ + ACImpl *This = impl_from_IAudioClock(iface); + return IAudioClient3_AddRef(&This->IAudioClient3_iface); +} + +static ULONG WINAPI AudioClock_Release(IAudioClock *iface) +{ + ACImpl *This = impl_from_IAudioClock(iface); + return IAudioClient3_Release(&This->IAudioClient3_iface); +} + +static HRESULT WINAPI AudioClock_GetFrequency(IAudioClock *iface, UINT64 *freq) +{ + ACImpl *This = impl_from_IAudioClock(iface); + struct get_frequency_params params; + + TRACE("(%p)->(%p)\n", This, freq); + + params.stream = This->stream; + params.freq = freq; + + ALSA_CALL(get_frequency, ¶ms); + + return params.result; +} + +static HRESULT WINAPI AudioClock_GetPosition(IAudioClock *iface, UINT64 *pos, + UINT64 *qpctime) +{ + ACImpl *This = impl_from_IAudioClock(iface); + struct get_position_params params; + + TRACE("(%p)->(%p, %p)\n", This, pos, qpctime); + + if(!pos) + return E_POINTER; + + params.stream = This->stream; + params.device = FALSE; + params.pos = pos; + params.qpctime = qpctime; + + ALSA_CALL(get_position, ¶ms); + + return params.result; +} + +static HRESULT WINAPI AudioClock_GetCharacteristics(IAudioClock *iface, + DWORD *chars) +{ + ACImpl *This = impl_from_IAudioClock(iface); + + TRACE("(%p)->(%p)\n", This, chars); + + if(!chars) + return E_POINTER; + + *chars = AUDIOCLOCK_CHARACTERISTIC_FIXED_FREQ; + + return S_OK; +} + +static const IAudioClockVtbl AudioClock_Vtbl = +{ + AudioClock_QueryInterface, + AudioClock_AddRef, + AudioClock_Release, + AudioClock_GetFrequency, + AudioClock_GetPosition, + AudioClock_GetCharacteristics +}; + +static HRESULT WINAPI AudioClock2_QueryInterface(IAudioClock2 *iface, + REFIID riid, void **ppv) +{ + ACImpl *This = impl_from_IAudioClock2(iface); + return IAudioClock_QueryInterface(&This->IAudioClock_iface, riid, ppv); +} + +static ULONG WINAPI AudioClock2_AddRef(IAudioClock2 *iface) +{ + ACImpl *This = impl_from_IAudioClock2(iface); + return IAudioClient3_AddRef(&This->IAudioClient3_iface); +} + +static ULONG WINAPI AudioClock2_Release(IAudioClock2 *iface) +{ + ACImpl *This = impl_from_IAudioClock2(iface); + return IAudioClient3_Release(&This->IAudioClient3_iface); +} + +static HRESULT WINAPI AudioClock2_GetDevicePosition(IAudioClock2 *iface, + UINT64 *pos, UINT64 *qpctime) +{ + ACImpl *This = impl_from_IAudioClock2(iface); + + FIXME("(%p)->(%p, %p)\n", This, pos, qpctime); + + return E_NOTIMPL; +} + +static const IAudioClock2Vtbl AudioClock2_Vtbl = +{ + AudioClock2_QueryInterface, + AudioClock2_AddRef, + AudioClock2_Release, + AudioClock2_GetDevicePosition +}; + +static AudioSessionWrapper *AudioSessionWrapper_Create(ACImpl *client) +{ + AudioSessionWrapper *ret; + + ret = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, + sizeof(AudioSessionWrapper)); + if(!ret) + return NULL; + + ret->IAudioSessionControl2_iface.lpVtbl = &AudioSessionControl2_Vtbl; + ret->ISimpleAudioVolume_iface.lpVtbl = &SimpleAudioVolume_Vtbl; + ret->IChannelAudioVolume_iface.lpVtbl = &ChannelAudioVolume_Vtbl; + + ret->ref = 1; + + ret->client = client; + if(client){ + ret->session = client->session; + AudioClient_AddRef(&client->IAudioClient3_iface); + } + + return ret; +} + +static HRESULT WINAPI AudioSessionControl_QueryInterface( + IAudioSessionControl2 *iface, REFIID riid, void **ppv) +{ + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + if(IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IAudioSessionControl) || + IsEqualIID(riid, &IID_IAudioSessionControl2)) + *ppv = iface; + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioSessionControl_AddRef(IAudioSessionControl2 *iface) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + ULONG ref; + ref = InterlockedIncrement(&This->ref); + TRACE("(%p) Refcount now %lu\n", This, ref); + return ref; +} + +static ULONG WINAPI AudioSessionControl_Release(IAudioSessionControl2 *iface) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + ULONG ref; + ref = InterlockedDecrement(&This->ref); + TRACE("(%p) Refcount now %lu\n", This, ref); + if(!ref){ + if(This->client){ + EnterCriticalSection(&g_sessions_lock); + This->client->session_wrapper = NULL; + LeaveCriticalSection(&g_sessions_lock); + AudioClient_Release(&This->client->IAudioClient3_iface); + } + HeapFree(GetProcessHeap(), 0, This); + } + return ref; +} + +static HRESULT WINAPI AudioSessionControl_GetState(IAudioSessionControl2 *iface, + AudioSessionState *state) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + struct is_started_params params; + ACImpl *client; + + TRACE("(%p)->(%p)\n", This, state); + + if(!state) + return NULL_PTR_ERR; + + EnterCriticalSection(&g_sessions_lock); + + if(list_empty(&This->session->clients)){ + *state = AudioSessionStateExpired; + LeaveCriticalSection(&g_sessions_lock); + return S_OK; + } + + LIST_FOR_EACH_ENTRY(client, &This->session->clients, ACImpl, entry){ + params.stream = client->stream; + ALSA_CALL(is_started, ¶ms); + if(params.result == S_OK){ + *state = AudioSessionStateActive; + LeaveCriticalSection(&g_sessions_lock); + return S_OK; + } + } + + LeaveCriticalSection(&g_sessions_lock); + + *state = AudioSessionStateInactive; + + return S_OK; +} + +static HRESULT WINAPI AudioSessionControl_GetDisplayName( + IAudioSessionControl2 *iface, WCHAR **name) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, name); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_SetDisplayName( + IAudioSessionControl2 *iface, const WCHAR *name, const GUID *session) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p, %s) - stub\n", This, name, debugstr_guid(session)); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_GetIconPath( + IAudioSessionControl2 *iface, WCHAR **path) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, path); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_SetIconPath( + IAudioSessionControl2 *iface, const WCHAR *path, const GUID *session) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p, %s) - stub\n", This, path, debugstr_guid(session)); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_GetGroupingParam( + IAudioSessionControl2 *iface, GUID *group) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, group); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_SetGroupingParam( + IAudioSessionControl2 *iface, const GUID *group, const GUID *session) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%s, %s) - stub\n", This, debugstr_guid(group), + debugstr_guid(session)); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_RegisterAudioSessionNotification( + IAudioSessionControl2 *iface, IAudioSessionEvents *events) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, events); + + return S_OK; +} + +static HRESULT WINAPI AudioSessionControl_UnregisterAudioSessionNotification( + IAudioSessionControl2 *iface, IAudioSessionEvents *events) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, events); + + return S_OK; +} + +static HRESULT WINAPI AudioSessionControl_GetSessionIdentifier( + IAudioSessionControl2 *iface, WCHAR **id) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, id); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_GetSessionInstanceIdentifier( + IAudioSessionControl2 *iface, WCHAR **id) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, id); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_GetProcessId( + IAudioSessionControl2 *iface, DWORD *pid) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + TRACE("(%p)->(%p)\n", This, pid); + + if(!pid) + return E_POINTER; + + *pid = GetCurrentProcessId(); + + return S_OK; +} + +static HRESULT WINAPI AudioSessionControl_IsSystemSoundsSession( + IAudioSessionControl2 *iface) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + TRACE("(%p)\n", This); + + return S_FALSE; +} + +static HRESULT WINAPI AudioSessionControl_SetDuckingPreference( + IAudioSessionControl2 *iface, BOOL optout) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + TRACE("(%p)->(%d)\n", This, optout); + + return S_OK; +} + +static const IAudioSessionControl2Vtbl AudioSessionControl2_Vtbl = +{ + AudioSessionControl_QueryInterface, + AudioSessionControl_AddRef, + AudioSessionControl_Release, + AudioSessionControl_GetState, + AudioSessionControl_GetDisplayName, + AudioSessionControl_SetDisplayName, + AudioSessionControl_GetIconPath, + AudioSessionControl_SetIconPath, + AudioSessionControl_GetGroupingParam, + AudioSessionControl_SetGroupingParam, + AudioSessionControl_RegisterAudioSessionNotification, + AudioSessionControl_UnregisterAudioSessionNotification, + AudioSessionControl_GetSessionIdentifier, + AudioSessionControl_GetSessionInstanceIdentifier, + AudioSessionControl_GetProcessId, + AudioSessionControl_IsSystemSoundsSession, + AudioSessionControl_SetDuckingPreference +}; + +static HRESULT WINAPI SimpleAudioVolume_QueryInterface( + ISimpleAudioVolume *iface, REFIID riid, void **ppv) +{ + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + if(IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_ISimpleAudioVolume)) + *ppv = iface; + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI SimpleAudioVolume_AddRef(ISimpleAudioVolume *iface) +{ + AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface); + return AudioSessionControl_AddRef(&This->IAudioSessionControl2_iface); +} + +static ULONG WINAPI SimpleAudioVolume_Release(ISimpleAudioVolume *iface) +{ + AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface); + return AudioSessionControl_Release(&This->IAudioSessionControl2_iface); +} + +static HRESULT WINAPI SimpleAudioVolume_SetMasterVolume( + ISimpleAudioVolume *iface, float level, const GUID *context) +{ + AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface); + AudioSession *session = This->session; + ACImpl *client; + + TRACE("(%p)->(%f, %s)\n", session, level, wine_dbgstr_guid(context)); + + if(level < 0.f || level > 1.f) + return E_INVALIDARG; + + if(context) + FIXME("Notifications not supported yet\n"); + + TRACE("ALSA does not support volume control\n"); + + EnterCriticalSection(&g_sessions_lock); + + session->master_vol = level; + + LIST_FOR_EACH_ENTRY(client, &session->clients, ACImpl, entry) + set_stream_volumes(client); + + LeaveCriticalSection(&g_sessions_lock); + + return S_OK; +} + +static HRESULT WINAPI SimpleAudioVolume_GetMasterVolume( + ISimpleAudioVolume *iface, float *level) +{ + AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface); + AudioSession *session = This->session; + + TRACE("(%p)->(%p)\n", session, level); + + if(!level) + return NULL_PTR_ERR; + + *level = session->master_vol; + + return S_OK; +} + +static HRESULT WINAPI SimpleAudioVolume_SetMute(ISimpleAudioVolume *iface, + BOOL mute, const GUID *context) +{ + AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface); + AudioSession *session = This->session; + ACImpl *client; + + TRACE("(%p)->(%u, %s)\n", session, mute, debugstr_guid(context)); + + if(context) + FIXME("Notifications not supported yet\n"); + + EnterCriticalSection(&g_sessions_lock); + + session->mute = mute; + LIST_FOR_EACH_ENTRY(client, &session->clients, ACImpl, entry) + set_stream_volumes(client); + + LeaveCriticalSection(&g_sessions_lock); + + return S_OK; +} + +static HRESULT WINAPI SimpleAudioVolume_GetMute(ISimpleAudioVolume *iface, + BOOL *mute) +{ + AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface); + AudioSession *session = This->session; + + TRACE("(%p)->(%p)\n", session, mute); + + if(!mute) + return NULL_PTR_ERR; + + *mute = session->mute; + + return S_OK; +} + +static const ISimpleAudioVolumeVtbl SimpleAudioVolume_Vtbl = +{ + SimpleAudioVolume_QueryInterface, + SimpleAudioVolume_AddRef, + SimpleAudioVolume_Release, + SimpleAudioVolume_SetMasterVolume, + SimpleAudioVolume_GetMasterVolume, + SimpleAudioVolume_SetMute, + SimpleAudioVolume_GetMute +}; + +static HRESULT WINAPI AudioStreamVolume_QueryInterface( + IAudioStreamVolume *iface, REFIID riid, void **ppv) +{ + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + if(IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IAudioStreamVolume)) + *ppv = iface; + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioStreamVolume_AddRef(IAudioStreamVolume *iface) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + return IAudioClient3_AddRef(&This->IAudioClient3_iface); +} + +static ULONG WINAPI AudioStreamVolume_Release(IAudioStreamVolume *iface) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + return IAudioClient3_Release(&This->IAudioClient3_iface); +} + +static HRESULT WINAPI AudioStreamVolume_GetChannelCount( + IAudioStreamVolume *iface, UINT32 *out) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + + TRACE("(%p)->(%p)\n", This, out); + + if(!out) + return E_POINTER; + + *out = This->channel_count; + + return S_OK; +} + +static HRESULT WINAPI AudioStreamVolume_SetChannelVolume( + IAudioStreamVolume *iface, UINT32 index, float level) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + + TRACE("(%p)->(%d, %f)\n", This, index, level); + + if(level < 0.f || level > 1.f) + return E_INVALIDARG; + + if(index >= This->channel_count) + return E_INVALIDARG; + + TRACE("ALSA does not support volume control\n"); + + EnterCriticalSection(&g_sessions_lock); + + This->vols[index] = level; + set_stream_volumes(This); + + LeaveCriticalSection(&g_sessions_lock); + + return S_OK; +} + +static HRESULT WINAPI AudioStreamVolume_GetChannelVolume( + IAudioStreamVolume *iface, UINT32 index, float *level) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + + TRACE("(%p)->(%d, %p)\n", This, index, level); + + if(!level) + return E_POINTER; + + if(index >= This->channel_count) + return E_INVALIDARG; + + *level = This->vols[index]; + + return S_OK; +} + +static HRESULT WINAPI AudioStreamVolume_SetAllVolumes( + IAudioStreamVolume *iface, UINT32 count, const float *levels) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + unsigned int i; + + TRACE("(%p)->(%d, %p)\n", This, count, levels); + + if(!levels) + return E_POINTER; + + if(count != This->channel_count) + return E_INVALIDARG; + + TRACE("ALSA does not support volume control\n"); + + EnterCriticalSection(&g_sessions_lock); + + for(i = 0; i < count; ++i) + This->vols[i] = levels[i]; + set_stream_volumes(This); + + LeaveCriticalSection(&g_sessions_lock); + + return S_OK; +} + +static HRESULT WINAPI AudioStreamVolume_GetAllVolumes( + IAudioStreamVolume *iface, UINT32 count, float *levels) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + unsigned int i; + + TRACE("(%p)->(%d, %p)\n", This, count, levels); + + if(!levels) + return E_POINTER; + + if(count != This->channel_count) + return E_INVALIDARG; + + EnterCriticalSection(&g_sessions_lock); + + for(i = 0; i < count; ++i) + levels[i] = This->vols[i]; + + LeaveCriticalSection(&g_sessions_lock); + + return S_OK; +} + +static const IAudioStreamVolumeVtbl AudioStreamVolume_Vtbl = +{ + AudioStreamVolume_QueryInterface, + AudioStreamVolume_AddRef, + AudioStreamVolume_Release, + AudioStreamVolume_GetChannelCount, + AudioStreamVolume_SetChannelVolume, + AudioStreamVolume_GetChannelVolume, + AudioStreamVolume_SetAllVolumes, + AudioStreamVolume_GetAllVolumes +}; + +static HRESULT WINAPI ChannelAudioVolume_QueryInterface( + IChannelAudioVolume *iface, REFIID riid, void **ppv) +{ + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + if(IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IChannelAudioVolume)) + *ppv = iface; + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI ChannelAudioVolume_AddRef(IChannelAudioVolume *iface) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + return AudioSessionControl_AddRef(&This->IAudioSessionControl2_iface); +} + +static ULONG WINAPI ChannelAudioVolume_Release(IChannelAudioVolume *iface) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + return AudioSessionControl_Release(&This->IAudioSessionControl2_iface); +} + +static HRESULT WINAPI ChannelAudioVolume_GetChannelCount( + IChannelAudioVolume *iface, UINT32 *out) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + AudioSession *session = This->session; + + TRACE("(%p)->(%p)\n", session, out); + + if(!out) + return NULL_PTR_ERR; + + *out = session->channel_count; + + return S_OK; +} + +static HRESULT WINAPI ChannelAudioVolume_SetChannelVolume( + IChannelAudioVolume *iface, UINT32 index, float level, + const GUID *context) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + AudioSession *session = This->session; + ACImpl *client; + + TRACE("(%p)->(%d, %f, %s)\n", session, index, level, + wine_dbgstr_guid(context)); + + if(level < 0.f || level > 1.f) + return E_INVALIDARG; + + if(index >= session->channel_count) + return E_INVALIDARG; + + if(context) + FIXME("Notifications not supported yet\n"); + + TRACE("ALSA does not support volume control\n"); + + EnterCriticalSection(&g_sessions_lock); + + session->channel_vols[index] = level; + + LIST_FOR_EACH_ENTRY(client, &session->clients, ACImpl, entry) + set_stream_volumes(client); + + LeaveCriticalSection(&g_sessions_lock); + + return S_OK; +} + +static HRESULT WINAPI ChannelAudioVolume_GetChannelVolume( + IChannelAudioVolume *iface, UINT32 index, float *level) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + AudioSession *session = This->session; + + TRACE("(%p)->(%d, %p)\n", session, index, level); + + if(!level) + return NULL_PTR_ERR; + + if(index >= session->channel_count) + return E_INVALIDARG; + + *level = session->channel_vols[index]; + + return S_OK; +} + +static HRESULT WINAPI ChannelAudioVolume_SetAllVolumes( + IChannelAudioVolume *iface, UINT32 count, const float *levels, + const GUID *context) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + AudioSession *session = This->session; + unsigned int i; + ACImpl *client; + + TRACE("(%p)->(%d, %p, %s)\n", session, count, levels, + wine_dbgstr_guid(context)); + + if(!levels) + return NULL_PTR_ERR; + + if(count != session->channel_count) + return E_INVALIDARG; + + if(context) + FIXME("Notifications not supported yet\n"); + + TRACE("ALSA does not support volume control\n"); + + EnterCriticalSection(&g_sessions_lock); + + for(i = 0; i < count; ++i) + session->channel_vols[i] = levels[i]; + + LIST_FOR_EACH_ENTRY(client, &session->clients, ACImpl, entry) + set_stream_volumes(client); + + LeaveCriticalSection(&g_sessions_lock); + + return S_OK; +} + +static HRESULT WINAPI ChannelAudioVolume_GetAllVolumes( + IChannelAudioVolume *iface, UINT32 count, float *levels) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + AudioSession *session = This->session; + unsigned int i; + + TRACE("(%p)->(%d, %p)\n", session, count, levels); + + if(!levels) + return NULL_PTR_ERR; + + if(count != session->channel_count) + return E_INVALIDARG; + + for(i = 0; i < count; ++i) + levels[i] = session->channel_vols[i]; + + return S_OK; +} + +static const IChannelAudioVolumeVtbl ChannelAudioVolume_Vtbl = +{ + ChannelAudioVolume_QueryInterface, + ChannelAudioVolume_AddRef, + ChannelAudioVolume_Release, + ChannelAudioVolume_GetChannelCount, + ChannelAudioVolume_SetChannelVolume, + ChannelAudioVolume_GetChannelVolume, + ChannelAudioVolume_SetAllVolumes, + ChannelAudioVolume_GetAllVolumes +}; + +static HRESULT WINAPI AudioSessionManager_QueryInterface(IAudioSessionManager2 *iface, + REFIID riid, void **ppv) +{ + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + if(IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IAudioSessionManager) || + IsEqualIID(riid, &IID_IAudioSessionManager2)) + *ppv = iface; + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioSessionManager_AddRef(IAudioSessionManager2 *iface) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + ULONG ref; + ref = InterlockedIncrement(&This->ref); + TRACE("(%p) Refcount now %lu\n", This, ref); + return ref; +} + +static ULONG WINAPI AudioSessionManager_Release(IAudioSessionManager2 *iface) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + ULONG ref; + ref = InterlockedDecrement(&This->ref); + TRACE("(%p) Refcount now %lu\n", This, ref); + if(!ref) + HeapFree(GetProcessHeap(), 0, This); + return ref; +} + +static HRESULT WINAPI AudioSessionManager_GetAudioSessionControl( + IAudioSessionManager2 *iface, const GUID *session_guid, DWORD flags, + IAudioSessionControl **out) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + AudioSession *session; + AudioSessionWrapper *wrapper; + HRESULT hr; + + TRACE("(%p)->(%s, %lx, %p)\n", This, debugstr_guid(session_guid), + flags, out); + + hr = get_audio_session(session_guid, This->device, 0, &session); + if(FAILED(hr)) + return hr; + + wrapper = AudioSessionWrapper_Create(NULL); + if(!wrapper) + return E_OUTOFMEMORY; + + wrapper->session = session; + + *out = (IAudioSessionControl*)&wrapper->IAudioSessionControl2_iface; + + return S_OK; +} + +static HRESULT WINAPI AudioSessionManager_GetSimpleAudioVolume( + IAudioSessionManager2 *iface, const GUID *session_guid, DWORD flags, + ISimpleAudioVolume **out) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + AudioSession *session; + AudioSessionWrapper *wrapper; + HRESULT hr; + + TRACE("(%p)->(%s, %lx, %p)\n", This, debugstr_guid(session_guid), + flags, out); + + hr = get_audio_session(session_guid, This->device, 0, &session); + if(FAILED(hr)) + return hr; + + wrapper = AudioSessionWrapper_Create(NULL); + if(!wrapper) + return E_OUTOFMEMORY; + + wrapper->session = session; + + *out = &wrapper->ISimpleAudioVolume_iface; + + return S_OK; +} + +static HRESULT WINAPI AudioSessionManager_GetSessionEnumerator( + IAudioSessionManager2 *iface, IAudioSessionEnumerator **out) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + FIXME("(%p)->(%p) - stub\n", This, out); + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionManager_RegisterSessionNotification( + IAudioSessionManager2 *iface, IAudioSessionNotification *notification) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + FIXME("(%p)->(%p) - stub\n", This, notification); + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionManager_UnregisterSessionNotification( + IAudioSessionManager2 *iface, IAudioSessionNotification *notification) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + FIXME("(%p)->(%p) - stub\n", This, notification); + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionManager_RegisterDuckNotification( + IAudioSessionManager2 *iface, const WCHAR *session_id, + IAudioVolumeDuckNotification *notification) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + FIXME("(%p)->(%p) - stub\n", This, notification); + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionManager_UnregisterDuckNotification( + IAudioSessionManager2 *iface, + IAudioVolumeDuckNotification *notification) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + FIXME("(%p)->(%p) - stub\n", This, notification); + return E_NOTIMPL; +} + +static const IAudioSessionManager2Vtbl AudioSessionManager2_Vtbl = +{ + AudioSessionManager_QueryInterface, + AudioSessionManager_AddRef, + AudioSessionManager_Release, + AudioSessionManager_GetAudioSessionControl, + AudioSessionManager_GetSimpleAudioVolume, + AudioSessionManager_GetSessionEnumerator, + AudioSessionManager_RegisterSessionNotification, + AudioSessionManager_UnregisterSessionNotification, + AudioSessionManager_RegisterDuckNotification, + AudioSessionManager_UnregisterDuckNotification +}; + +HRESULT WINAPI AUDDRV_GetAudioSessionManager(IMMDevice *device, + IAudioSessionManager2 **out) +{ + SessionMgr *This; + + This = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(SessionMgr)); + if(!This) + return E_OUTOFMEMORY; + + This->IAudioSessionManager2_iface.lpVtbl = &AudioSessionManager2_Vtbl; + This->device = device; + This->ref = 1; + + *out = &This->IAudioSessionManager2_iface; + + return S_OK; +} + +HRESULT WINAPI AUDDRV_GetPropValue(GUID *guid, const PROPERTYKEY *prop, PROPVARIANT *out) +{ + struct get_prop_value_params params; + char name[256]; + EDataFlow flow; + unsigned int size = 0; + + TRACE("%s, (%s,%lu), %p\n", wine_dbgstr_guid(guid), wine_dbgstr_guid(&prop->fmtid), prop->pid, out); + + if(!get_alsa_name_by_guid(guid, name, sizeof(name), &flow)) + { + WARN("Unknown interface %s\n", debugstr_guid(guid)); + return E_NOINTERFACE; + } + + params.device = name; + params.flow = flow; + params.guid = guid; + params.prop = prop; + params.value = out; + params.buffer = NULL; + params.buffer_size = &size; + + while(1) { + ALSA_CALL(get_prop_value, ¶ms); + + if(params.result != E_NOT_SUFFICIENT_BUFFER) + break; + + CoTaskMemFree(params.buffer); + params.buffer = CoTaskMemAlloc(*params.buffer_size); + if(!params.buffer) + return E_OUTOFMEMORY; + } + if(FAILED(params.result)) + CoTaskMemFree(params.buffer); + + return params.result; +} diff --git a/pkgs/osu-wine/audio-revert/winealsa.drv/unixlib.h b/pkgs/osu-wine/audio-revert/winealsa.drv/unixlib.h new file mode 100644 index 0000000..5784d5f --- /dev/null +++ b/pkgs/osu-wine/audio-revert/winealsa.drv/unixlib.h @@ -0,0 +1,32 @@ +/* + * Copyright 2022 Huw Davies + * + * 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 "../mmdevapi/unixlib.h" + +NTSTATUS alsa_midi_release(void *args); +NTSTATUS alsa_midi_out_message(void *args); +NTSTATUS alsa_midi_in_message(void *args); +NTSTATUS alsa_midi_notify_wait(void *args); + +#ifdef _WIN64 +NTSTATUS alsa_wow64_midi_out_message(void *args); +NTSTATUS alsa_wow64_midi_in_message(void *args); +NTSTATUS alsa_wow64_midi_notify_wait(void *args); +#endif + +#define ALSA_CALL(func, params) WINE_UNIX_CALL(func, params) diff --git a/pkgs/osu-wine/audio-revert/winealsa.drv/winealsa.drv.spec b/pkgs/osu-wine/audio-revert/winealsa.drv/winealsa.drv.spec new file mode 100644 index 0000000..bd83ea2 --- /dev/null +++ b/pkgs/osu-wine/audio-revert/winealsa.drv/winealsa.drv.spec @@ -0,0 +1,11 @@ +# WinMM driver functions +@ stdcall -private DriverProc(long long long long long) ALSA_DriverProc +@ stdcall -private midMessage(long long long long long) ALSA_midMessage +@ stdcall -private modMessage(long long long long long) ALSA_modMessage + +# MMDevAPI driver functions +@ stdcall -private GetPriority() AUDDRV_GetPriority +@ stdcall -private GetEndpointIDs(long ptr ptr ptr ptr) AUDDRV_GetEndpointIDs +@ stdcall -private GetAudioEndpoint(ptr ptr ptr) AUDDRV_GetAudioEndpoint +@ stdcall -private GetAudioSessionManager(ptr ptr) AUDDRV_GetAudioSessionManager +@ stdcall -private GetPropValue(ptr ptr ptr) AUDDRV_GetPropValue diff --git a/pkgs/osu-wine/audio-revert/winecoreaudio.drv/Makefile.in b/pkgs/osu-wine/audio-revert/winecoreaudio.drv/Makefile.in new file mode 100644 index 0000000..1bcc58d --- /dev/null +++ b/pkgs/osu-wine/audio-revert/winecoreaudio.drv/Makefile.in @@ -0,0 +1,11 @@ +MODULE = winecoreaudio.drv +UNIXLIB = winecoreaudio.so +IMPORTS = uuid ole32 user32 advapi32 +DELAYIMPORTS = winmm +UNIX_LIBS = $(COREAUDIO_LIBS) + +SOURCES = \ + coreaudio.c \ + coremidi.c \ + midi.c \ + mmdevdrv.c diff --git a/pkgs/osu-wine/audio-revert/winecoreaudio.drv/coreaudio.c b/pkgs/osu-wine/audio-revert/winecoreaudio.drv/coreaudio.c new file mode 100644 index 0000000..b48a0c7 --- /dev/null +++ b/pkgs/osu-wine/audio-revert/winecoreaudio.drv/coreaudio.c @@ -0,0 +1,2030 @@ +/* + * Unixlib for winecoreaudio driver. + * + * Copyright 2011 Andrew Eikum for CodeWeavers + * Copyright 2021 Huw Davies + * + * 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 + */ +#if 0 +#pragma makedep unix +#endif + +#include "config.h" + +#define LoadResource __carbon_LoadResource +#define CompareString __carbon_CompareString +#define GetCurrentThread __carbon_GetCurrentThread +#define GetCurrentProcess __carbon_GetCurrentProcess + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#undef LoadResource +#undef CompareString +#undef GetCurrentThread +#undef GetCurrentProcess +#undef _CDECL + +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "windef.h" +#include "winbase.h" +#include "winnls.h" +#include "winreg.h" +#include "winternl.h" +#include "mmdeviceapi.h" +#include "initguid.h" +#include "audioclient.h" +#include "wine/debug.h" +#include "wine/unixlib.h" + +#include "unixlib.h" + +WINE_DEFAULT_DEBUG_CHANNEL(coreaudio); + +#define MAX_DEV_NAME_LEN 10 /* Max 32 bit digits */ + +struct coreaudio_stream +{ + OSSpinLock lock; + AudioComponentInstance unit; + AudioConverterRef converter; + AudioStreamBasicDescription dev_desc; /* audio unit format, not necessarily the same as fmt */ + AudioDeviceID dev_id; + EDataFlow flow; + AUDCLNT_SHAREMODE share; + + BOOL playing; + UINT32 period_ms, period_frames; + UINT32 bufsize_frames, resamp_bufsize_frames; + UINT32 lcl_offs_frames, held_frames, wri_offs_frames, tmp_buffer_frames; + UINT32 cap_bufsize_frames, cap_offs_frames, cap_held_frames; + UINT32 wrap_bufsize_frames; + UINT64 written_frames; + INT32 getbuf_last; + WAVEFORMATEX *fmt; + BYTE *local_buffer, *cap_buffer, *wrap_buffer, *resamp_buffer, *tmp_buffer; +}; + +static HRESULT osstatus_to_hresult(OSStatus sc) +{ + switch(sc){ + case kAudioFormatUnsupportedDataFormatError: + case kAudioFormatUnknownFormatError: + case kAudioDeviceUnsupportedFormatError: + return AUDCLNT_E_UNSUPPORTED_FORMAT; + case kAudioHardwareBadDeviceError: + return AUDCLNT_E_DEVICE_INVALIDATED; + } + return E_FAIL; +} + +static struct coreaudio_stream *handle_get_stream(stream_handle h) +{ + return (struct coreaudio_stream *)(UINT_PTR)h; +} + +/* copied from kernelbase */ +static int muldiv( int a, int b, int c ) +{ + LONGLONG ret; + + if (!c) return -1; + + /* We want to deal with a positive divisor to simplify the logic. */ + if (c < 0) + { + a = -a; + c = -c; + } + + /* If the result is positive, we "add" to round. else, we subtract to round. */ + if ((a < 0 && b < 0) || (a >= 0 && b >= 0)) + ret = (((LONGLONG)a * b) + (c / 2)) / c; + else + ret = (((LONGLONG)a * b) - (c / 2)) / c; + + if (ret > 2147483647 || ret < -2147483647) return -1; + return ret; +} + +static AudioObjectPropertyScope get_scope(EDataFlow flow) +{ + return (flow == eRender) ? kAudioDevicePropertyScopeOutput : kAudioDevicePropertyScopeInput; +} + +static BOOL device_has_channels(AudioDeviceID device, EDataFlow flow) +{ + AudioObjectPropertyAddress addr; + AudioBufferList *buffers; + BOOL ret = FALSE; + OSStatus sc; + UInt32 size; + int i; + + addr.mSelector = kAudioDevicePropertyStreamConfiguration; + addr.mScope = get_scope(flow); + addr.mElement = 0; + + sc = AudioObjectGetPropertyDataSize(device, &addr, 0, NULL, &size); + if(sc != noErr){ + WARN("Unable to get _StreamConfiguration property size for device %u: %x\n", + (unsigned int)device, (int)sc); + return FALSE; + } + + buffers = malloc(size); + if(!buffers) return FALSE; + + sc = AudioObjectGetPropertyData(device, &addr, 0, NULL, &size, buffers); + if(sc != noErr){ + WARN("Unable to get _StreamConfiguration property for device %u: %x\n", + (unsigned int)device, (int)sc); + free(buffers); + return FALSE; + } + + for(i = 0; i < buffers->mNumberBuffers; i++){ + if(buffers->mBuffers[i].mNumberChannels > 0){ + ret = TRUE; + break; + } + } + free(buffers); + return ret; +} + +static NTSTATUS unix_get_endpoint_ids(void *args) +{ + struct get_endpoint_ids_params *params = args; + unsigned int num_devices, i, needed, offset; + AudioDeviceID *devices, default_id; + AudioObjectPropertyAddress addr; + struct endpoint *endpoint; + UInt32 devsize, size; + struct endpoint_info + { + CFStringRef name; + AudioDeviceID id; + } *info; + OSStatus sc; + UniChar *ptr; + + params->num = 0; + params->default_idx = 0; + + addr.mScope = kAudioObjectPropertyScopeGlobal; + addr.mElement = kAudioObjectPropertyElementMaster; + if(params->flow == eRender) addr.mSelector = kAudioHardwarePropertyDefaultOutputDevice; + else if(params->flow == eCapture) addr.mSelector = kAudioHardwarePropertyDefaultInputDevice; + else{ + params->result = E_INVALIDARG; + return STATUS_SUCCESS; + } + + size = sizeof(default_id); + sc = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, 0, NULL, &size, &default_id); + if(sc != noErr){ + WARN("Getting _DefaultInputDevice property failed: %x\n", (int)sc); + default_id = -1; + } + + addr.mSelector = kAudioHardwarePropertyDevices; + sc = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &addr, 0, NULL, &devsize); + if(sc != noErr){ + WARN("Getting _Devices property size failed: %x\n", (int)sc); + params->result = osstatus_to_hresult(sc); + return STATUS_SUCCESS; + } + + num_devices = devsize / sizeof(AudioDeviceID); + devices = malloc(devsize); + info = malloc(num_devices * sizeof(*info)); + if(!devices || !info){ + free(info); + free(devices); + params->result = E_OUTOFMEMORY; + return STATUS_SUCCESS; + } + + sc = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, 0, NULL, &devsize, devices); + if(sc != noErr){ + WARN("Getting _Devices property failed: %x\n", (int)sc); + free(info); + free(devices); + params->result = osstatus_to_hresult(sc); + return STATUS_SUCCESS; + } + + addr.mSelector = kAudioObjectPropertyName; + addr.mScope = get_scope(params->flow); + addr.mElement = 0; + + for(i = 0; i < num_devices; i++){ + if(!device_has_channels(devices[i], params->flow)) continue; + + size = sizeof(CFStringRef); + sc = AudioObjectGetPropertyData(devices[i], &addr, 0, NULL, &size, &info[params->num].name); + if(sc != noErr){ + WARN("Unable to get _Name property for device %u: %x\n", + (unsigned int)devices[i], (int)sc); + continue; + } + info[params->num++].id = devices[i]; + } + free(devices); + + offset = needed = sizeof(*endpoint) * params->num; + endpoint = params->endpoints; + + for(i = 0; i < params->num; i++){ + const SIZE_T name_len = CFStringGetLength(info[i].name) + 1; + const SIZE_T device_len = MAX_DEV_NAME_LEN + 1; + needed += name_len * sizeof(WCHAR) + ((device_len + 1) & ~1); + + if(needed <= params->size){ + endpoint->name = offset; + ptr = (UniChar *)((char *)params->endpoints + offset); + CFStringGetCharacters(info[i].name, CFRangeMake(0, name_len - 1), ptr); + ptr[name_len - 1] = 0; + offset += name_len * sizeof(WCHAR); + endpoint->device = offset; + sprintf((char *)params->endpoints + offset, "%u", (unsigned int)info[i].id); + offset += (device_len + 1) & ~1; + endpoint++; + } + CFRelease(info[i].name); + if(info[i].id == default_id) params->default_idx = i; + } + free(info); + + if(needed > params->size){ + params->size = needed; + params->result = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } + else params->result = S_OK; + + return STATUS_SUCCESS; +} + +static WAVEFORMATEX *clone_format(const WAVEFORMATEX *fmt) +{ + WAVEFORMATEX *ret; + size_t size; + + if(fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE) + size = sizeof(WAVEFORMATEXTENSIBLE); + else + size = sizeof(WAVEFORMATEX); + + ret = malloc(size); + if(!ret) + return NULL; + + memcpy(ret, fmt, size); + + ret->cbSize = size - sizeof(WAVEFORMATEX); + + return ret; +} + +static void silence_buffer(struct coreaudio_stream *stream, BYTE *buffer, UINT32 frames) +{ + WAVEFORMATEXTENSIBLE *fmtex = (WAVEFORMATEXTENSIBLE*)stream->fmt; + if((stream->fmt->wFormatTag == WAVE_FORMAT_PCM || + (stream->fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + IsEqualGUID(&fmtex->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM))) && + stream->fmt->wBitsPerSample == 8) + memset(buffer, 128, frames * stream->fmt->nBlockAlign); + else + memset(buffer, 0, frames * stream->fmt->nBlockAlign); +} + +/* CA is pulling data from us */ +static OSStatus ca_render_cb(void *user, AudioUnitRenderActionFlags *flags, + const AudioTimeStamp *ts, UInt32 bus, UInt32 nframes, + AudioBufferList *data) +{ + struct coreaudio_stream *stream = user; + UINT32 to_copy_bytes, to_copy_frames, chunk_bytes, lcl_offs_bytes; + + OSSpinLockLock(&stream->lock); + + if(stream->playing){ + lcl_offs_bytes = stream->lcl_offs_frames * stream->fmt->nBlockAlign; + to_copy_frames = min(nframes, stream->held_frames); + to_copy_bytes = to_copy_frames * stream->fmt->nBlockAlign; + + chunk_bytes = (stream->bufsize_frames - stream->lcl_offs_frames) * stream->fmt->nBlockAlign; + + if(to_copy_bytes > chunk_bytes){ + memcpy(data->mBuffers[0].mData, stream->local_buffer + lcl_offs_bytes, chunk_bytes); + memcpy(((BYTE *)data->mBuffers[0].mData) + chunk_bytes, stream->local_buffer, to_copy_bytes - chunk_bytes); + }else + memcpy(data->mBuffers[0].mData, stream->local_buffer + lcl_offs_bytes, to_copy_bytes); + + stream->lcl_offs_frames += to_copy_frames; + stream->lcl_offs_frames %= stream->bufsize_frames; + stream->held_frames -= to_copy_frames; + }else + to_copy_bytes = to_copy_frames = 0; + + if(nframes > to_copy_frames) + silence_buffer(stream, ((BYTE *)data->mBuffers[0].mData) + to_copy_bytes, nframes - to_copy_frames); + + OSSpinLockUnlock(&stream->lock); + + return noErr; +} + +static void ca_wrap_buffer(BYTE *dst, UINT32 dst_offs, UINT32 dst_bytes, + BYTE *src, UINT32 src_bytes) +{ + UINT32 chunk_bytes = dst_bytes - dst_offs; + + if(chunk_bytes < src_bytes){ + memcpy(dst + dst_offs, src, chunk_bytes); + memcpy(dst, src + chunk_bytes, src_bytes - chunk_bytes); + }else + memcpy(dst + dst_offs, src, src_bytes); +} + +/* we need to trigger CA to pull data from the device and give it to us + * + * raw data from CA is stored in cap_buffer, possibly via wrap_buffer + * + * raw data is resampled from cap_buffer into resamp_buffer in period-size + * chunks and copied to local_buffer + */ +static OSStatus ca_capture_cb(void *user, AudioUnitRenderActionFlags *flags, + const AudioTimeStamp *ts, UInt32 bus, UInt32 nframes, + AudioBufferList *data) +{ + struct coreaudio_stream *stream = user; + AudioBufferList list; + OSStatus sc; + UINT32 cap_wri_offs_frames; + + OSSpinLockLock(&stream->lock); + + cap_wri_offs_frames = (stream->cap_offs_frames + stream->cap_held_frames) % stream->cap_bufsize_frames; + + list.mNumberBuffers = 1; + list.mBuffers[0].mNumberChannels = stream->fmt->nChannels; + list.mBuffers[0].mDataByteSize = nframes * stream->fmt->nBlockAlign; + + if(!stream->playing || cap_wri_offs_frames + nframes > stream->cap_bufsize_frames){ + if(stream->wrap_bufsize_frames < nframes){ + free(stream->wrap_buffer); + stream->wrap_buffer = malloc(list.mBuffers[0].mDataByteSize); + stream->wrap_bufsize_frames = nframes; + } + + list.mBuffers[0].mData = stream->wrap_buffer; + }else + list.mBuffers[0].mData = stream->cap_buffer + cap_wri_offs_frames * stream->fmt->nBlockAlign; + + sc = AudioUnitRender(stream->unit, flags, ts, bus, nframes, &list); + if(sc != noErr){ + OSSpinLockUnlock(&stream->lock); + return sc; + } + + if(stream->playing){ + if(list.mBuffers[0].mData == stream->wrap_buffer){ + ca_wrap_buffer(stream->cap_buffer, + cap_wri_offs_frames * stream->fmt->nBlockAlign, + stream->cap_bufsize_frames * stream->fmt->nBlockAlign, + stream->wrap_buffer, list.mBuffers[0].mDataByteSize); + } + + stream->cap_held_frames += list.mBuffers[0].mDataByteSize / stream->fmt->nBlockAlign; + if(stream->cap_held_frames > stream->cap_bufsize_frames){ + stream->cap_offs_frames += stream->cap_held_frames % stream->cap_bufsize_frames; + stream->cap_offs_frames %= stream->cap_bufsize_frames; + stream->cap_held_frames = stream->cap_bufsize_frames; + } + } + + OSSpinLockUnlock(&stream->lock); + return noErr; +} + +static AudioComponentInstance get_audiounit(EDataFlow dataflow, AudioDeviceID adevid) +{ + AudioComponentInstance unit; + AudioComponent comp; + AudioComponentDescription desc; + OSStatus sc; + + memset(&desc, 0, sizeof(desc)); + desc.componentType = kAudioUnitType_Output; + desc.componentSubType = kAudioUnitSubType_HALOutput; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + + if(!(comp = AudioComponentFindNext(NULL, &desc))){ + WARN("AudioComponentFindNext failed\n"); + return NULL; + } + + sc = AudioComponentInstanceNew(comp, &unit); + if(sc != noErr){ + WARN("AudioComponentInstanceNew failed: %x\n", (int)sc); + return NULL; + } + + if(dataflow == eCapture){ + UInt32 enableio; + + enableio = 1; + sc = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, + kAudioUnitScope_Input, 1, &enableio, sizeof(enableio)); + if(sc != noErr){ + WARN("Couldn't enable I/O on input element: %x\n", (int)sc); + AudioComponentInstanceDispose(unit); + return NULL; + } + + enableio = 0; + sc = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, + kAudioUnitScope_Output, 0, &enableio, sizeof(enableio)); + if(sc != noErr){ + WARN("Couldn't disable I/O on output element: %x\n", (int)sc); + AudioComponentInstanceDispose(unit); + return NULL; + } + } + + sc = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, 0, &adevid, sizeof(adevid)); + if(sc != noErr){ + WARN("Couldn't set audio unit device\n"); + AudioComponentInstanceDispose(unit); + return NULL; + } + + return unit; +} + +static void dump_adesc(const char *aux, AudioStreamBasicDescription *desc) +{ + TRACE("%s: mSampleRate: %f\n", aux, desc->mSampleRate); + TRACE("%s: mBytesPerPacket: %u\n", aux, (unsigned int)desc->mBytesPerPacket); + TRACE("%s: mFramesPerPacket: %u\n", aux, (unsigned int)desc->mFramesPerPacket); + TRACE("%s: mBytesPerFrame: %u\n", aux, (unsigned int)desc->mBytesPerFrame); + TRACE("%s: mChannelsPerFrame: %u\n", aux, (unsigned int)desc->mChannelsPerFrame); + TRACE("%s: mBitsPerChannel: %u\n", aux, (unsigned int)desc->mBitsPerChannel); +} + +static HRESULT ca_get_audiodesc(AudioStreamBasicDescription *desc, + const WAVEFORMATEX *fmt) +{ + const WAVEFORMATEXTENSIBLE *fmtex = (const WAVEFORMATEXTENSIBLE *)fmt; + + desc->mFormatFlags = 0; + + if(fmt->wFormatTag == WAVE_FORMAT_PCM || + (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + IsEqualGUID(&fmtex->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM))){ + desc->mFormatID = kAudioFormatLinearPCM; + if(fmt->wBitsPerSample > 8) + desc->mFormatFlags = kAudioFormatFlagIsSignedInteger; + }else if(fmt->wFormatTag == WAVE_FORMAT_IEEE_FLOAT || + (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + IsEqualGUID(&fmtex->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))){ + desc->mFormatID = kAudioFormatLinearPCM; + desc->mFormatFlags = kAudioFormatFlagIsFloat; + }else if(fmt->wFormatTag == WAVE_FORMAT_MULAW || + (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + IsEqualGUID(&fmtex->SubFormat, &KSDATAFORMAT_SUBTYPE_MULAW))){ + desc->mFormatID = kAudioFormatULaw; + }else if(fmt->wFormatTag == WAVE_FORMAT_ALAW || + (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + IsEqualGUID(&fmtex->SubFormat, &KSDATAFORMAT_SUBTYPE_ALAW))){ + desc->mFormatID = kAudioFormatALaw; + }else + return AUDCLNT_E_UNSUPPORTED_FORMAT; + + desc->mSampleRate = fmt->nSamplesPerSec; + desc->mBytesPerPacket = fmt->nBlockAlign; + desc->mFramesPerPacket = 1; + desc->mBytesPerFrame = fmt->nBlockAlign; + desc->mChannelsPerFrame = fmt->nChannels; + desc->mBitsPerChannel = fmt->wBitsPerSample; + desc->mReserved = 0; + + return S_OK; +} + +static HRESULT ca_setup_audiounit(EDataFlow dataflow, AudioComponentInstance unit, + const WAVEFORMATEX *fmt, AudioStreamBasicDescription *dev_desc, + AudioConverterRef *converter) +{ + OSStatus sc; + HRESULT hr; + + if(dataflow == eCapture){ + AudioStreamBasicDescription desc; + UInt32 size; + Float64 rate; + fenv_t fenv; + BOOL fenv_stored = TRUE; + + hr = ca_get_audiodesc(&desc, fmt); + if(FAILED(hr)) + return hr; + dump_adesc("requested", &desc); + + /* input-only units can't perform sample rate conversion, so we have to + * set up our own AudioConverter to support arbitrary sample rates. */ + size = sizeof(*dev_desc); + sc = AudioUnitGetProperty(unit, kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, 1, dev_desc, &size); + if(sc != noErr){ + WARN("Couldn't get unit format: %x\n", (int)sc); + return osstatus_to_hresult(sc); + } + dump_adesc("hardware", dev_desc); + + rate = dev_desc->mSampleRate; + *dev_desc = desc; + dev_desc->mSampleRate = rate; + + dump_adesc("final", dev_desc); + sc = AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Output, 1, dev_desc, sizeof(*dev_desc)); + if(sc != noErr){ + WARN("Couldn't set unit format: %x\n", (int)sc); + return osstatus_to_hresult(sc); + } + + /* AudioConverterNew requires divide-by-zero SSE exceptions to be masked */ + if(feholdexcept(&fenv)){ + WARN("Failed to store fenv state\n"); + fenv_stored = FALSE; + } + + sc = AudioConverterNew(dev_desc, &desc, converter); + + if(fenv_stored && fesetenv(&fenv)) + WARN("Failed to restore fenv state\n"); + + if(sc != noErr){ + WARN("Couldn't create audio converter: %x\n", (int)sc); + return osstatus_to_hresult(sc); + } + }else{ + hr = ca_get_audiodesc(dev_desc, fmt); + if(FAILED(hr)) + return hr; + + dump_adesc("final", dev_desc); + sc = AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, 0, dev_desc, sizeof(*dev_desc)); + if(sc != noErr){ + WARN("Couldn't set format: %x\n", (int)sc); + return osstatus_to_hresult(sc); + } + } + + return S_OK; +} + +static ULONG_PTR zero_bits(void) +{ +#ifdef _WIN64 + return !NtCurrentTeb()->WowTebOffset ? 0 : 0x7fffffff; +#else + return 0; +#endif +} + +static AudioDeviceID dev_id_from_device(const char *device) +{ + return strtoul(device, NULL, 10); +} + +static NTSTATUS unix_create_stream(void *args) +{ + struct create_stream_params *params = args; + struct coreaudio_stream *stream = calloc(1, sizeof(*stream)); + AURenderCallbackStruct input; + OSStatus sc; + SIZE_T size; + + if(!stream){ + params->result = E_OUTOFMEMORY; + return STATUS_SUCCESS; + } + + stream->fmt = clone_format(params->fmt); + if(!stream->fmt){ + params->result = E_OUTOFMEMORY; + goto end; + } + + stream->period_ms = params->period / 10000; + stream->period_frames = muldiv(params->period, stream->fmt->nSamplesPerSec, 10000000); + stream->dev_id = dev_id_from_device(params->device); + stream->flow = params->flow; + stream->share = params->share; + + stream->bufsize_frames = muldiv(params->duration, stream->fmt->nSamplesPerSec, 10000000); + if(params->share == AUDCLNT_SHAREMODE_EXCLUSIVE) + stream->bufsize_frames -= stream->bufsize_frames % stream->period_frames; + + if(!(stream->unit = get_audiounit(stream->flow, stream->dev_id))){ + params->result = AUDCLNT_E_DEVICE_INVALIDATED; + goto end; + } + + params->result = ca_setup_audiounit(stream->flow, stream->unit, stream->fmt, &stream->dev_desc, &stream->converter); + if(FAILED(params->result)) goto end; + + input.inputProcRefCon = stream; + if(stream->flow == eCapture){ + input.inputProc = ca_capture_cb; + sc = AudioUnitSetProperty(stream->unit, kAudioOutputUnitProperty_SetInputCallback, + kAudioUnitScope_Output, 1, &input, sizeof(input)); + }else{ + input.inputProc = ca_render_cb; + sc = AudioUnitSetProperty(stream->unit, kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Input, 0, &input, sizeof(input)); + } + if(sc != noErr){ + WARN("Couldn't set callback: %x\n", (int)sc); + params->result = osstatus_to_hresult(sc); + goto end; + } + + sc = AudioUnitInitialize(stream->unit); + if(sc != noErr){ + WARN("Couldn't initialize: %x\n", (int)sc); + params->result = osstatus_to_hresult(sc); + goto end; + } + + /* we play audio continuously because AudioOutputUnitStart sometimes takes + * a while to return */ + sc = AudioOutputUnitStart(stream->unit); + if(sc != noErr){ + WARN("Unit failed to start: %x\n", (int)sc); + params->result = osstatus_to_hresult(sc); + goto end; + } + + size = stream->bufsize_frames * stream->fmt->nBlockAlign; + if(NtAllocateVirtualMemory(GetCurrentProcess(), (void **)&stream->local_buffer, zero_bits(), + &size, MEM_COMMIT, PAGE_READWRITE)){ + params->result = E_OUTOFMEMORY; + goto end; + } + silence_buffer(stream, stream->local_buffer, stream->bufsize_frames); + + if(stream->flow == eCapture){ + stream->cap_bufsize_frames = muldiv(params->duration, stream->dev_desc.mSampleRate, 10000000); + stream->cap_buffer = malloc(stream->cap_bufsize_frames * stream->fmt->nBlockAlign); + } + params->result = S_OK; + +end: + if(FAILED(params->result)){ + if(stream->converter) AudioConverterDispose(stream->converter); + if(stream->unit) AudioComponentInstanceDispose(stream->unit); + free(stream->fmt); + free(stream); + } else + *params->stream = (stream_handle)(UINT_PTR)stream; + + return STATUS_SUCCESS; +} + +static NTSTATUS unix_release_stream( void *args ) +{ + struct release_stream_params *params = args; + struct coreaudio_stream *stream = handle_get_stream(params->stream); + SIZE_T size; + + if(stream->unit){ + AudioOutputUnitStop(stream->unit); + AudioComponentInstanceDispose(stream->unit); + } + + if(stream->converter) AudioConverterDispose(stream->converter); + free(stream->resamp_buffer); + free(stream->wrap_buffer); + free(stream->cap_buffer); + if(stream->local_buffer){ + size = 0; + NtFreeVirtualMemory(GetCurrentProcess(), (void **)&stream->local_buffer, + &size, MEM_RELEASE); + } + if(stream->tmp_buffer){ + size = 0; + NtFreeVirtualMemory(GetCurrentProcess(), (void **)&stream->tmp_buffer, + &size, MEM_RELEASE); + } + free(stream->fmt); + free(stream); + params->result = S_OK; + return STATUS_SUCCESS; +} + +static UINT ca_channel_layout_to_channel_mask(const AudioChannelLayout *layout) +{ + int i; + UINT mask = 0; + + for (i = 0; i < layout->mNumberChannelDescriptions; ++i) { + switch (layout->mChannelDescriptions[i].mChannelLabel) { + default: FIXME("Unhandled channel 0x%x\n", + (unsigned int)layout->mChannelDescriptions[i].mChannelLabel); break; + case kAudioChannelLabel_Left: mask |= SPEAKER_FRONT_LEFT; break; + case kAudioChannelLabel_Mono: + case kAudioChannelLabel_Center: mask |= SPEAKER_FRONT_CENTER; break; + case kAudioChannelLabel_Right: mask |= SPEAKER_FRONT_RIGHT; break; + case kAudioChannelLabel_LeftSurround: mask |= SPEAKER_BACK_LEFT; break; + case kAudioChannelLabel_CenterSurround: mask |= SPEAKER_BACK_CENTER; break; + case kAudioChannelLabel_RightSurround: mask |= SPEAKER_BACK_RIGHT; break; + case kAudioChannelLabel_LFEScreen: mask |= SPEAKER_LOW_FREQUENCY; break; + case kAudioChannelLabel_LeftSurroundDirect: mask |= SPEAKER_SIDE_LEFT; break; + case kAudioChannelLabel_RightSurroundDirect: mask |= SPEAKER_SIDE_RIGHT; break; + case kAudioChannelLabel_TopCenterSurround: mask |= SPEAKER_TOP_CENTER; break; + case kAudioChannelLabel_VerticalHeightLeft: mask |= SPEAKER_TOP_FRONT_LEFT; break; + case kAudioChannelLabel_VerticalHeightCenter: mask |= SPEAKER_TOP_FRONT_CENTER; break; + case kAudioChannelLabel_VerticalHeightRight: mask |= SPEAKER_TOP_FRONT_RIGHT; break; + case kAudioChannelLabel_TopBackLeft: mask |= SPEAKER_TOP_BACK_LEFT; break; + case kAudioChannelLabel_TopBackCenter: mask |= SPEAKER_TOP_BACK_CENTER; break; + case kAudioChannelLabel_TopBackRight: mask |= SPEAKER_TOP_BACK_RIGHT; break; + case kAudioChannelLabel_LeftCenter: mask |= SPEAKER_FRONT_LEFT_OF_CENTER; break; + case kAudioChannelLabel_RightCenter: mask |= SPEAKER_FRONT_RIGHT_OF_CENTER; break; + } + } + + return mask; +} + +/* For most hardware on Windows, users must choose a configuration with an even + * number of channels (stereo, quad, 5.1, 7.1). Users can then disable + * channels, but those channels are still reported to applications from + * GetMixFormat! Some applications behave badly if given an odd number of + * channels (e.g. 2.1). Here, we find the nearest configuration that Windows + * would report for a given channel layout. */ +static void convert_channel_layout(const AudioChannelLayout *ca_layout, WAVEFORMATEXTENSIBLE *fmt) +{ + UINT ca_mask = ca_channel_layout_to_channel_mask(ca_layout); + + TRACE("Got channel mask for CA: 0x%x\n", ca_mask); + + if (ca_layout->mNumberChannelDescriptions == 1) + { + fmt->Format.nChannels = 1; + fmt->dwChannelMask = ca_mask; + return; + } + + /* compare against known configurations and find smallest configuration + * which is a superset of the given speakers */ + + if (ca_layout->mNumberChannelDescriptions <= 2 && + (ca_mask & ~KSAUDIO_SPEAKER_STEREO) == 0) + { + fmt->Format.nChannels = 2; + fmt->dwChannelMask = KSAUDIO_SPEAKER_STEREO; + return; + } + + if (ca_layout->mNumberChannelDescriptions <= 4 && + (ca_mask & ~KSAUDIO_SPEAKER_QUAD) == 0) + { + fmt->Format.nChannels = 4; + fmt->dwChannelMask = KSAUDIO_SPEAKER_QUAD; + return; + } + + if (ca_layout->mNumberChannelDescriptions <= 4 && + (ca_mask & ~KSAUDIO_SPEAKER_SURROUND) == 0) + { + fmt->Format.nChannels = 4; + fmt->dwChannelMask = KSAUDIO_SPEAKER_SURROUND; + return; + } + + if (ca_layout->mNumberChannelDescriptions <= 6 && + (ca_mask & ~KSAUDIO_SPEAKER_5POINT1) == 0) + { + fmt->Format.nChannels = 6; + fmt->dwChannelMask = KSAUDIO_SPEAKER_5POINT1; + return; + } + + if (ca_layout->mNumberChannelDescriptions <= 6 && + (ca_mask & ~KSAUDIO_SPEAKER_5POINT1_SURROUND) == 0) + { + fmt->Format.nChannels = 6; + fmt->dwChannelMask = KSAUDIO_SPEAKER_5POINT1_SURROUND; + return; + } + + if (ca_layout->mNumberChannelDescriptions <= 8 && + (ca_mask & ~KSAUDIO_SPEAKER_7POINT1) == 0) + { + fmt->Format.nChannels = 8; + fmt->dwChannelMask = KSAUDIO_SPEAKER_7POINT1; + return; + } + + if (ca_layout->mNumberChannelDescriptions <= 8 && + (ca_mask & ~KSAUDIO_SPEAKER_7POINT1_SURROUND) == 0) + { + fmt->Format.nChannels = 8; + fmt->dwChannelMask = KSAUDIO_SPEAKER_7POINT1_SURROUND; + return; + } + + /* oddball format, report truthfully */ + fmt->Format.nChannels = ca_layout->mNumberChannelDescriptions; + fmt->dwChannelMask = ca_mask; +} + +static DWORD get_channel_mask(unsigned int channels) +{ + switch(channels){ + case 0: + return 0; + case 1: + return KSAUDIO_SPEAKER_MONO; + case 2: + return KSAUDIO_SPEAKER_STEREO; + case 3: + return KSAUDIO_SPEAKER_STEREO | SPEAKER_LOW_FREQUENCY; + case 4: + return KSAUDIO_SPEAKER_QUAD; /* not _SURROUND */ + case 5: + return KSAUDIO_SPEAKER_QUAD | SPEAKER_LOW_FREQUENCY; + case 6: + return KSAUDIO_SPEAKER_5POINT1; /* not 5POINT1_SURROUND */ + case 7: + return KSAUDIO_SPEAKER_5POINT1 | SPEAKER_BACK_CENTER; + case 8: + return KSAUDIO_SPEAKER_7POINT1_SURROUND; /* Vista deprecates 7POINT1 */ + } + FIXME("Unknown speaker configuration: %u\n", channels); + return 0; +} + +static NTSTATUS unix_get_mix_format(void *args) +{ + struct get_mix_format_params *params = args; + AudioObjectPropertyAddress addr; + AudioChannelLayout *layout; + AudioBufferList *buffers; + Float64 rate; + UInt32 size; + OSStatus sc; + int i; + const AudioDeviceID dev_id = dev_id_from_device(params->device); + + params->fmt->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + + addr.mScope = get_scope(params->flow); + addr.mElement = 0; + addr.mSelector = kAudioDevicePropertyPreferredChannelLayout; + + sc = AudioObjectGetPropertyDataSize(dev_id, &addr, 0, NULL, &size); + if(sc == noErr){ + layout = malloc(size); + sc = AudioObjectGetPropertyData(dev_id, &addr, 0, NULL, &size, layout); + if(sc == noErr){ + TRACE("Got channel layout: {tag: 0x%x, bitmap: 0x%x, num_descs: %u}\n", + (unsigned int)layout->mChannelLayoutTag, (unsigned int)layout->mChannelBitmap, + (unsigned int)layout->mNumberChannelDescriptions); + + if(layout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelDescriptions){ + convert_channel_layout(layout, params->fmt); + }else{ + WARN("Haven't implemented support for this layout tag: 0x%x, guessing at layout\n", + (unsigned int)layout->mChannelLayoutTag); + params->fmt->Format.nChannels = 0; + } + }else{ + TRACE("Unable to get _PreferredChannelLayout property: %x, guessing at layout\n", (int)sc); + params->fmt->Format.nChannels = 0; + } + + free(layout); + }else{ + TRACE("Unable to get size for _PreferredChannelLayout property: %x, guessing at layout\n", (int)sc); + params->fmt->Format.nChannels = 0; + } + + if(params->fmt->Format.nChannels == 0){ + addr.mScope = get_scope(params->flow); + addr.mElement = 0; + addr.mSelector = kAudioDevicePropertyStreamConfiguration; + + sc = AudioObjectGetPropertyDataSize(dev_id, &addr, 0, NULL, &size); + if(sc != noErr){ + WARN("Unable to get size for _StreamConfiguration property: %x\n", (int)sc); + params->result = osstatus_to_hresult(sc); + return STATUS_SUCCESS; + } + + buffers = malloc(size); + if(!buffers){ + params->result = E_OUTOFMEMORY; + return STATUS_SUCCESS; + } + + sc = AudioObjectGetPropertyData(dev_id, &addr, 0, NULL, &size, buffers); + if(sc != noErr){ + free(buffers); + WARN("Unable to get _StreamConfiguration property: %x\n", (int)sc); + params->result = osstatus_to_hresult(sc); + return STATUS_SUCCESS; + } + + for(i = 0; i < buffers->mNumberBuffers; ++i) + params->fmt->Format.nChannels += buffers->mBuffers[i].mNumberChannels; + + free(buffers); + + params->fmt->dwChannelMask = get_channel_mask(params->fmt->Format.nChannels); + } + + addr.mSelector = kAudioDevicePropertyNominalSampleRate; + size = sizeof(Float64); + sc = AudioObjectGetPropertyData(dev_id, &addr, 0, NULL, &size, &rate); + if(sc != noErr){ + WARN("Unable to get _NominalSampleRate property: %x\n", (int)sc); + params->result = osstatus_to_hresult(sc); + return STATUS_SUCCESS; + } + params->fmt->Format.nSamplesPerSec = rate; + + params->fmt->Format.wBitsPerSample = 32; + params->fmt->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + + params->fmt->Format.nBlockAlign = (params->fmt->Format.wBitsPerSample * + params->fmt->Format.nChannels) / 8; + params->fmt->Format.nAvgBytesPerSec = params->fmt->Format.nSamplesPerSec * + params->fmt->Format.nBlockAlign; + + params->fmt->Samples.wValidBitsPerSample = params->fmt->Format.wBitsPerSample; + params->fmt->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); + params->result = S_OK; + return STATUS_SUCCESS; +} + +static NTSTATUS unix_is_format_supported(void *args) +{ + struct is_format_supported_params *params = args; + const WAVEFORMATEXTENSIBLE *fmtex = (const WAVEFORMATEXTENSIBLE *)params->fmt_in; + AudioStreamBasicDescription dev_desc; + AudioConverterRef converter; + AudioComponentInstance unit; + const AudioDeviceID dev_id = dev_id_from_device(params->device); + + params->result = S_OK; + + if(!params->fmt_in || (params->share == AUDCLNT_SHAREMODE_SHARED && !params->fmt_out)) + params->result = E_POINTER; + else if(params->share != AUDCLNT_SHAREMODE_SHARED && params->share != AUDCLNT_SHAREMODE_EXCLUSIVE) + params->result = E_INVALIDARG; + else if(params->fmt_in->wFormatTag == WAVE_FORMAT_EXTENSIBLE){ + if(params->fmt_in->cbSize < sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX)) + params->result = E_INVALIDARG; + else if(params->fmt_in->nAvgBytesPerSec == 0 || params->fmt_in->nBlockAlign == 0 || + fmtex->Samples.wValidBitsPerSample > params->fmt_in->wBitsPerSample) + params->result = E_INVALIDARG; + else if(fmtex->Samples.wValidBitsPerSample < params->fmt_in->wBitsPerSample) + goto unsupported; + else if(params->share == AUDCLNT_SHAREMODE_EXCLUSIVE && + (fmtex->dwChannelMask == 0 || fmtex->dwChannelMask & SPEAKER_RESERVED)) + goto unsupported; + } + if(FAILED(params->result)) return STATUS_SUCCESS; + + if(params->fmt_in->nBlockAlign != params->fmt_in->nChannels * params->fmt_in->wBitsPerSample / 8 || + params->fmt_in->nAvgBytesPerSec != params->fmt_in->nBlockAlign * params->fmt_in->nSamplesPerSec) + goto unsupported; + + if(params->fmt_in->nChannels == 0){ + params->result = AUDCLNT_E_UNSUPPORTED_FORMAT; + return STATUS_SUCCESS; + } + unit = get_audiounit(params->flow, dev_id); + + converter = NULL; + params->result = ca_setup_audiounit(params->flow, unit, params->fmt_in, &dev_desc, &converter); + AudioComponentInstanceDispose(unit); + if(FAILED(params->result)) goto unsupported; + if(converter) AudioConverterDispose(converter); + + params->result = S_OK; + return STATUS_SUCCESS; + +unsupported: + if(params->fmt_out){ + struct get_mix_format_params get_mix_params = + { + .device = params->device, + .flow = params->flow, + .fmt = params->fmt_out, + }; + + unix_get_mix_format(&get_mix_params); + params->result = get_mix_params.result; + if(SUCCEEDED(params->result)) params->result = S_FALSE; + } + else params->result = AUDCLNT_E_UNSUPPORTED_FORMAT; + return STATUS_SUCCESS; +} + +static UINT buf_ptr_diff(UINT left, UINT right, UINT bufsize) +{ + if(left <= right) + return right - left; + return bufsize - (left - right); +} + +/* place data from cap_buffer into provided AudioBufferList */ +static OSStatus feed_cb(AudioConverterRef converter, UInt32 *nframes, AudioBufferList *data, + AudioStreamPacketDescription **packets, void *user) +{ + struct coreaudio_stream *stream = user; + + *nframes = min(*nframes, stream->cap_held_frames); + if(!*nframes){ + data->mBuffers[0].mData = NULL; + data->mBuffers[0].mDataByteSize = 0; + data->mBuffers[0].mNumberChannels = stream->fmt->nChannels; + return noErr; + } + + data->mBuffers[0].mDataByteSize = *nframes * stream->fmt->nBlockAlign; + data->mBuffers[0].mNumberChannels = stream->fmt->nChannels; + + if(stream->cap_offs_frames + *nframes > stream->cap_bufsize_frames){ + UINT32 chunk_frames = stream->cap_bufsize_frames - stream->cap_offs_frames; + + if(stream->wrap_bufsize_frames < *nframes){ + free(stream->wrap_buffer); + stream->wrap_buffer = malloc(data->mBuffers[0].mDataByteSize); + stream->wrap_bufsize_frames = *nframes; + } + + memcpy(stream->wrap_buffer, stream->cap_buffer + stream->cap_offs_frames * stream->fmt->nBlockAlign, + chunk_frames * stream->fmt->nBlockAlign); + memcpy(stream->wrap_buffer + chunk_frames * stream->fmt->nBlockAlign, stream->cap_buffer, + (*nframes - chunk_frames) * stream->fmt->nBlockAlign); + + data->mBuffers[0].mData = stream->wrap_buffer; + }else + data->mBuffers[0].mData = stream->cap_buffer + stream->cap_offs_frames * stream->fmt->nBlockAlign; + + stream->cap_offs_frames += *nframes; + stream->cap_offs_frames %= stream->cap_bufsize_frames; + stream->cap_held_frames -= *nframes; + + if(packets) + *packets = NULL; + + return noErr; +} + +static void capture_resample(struct coreaudio_stream *stream) +{ + UINT32 resamp_period_frames = muldiv(stream->period_frames, stream->dev_desc.mSampleRate, + stream->fmt->nSamplesPerSec); + OSStatus sc; + + /* the resampling process often needs more source frames than we'd + * guess from a straight conversion using the sample rate ratio. so + * only convert if we have extra source data. */ + while(stream->cap_held_frames > resamp_period_frames * 2){ + AudioBufferList converted_list; + UInt32 wanted_frames = stream->period_frames; + + converted_list.mNumberBuffers = 1; + converted_list.mBuffers[0].mNumberChannels = stream->fmt->nChannels; + converted_list.mBuffers[0].mDataByteSize = wanted_frames * stream->fmt->nBlockAlign; + + if(stream->resamp_bufsize_frames < wanted_frames){ + free(stream->resamp_buffer); + stream->resamp_buffer = malloc(converted_list.mBuffers[0].mDataByteSize); + stream->resamp_bufsize_frames = wanted_frames; + } + + converted_list.mBuffers[0].mData = stream->resamp_buffer; + + sc = AudioConverterFillComplexBuffer(stream->converter, feed_cb, + stream, &wanted_frames, &converted_list, NULL); + if(sc != noErr){ + WARN("AudioConverterFillComplexBuffer failed: %x\n", (int)sc); + break; + } + + ca_wrap_buffer(stream->local_buffer, + stream->wri_offs_frames * stream->fmt->nBlockAlign, + stream->bufsize_frames * stream->fmt->nBlockAlign, + stream->resamp_buffer, wanted_frames * stream->fmt->nBlockAlign); + + stream->wri_offs_frames += wanted_frames; + stream->wri_offs_frames %= stream->bufsize_frames; + if(stream->held_frames + wanted_frames > stream->bufsize_frames){ + stream->lcl_offs_frames += buf_ptr_diff(stream->lcl_offs_frames, stream->wri_offs_frames, + stream->bufsize_frames); + stream->held_frames = stream->bufsize_frames; + }else + stream->held_frames += wanted_frames; + } +} + +static NTSTATUS unix_get_buffer_size(void *args) +{ + struct get_buffer_size_params *params = args; + struct coreaudio_stream *stream = handle_get_stream(params->stream); + + OSSpinLockLock(&stream->lock); + *params->frames = stream->bufsize_frames; + OSSpinLockUnlock(&stream->lock); + params->result = S_OK; + return STATUS_SUCCESS; +} + +static HRESULT ca_get_max_stream_latency(struct coreaudio_stream *stream, UInt32 *max) +{ + AudioObjectPropertyAddress addr; + AudioStreamID *ids; + UInt32 size; + OSStatus sc; + int nstreams, i; + + addr.mScope = get_scope(stream->flow); + addr.mElement = 0; + addr.mSelector = kAudioDevicePropertyStreams; + + sc = AudioObjectGetPropertyDataSize(stream->dev_id, &addr, 0, NULL, &size); + if(sc != noErr){ + WARN("Unable to get size for _Streams property: %x\n", (int)sc); + return osstatus_to_hresult(sc); + } + + ids = malloc(size); + if(!ids) + return E_OUTOFMEMORY; + + sc = AudioObjectGetPropertyData(stream->dev_id, &addr, 0, NULL, &size, ids); + if(sc != noErr){ + WARN("Unable to get _Streams property: %x\n", (int)sc); + free(ids); + return osstatus_to_hresult(sc); + } + + nstreams = size / sizeof(AudioStreamID); + *max = 0; + + addr.mSelector = kAudioStreamPropertyLatency; + for(i = 0; i < nstreams; ++i){ + UInt32 latency; + + size = sizeof(latency); + sc = AudioObjectGetPropertyData(ids[i], &addr, 0, NULL, &size, &latency); + if(sc != noErr){ + WARN("Unable to get _Latency property: %x\n", (int)sc); + continue; + } + + if(latency > *max) + *max = latency; + } + + free(ids); + + return S_OK; +} + +static NTSTATUS unix_get_latency(void *args) +{ + struct get_latency_params *params = args; + struct coreaudio_stream *stream = handle_get_stream(params->stream); + UInt32 latency, stream_latency, size; + AudioObjectPropertyAddress addr; + OSStatus sc; + + OSSpinLockLock(&stream->lock); + + addr.mScope = get_scope(stream->flow); + addr.mSelector = kAudioDevicePropertyLatency; + addr.mElement = 0; + + size = sizeof(latency); + sc = AudioObjectGetPropertyData(stream->dev_id, &addr, 0, NULL, &size, &latency); + if(sc != noErr){ + WARN("Couldn't get _Latency property: %x\n", (int)sc); + OSSpinLockUnlock(&stream->lock); + params->result = osstatus_to_hresult(sc); + return STATUS_SUCCESS; + } + + params->result = ca_get_max_stream_latency(stream, &stream_latency); + if(FAILED(params->result)){ + OSSpinLockUnlock(&stream->lock); + return STATUS_SUCCESS; + } + + latency += stream_latency; + /* pretend we process audio in Period chunks, so max latency includes + * the period time */ + *params->latency = muldiv(latency, 10000000, stream->fmt->nSamplesPerSec) + + stream->period_ms * 10000; + + OSSpinLockUnlock(&stream->lock); + params->result = S_OK; + return STATUS_SUCCESS; +} + +static UINT32 get_current_padding_nolock(struct coreaudio_stream *stream) +{ + if(stream->flow == eCapture) capture_resample(stream); + return stream->held_frames; +} + +static NTSTATUS unix_get_current_padding(void *args) +{ + struct get_current_padding_params *params = args; + struct coreaudio_stream *stream = handle_get_stream(params->stream); + + OSSpinLockLock(&stream->lock); + *params->padding = get_current_padding_nolock(stream); + OSSpinLockUnlock(&stream->lock); + params->result = S_OK; + return STATUS_SUCCESS; +} + +static NTSTATUS unix_start(void *args) +{ + struct start_params *params = args; + struct coreaudio_stream *stream = handle_get_stream(params->stream); + + OSSpinLockLock(&stream->lock); + + if(stream->playing) + params->result = AUDCLNT_E_NOT_STOPPED; + else{ + stream->playing = TRUE; + params->result = S_OK; + } + + OSSpinLockUnlock(&stream->lock); + + return STATUS_SUCCESS; +} + +static NTSTATUS unix_stop(void *args) +{ + struct stop_params *params = args; + struct coreaudio_stream *stream = handle_get_stream(params->stream); + + OSSpinLockLock(&stream->lock); + + if(!stream->playing) + params->result = S_FALSE; + else{ + stream->playing = FALSE; + params->result = S_OK; + } + + OSSpinLockUnlock(&stream->lock); + + return STATUS_SUCCESS; +} + +static NTSTATUS unix_reset(void *args) +{ + struct reset_params *params = args; + struct coreaudio_stream *stream = handle_get_stream(params->stream); + + OSSpinLockLock(&stream->lock); + + if(stream->playing) + params->result = AUDCLNT_E_NOT_STOPPED; + else if(stream->getbuf_last) + params->result = AUDCLNT_E_BUFFER_OPERATION_PENDING; + else{ + if(stream->flow == eRender) + stream->written_frames = 0; + else + stream->written_frames += stream->held_frames; + stream->held_frames = 0; + stream->lcl_offs_frames = 0; + stream->wri_offs_frames = 0; + stream->cap_offs_frames = 0; + stream->cap_held_frames = 0; + params->result = S_OK; + } + + OSSpinLockUnlock(&stream->lock); + return STATUS_SUCCESS; +} + +static NTSTATUS unix_get_render_buffer(void *args) +{ + struct get_render_buffer_params *params = args; + struct coreaudio_stream *stream = handle_get_stream(params->stream); + SIZE_T size; + UINT32 pad; + + OSSpinLockLock(&stream->lock); + + pad = get_current_padding_nolock(stream); + + if(stream->getbuf_last){ + params->result = AUDCLNT_E_OUT_OF_ORDER; + goto end; + } + if(!params->frames){ + params->result = S_OK; + goto end; + } + if(pad + params->frames > stream->bufsize_frames){ + params->result = AUDCLNT_E_BUFFER_TOO_LARGE; + goto end; + } + + if(stream->wri_offs_frames + params->frames > stream->bufsize_frames){ + if(stream->tmp_buffer_frames < params->frames){ + if(stream->tmp_buffer){ + size = 0; + NtFreeVirtualMemory(GetCurrentProcess(), (void **)&stream->tmp_buffer, + &size, MEM_RELEASE); + stream->tmp_buffer = NULL; + } + size = params->frames * stream->fmt->nBlockAlign; + if(NtAllocateVirtualMemory(GetCurrentProcess(), (void **)&stream->tmp_buffer, zero_bits(), + &size, MEM_COMMIT, PAGE_READWRITE)){ + stream->tmp_buffer_frames = 0; + params->result = E_OUTOFMEMORY; + goto end; + } + stream->tmp_buffer_frames = params->frames; + } + *params->data = stream->tmp_buffer; + stream->getbuf_last = -params->frames; + }else{ + *params->data = stream->local_buffer + stream->wri_offs_frames * stream->fmt->nBlockAlign; + stream->getbuf_last = params->frames; + } + + silence_buffer(stream, *params->data, params->frames); + params->result = S_OK; + +end: + OSSpinLockUnlock(&stream->lock); + + return STATUS_SUCCESS; +} + +static NTSTATUS unix_release_render_buffer(void *args) +{ + struct release_render_buffer_params *params = args; + struct coreaudio_stream *stream = handle_get_stream(params->stream); + BYTE *buffer; + + OSSpinLockLock(&stream->lock); + + if(!params->written_frames){ + stream->getbuf_last = 0; + params->result = S_OK; + }else if(!stream->getbuf_last) + params->result = AUDCLNT_E_OUT_OF_ORDER; + else if(params->written_frames > (stream->getbuf_last >= 0 ? stream->getbuf_last : -stream->getbuf_last)) + params->result = AUDCLNT_E_INVALID_SIZE; + else{ + if(stream->getbuf_last >= 0) + buffer = stream->local_buffer + stream->wri_offs_frames * stream->fmt->nBlockAlign; + else + buffer = stream->tmp_buffer; + + if(params->flags & AUDCLNT_BUFFERFLAGS_SILENT) + silence_buffer(stream, buffer, params->written_frames); + + if(stream->getbuf_last < 0) + ca_wrap_buffer(stream->local_buffer, + stream->wri_offs_frames * stream->fmt->nBlockAlign, + stream->bufsize_frames * stream->fmt->nBlockAlign, + buffer, params->written_frames * stream->fmt->nBlockAlign); + + stream->wri_offs_frames += params->written_frames; + stream->wri_offs_frames %= stream->bufsize_frames; + stream->held_frames += params->written_frames; + stream->written_frames += params->written_frames; + stream->getbuf_last = 0; + + params->result = S_OK; + } + + OSSpinLockUnlock(&stream->lock); + + return STATUS_SUCCESS; +} + +static NTSTATUS unix_get_capture_buffer(void *args) +{ + struct get_capture_buffer_params *params = args; + struct coreaudio_stream *stream = handle_get_stream(params->stream); + UINT32 chunk_bytes, chunk_frames; + LARGE_INTEGER stamp, freq; + SIZE_T size; + + OSSpinLockLock(&stream->lock); + + if(stream->getbuf_last){ + params->result = AUDCLNT_E_OUT_OF_ORDER; + goto end; + } + + capture_resample(stream); + + *params->frames = 0; + + if(stream->held_frames < stream->period_frames){ + params->result = AUDCLNT_S_BUFFER_EMPTY; + goto end; + } + + *params->flags = 0; + chunk_frames = stream->bufsize_frames - stream->lcl_offs_frames; + if(chunk_frames < stream->period_frames){ + chunk_bytes = chunk_frames * stream->fmt->nBlockAlign; + if(!stream->tmp_buffer){ + size = stream->period_frames * stream->fmt->nBlockAlign; + NtAllocateVirtualMemory(GetCurrentProcess(), (void **)&stream->tmp_buffer, zero_bits(), + &size, MEM_COMMIT, PAGE_READWRITE); + } + *params->data = stream->tmp_buffer; + memcpy(*params->data, stream->local_buffer + stream->lcl_offs_frames * stream->fmt->nBlockAlign, + chunk_bytes); + memcpy(*params->data + chunk_bytes, stream->local_buffer, + stream->period_frames * stream->fmt->nBlockAlign - chunk_bytes); + }else + *params->data = stream->local_buffer + stream->lcl_offs_frames * stream->fmt->nBlockAlign; + + stream->getbuf_last = *params->frames = stream->period_frames; + + if(params->devpos) + *params->devpos = stream->written_frames; + if(params->qpcpos){ /* fixme: qpc of recording time */ + NtQueryPerformanceCounter(&stamp, &freq); + *params->qpcpos = (stamp.QuadPart * (INT64)10000000) / freq.QuadPart; + } + params->result = S_OK; + +end: + OSSpinLockUnlock(&stream->lock); + return STATUS_SUCCESS; +} + +static NTSTATUS unix_release_capture_buffer(void *args) +{ + struct release_capture_buffer_params *params = args; + struct coreaudio_stream *stream = handle_get_stream(params->stream); + + OSSpinLockLock(&stream->lock); + + if(!params->done){ + stream->getbuf_last = 0; + params->result = S_OK; + }else if(!stream->getbuf_last) + params->result = AUDCLNT_E_OUT_OF_ORDER; + else if(stream->getbuf_last != params->done) + params->result = AUDCLNT_E_INVALID_SIZE; + else{ + stream->written_frames += params->done; + stream->held_frames -= params->done; + stream->lcl_offs_frames += params->done; + stream->lcl_offs_frames %= stream->bufsize_frames; + stream->getbuf_last = 0; + params->result = S_OK; + } + + OSSpinLockUnlock(&stream->lock); + + return STATUS_SUCCESS; +} + +static NTSTATUS unix_get_next_packet_size(void *args) +{ + struct get_next_packet_size_params *params = args; + struct coreaudio_stream *stream = handle_get_stream(params->stream); + + OSSpinLockLock(&stream->lock); + + capture_resample(stream); + + if(stream->held_frames >= stream->period_frames) + *params->frames = stream->period_frames; + else + *params->frames = 0; + + OSSpinLockUnlock(&stream->lock); + + params->result = S_OK; + return STATUS_SUCCESS; +} + +static NTSTATUS unix_get_position(void *args) +{ + struct get_position_params *params = args; + struct coreaudio_stream *stream = handle_get_stream(params->stream); + LARGE_INTEGER stamp, freq; + + OSSpinLockLock(&stream->lock); + + *params->pos = stream->written_frames - stream->held_frames; + + if(stream->share == AUDCLNT_SHAREMODE_SHARED) + *params->pos *= stream->fmt->nBlockAlign; + + if(params->qpctime){ + NtQueryPerformanceCounter(&stamp, &freq); + *params->qpctime = (stamp.QuadPart * (INT64)10000000) / freq.QuadPart; + } + + OSSpinLockUnlock(&stream->lock); + + params->result = S_OK; + return STATUS_SUCCESS; +} + +static NTSTATUS unix_get_frequency(void *args) +{ + struct get_frequency_params *params = args; + struct coreaudio_stream *stream = handle_get_stream(params->stream); + + if(stream->share == AUDCLNT_SHAREMODE_SHARED) + *params->freq = (UINT64)stream->fmt->nSamplesPerSec * stream->fmt->nBlockAlign; + else + *params->freq = stream->fmt->nSamplesPerSec; + + params->result = S_OK; + return STATUS_SUCCESS; +} + +static NTSTATUS unix_is_started(void *args) +{ + struct is_started_params *params = args; + struct coreaudio_stream *stream = handle_get_stream(params->stream); + + if(stream->playing) + params->result = S_OK; + else + params->result = S_FALSE; + + return STATUS_SUCCESS; +} + +static NTSTATUS unix_set_volumes(void *args) +{ + struct set_volumes_params *params = args; + struct coreaudio_stream *stream = handle_get_stream(params->stream); + Float32 level = 1.0, tmp; + OSStatus sc; + UINT32 i; + + if(params->channel >= stream->fmt->nChannels || params->channel < -1){ + ERR("Incorrect channel %d\n", params->channel); + return STATUS_SUCCESS; + } + + if(params->channel == -1){ + for(i = 0; i < stream->fmt->nChannels; ++i){ + tmp = params->master_volume * params->volumes[i] * params->session_volumes[i]; + level = tmp < level ? tmp : level; + } + }else + level = params->master_volume * params->volumes[params->channel] * + params->session_volumes[params->channel]; + + sc = AudioUnitSetParameter(stream->unit, kHALOutputParam_Volume, + kAudioUnitScope_Global, 0, level, 0); + if(sc != noErr) + WARN("Couldn't set volume: %x\n", (int)sc); + + return STATUS_SUCCESS; +} + +unixlib_entry_t __wine_unix_call_funcs[] = +{ + NULL, + NULL, + NULL, + unix_get_endpoint_ids, + unix_create_stream, + unix_release_stream, + unix_start, + unix_stop, + unix_reset, + NULL, + unix_get_render_buffer, + unix_release_render_buffer, + unix_get_capture_buffer, + unix_release_capture_buffer, + unix_is_format_supported, + unix_get_mix_format, + NULL, + unix_get_buffer_size, + unix_get_latency, + unix_get_current_padding, + unix_get_next_packet_size, + unix_get_frequency, + unix_get_position, + unix_set_volumes, + NULL, + NULL, + unix_is_started, + NULL, + unix_midi_init, + unix_midi_release, + unix_midi_out_message, + unix_midi_in_message, + unix_midi_notify_wait, + NULL, +}; + +#ifdef _WIN64 + +typedef UINT PTR32; + +static NTSTATUS unix_wow64_get_endpoint_ids(void *args) +{ + struct + { + EDataFlow flow; + PTR32 endpoints; + unsigned int size; + HRESULT result; + unsigned int num; + unsigned int default_idx; + } *params32 = args; + struct get_endpoint_ids_params params = + { + .flow = params32->flow, + .endpoints = ULongToPtr(params32->endpoints), + .size = params32->size + }; + unix_get_endpoint_ids(¶ms); + params32->size = params.size; + params32->result = params.result; + params32->num = params.num; + params32->default_idx = params.default_idx; + return STATUS_SUCCESS; +} + +static NTSTATUS unix_wow64_create_stream(void *args) +{ + struct + { + PTR32 name; + PTR32 device; + EDataFlow flow; + AUDCLNT_SHAREMODE share; + DWORD flags; + REFERENCE_TIME duration; + REFERENCE_TIME period; + PTR32 fmt; + HRESULT result; + PTR32 channel_count; + PTR32 stream; + } *params32 = args; + struct create_stream_params params = + { + .name = ULongToPtr(params32->name), + .device = ULongToPtr(params32->device), + .flow = params32->flow, + .share = params32->share, + .flags = params32->flags, + .duration = params32->duration, + .period = params32->period, + .fmt = ULongToPtr(params32->fmt), + .channel_count = ULongToPtr(params32->channel_count), + .stream = ULongToPtr(params32->stream) + }; + unix_create_stream(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + +static NTSTATUS unix_wow64_release_stream(void *args) +{ + struct + { + stream_handle stream; + PTR32 timer_thread; + HRESULT result; + } *params32 = args; + struct release_stream_params params = + { + .stream = params32->stream, + .timer_thread = ULongToHandle(params32->timer_thread) + }; + unix_release_stream(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + +static NTSTATUS unix_wow64_get_render_buffer(void *args) +{ + struct + { + stream_handle stream; + UINT32 frames; + HRESULT result; + PTR32 data; + } *params32 = args; + BYTE *data = NULL; + struct get_render_buffer_params params = + { + .stream = params32->stream, + .frames = params32->frames, + .data = &data + }; + unix_get_render_buffer(¶ms); + params32->result = params.result; + *(unsigned int *)ULongToPtr(params32->data) = PtrToUlong(data); + return STATUS_SUCCESS; +} + +static NTSTATUS unix_wow64_get_capture_buffer(void *args) +{ + struct + { + stream_handle stream; + HRESULT result; + PTR32 data; + PTR32 frames; + PTR32 flags; + PTR32 devpos; + PTR32 qpcpos; + } *params32 = args; + BYTE *data = NULL; + struct get_capture_buffer_params params = + { + .stream = params32->stream, + .data = &data, + .frames = ULongToPtr(params32->frames), + .flags = ULongToPtr(params32->flags), + .devpos = ULongToPtr(params32->devpos), + .qpcpos = ULongToPtr(params32->qpcpos) + }; + unix_get_capture_buffer(¶ms); + params32->result = params.result; + *(unsigned int *)ULongToPtr(params32->data) = PtrToUlong(data); + return STATUS_SUCCESS; +}; + +static NTSTATUS unix_wow64_is_format_supported(void *args) +{ + struct + { + PTR32 device; + EDataFlow flow; + AUDCLNT_SHAREMODE share; + PTR32 fmt_in; + PTR32 fmt_out; + HRESULT result; + } *params32 = args; + struct is_format_supported_params params = + { + .device = ULongToPtr(params32->device), + .flow = params32->flow, + .share = params32->share, + .fmt_in = ULongToPtr(params32->fmt_in), + .fmt_out = ULongToPtr(params32->fmt_out) + }; + unix_is_format_supported(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + +static NTSTATUS unix_wow64_get_mix_format(void *args) +{ + struct + { + PTR32 device; + EDataFlow flow; + PTR32 fmt; + HRESULT result; + } *params32 = args; + struct get_mix_format_params params = + { + .device = ULongToPtr(params32->device), + .flow = params32->flow, + .fmt = ULongToPtr(params32->fmt) + }; + unix_get_mix_format(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + +static NTSTATUS unix_wow64_get_buffer_size(void *args) +{ + struct + { + stream_handle stream; + HRESULT result; + PTR32 frames; + } *params32 = args; + struct get_buffer_size_params params = + { + .stream = params32->stream, + .frames = ULongToPtr(params32->frames) + }; + unix_get_buffer_size(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + +static NTSTATUS unix_wow64_get_latency(void *args) +{ + struct + { + stream_handle stream; + HRESULT result; + PTR32 latency; + } *params32 = args; + struct get_latency_params params = + { + .stream = params32->stream, + .latency = ULongToPtr(params32->latency) + }; + unix_get_latency(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + +static NTSTATUS unix_wow64_get_current_padding(void *args) +{ + struct + { + stream_handle stream; + HRESULT result; + PTR32 padding; + } *params32 = args; + struct get_current_padding_params params = + { + .stream = params32->stream, + .padding = ULongToPtr(params32->padding) + }; + unix_get_current_padding(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + +static NTSTATUS unix_wow64_get_next_packet_size(void *args) +{ + struct + { + stream_handle stream; + HRESULT result; + PTR32 frames; + } *params32 = args; + struct get_next_packet_size_params params = + { + .stream = params32->stream, + .frames = ULongToPtr(params32->frames) + }; + unix_get_next_packet_size(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + +static NTSTATUS unix_wow64_get_position(void *args) +{ + struct + { + stream_handle stream; + HRESULT result; + PTR32 pos; + PTR32 qpctime; + } *params32 = args; + struct get_position_params params = + { + .stream = params32->stream, + .pos = ULongToPtr(params32->pos), + .qpctime = ULongToPtr(params32->qpctime) + }; + unix_get_position(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + +static NTSTATUS unix_wow64_get_frequency(void *args) +{ + struct + { + stream_handle stream; + HRESULT result; + PTR32 freq; + } *params32 = args; + struct get_frequency_params params = + { + .stream = params32->stream, + .freq = ULongToPtr(params32->freq) + }; + unix_get_frequency(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + +static NTSTATUS unix_wow64_set_volumes(void *args) +{ + struct + { + stream_handle stream; + float master_volume; + PTR32 volumes; + PTR32 session_volumes; + int channel; + } *params32 = args; + struct set_volumes_params params = + { + .stream = params32->stream, + .master_volume = params32->master_volume, + .volumes = ULongToPtr(params32->volumes), + .session_volumes = ULongToPtr(params32->session_volumes), + .channel = params32->channel + }; + return unix_set_volumes(¶ms); +} + +unixlib_entry_t __wine_unix_call_wow64_funcs[] = +{ + NULL, + NULL, + NULL, + unix_wow64_get_endpoint_ids, + unix_wow64_create_stream, + unix_wow64_release_stream, + unix_start, + unix_stop, + unix_reset, + NULL, + unix_wow64_get_render_buffer, + unix_release_render_buffer, + unix_wow64_get_capture_buffer, + unix_release_capture_buffer, + unix_wow64_is_format_supported, + unix_wow64_get_mix_format, + NULL, + unix_wow64_get_buffer_size, + unix_wow64_get_latency, + unix_wow64_get_current_padding, + unix_wow64_get_next_packet_size, + unix_wow64_get_frequency, + unix_wow64_get_position, + unix_wow64_set_volumes, + NULL, + NULL, + unix_is_started, + NULL, + unix_wow64_midi_init, + unix_midi_release, + unix_wow64_midi_out_message, + unix_wow64_midi_in_message, + unix_wow64_midi_notify_wait, + NULL, +}; + +#endif /* _WIN64 */ diff --git a/pkgs/osu-wine/audio-revert/winecoreaudio.drv/coreaudio.h b/pkgs/osu-wine/audio-revert/winecoreaudio.drv/coreaudio.h new file mode 100644 index 0000000..e04043c --- /dev/null +++ b/pkgs/osu-wine/audio-revert/winecoreaudio.drv/coreaudio.h @@ -0,0 +1,36 @@ +/* Definition for CoreAudio drivers : wine multimedia system + * + * Copyright 2005-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 + */ + +#ifndef __WINE_COREAUDIO_H +#define __WINE_COREAUDIO_H + +#include "wine/debug.h" + +/* fourcc is in native order, where MSB is the first character. */ +static inline const char* wine_dbgstr_fourcc(INT32 fourcc) +{ + char buf[4] = { (char) (fourcc >> 24), (char) (fourcc >> 16), + (char) (fourcc >> 8), (char) fourcc }; + /* OSStatus is a signed decimal except in parts of CoreAudio */ + if ((buf[0] < 32) || (buf[1] < 32) || (buf[2] < 32) || (buf[3] < 32)) + return wine_dbg_sprintf("%d", fourcc); + return wine_dbgstr_an(buf, sizeof(buf)); +} + +#endif /* __WINE_COREAUDIO_H */ diff --git a/pkgs/osu-wine/audio-revert/winecoreaudio.drv/coremidi.c b/pkgs/osu-wine/audio-revert/winecoreaudio.drv/coremidi.c new file mode 100644 index 0000000..c9b377f --- /dev/null +++ b/pkgs/osu-wine/audio-revert/winecoreaudio.drv/coremidi.c @@ -0,0 +1,1614 @@ +/* + * MIDI driver for macOS (unixlib) + * + * Copyright 1994 Martin Ayotte + * Copyright 1998 Luiz Otavio L. Zorzella + * Copyright 1998, 1999 Eric POUECH + * Copyright 2005, 2006 Emmanuel Maillard + * Copyright 2021 Huw Davies + * + * 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 + */ +#if 0 +#pragma makedep unix +#endif + +#include "config.h" + +#define ULONG __carbon_ULONG +#define E_INVALIDARG __carbon_E_INVALIDARG +#define E_OUTOFMEMORY __carbon_E_OUTOFMEMORY +#define E_HANDLE __carbon_E_HANDLE +#define E_ACCESSDENIED __carbon_E_ACCESSDENIED +#define E_UNEXPECTED __carbon_E_UNEXPECTED +#define E_FAIL __carbon_E_FAIL +#define E_ABORT __carbon_E_ABORT +#define E_POINTER __carbon_E_POINTER +#define E_NOINTERFACE __carbon_E_NOINTERFACE +#define E_NOTIMPL __carbon_E_NOTIMPL +#define S_FALSE __carbon_S_FALSE +#define S_OK __carbon_S_OK +#define HRESULT_FACILITY __carbon_HRESULT_FACILITY +#define IS_ERROR __carbon_IS_ERROR +#define FAILED __carbon_FAILED +#define SUCCEEDED __carbon_SUCCEEDED +#define MAKE_HRESULT __carbon_MAKE_HRESULT +#define HRESULT __carbon_HRESULT +#define STDMETHODCALLTYPE __carbon_STDMETHODCALLT +#include +#include +#include +#include +#undef ULONG +#undef E_INVALIDARG +#undef E_OUTOFMEMORY +#undef E_HANDLE +#undef E_ACCESSDENIED +#undef E_UNEXPECTED +#undef E_FAIL +#undef E_ABORT +#undef E_POINTER +#undef E_NOINTERFACE +#undef E_NOTIMPL +#undef S_FALSE +#undef S_OK +#undef HRESULT_FACILITY +#undef IS_ERROR +#undef FAILED +#undef SUCCEEDED +#undef MAKE_HRESULT +#undef HRESULT +#undef STDMETHODCALLTYPE + +#include + +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "windef.h" +#include "winbase.h" +#include "winnls.h" +#include "winreg.h" +#include "winternl.h" +#include "mmsystem.h" +#include "mmddk.h" +#include "mmdeviceapi.h" +#include "audioclient.h" +#include "wine/debug.h" +#include "wine/unixlib.h" + +#include "coreaudio.h" +#include "unixlib.h" + +WINE_DEFAULT_DEBUG_CHANNEL(midi); + +struct midi_dest +{ + /* graph and synth are only used for MIDI Synth */ + AUGraph graph; + AudioUnit synth; + + MIDIEndpointRef dest; + + MIDIOUTCAPSW caps; + MIDIOPENDESC midiDesc; + BYTE runningStatus; + WORD wFlags; +}; + +struct midi_src +{ + MIDIEndpointRef source; + + WORD wDevID; + int state; /* 0 is no recording started, 1 in recording, bit 2 set if in sys exclusive recording */ + MIDIINCAPSW caps; + MIDIOPENDESC midiDesc; + LPMIDIHDR lpQueueHdr; + WORD wFlags; + UINT startTime; +}; + +static MIDIClientRef midi_client; +static MIDIPortRef midi_out_port, midi_in_port; +static UINT num_dests, num_srcs; +static struct midi_dest *dests; +static struct midi_src *srcs; + +static pthread_mutex_t midi_in_mutex = PTHREAD_MUTEX_INITIALIZER; + +#define NOTIFY_BUFFER_SIZE 64 + 1 /* + 1 for the sentinel */ +static pthread_mutex_t notify_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t notify_cond = PTHREAD_COND_INITIALIZER; +static BOOL notify_quit; +static struct notify_context notify_buffer[NOTIFY_BUFFER_SIZE]; +static struct notify_context *notify_read, *notify_write; + +#define MAX_MIDI_SYNTHS 1 + +static void midi_in_lock(BOOL lock) +{ + if (lock) pthread_mutex_lock(&midi_in_mutex); + else pthread_mutex_unlock(&midi_in_mutex); +} + +static void set_in_notify(struct notify_context *notify, struct midi_src *src, WORD dev_id, WORD msg, + UINT_PTR param_1, UINT_PTR param_2) +{ + notify->send_notify = TRUE; + notify->dev_id = dev_id; + notify->msg = msg; + notify->param_1 = param_1; + notify->param_2 = param_2; + notify->callback = src->midiDesc.dwCallback; + notify->flags = src->wFlags; + notify->device = src->midiDesc.hMidi; + notify->instance = src->midiDesc.dwInstance; +} + +/* + * notify buffer: The notification ring buffer is implemented so that + * there is always at least one unused sentinel before the current + * read position in order to allow detection of the full vs empty + * state. + */ +static struct notify_context *notify_buffer_next(struct notify_context *notify) +{ + if (++notify >= notify_buffer + ARRAY_SIZE(notify_buffer)) + notify = notify_buffer; + + return notify; +} + +static void notify_buffer_add(struct notify_context *notify) +{ + struct notify_context *next = notify_buffer_next(notify_write); + + if (next == notify_read) /* buffer is full - we can't issue a WARN() in a non-Win32 thread */ + notify_read = notify_buffer_next(notify_read); /* drop the oldest notification */ + *notify_write = *notify; + notify_write = next; +} + +static BOOL notify_buffer_empty(void) +{ + return notify_read == notify_write; +} + +static BOOL notify_buffer_remove(struct notify_context *notify) +{ + if (notify_buffer_empty()) return FALSE; + + *notify = *notify_read; + notify_read = notify_buffer_next(notify_read); + return TRUE; +} + +static void notify_post(struct notify_context *notify) +{ + pthread_mutex_lock(¬ify_mutex); + + if (notify) notify_buffer_add(notify); + else notify_quit = TRUE; + pthread_cond_signal(¬ify_cond); + + pthread_mutex_unlock(¬ify_mutex); +} + +/* + * CoreMIDI IO threaded callback, + * we can't call Wine debug channels, critical section or anything using NtCurrentTeb here. + */ +static uint64_t get_time_ms(void) +{ + static mach_timebase_info_data_t timebase; + + if (!timebase.denom) mach_timebase_info(&timebase); + return mach_absolute_time() / 1000000 * timebase.numer / timebase.denom; +} + +static void process_sysex_packet(struct midi_src *src, MIDIPacket *packet) +{ + unsigned int pos = 0, len = packet->length, copy_len; + UINT current_time = get_time_ms() - src->startTime; + struct notify_context notify; + + src->state |= 2; + + midi_in_lock(TRUE); + + while (len) + { + MIDIHDR *hdr = src->lpQueueHdr; + if (!hdr) break; + + copy_len = min(len, hdr->dwBufferLength - hdr->dwBytesRecorded); + memcpy(hdr->lpData + hdr->dwBytesRecorded, packet->data + pos, copy_len); + hdr->dwBytesRecorded += copy_len; + len -= copy_len; + pos += copy_len; + + if ((hdr->dwBytesRecorded == hdr->dwBufferLength) || + (*(BYTE*)(hdr->lpData + hdr->dwBytesRecorded - 1) == 0xf7)) + { /* buffer full or end of sysex message */ + src->lpQueueHdr = hdr->lpNext; + hdr->dwFlags &= ~MHDR_INQUEUE; + hdr->dwFlags |= MHDR_DONE; + set_in_notify(¬ify, src, src->wDevID, MIM_LONGDATA, (UINT_PTR)hdr, current_time); + notify_post(¬ify); + src->state &= ~2; + } + } + + midi_in_lock(FALSE); +} + +static void process_small_packet(struct midi_src *src, MIDIPacket *packet) +{ + UINT current_time = get_time_ms() - src->startTime, data; + struct notify_context notify; + unsigned int pos = 0; + + while (pos < packet->length) + { + data = 0; + switch (packet->data[pos] & 0xf0) + { + case 0xf0: + data = packet->data[pos]; + pos++; + break; + case 0xc0: + case 0xd0: + data = (packet->data[pos + 1] << 8) | packet->data[pos]; + pos += 2; + break; + default: + data = (packet->data[pos + 2] << 16) | (packet->data[pos + 1] << 8) | + packet->data[pos]; + pos += 3; + break; + } + set_in_notify(¬ify, src, src->wDevID, MIM_DATA, data, current_time); + notify_post(¬ify); + } +} + +static void midi_in_read_proc(const MIDIPacketList *pktlist, void *refCon, void *connRefCon) +{ + MIDIPacket *packet = (MIDIPacket *)pktlist->packet; + WORD dev_id = *(WORD *)connRefCon; + struct midi_src *src; + unsigned int i; + + if (dev_id >= num_srcs) return; + src = srcs + dev_id; + if (src->state < 1) /* input not started */ + return; + + for (i = 0; i < pktlist->numPackets; ++i) + { + if (packet->data[0] == 0xf0 || src->state & 2) + process_sysex_packet(src, packet); + else + process_small_packet(src, packet); + + packet = MIDIPacketNext(packet); + } +} + +NTSTATUS unix_midi_init(void *args) +{ + CFStringRef name = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("wineMIDIClient.%d"), getpid()); + struct midi_init_params *params = args; + OSStatus sc; + UINT i; + + pthread_mutex_lock(¬ify_mutex); + notify_quit = FALSE; + notify_read = notify_write = notify_buffer; + pthread_mutex_unlock(¬ify_mutex); + + sc = MIDIClientCreate(name, NULL /* FIXME use notify proc */, NULL, &midi_client); + CFRelease(name); + if (sc) + { + ERR("can't create MIDI Client\n"); + *params->err = DRV_FAILURE; + return STATUS_SUCCESS; + } + + num_dests = MAX_MIDI_SYNTHS + MIDIGetNumberOfDestinations(); + num_srcs = MIDIGetNumberOfSources(); + + TRACE("num_dests %d num_srcs %d\n", num_dests, num_srcs); + + dests = calloc(num_dests, sizeof(*dests)); + srcs = calloc(num_srcs, sizeof(*srcs)); + + if (num_srcs > 0) + { + name = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("WineInputPort.%u"), getpid()); + MIDIInputPortCreate(midi_client, name, midi_in_read_proc, NULL, &midi_in_port); + CFRelease(name); + } + + if (num_dests > MAX_MIDI_SYNTHS) + { + name = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("WineOutputPort.%u"), getpid()); + MIDIOutputPortCreate(midi_client, name, &midi_out_port); + CFRelease(name); + } + + /* initialize sources */ + for (i = 0; i < num_srcs; i++) + { + srcs[i].wDevID = i; + srcs[i].source = MIDIGetSource(i); + + sc = MIDIObjectGetStringProperty(srcs[i].source, kMIDIPropertyName, &name); + if (!sc) + { + int len = min(CFStringGetLength(name), ARRAY_SIZE(srcs[i].caps.szPname) - 1); + CFStringGetCharacters(name, CFRangeMake(0, len), srcs[i].caps.szPname); + srcs[i].caps.szPname[len] = '\0'; + } + MIDIPortConnectSource(midi_in_port, srcs[i].source, &srcs[i].wDevID); + + srcs[i].state = 0; + /* FIXME */ + srcs[i].caps.wMid = 0x00FF; /* Manufac ID */ + srcs[i].caps.wPid = 0x0001; /* Product ID */ + srcs[i].caps.vDriverVersion = 0x0001; + srcs[i].caps.dwSupport = 0; + } + + /* initialise MIDI synths */ + for (i = 0; i < MAX_MIDI_SYNTHS; i++) + { + static const WCHAR synth_name[] = {'C','o','r','e','A','u','d','i','o',' ','M','I','D','I',' ','S','y','n','t','h',' '}; + + C_ASSERT(MAX_MIDI_SYNTHS < 10); + memcpy(dests[i].caps.szPname, synth_name, sizeof(synth_name)); + dests[i].caps.szPname[ARRAY_SIZE(synth_name)] = '1' + i; + dests[i].caps.szPname[ARRAY_SIZE(synth_name) + 1] = '\0'; + + dests[i].caps.wTechnology = MOD_SYNTH; + dests[i].caps.wChannelMask = 0xFFFF; + + dests[i].caps.wMid = 0x00FF; /* Manufac ID */ + dests[i].caps.wPid = 0x0001; /* Product ID */ + dests[i].caps.vDriverVersion = 0x0001; + dests[i].caps.dwSupport = MIDICAPS_VOLUME; + dests[i].caps.wVoices = 16; + dests[i].caps.wNotes = 16; + } + /* initialise available destinations */ + for (i = MAX_MIDI_SYNTHS; i < num_dests; i++) + { + dests[i].dest = MIDIGetDestination(i - MAX_MIDI_SYNTHS); + + sc = MIDIObjectGetStringProperty(dests[i].dest, kMIDIPropertyName, &name); + if (!sc) + { + int len = min(CFStringGetLength(name), ARRAY_SIZE(dests[i].caps.szPname) - 1); + CFStringGetCharacters(name, CFRangeMake(0, len), dests[i].caps.szPname); + dests[i].caps.szPname[len] = '\0'; + } + + dests[i].caps.wTechnology = MOD_MIDIPORT; + dests[i].caps.wChannelMask = 0xFFFF; + + dests[i].caps.wMid = 0x00FF; /* Manufac ID */ + dests[i].caps.wPid = 0x0001; + dests[i].caps.vDriverVersion = 0x0001; + dests[i].caps.dwSupport = 0; + dests[i].caps.wVoices = 0; + dests[i].caps.wNotes = 0; + } + + *params->err = DRV_SUCCESS; + return STATUS_SUCCESS; +} + +NTSTATUS unix_midi_release(void *args) +{ + /* stop the notify_wait thread */ + notify_post(NULL); + + if (midi_client) MIDIClientDispose(midi_client); /* MIDIClientDispose will close all ports */ + + free(srcs); + free(dests); + + return STATUS_SUCCESS; +} + +/* + * MIDI Synth Unit + */ +static BOOL synth_unit_create_default(AUGraph *graph, AudioUnit *synth) +{ + AudioComponentDescription desc; + AUNode synth_node; + AUNode out_node; + OSStatus sc; + + sc = NewAUGraph(graph); + if (sc != noErr) + { + ERR("NewAUGraph return %s\n", wine_dbgstr_fourcc(sc)); + return FALSE; + } + + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + + /* create synth node */ + desc.componentType = kAudioUnitType_MusicDevice; + desc.componentSubType = kAudioUnitSubType_DLSSynth; + + sc = AUGraphAddNode(*graph, &desc, &synth_node); + if (sc != noErr) + { + ERR("AUGraphAddNode cannot create synthNode : %s\n", wine_dbgstr_fourcc(sc)); + return FALSE; + } + + /* create out node */ + desc.componentType = kAudioUnitType_Output; + desc.componentSubType = kAudioUnitSubType_DefaultOutput; + + sc = AUGraphAddNode(*graph, &desc, &out_node); + if (sc != noErr) + { + ERR("AUGraphAddNode cannot create outNode %s\n", wine_dbgstr_fourcc(sc)); + return FALSE; + } + + sc = AUGraphOpen(*graph); + if (sc != noErr) + { + ERR("AUGraphOpen returns %s\n", wine_dbgstr_fourcc(sc)); + return FALSE; + } + + /* connecting the nodes */ + sc = AUGraphConnectNodeInput(*graph, synth_node, 0, out_node, 0); + if (sc != noErr) + { + ERR("AUGraphConnectNodeInput cannot connect synthNode to outNode : %s\n", + wine_dbgstr_fourcc(sc)); + return FALSE; + } + + /* Get the synth unit */ + sc = AUGraphNodeInfo(*graph, synth_node, 0, synth); + if (sc != noErr) + { + ERR("AUGraphNodeInfo return %s\n", wine_dbgstr_fourcc(sc)); + return FALSE; + } + + return TRUE; +} + +static BOOL synth_unit_init(AudioUnit synth, AUGraph graph) +{ + OSStatus sc; + + sc = AUGraphInitialize(graph); + if (sc != noErr) + { + ERR("AUGraphInitialize(%p) returns %s\n", graph, wine_dbgstr_fourcc(sc)); + return FALSE; + } + + sc = AUGraphStart(graph); + if (sc != noErr) + { + ERR("AUGraphStart(%p) returns %s\n", graph, wine_dbgstr_fourcc(sc)); + return FALSE; + } + + return TRUE; +} + +static BOOL synth_unit_close(AUGraph graph) +{ + OSStatus sc; + + sc = AUGraphStop(graph); + if (sc != noErr) + { + ERR("AUGraphStop(%p) returns %s\n", graph, wine_dbgstr_fourcc(sc)); + return FALSE; + } + + sc = DisposeAUGraph(graph); + if (sc != noErr) + { + ERR("DisposeAUGraph(%p) returns %s\n", graph, wine_dbgstr_fourcc(sc)); + return FALSE; + } + + return TRUE; +} + +static void set_out_notify(struct notify_context *notify, struct midi_dest *dest, WORD dev_id, WORD msg, + UINT_PTR param_1, UINT_PTR param_2) +{ + notify->send_notify = TRUE; + notify->dev_id = dev_id; + notify->msg = msg; + notify->param_1 = param_1; + notify->param_2 = param_2; + notify->callback = dest->midiDesc.dwCallback; + notify->flags = dest->wFlags; + notify->device = dest->midiDesc.hMidi; + notify->instance = dest->midiDesc.dwInstance; +} + +static UINT midi_out_open(WORD dev_id, MIDIOPENDESC *midi_desc, UINT flags, struct notify_context *notify) +{ + struct midi_dest *dest; + + TRACE("dev_id = %d desc = %p flags = %08x\n", dev_id, midi_desc, flags); + + if (!midi_desc) return MMSYSERR_INVALPARAM; + + if (dev_id >= num_dests) + { + WARN("bad device ID : %d\n", dev_id); + return MMSYSERR_BADDEVICEID; + } + if (dests[dev_id].midiDesc.hMidi != 0) + { + WARN("device already open!\n"); + return MMSYSERR_ALLOCATED; + } + if ((flags & ~CALLBACK_TYPEMASK) != 0) + { + WARN("bad flags\n"); + return MMSYSERR_INVALFLAG; + } + + dest = dests + dev_id; + if (dest->caps.wTechnology == MOD_SYNTH) + { + if (!synth_unit_create_default(&dest->graph, &dest->synth)) + { + ERR("SynthUnit_CreateDefaultSynthUnit dest=%p failed\n", dest); + return MMSYSERR_ERROR; + } + if (!synth_unit_init(dest->synth, dest->graph)) + { + ERR("SynthUnit_Initialise dest=%p failed\n", dest); + return MMSYSERR_ERROR; + } + } + dest->runningStatus = 0; + dest->wFlags = HIWORD(flags & CALLBACK_TYPEMASK); + dest->midiDesc = *midi_desc; + + set_out_notify(notify, dest, dev_id, MOM_OPEN, 0, 0); + + return MMSYSERR_NOERROR; +} + +static UINT midi_out_close(WORD dev_id, struct notify_context *notify) +{ + struct midi_dest *dest; + + TRACE("dev_id = %d\n", dev_id); + + if (dev_id >= num_dests) + { + WARN("bad device ID : %d\n", dev_id); + return MMSYSERR_BADDEVICEID; + } + + dest = dests + dev_id; + + if (dest->caps.wTechnology == MOD_SYNTH) + synth_unit_close(dest->graph); + dest->graph = 0; + dest->synth = 0; + + set_out_notify(notify, dest, dev_id, MOM_CLOSE, 0, 0); + + dest->midiDesc.hMidi = 0; + + return MMSYSERR_NOERROR; +} + +static void midi_send(MIDIPortRef port, MIDIEndpointRef dest, UInt8 *buffer, unsigned len) +{ + Byte packet_buf[512]; + MIDIPacketList *packet_list = (MIDIPacketList *)packet_buf; + MIDIPacket *packet = MIDIPacketListInit(packet_list); + + packet = MIDIPacketListAdd(packet_list, sizeof(packet_buf), packet, mach_absolute_time(), len, buffer); + if (packet) MIDISend(port, dest, packet_list); +} + +static UINT midi_out_data(WORD dev_id, UINT data) +{ + struct midi_dest *dest; + UInt8 bytes[3]; + OSStatus sc; + + TRACE("dev_id = %d data = %08x\n", dev_id, data); + + if (dev_id >= num_dests) + { + WARN("bad device ID : %d\n", dev_id); + return MMSYSERR_BADDEVICEID; + } + dest = dests + dev_id; + + bytes[0] = data & 0xff; + if (bytes[0] & 0x80) + { + bytes[1] = (data >> 8) & 0xff; + bytes[2] = (data >> 16) & 0xff; + if (bytes[0] < 0xF0) + dest->runningStatus = bytes[0]; + else if (bytes[0] <= 0xF7) + dest->runningStatus = 0; + } + else if (dest->runningStatus) + { + bytes[0] = dest->runningStatus; + bytes[1] = data & 0xff; + bytes[2] = (data >> 8) & 0xff; + } + else + { + FIXME("ooch %x\n", data); + return MMSYSERR_NOERROR; + } + + if (dest->caps.wTechnology == MOD_SYNTH) + { + sc = MusicDeviceMIDIEvent(dest->synth, bytes[0], bytes[1], bytes[2], 0); + if (sc != noErr) + { + ERR("MusicDeviceMIDIEvent returns %s\n", wine_dbgstr_fourcc(sc)); + return MMSYSERR_ERROR; + } + } + else + { + midi_send(midi_out_port, dest->dest, bytes, sizeof(bytes)); + } + + return MMSYSERR_NOERROR; +} + +static UINT midi_out_long_data(WORD dev_id, MIDIHDR *hdr, UINT hdr_size, struct notify_context *notify) +{ + struct midi_dest *dest; + OSStatus sc; + + TRACE("dev_id = %d midi_hdr = %p hdr_size = %d\n", dev_id, hdr, hdr_size); + + if (dev_id >= num_dests) + { + WARN("bad device ID : %d\n", dev_id); + return MMSYSERR_BADDEVICEID; + } + if (!hdr) + { + WARN("Invalid Parameter\n"); + return MMSYSERR_INVALPARAM; + } + if (!hdr->lpData || !(hdr->dwFlags & MHDR_PREPARED)) + return MIDIERR_UNPREPARED; + + if (hdr->dwFlags & MHDR_INQUEUE) + return MIDIERR_STILLPLAYING; + + hdr->dwFlags &= ~MHDR_DONE; + hdr->dwFlags |= MHDR_INQUEUE; + + if ((UInt8)hdr->lpData[0] != 0xf0) + /* System Exclusive */ + ERR("Add missing 0xf0 marker at the beginning of system exclusive byte stream\n"); + + if ((UInt8)hdr->lpData[hdr->dwBufferLength - 1] != 0xF7) + /* Send end of System Exclusive */ + ERR("Add missing 0xf7 marker at the end of system exclusive byte stream\n"); + + dest = dests + dev_id; + + if (dest->caps.wTechnology == MOD_SYNTH) /* FIXME */ + { + sc = MusicDeviceSysEx(dest->synth, (const UInt8 *)hdr->lpData, hdr->dwBufferLength); + if (sc != noErr) + { + ERR("MusicDeviceSysEx returns %s\n", wine_dbgstr_fourcc(sc)); + return MMSYSERR_ERROR; + } + } + else if (dest->caps.wTechnology == MOD_MIDIPORT) + midi_send(midi_out_port, dest->dest, (UInt8 *)hdr->lpData, hdr->dwBufferLength); + + dest->runningStatus = 0; + hdr->dwFlags &= ~MHDR_INQUEUE; + hdr->dwFlags |= MHDR_DONE; + + set_out_notify(notify, dest, dev_id, MOM_DONE, (UINT_PTR)hdr, 0); + + return MMSYSERR_NOERROR; +} + +static UINT midi_out_prepare(WORD dev_id, MIDIHDR *hdr, UINT hdr_size) +{ + TRACE("dev_id = %d midi_hdr = %p hdr_size = %d\n", dev_id, hdr, hdr_size); + + if (hdr_size < offsetof(MIDIHDR, dwOffset) || !hdr || !hdr->lpData) + return MMSYSERR_INVALPARAM; + if (hdr->dwFlags & MHDR_PREPARED) + return MMSYSERR_NOERROR; + + hdr->lpNext = 0; + hdr->dwFlags |= MHDR_PREPARED; + hdr->dwFlags &= ~(MHDR_DONE | MHDR_INQUEUE); + return MMSYSERR_NOERROR; +} + +static UINT midi_out_unprepare(WORD dev_id, MIDIHDR *hdr, UINT hdr_size) +{ + TRACE("dev_id = %d midi_hdr = %p hdr_size = %d\n", dev_id, hdr, hdr_size); + + if (hdr_size < offsetof(MIDIHDR, dwOffset) || !hdr || !hdr->lpData) + return MMSYSERR_INVALPARAM; + if (!(hdr->dwFlags & MHDR_PREPARED)) + return MMSYSERR_NOERROR; + if (hdr->dwFlags & MHDR_INQUEUE) + return MIDIERR_STILLPLAYING; + + hdr->dwFlags &= ~MHDR_PREPARED; + return MMSYSERR_NOERROR; +} + +static UINT midi_out_get_devcaps(WORD dev_id, MIDIOUTCAPSW *caps, UINT size) +{ + TRACE("dev_id = %d caps = %p size = %d\n", dev_id, caps, size); + + if (!caps) + { + WARN("Invalid Parameter\n"); + return MMSYSERR_INVALPARAM; + } + + if (dev_id >= num_dests) + { + WARN("bad device ID : %d\n", dev_id); + return MMSYSERR_BADDEVICEID; + } + memcpy(caps, &dests[dev_id].caps, min(size, sizeof(*caps))); + return MMSYSERR_NOERROR; +} + +static UINT midi_out_get_num_devs(void) +{ + TRACE("\n"); + return num_dests; +} + +static UINT midi_out_get_volume(WORD dev_id, UINT *volume) +{ + TRACE("%d\n", dev_id); + + if (dev_id >= num_dests) + { + WARN("bad device ID : %d\n", dev_id); + return MMSYSERR_BADDEVICEID; + } + if (!volume) + { + WARN("Invalid Parameter\n"); + return MMSYSERR_INVALPARAM; + } + + if (dests[dev_id].caps.wTechnology == MOD_SYNTH) + { + static int once; + float left; + + if (!once++) FIXME("independent left/right volume not implemented\n"); + AudioUnitGetParameter(dests[dev_id].synth, kHALOutputParam_Volume, kAudioUnitParameterFlag_Output, 0, &left); + *volume = (WORD)(left * 0xffff) + ((WORD)(left * 0xffff) << 16); + return MMSYSERR_NOERROR; + } + + return MMSYSERR_NOTSUPPORTED; +} + +static UINT midi_out_set_volume(WORD dev_id, UINT volume) +{ + TRACE("dev_id = %d vol = %08x\n", dev_id, volume); + + if (dev_id >= num_dests) + { + WARN("bad device ID : %d\n", dev_id); + return MMSYSERR_BADDEVICEID; + } + if (dests[dev_id].caps.wTechnology == MOD_SYNTH) + { + float left, right; + static int once; + + if (!once++) FIXME("independent left/right volume not implemented\n"); + left = LOWORD(volume) / 65535.0f; + right = HIWORD(volume) / 65535.0f; + + AudioUnitSetParameter(dests[dev_id].synth, kHALOutputParam_Volume, kAudioUnitParameterFlag_Output, 0, left, 0); + return MMSYSERR_NOERROR; + } + + return MMSYSERR_NOTSUPPORTED; +} + +static UINT midi_out_reset(WORD dev_id) +{ + unsigned chn; + + TRACE("%d\n", dev_id); + + if (dev_id >= num_dests) + { + WARN("bad device ID : %d\n", dev_id); + return MMSYSERR_BADDEVICEID; + } + if (dests[dev_id].caps.wTechnology == MOD_SYNTH) + { + for (chn = 0; chn < 16; chn++) + { + /* turn off every note */ + MusicDeviceMIDIEvent(dests[dev_id].synth, 0xB0 | chn, 0x7B, 0, 0); + /* remove sustain on channel */ + MusicDeviceMIDIEvent(dests[dev_id].synth, 0xB0 | chn, 0x40, 0, 0); + } + } + else FIXME("MOD_MIDIPORT\n"); + + dests[dev_id].runningStatus = 0; + + /* FIXME: the LongData buffers must also be returned to the app */ + return MMSYSERR_NOERROR; +} + +static UINT midi_in_open(WORD dev_id, MIDIOPENDESC *midi_desc, UINT flags, struct notify_context *notify) +{ + struct midi_src *src; + + TRACE("dev_id = %d desc = %p flags = %08x\n", dev_id, midi_desc, flags); + + if (!midi_desc) + { + WARN("Invalid Parameter\n"); + return MMSYSERR_INVALPARAM; + } + if (dev_id >= num_srcs) + { + WARN("bad device ID : %d\n", dev_id); + return MMSYSERR_BADDEVICEID; + } + src = srcs + dev_id; + + if (src->midiDesc.hMidi) + { + WARN("device already open !\n"); + return MMSYSERR_ALLOCATED; + } + if (flags & MIDI_IO_STATUS) + { + FIXME("No support for MIDI_IO_STATUS in flags yet, ignoring it\n"); + flags &= ~MIDI_IO_STATUS; + } + if (flags & ~CALLBACK_TYPEMASK) + { + FIXME("Bad flags\n"); + return MMSYSERR_INVALFLAG; + } + + src->wFlags = HIWORD(flags & CALLBACK_TYPEMASK); + src->lpQueueHdr = NULL; + src->midiDesc = *midi_desc; + src->startTime = 0; + src->state = 0; + + set_in_notify(notify, src, dev_id, MIM_OPEN, 0, 0); + + return MMSYSERR_NOERROR; +} + +static UINT midi_in_close(WORD dev_id, struct notify_context *notify) +{ + struct midi_src *src; + + TRACE("dev_id = %d\n", dev_id); + + if (dev_id >= num_srcs) + { + WARN("bad device ID : %d\n", dev_id); + return MMSYSERR_BADDEVICEID; + } + src = srcs + dev_id; + + if (!src->midiDesc.hMidi) + { + WARN("device not opened !\n"); + return MMSYSERR_ERROR; + } + if (src->lpQueueHdr) return MIDIERR_STILLPLAYING; + + set_in_notify(notify, src, dev_id, MIM_CLOSE, 0, 0); + src->midiDesc.hMidi = 0; + + return MMSYSERR_NOERROR; +} + +static UINT midi_in_add_buffer(WORD dev_id, MIDIHDR *hdr, UINT hdr_size) +{ + MIDIHDR **next; + + TRACE("dev_id = %d hdr = %p hdr_size = %d\n", dev_id, hdr, hdr_size); + + if (dev_id >= num_srcs) + { + WARN("bad device ID : %d\n", dev_id); + return MMSYSERR_BADDEVICEID; + } + if (!hdr || hdr_size < offsetof(MIDIHDR, dwOffset) || !hdr->dwBufferLength) + { + WARN("Invalid Parameter\n"); + return MMSYSERR_INVALPARAM; + } + if (hdr->dwFlags & MHDR_INQUEUE) + { + WARN("Still playing\n"); + return MIDIERR_STILLPLAYING; + } + if (!(hdr->dwFlags & MHDR_PREPARED)) + { + WARN("Unprepared\n"); + return MIDIERR_UNPREPARED; + } + + hdr->dwFlags &= ~WHDR_DONE; + hdr->dwFlags |= MHDR_INQUEUE; + hdr->dwBytesRecorded = 0; + hdr->lpNext = NULL; + + midi_in_lock(TRUE); + + next = &srcs[dev_id].lpQueueHdr; + while (*next) next = &(*next)->lpNext; + *next = hdr; + + midi_in_lock(FALSE); + + return MMSYSERR_NOERROR; +} + +static UINT midi_in_prepare(WORD dev_id, MIDIHDR *hdr, UINT hdr_size) +{ + TRACE("dev_id = %d hdr = %p hdr_size = %d\n", dev_id, hdr, hdr_size); + + if (hdr_size < offsetof(MIDIHDR, dwOffset) || !hdr || !hdr->lpData) + return MMSYSERR_INVALPARAM; + if (hdr->dwFlags & MHDR_PREPARED) + return MMSYSERR_NOERROR; + + hdr->lpNext = NULL; + hdr->dwFlags |= MHDR_PREPARED; + hdr->dwFlags &= ~(MHDR_DONE | MHDR_INQUEUE); + return MMSYSERR_NOERROR; +} + +static UINT midi_in_unprepare(WORD dev_id, MIDIHDR *hdr, UINT hdr_size) +{ + TRACE("dev_id = %d hdr = %p hdr_size = %d\n", dev_id, hdr, hdr_size); + + if (hdr_size < offsetof(MIDIHDR, dwOffset) || !hdr || !hdr->lpData) + return MMSYSERR_INVALPARAM; + if (!(hdr->dwFlags & MHDR_PREPARED)) + return MMSYSERR_NOERROR; + if (hdr->dwFlags & MHDR_INQUEUE) + return MIDIERR_STILLPLAYING; + + hdr->dwFlags &= ~MHDR_PREPARED; + return MMSYSERR_NOERROR; +} + +static UINT midi_in_get_devcaps(WORD dev_id, MIDIINCAPSW *caps, UINT size) +{ + TRACE("dev_id = %d caps = %p size = %d\n", dev_id, caps, size); + + if (!caps) + { + WARN("Invalid Parameter\n"); + return MMSYSERR_INVALPARAM; + } + if (dev_id >= num_srcs) + { + WARN("bad device ID : %d\n", dev_id); + return MMSYSERR_BADDEVICEID; + } + + memcpy(caps, &srcs[dev_id].caps, min(size, sizeof(*caps))); + return MMSYSERR_NOERROR; +} + +static UINT midi_in_get_num_devs(void) +{ + TRACE("\n"); + return num_srcs; +} + +static UINT midi_in_start(WORD dev_id) +{ + TRACE("%d\n", dev_id); + + if (dev_id >= num_srcs) + { + WARN("bad device ID : %d\n", dev_id); + return MMSYSERR_BADDEVICEID; + } + srcs[dev_id].state = 1; + srcs[dev_id].startTime = get_time_ms(); + return MMSYSERR_NOERROR; +} + +static UINT midi_in_stop(WORD dev_id) +{ + TRACE("%d\n", dev_id); + + if (dev_id >= num_srcs) + { + WARN("bad device ID : %d\n", dev_id); + return MMSYSERR_BADDEVICEID; + } + srcs[dev_id].state = 0; + return MMSYSERR_NOERROR; +} + +static UINT midi_in_reset(WORD dev_id, struct notify_context *notify) +{ + UINT cur_time = get_time_ms(); + UINT err = MMSYSERR_NOERROR; + struct midi_src *src; + MIDIHDR *hdr; + + TRACE("%d\n", dev_id); + + if (dev_id >= num_srcs) + { + WARN("bad device ID : %d\n", dev_id); + return MMSYSERR_BADDEVICEID; + } + src = srcs + dev_id; + + midi_in_lock(TRUE); + + if (src->lpQueueHdr) + { + hdr = src->lpQueueHdr; + src->lpQueueHdr = hdr->lpNext; + hdr->dwFlags &= ~MHDR_INQUEUE; + hdr->dwFlags |= MHDR_DONE; + set_in_notify(notify, src, dev_id, MIM_LONGDATA, (UINT_PTR)hdr, cur_time - src->startTime); + if (src->lpQueueHdr) err = ERROR_RETRY; /* ask the client to call again */ + } + + midi_in_lock(FALSE); + + return err; +} + +NTSTATUS unix_midi_out_message(void *args) +{ + struct midi_out_message_params *params = args; + + params->notify->send_notify = FALSE; + + switch (params->msg) + { + case DRVM_INIT: + case DRVM_EXIT: + case DRVM_ENABLE: + case DRVM_DISABLE: + *params->err = MMSYSERR_NOERROR; + break; + case MODM_OPEN: + *params->err = midi_out_open(params->dev_id, (MIDIOPENDESC *)params->param_1, params->param_2, params->notify); + break; + case MODM_CLOSE: + *params->err = midi_out_close(params->dev_id, params->notify); + break; + case MODM_DATA: + *params->err = midi_out_data(params->dev_id, params->param_1); + break; + case MODM_LONGDATA: + *params->err = midi_out_long_data(params->dev_id, (MIDIHDR *)params->param_1, params->param_2, params->notify); + break; + case MODM_PREPARE: + *params->err = midi_out_prepare(params->dev_id, (MIDIHDR *)params->param_1, params->param_2); + break; + case MODM_UNPREPARE: + *params->err = midi_out_unprepare(params->dev_id, (MIDIHDR *)params->param_1, params->param_2); + break; + case MODM_GETDEVCAPS: + *params->err = midi_out_get_devcaps(params->dev_id, (MIDIOUTCAPSW *)params->param_1, params->param_2); + break; + case MODM_GETNUMDEVS: + *params->err = midi_out_get_num_devs(); + break; + case MODM_GETVOLUME: + *params->err = midi_out_get_volume(params->dev_id, (UINT *)params->param_1); + break; + case MODM_SETVOLUME: + *params->err = midi_out_set_volume(params->dev_id, params->param_1); + break; + case MODM_RESET: + *params->err = midi_out_reset(params->dev_id); + break; + default: + TRACE("Unsupported message\n"); + *params->err = MMSYSERR_NOTSUPPORTED; + } + + return STATUS_SUCCESS; +} + +NTSTATUS unix_midi_in_message(void *args) +{ + struct midi_in_message_params *params = args; + + params->notify->send_notify = FALSE; + + switch (params->msg) + { + case DRVM_INIT: + case DRVM_EXIT: + case DRVM_ENABLE: + case DRVM_DISABLE: + *params->err = MMSYSERR_NOERROR; + break; + case MIDM_OPEN: + *params->err = midi_in_open(params->dev_id, (MIDIOPENDESC *)params->param_1, params->param_2, params->notify); + break; + case MIDM_CLOSE: + *params->err = midi_in_close(params->dev_id, params->notify); + break; + case MIDM_ADDBUFFER: + *params->err = midi_in_add_buffer(params->dev_id, (MIDIHDR *)params->param_1, params->param_2); + break; + case MIDM_PREPARE: + *params->err = midi_in_prepare(params->dev_id, (MIDIHDR *)params->param_1, params->param_2); + break; + case MIDM_UNPREPARE: + *params->err = midi_in_unprepare(params->dev_id, (MIDIHDR *)params->param_1, params->param_2); + break; + case MIDM_GETDEVCAPS: + *params->err = midi_in_get_devcaps(params->dev_id, (MIDIINCAPSW *)params->param_1, params->param_2); + break; + case MIDM_GETNUMDEVS: + *params->err = midi_in_get_num_devs(); + break; + case MIDM_START: + *params->err = midi_in_start(params->dev_id); + break; + case MIDM_STOP: + *params->err = midi_in_stop(params->dev_id); + break; + case MIDM_RESET: + *params->err = midi_in_reset(params->dev_id, params->notify); + break; + default: + TRACE("Unsupported message\n"); + *params->err = MMSYSERR_NOTSUPPORTED; + } + + return STATUS_SUCCESS; +} + +NTSTATUS unix_midi_notify_wait(void *args) +{ + struct midi_notify_wait_params *params = args; + + pthread_mutex_lock(¬ify_mutex); + + while (!notify_quit && notify_buffer_empty()) + pthread_cond_wait(¬ify_cond, ¬ify_mutex); + + *params->quit = notify_quit; + if (!notify_quit) notify_buffer_remove(params->notify); + + pthread_mutex_unlock(¬ify_mutex); + + return STATUS_SUCCESS; +} + +#ifdef _WIN64 + +typedef UINT PTR32; + +NTSTATUS unix_wow64_midi_init(void *args) +{ + struct + { + PTR32 err; + } *params32 = args; + struct midi_init_params params = + { + .err = ULongToPtr(params32->err) + }; + return unix_midi_init(¶ms); +} + +struct notify_context32 +{ + BOOL send_notify; + WORD dev_id; + WORD msg; + UINT param_1; + UINT param_2; + UINT callback; + UINT flags; + PTR32 device; + UINT instance; +}; + +static void notify_to_notify32(struct notify_context32 *notify32, + const struct notify_context *notify) +{ + notify32->send_notify = notify->send_notify; + notify32->dev_id = notify->dev_id; + notify32->msg = notify->msg; + notify32->param_1 = notify->param_1; + notify32->param_2 = notify->param_2; + notify32->callback = notify->callback; + notify32->flags = notify->flags; + notify32->device = PtrToUlong(notify->device); + notify32->instance = notify->instance; +} + +struct midi_open_desc32 +{ + PTR32 hMidi; + UINT dwCallback; + UINT dwInstance; + UINT dnDevNode; + UINT cIds; + MIDIOPENSTRMID rgIds; +}; + +struct midi_hdr32 +{ + PTR32 lpData; + UINT dwBufferLength; + UINT dwBytesRecorded; + UINT dwUser; + UINT dwFlags; + PTR32 lpNext; + UINT reserved; + UINT dwOffset; + UINT dwReserved[8]; +}; + +static UINT wow64_midi_out_prepare(WORD dev_id, struct midi_hdr32 *hdr, UINT hdr_size) +{ + TRACE("(%04X, %p, %d);\n", dev_id, hdr, hdr_size); + + if (hdr_size < offsetof(struct midi_hdr32, dwOffset) || !hdr || !hdr->lpData) + return MMSYSERR_INVALPARAM; + if (hdr->dwFlags & MHDR_PREPARED) + return MMSYSERR_NOERROR; + + hdr->lpNext = 0; + hdr->dwFlags |= MHDR_PREPARED; + hdr->dwFlags &= ~(MHDR_DONE | MHDR_INQUEUE); + return MMSYSERR_NOERROR; +} + +static UINT wow64_midi_out_unprepare(WORD dev_id, struct midi_hdr32 *hdr, UINT hdr_size) +{ + TRACE("(%04X, %p, %d);\n", dev_id, hdr, hdr_size); + + if (hdr_size < offsetof(struct midi_hdr32, dwOffset) || !hdr || !hdr->lpData) + return MMSYSERR_INVALPARAM; + if (!(hdr->dwFlags & MHDR_PREPARED)) + return MMSYSERR_NOERROR; + if (hdr->dwFlags & MHDR_INQUEUE) + return MIDIERR_STILLPLAYING; + + hdr->dwFlags &= ~MHDR_PREPARED; + return MMSYSERR_NOERROR; +} + +NTSTATUS unix_wow64_midi_out_message(void *args) +{ + struct + { + UINT dev_id; + UINT msg; + UINT user; + UINT param_1; + UINT param_2; + PTR32 err; + PTR32 notify; + } *params32 = args; + struct notify_context32 *notify32 = ULongToPtr(params32->notify); + struct midi_open_desc32 *desc32; + struct midi_hdr32 *hdr32; + struct notify_context notify; + MIDIOPENDESC open_desc; + MIDIHDR hdr; + struct midi_out_message_params params = + { + .dev_id = params32->dev_id, + .msg = params32->msg, + .user = params32->user, + .param_1 = params32->param_1, + .param_2 = params32->param_2, + .err = ULongToPtr(params32->err), + .notify = ¬ify + }; + notify32->send_notify = FALSE; + + switch (params32->msg) + { + case MODM_OPEN: + desc32 = ULongToPtr(params32->param_1); + + open_desc.hMidi = ULongToPtr(desc32->hMidi); + open_desc.dwCallback = desc32->dwCallback; + open_desc.dwInstance = desc32->dwInstance; + open_desc.dnDevNode = desc32->dnDevNode; + open_desc.cIds = desc32->cIds; + open_desc.rgIds.dwStreamID = desc32->rgIds.dwStreamID; + open_desc.rgIds.wDeviceID = desc32->rgIds.wDeviceID; + + params.param_1 = (UINT_PTR)&open_desc; + break; + + case MODM_LONGDATA: + hdr32 = ULongToPtr(params32->param_1); + + memset(&hdr, 0, sizeof(hdr)); + hdr.lpData = ULongToPtr(hdr32->lpData); + hdr.dwBufferLength = hdr32->dwBufferLength; + hdr.dwFlags = hdr32->dwFlags; + + params.param_1 = (UINT_PTR)&hdr; + params.param_2 = sizeof(hdr); + break; + + case MODM_PREPARE: /* prepare and unprepare are easier to handle explicitly */ + hdr32 = ULongToPtr(params32->param_1); + + *params.err = wow64_midi_out_prepare(params32->dev_id, hdr32, params32->param_2); + return STATUS_SUCCESS; + + case MODM_UNPREPARE: + hdr32 = ULongToPtr(params32->param_1); + + *params.err = wow64_midi_out_unprepare(params32->dev_id, hdr32, params32->param_2); + return STATUS_SUCCESS; + } + + unix_midi_out_message(¶ms); + + switch (params32->msg) + { + case MODM_LONGDATA: + hdr32 = ULongToPtr(params32->param_1); + + hdr32->dwFlags = hdr.dwFlags; + break; + } + + if (notify.send_notify) + { + notify_to_notify32(notify32, ¬ify); + + if (notify.msg == MOM_DONE) + notify32->param_1 = params32->param_1; /* restore the 32-bit hdr */ + } + return STATUS_SUCCESS; +} + +static UINT wow64_midi_in_prepare(WORD dev_id, struct midi_hdr32 *hdr, UINT hdr_size) +{ + TRACE("(%04X, %p, %d);\n", dev_id, hdr, hdr_size); + + if (hdr_size < offsetof(struct midi_hdr32, dwOffset) || !hdr || !hdr->lpData) + return MMSYSERR_INVALPARAM; + if (hdr->dwFlags & MHDR_PREPARED) + return MMSYSERR_NOERROR; + + hdr->lpNext = 0; + hdr->dwFlags |= MHDR_PREPARED; + hdr->dwFlags &= ~(MHDR_DONE | MHDR_INQUEUE); + + return MMSYSERR_NOERROR; +} + +static UINT wow64_midi_in_unprepare(WORD dev_id, struct midi_hdr32 *hdr, UINT hdr_size) +{ + TRACE("(%04X, %p, %d);\n", dev_id, hdr, hdr_size); + + if (hdr_size < offsetof(struct midi_hdr32, dwOffset) || !hdr || !hdr->lpData) + return MMSYSERR_INVALPARAM; + if (!(hdr->dwFlags & MHDR_PREPARED)) + return MMSYSERR_NOERROR; + if (hdr->dwFlags & MHDR_INQUEUE) + return MIDIERR_STILLPLAYING; + + hdr->dwFlags &= ~MHDR_PREPARED; + + return MMSYSERR_NOERROR; +} + +NTSTATUS unix_wow64_midi_in_message(void *args) +{ + struct + { + UINT dev_id; + UINT msg; + UINT user; + UINT param_1; + UINT param_2; + PTR32 err; + PTR32 notify; + } *params32 = args; + struct notify_context32 *notify32 = ULongToPtr(params32->notify); + struct midi_open_desc32 *desc32; + struct midi_hdr32 *hdr32; + struct notify_context notify; + MIDIOPENDESC open_desc; + MIDIHDR *hdr = NULL; + struct midi_in_message_params params = + { + .dev_id = params32->dev_id, + .msg = params32->msg, + .user = params32->user, + .param_1 = params32->param_1, + .param_2 = params32->param_2, + .err = ULongToPtr(params32->err), + .notify = ¬ify + }; + notify32->send_notify = FALSE; + + switch (params32->msg) + { + case MIDM_OPEN: + desc32 = ULongToPtr(params32->param_1); + + open_desc.hMidi = ULongToPtr(desc32->hMidi); + open_desc.dwCallback = desc32->dwCallback; + open_desc.dwInstance = desc32->dwInstance; + open_desc.dnDevNode = desc32->dnDevNode; + open_desc.cIds = desc32->cIds; + open_desc.rgIds.dwStreamID = desc32->rgIds.dwStreamID; + open_desc.rgIds.wDeviceID = desc32->rgIds.wDeviceID; + + params.param_1 = (UINT_PTR)&open_desc; + break; + + case MIDM_ADDBUFFER: + hdr32 = ULongToPtr(params32->param_1); + + hdr = calloc(1, sizeof(*hdr)); + hdr->lpData = ULongToPtr(hdr32->lpData); + hdr->dwBufferLength = hdr32->dwBufferLength; + hdr->dwFlags = hdr32->dwFlags; + hdr->dwReserved[7] = params32->param_1; /* keep hdr32 for MIM_LONGDATA notification */ + + params.param_1 = (UINT_PTR)hdr; + params.param_2 = sizeof(*hdr); + break; + + case MIDM_PREPARE: /* prepare and unprepare are easier to handle explicitly */ + hdr32 = ULongToPtr(params32->param_1); + + *params.err = wow64_midi_in_prepare(params32->dev_id, hdr32, params32->param_2); + return STATUS_SUCCESS; + + case MIDM_UNPREPARE: + hdr32 = ULongToPtr(params32->param_1); + + *params.err = wow64_midi_in_unprepare(params32->dev_id, hdr32, params32->param_2); + return STATUS_SUCCESS; + } + + unix_midi_in_message(¶ms); + + switch (params32->msg) + { + case MIDM_ADDBUFFER: + hdr32 = ULongToPtr(params32->param_1); + + if (!*params.err) + { + hdr32->dwFlags = hdr->dwFlags; + hdr32->dwBytesRecorded = hdr->dwBytesRecorded; + hdr32->lpNext = 0; + } + else + free(hdr); + break; + } + + if (notify.send_notify) + { + notify_to_notify32(notify32, ¬ify); + + if (notify.msg == MIM_LONGDATA) + { + hdr = (MIDIHDR *)notify.param_1; + notify32->param_1 = hdr->dwReserved[7]; + hdr32 = ULongToPtr(notify32->param_1); + hdr32->dwBytesRecorded = hdr->dwBytesRecorded; + hdr32->dwFlags = hdr->dwFlags; + free(hdr); + } + } + return STATUS_SUCCESS; +} + +NTSTATUS unix_wow64_midi_notify_wait(void *args) +{ + struct + { + PTR32 quit; + PTR32 notify; + } *params32 = args; + struct notify_context32 *notify32 = ULongToPtr(params32->notify); + struct midi_hdr32 *hdr32; + struct notify_context notify; + MIDIHDR *hdr; + struct midi_notify_wait_params params = + { + .quit = ULongToPtr(params32->quit), + .notify = ¬ify + }; + notify32->send_notify = FALSE; + + unix_midi_notify_wait(¶ms); + + if (!*params.quit && notify.send_notify) + { + notify_to_notify32(notify32, ¬ify); + + if (notify.msg == MIM_LONGDATA) + { + hdr = (MIDIHDR *)notify.param_1; + notify32->param_1 = hdr->dwReserved[7]; + hdr32 = ULongToPtr(notify32->param_1); + hdr32->dwBytesRecorded = hdr->dwBytesRecorded; + hdr32->dwFlags = hdr->dwFlags; + free(hdr); + } + } + return STATUS_SUCCESS; +} + +#endif /* _WIN64 */ diff --git a/pkgs/osu-wine/audio-revert/winecoreaudio.drv/midi.c b/pkgs/osu-wine/audio-revert/winecoreaudio.drv/midi.c new file mode 100644 index 0000000..4934923 --- /dev/null +++ b/pkgs/osu-wine/audio-revert/winecoreaudio.drv/midi.c @@ -0,0 +1,226 @@ +/* + * MIDI driver for macOS (PE-side) + * + * Copyright 1994 Martin Ayotte + * Copyright 1998 Luiz Otavio L. Zorzella + * Copyright 1998, 1999 Eric POUECH + * Copyright 2005, 2006 Emmanuel Maillard + * Copyright 2021 Huw Davies + * + * 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 +#include +#include + +#include "windef.h" +#include "winbase.h" +#include "winternl.h" +#include "wingdi.h" +#include "winuser.h" +#include "winnls.h" +#include "mmddk.h" +#include "mmdeviceapi.h" +#include "audioclient.h" +#include "wine/debug.h" +#include "wine/unixlib.h" +#include "coreaudio.h" +#include "unixlib.h" + +WINE_DEFAULT_DEBUG_CHANNEL(midi); + +static void notify_client(struct notify_context *notify) +{ + TRACE("dev_id=%d msg=%d param1=%04IX param2=%04IX\n", notify->dev_id, notify->msg, notify->param_1, notify->param_2); + + DriverCallback(notify->callback, notify->flags, notify->device, notify->msg, + notify->instance, notify->param_1, notify->param_2); +} + +static DWORD WINAPI notify_thread(void *p) +{ + struct midi_notify_wait_params params; + struct notify_context notify; + BOOL quit; + + params.notify = ¬ify; + params.quit = &quit; + + while (1) + { + UNIX_CALL(midi_notify_wait, ¶ms); + if (quit) break; + if (notify.send_notify) notify_client(¬ify); + } + return 0; +} + +static LONG CoreAudio_MIDIInit(void) +{ + struct midi_init_params params; + UINT err; + + params.err = &err; + + UNIX_CALL(midi_init, ¶ms); + if (err != DRV_SUCCESS) + { + ERR("can't create midi client\n"); + return err; + } + + CloseHandle(CreateThread(NULL, 0, notify_thread, NULL, 0, NULL)); + + return err; +} + +static LONG CoreAudio_MIDIRelease(void) +{ + TRACE("\n"); + + UNIX_CALL(midi_release, NULL); + + return DRV_SUCCESS; +} + +/************************************************************************** +* modMessage +*/ +DWORD WINAPI CoreAudio_modMessage(UINT wDevID, UINT wMsg, DWORD_PTR dwUser, DWORD_PTR dwParam1, DWORD_PTR dwParam2) +{ + struct midi_out_message_params params; + struct notify_context notify; + UINT err; + + TRACE("%d %08x %08Ix %08Ix %08Ix\n", wDevID, wMsg, dwUser, dwParam1, dwParam2); + + params.dev_id = wDevID; + params.msg = wMsg; + params.user = dwUser; + params.param_1 = dwParam1; + params.param_2 = dwParam2; + params.err = &err; + params.notify = ¬ify; + + UNIX_CALL(midi_out_message, ¶ms); + + if (!err && notify.send_notify) notify_client(¬ify); + + return err; +} + +/************************************************************************** +* midMessage +*/ +DWORD WINAPI CoreAudio_midMessage(UINT wDevID, UINT wMsg, DWORD_PTR dwUser, DWORD_PTR dwParam1, DWORD_PTR dwParam2) +{ + struct midi_in_message_params params; + struct notify_context notify; + UINT err; + + TRACE("%d %08x %08Ix %08Ix %08Ix\n", wDevID, wMsg, dwUser, dwParam1, dwParam2); + + params.dev_id = wDevID; + params.msg = wMsg; + params.user = dwUser; + params.param_1 = dwParam1; + params.param_2 = dwParam2; + params.err = &err; + params.notify = ¬ify; + + do + { + UNIX_CALL(midi_in_message, ¶ms); + if ((!err || err == ERROR_RETRY) && notify.send_notify) notify_client(¬ify); + } while (err == ERROR_RETRY); + + return err; +} + +/************************************************************************** + * CoreAudio_drvLoad [internal] + */ +static LRESULT CoreAudio_drvLoad(void) +{ + TRACE("()\n"); + + if (CoreAudio_MIDIInit() != DRV_SUCCESS) + return DRV_FAILURE; + + return DRV_SUCCESS; +} + +/************************************************************************** + * CoreAudio_drvFree [internal] + */ +static LRESULT CoreAudio_drvFree(void) +{ + TRACE("()\n"); + CoreAudio_MIDIRelease(); + return DRV_SUCCESS; +} + +/************************************************************************** + * CoreAudio_drvOpen [internal] + */ +static LRESULT CoreAudio_drvOpen(LPSTR str) +{ + TRACE("(%s)\n", str); + return 1; +} + +/************************************************************************** + * CoreAudio_drvClose [internal] + */ +static DWORD CoreAudio_drvClose(DWORD dwDevID) +{ + TRACE("(%08lx)\n", dwDevID); + return 1; +} + +/************************************************************************** + * DriverProc (WINECOREAUDIO.1) + */ +LRESULT CALLBACK CoreAudio_DriverProc(DWORD_PTR dwDevID, HDRVR hDriv, UINT wMsg, + LPARAM dwParam1, LPARAM dwParam2) +{ + TRACE("(%08IX, %p, %s (%08X), %08IX, %08IX)\n", + dwDevID, hDriv, wMsg == DRV_LOAD ? "DRV_LOAD" : + wMsg == DRV_FREE ? "DRV_FREE" : + wMsg == DRV_OPEN ? "DRV_OPEN" : + wMsg == DRV_CLOSE ? "DRV_CLOSE" : + wMsg == DRV_ENABLE ? "DRV_ENABLE" : + wMsg == DRV_DISABLE ? "DRV_DISABLE" : + wMsg == DRV_QUERYCONFIGURE ? "DRV_QUERYCONFIGURE" : + wMsg == DRV_CONFIGURE ? "DRV_CONFIGURE" : + wMsg == DRV_INSTALL ? "DRV_INSTALL" : + wMsg == DRV_REMOVE ? "DRV_REMOVE" : "UNKNOWN", + wMsg, dwParam1, dwParam2); + + switch(wMsg) { + case DRV_LOAD: return CoreAudio_drvLoad(); + case DRV_FREE: return CoreAudio_drvFree(); + case DRV_OPEN: return CoreAudio_drvOpen((LPSTR)dwParam1); + case DRV_CLOSE: return CoreAudio_drvClose(dwDevID); + case DRV_ENABLE: return 1; + case DRV_DISABLE: return 1; + case DRV_QUERYCONFIGURE: return 1; + case DRV_CONFIGURE: MessageBoxA(0, "CoreAudio driver!", "CoreAudio driver", MB_OK); return 1; + case DRV_INSTALL: return DRVCNF_RESTART; + case DRV_REMOVE: return DRVCNF_RESTART; + default: + return DefDriverProc(dwDevID, hDriv, wMsg, dwParam1, dwParam2); + } +} diff --git a/pkgs/osu-wine/audio-revert/winecoreaudio.drv/mmdevdrv.c b/pkgs/osu-wine/audio-revert/winecoreaudio.drv/mmdevdrv.c new file mode 100644 index 0000000..367f7ba --- /dev/null +++ b/pkgs/osu-wine/audio-revert/winecoreaudio.drv/mmdevdrv.c @@ -0,0 +1,2414 @@ +/* + * Copyright 2011 Andrew Eikum for CodeWeavers + * + * 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 + */ +#define COBJMACROS + +#include + +#include "windef.h" +#include "winbase.h" +#include "winternl.h" +#include "winnls.h" +#include "winreg.h" +#include "wine/debug.h" +#include "wine/heap.h" +#include "wine/list.h" +#include "wine/unixlib.h" + +#include "ole2.h" +#include "mmdeviceapi.h" +#include "devpkey.h" +#include "dshow.h" +#include "dsound.h" + +#include "initguid.h" +#include "endpointvolume.h" +#include "audioclient.h" +#include "audiopolicy.h" +#include "unixlib.h" + +WINE_DEFAULT_DEBUG_CHANNEL(coreaudio); + +#define NULL_PTR_ERR MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, RPC_X_NULL_REF_POINTER) + +static const REFERENCE_TIME DefaultPeriod = 100000; +static const REFERENCE_TIME MinimumPeriod = 50000; + +struct ACImpl; +typedef struct ACImpl ACImpl; + +typedef struct _AudioSession { + GUID guid; + struct list clients; + + IMMDevice *device; + + float master_vol; + UINT32 channel_count; + float *channel_vols; + BOOL mute; + + struct list entry; +} AudioSession; + +typedef struct _AudioSessionWrapper { + IAudioSessionControl2 IAudioSessionControl2_iface; + IChannelAudioVolume IChannelAudioVolume_iface; + ISimpleAudioVolume ISimpleAudioVolume_iface; + + LONG ref; + + ACImpl *client; + AudioSession *session; +} AudioSessionWrapper; + +struct ACImpl { + IAudioClient3 IAudioClient3_iface; + IAudioRenderClient IAudioRenderClient_iface; + IAudioCaptureClient IAudioCaptureClient_iface; + IAudioClock IAudioClock_iface; + IAudioClock2 IAudioClock2_iface; + IAudioStreamVolume IAudioStreamVolume_iface; + + LONG ref; + + IMMDevice *parent; + IUnknown *pUnkFTMarshal; + + EDataFlow dataflow; + UINT32 channel_count, period_ms; + DWORD flags; + HANDLE event; + float *vols; + + HANDLE timer; + + AudioSession *session; + AudioSessionWrapper *session_wrapper; + + stream_handle stream; + struct list entry; + + char device_name[1]; +}; + +static const IAudioClient3Vtbl AudioClient3_Vtbl; +static const IAudioRenderClientVtbl AudioRenderClient_Vtbl; +static const IAudioCaptureClientVtbl AudioCaptureClient_Vtbl; +static const IAudioSessionControl2Vtbl AudioSessionControl2_Vtbl; +static const ISimpleAudioVolumeVtbl SimpleAudioVolume_Vtbl; +static const IAudioClockVtbl AudioClock_Vtbl; +static const IAudioClock2Vtbl AudioClock2_Vtbl; +static const IAudioStreamVolumeVtbl AudioStreamVolume_Vtbl; +static const IChannelAudioVolumeVtbl ChannelAudioVolume_Vtbl; +static const IAudioSessionManager2Vtbl AudioSessionManager2_Vtbl; + +typedef struct _SessionMgr { + IAudioSessionManager2 IAudioSessionManager2_iface; + + LONG ref; + + IMMDevice *device; +} SessionMgr; + +static const WCHAR *drv_key_devicesW = L"Software\\Wine\\Drivers\\winecoreaudio.drv\\devices"; + +static HANDLE g_timer_q; + +static CRITICAL_SECTION g_sessions_lock; +static CRITICAL_SECTION_DEBUG g_sessions_lock_debug = +{ + 0, 0, &g_sessions_lock, + { &g_sessions_lock_debug.ProcessLocksList, &g_sessions_lock_debug.ProcessLocksList }, + 0, 0, { (DWORD_PTR)(__FILE__ ": g_sessions_lock") } +}; +static CRITICAL_SECTION g_sessions_lock = { &g_sessions_lock_debug, -1, 0, 0, 0, 0 }; +static struct list g_sessions = LIST_INIT(g_sessions); + +static AudioSessionWrapper *AudioSessionWrapper_Create(ACImpl *client); + +static inline ACImpl *impl_from_IAudioClient3(IAudioClient3 *iface) +{ + return CONTAINING_RECORD(iface, ACImpl, IAudioClient3_iface); +} + +static inline ACImpl *impl_from_IAudioRenderClient(IAudioRenderClient *iface) +{ + return CONTAINING_RECORD(iface, ACImpl, IAudioRenderClient_iface); +} + +static inline ACImpl *impl_from_IAudioCaptureClient(IAudioCaptureClient *iface) +{ + return CONTAINING_RECORD(iface, ACImpl, IAudioCaptureClient_iface); +} + +static inline AudioSessionWrapper *impl_from_IAudioSessionControl2(IAudioSessionControl2 *iface) +{ + return CONTAINING_RECORD(iface, AudioSessionWrapper, IAudioSessionControl2_iface); +} + +static inline AudioSessionWrapper *impl_from_ISimpleAudioVolume(ISimpleAudioVolume *iface) +{ + return CONTAINING_RECORD(iface, AudioSessionWrapper, ISimpleAudioVolume_iface); +} + +static inline AudioSessionWrapper *impl_from_IChannelAudioVolume(IChannelAudioVolume *iface) +{ + return CONTAINING_RECORD(iface, AudioSessionWrapper, IChannelAudioVolume_iface); +} + +static inline ACImpl *impl_from_IAudioClock(IAudioClock *iface) +{ + return CONTAINING_RECORD(iface, ACImpl, IAudioClock_iface); +} + +static inline ACImpl *impl_from_IAudioClock2(IAudioClock2 *iface) +{ + return CONTAINING_RECORD(iface, ACImpl, IAudioClock2_iface); +} + +static inline ACImpl *impl_from_IAudioStreamVolume(IAudioStreamVolume *iface) +{ + return CONTAINING_RECORD(iface, ACImpl, IAudioStreamVolume_iface); +} + +static inline SessionMgr *impl_from_IAudioSessionManager2(IAudioSessionManager2 *iface) +{ + return CONTAINING_RECORD(iface, SessionMgr, IAudioSessionManager2_iface); +} + +BOOL WINAPI DllMain(HINSTANCE dll, DWORD reason, void *reserved) +{ + switch (reason) + { + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls(dll); + if (__wine_init_unix_call()) + return FALSE; + g_timer_q = CreateTimerQueue(); + if(!g_timer_q) + return FALSE; + break; + + case DLL_PROCESS_DETACH: + if (reserved) break; + DeleteCriticalSection(&g_sessions_lock); + CloseHandle(g_timer_q); + break; + } + return TRUE; +} + +int WINAPI AUDDRV_GetPriority(void) +{ + return Priority_Neutral; +} + +static void set_device_guid(EDataFlow flow, HKEY drv_key, const WCHAR *key_name, + GUID *guid) +{ + HKEY key; + BOOL opened = FALSE; + LONG lr; + + if(!drv_key){ + lr = RegCreateKeyExW(HKEY_CURRENT_USER, drv_key_devicesW, 0, NULL, 0, KEY_WRITE, + NULL, &drv_key, NULL); + if(lr != ERROR_SUCCESS){ + ERR("RegCreateKeyEx(drv_key) failed: %lu\n", lr); + return; + } + opened = TRUE; + } + + lr = RegCreateKeyExW(drv_key, key_name, 0, NULL, 0, KEY_WRITE, + NULL, &key, NULL); + if(lr != ERROR_SUCCESS){ + ERR("RegCreateKeyEx(%s) failed: %lu\n", wine_dbgstr_w(key_name), lr); + goto exit; + } + + lr = RegSetValueExW(key, L"guid", 0, REG_BINARY, (BYTE*)guid, + sizeof(GUID)); + if(lr != ERROR_SUCCESS) + ERR("RegSetValueEx(%s\\guid) failed: %lu\n", wine_dbgstr_w(key_name), lr); + + RegCloseKey(key); +exit: + if(opened) + RegCloseKey(drv_key); +} + +static void get_device_guid(EDataFlow flow, const char *dev, GUID *guid) +{ + HKEY key = NULL, dev_key; + DWORD type, size = sizeof(*guid); + WCHAR key_name[256]; + + if(flow == eCapture) + key_name[0] = '1'; + else + key_name[0] = '0'; + key_name[1] = ','; + + MultiByteToWideChar(CP_UNIXCP, 0, dev, -1, key_name + 2, ARRAY_SIZE(key_name) - 2); + + if(RegOpenKeyExW(HKEY_CURRENT_USER, drv_key_devicesW, 0, KEY_WRITE|KEY_READ, &key) == ERROR_SUCCESS){ + if(RegOpenKeyExW(key, key_name, 0, KEY_READ, &dev_key) == ERROR_SUCCESS){ + if(RegQueryValueExW(dev_key, L"guid", 0, &type, + (BYTE*)guid, &size) == ERROR_SUCCESS){ + if(type == REG_BINARY){ + RegCloseKey(dev_key); + RegCloseKey(key); + return; + } + ERR("Invalid type for device %s GUID: %lu; ignoring and overwriting\n", + wine_dbgstr_w(key_name), type); + } + RegCloseKey(dev_key); + } + } + + CoCreateGuid(guid); + + set_device_guid(flow, key, key_name, guid); + + if(key) + RegCloseKey(key); +} + +static void set_stream_volumes(ACImpl *This, int channel) +{ + struct set_volumes_params params; + + params.stream = This->stream; + params.master_volume = This->session->mute ? 0.0f : This->session->master_vol; + params.volumes = This->vols; + params.session_volumes = This->session->channel_vols; + params.channel = channel; + + UNIX_CALL(set_volumes, ¶ms); +} + +HRESULT WINAPI AUDDRV_GetEndpointIDs(EDataFlow flow, WCHAR ***ids_out, + GUID **guids_out, UINT *num, UINT *def_index) +{ + struct get_endpoint_ids_params params; + unsigned int i; + GUID *guids = NULL; + WCHAR **ids = NULL; + + TRACE("%d %p %p %p\n", flow, ids_out, num, def_index); + + params.flow = flow; + params.size = 1000; + params.endpoints = NULL; + do{ + heap_free(params.endpoints); + params.endpoints = heap_alloc(params.size); + UNIX_CALL(get_endpoint_ids, ¶ms); + }while(params.result == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)); + + if(FAILED(params.result)) goto end; + + ids = heap_alloc_zero(params.num * sizeof(*ids)); + guids = heap_alloc(params.num * sizeof(*guids)); + if(!ids || !guids){ + params.result = E_OUTOFMEMORY; + goto end; + } + + for(i = 0; i < params.num; i++){ + const WCHAR *name = (WCHAR *)((char *)params.endpoints + params.endpoints[i].name); + const char *device = (char *)params.endpoints + params.endpoints[i].device; + const unsigned int size = (wcslen(name) + 1) * sizeof(WCHAR); + + ids[i] = heap_alloc(size); + if(!ids[i]){ + params.result = E_OUTOFMEMORY; + goto end; + } + memcpy(ids[i], name, size); + get_device_guid(flow, device, guids + i); + } + *def_index = params.default_idx; + +end: + heap_free(params.endpoints); + if(FAILED(params.result)){ + heap_free(guids); + if(ids){ + for(i = 0; i < params.num; i++) heap_free(ids[i]); + heap_free(ids); + } + }else{ + *ids_out = ids; + *guids_out = guids; + *num = params.num; + } + + return params.result; +} + +static BOOL get_device_name_by_guid(const GUID *guid, char *name, const SIZE_T name_size, EDataFlow *flow) +{ + HKEY devices_key; + UINT i = 0; + WCHAR key_name[256]; + DWORD key_name_size; + + if(RegOpenKeyExW(HKEY_CURRENT_USER, drv_key_devicesW, 0, KEY_READ, &devices_key) != ERROR_SUCCESS){ + ERR("No devices in registry?\n"); + return FALSE; + } + + while(1){ + HKEY key; + DWORD size, type; + GUID reg_guid; + + key_name_size = ARRAY_SIZE(key_name); + if(RegEnumKeyExW(devices_key, i++, key_name, &key_name_size, NULL, + NULL, NULL, NULL) != ERROR_SUCCESS) + break; + + if(RegOpenKeyExW(devices_key, key_name, 0, KEY_READ, &key) != ERROR_SUCCESS){ + WARN("Couldn't open key: %s\n", wine_dbgstr_w(key_name)); + continue; + } + + size = sizeof(reg_guid); + if(RegQueryValueExW(key, L"guid", 0, &type, + (BYTE*)®_guid, &size) == ERROR_SUCCESS){ + if(IsEqualGUID(®_guid, guid)){ + RegCloseKey(key); + RegCloseKey(devices_key); + + TRACE("Found matching device key: %s\n", wine_dbgstr_w(key_name)); + + if(key_name[0] == '0') + *flow = eRender; + else if(key_name[0] == '1') + *flow = eCapture; + else{ + ERR("Unknown device type: %c\n", key_name[0]); + return FALSE; + } + + WideCharToMultiByte(CP_UNIXCP, 0, key_name + 2, -1, name, name_size, NULL, NULL); + + return TRUE; + } + } + + RegCloseKey(key); + } + + RegCloseKey(devices_key); + + WARN("No matching device in registry for GUID %s\n", debugstr_guid(guid)); + + return FALSE; +} + +HRESULT WINAPI AUDDRV_GetAudioEndpoint(GUID *guid, IMMDevice *dev, IAudioClient **out) +{ + ACImpl *This; + char name[256]; + SIZE_T name_len; + EDataFlow dataflow; + HRESULT hr; + + TRACE("%s %p %p\n", debugstr_guid(guid), dev, out); + + if(!get_device_name_by_guid(guid, name, sizeof(name), &dataflow)) + return AUDCLNT_E_DEVICE_INVALIDATED; + + if(dataflow != eRender && dataflow != eCapture) + return E_INVALIDARG; + + name_len = strlen(name); + This = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, offsetof(ACImpl, device_name[name_len + 1])); + if(!This) + return E_OUTOFMEMORY; + + This->IAudioClient3_iface.lpVtbl = &AudioClient3_Vtbl; + This->IAudioRenderClient_iface.lpVtbl = &AudioRenderClient_Vtbl; + This->IAudioCaptureClient_iface.lpVtbl = &AudioCaptureClient_Vtbl; + This->IAudioClock_iface.lpVtbl = &AudioClock_Vtbl; + This->IAudioClock2_iface.lpVtbl = &AudioClock2_Vtbl; + This->IAudioStreamVolume_iface.lpVtbl = &AudioStreamVolume_Vtbl; + + This->dataflow = dataflow; + memcpy(This->device_name, name, name_len + 1); + + hr = CoCreateFreeThreadedMarshaler((IUnknown *)&This->IAudioClient3_iface, &This->pUnkFTMarshal); + if (FAILED(hr)) { + HeapFree(GetProcessHeap(), 0, This); + return hr; + } + + This->parent = dev; + IMMDevice_AddRef(This->parent); + + *out = (IAudioClient *)&This->IAudioClient3_iface; + IAudioClient3_AddRef(&This->IAudioClient3_iface); + + return S_OK; +} + +static HRESULT WINAPI AudioClient_QueryInterface(IAudioClient3 *iface, + REFIID riid, void **ppv) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + if(IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IAudioClient) || + IsEqualIID(riid, &IID_IAudioClient2) || + IsEqualIID(riid, &IID_IAudioClient3)) + *ppv = iface; + else if(IsEqualIID(riid, &IID_IMarshal)) + return IUnknown_QueryInterface(This->pUnkFTMarshal, riid, ppv); + + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioClient_AddRef(IAudioClient3 *iface) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + ULONG ref; + ref = InterlockedIncrement(&This->ref); + TRACE("(%p) Refcount now %lu\n", This, ref); + return ref; +} + +static ULONG WINAPI AudioClient_Release(IAudioClient3 *iface) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct release_stream_params params; + ULONG ref; + + ref = InterlockedDecrement(&This->ref); + TRACE("(%p) Refcount now %lu\n", This, ref); + if(!ref){ + if(This->timer){ + HANDLE event; + BOOL wait; + event = CreateEventW(NULL, TRUE, FALSE, NULL); + wait = !DeleteTimerQueueTimer(g_timer_q, This->timer, event); + wait = wait && GetLastError() == ERROR_IO_PENDING; + if(event && wait) + WaitForSingleObject(event, INFINITE); + CloseHandle(event); + } + if(This->stream){ + params.stream = This->stream; + params.timer_thread = NULL; + UNIX_CALL(release_stream, ¶ms); + } + if(This->session){ + EnterCriticalSection(&g_sessions_lock); + list_remove(&This->entry); + LeaveCriticalSection(&g_sessions_lock); + } + HeapFree(GetProcessHeap(), 0, This->vols); + IMMDevice_Release(This->parent); + IUnknown_Release(This->pUnkFTMarshal); + HeapFree(GetProcessHeap(), 0, This); + } + return ref; +} + +static void dump_fmt(const WAVEFORMATEX *fmt) +{ + TRACE("wFormatTag: 0x%x (", fmt->wFormatTag); + switch(fmt->wFormatTag){ + case WAVE_FORMAT_PCM: + TRACE("WAVE_FORMAT_PCM"); + break; + case WAVE_FORMAT_IEEE_FLOAT: + TRACE("WAVE_FORMAT_IEEE_FLOAT"); + break; + case WAVE_FORMAT_EXTENSIBLE: + TRACE("WAVE_FORMAT_EXTENSIBLE"); + break; + default: + TRACE("Unknown"); + break; + } + TRACE(")\n"); + + TRACE("nChannels: %u\n", fmt->nChannels); + TRACE("nSamplesPerSec: %lu\n", fmt->nSamplesPerSec); + TRACE("nAvgBytesPerSec: %lu\n", fmt->nAvgBytesPerSec); + TRACE("nBlockAlign: %u\n", fmt->nBlockAlign); + TRACE("wBitsPerSample: %u\n", fmt->wBitsPerSample); + TRACE("cbSize: %u\n", fmt->cbSize); + + if(fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE){ + WAVEFORMATEXTENSIBLE *fmtex = (void*)fmt; + TRACE("dwChannelMask: %08lx\n", fmtex->dwChannelMask); + TRACE("Samples: %04x\n", fmtex->Samples.wReserved); + TRACE("SubFormat: %s\n", wine_dbgstr_guid(&fmtex->SubFormat)); + } +} + +static void session_init_vols(AudioSession *session, UINT channels) +{ + if(session->channel_count < channels){ + UINT i; + + if(session->channel_vols) + session->channel_vols = HeapReAlloc(GetProcessHeap(), 0, + session->channel_vols, sizeof(float) * channels); + else + session->channel_vols = HeapAlloc(GetProcessHeap(), 0, + sizeof(float) * channels); + if(!session->channel_vols) + return; + + for(i = session->channel_count; i < channels; ++i) + session->channel_vols[i] = 1.f; + + session->channel_count = channels; + } +} + +static AudioSession *create_session(const GUID *guid, IMMDevice *device, + UINT num_channels) +{ + AudioSession *ret; + + ret = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(AudioSession)); + if(!ret) + return NULL; + + memcpy(&ret->guid, guid, sizeof(GUID)); + + ret->device = device; + + list_init(&ret->clients); + + list_add_head(&g_sessions, &ret->entry); + + session_init_vols(ret, num_channels); + + ret->master_vol = 1.f; + + return ret; +} + +/* if channels == 0, then this will return or create a session with + * matching dataflow and GUID. otherwise, channels must also match */ +static HRESULT get_audio_session(const GUID *sessionguid, + IMMDevice *device, UINT channels, AudioSession **out) +{ + AudioSession *session; + + if(!sessionguid || IsEqualGUID(sessionguid, &GUID_NULL)){ + *out = create_session(&GUID_NULL, device, channels); + if(!*out) + return E_OUTOFMEMORY; + + return S_OK; + } + + *out = NULL; + LIST_FOR_EACH_ENTRY(session, &g_sessions, AudioSession, entry){ + if(session->device == device && + IsEqualGUID(sessionguid, &session->guid)){ + session_init_vols(session, channels); + *out = session; + break; + } + } + + if(!*out){ + *out = create_session(sessionguid, device, channels); + if(!*out) + return E_OUTOFMEMORY; + } + + return S_OK; +} + +static HRESULT WINAPI AudioClient_Initialize(IAudioClient3 *iface, + AUDCLNT_SHAREMODE mode, DWORD flags, REFERENCE_TIME duration, + REFERENCE_TIME period, const WAVEFORMATEX *fmt, + const GUID *sessionguid) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct release_stream_params release_params; + struct create_stream_params params; + stream_handle stream; + UINT32 i; + + TRACE("(%p)->(%x, %lx, %s, %s, %p, %s)\n", This, mode, flags, + wine_dbgstr_longlong(duration), wine_dbgstr_longlong(period), fmt, debugstr_guid(sessionguid)); + + if(!fmt) + return E_POINTER; + + dump_fmt(fmt); + + if(mode != AUDCLNT_SHAREMODE_SHARED && mode != AUDCLNT_SHAREMODE_EXCLUSIVE) + return E_INVALIDARG; + + if(flags & ~(AUDCLNT_STREAMFLAGS_CROSSPROCESS | + AUDCLNT_STREAMFLAGS_LOOPBACK | + AUDCLNT_STREAMFLAGS_EVENTCALLBACK | + AUDCLNT_STREAMFLAGS_NOPERSIST | + AUDCLNT_STREAMFLAGS_RATEADJUST | + AUDCLNT_SESSIONFLAGS_EXPIREWHENUNOWNED | + AUDCLNT_SESSIONFLAGS_DISPLAY_HIDE | + AUDCLNT_SESSIONFLAGS_DISPLAY_HIDEWHENEXPIRED | + AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY | + AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM)){ + FIXME("Unknown flags: %08lx\n", flags); + return E_INVALIDARG; + } + + if(mode == AUDCLNT_SHAREMODE_SHARED){ + period = DefaultPeriod; + if( duration < 3 * period) + duration = 3 * period; + }else{ + if(fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE){ + if(((WAVEFORMATEXTENSIBLE*)fmt)->dwChannelMask == 0 || + ((WAVEFORMATEXTENSIBLE*)fmt)->dwChannelMask & SPEAKER_RESERVED) + return AUDCLNT_E_UNSUPPORTED_FORMAT; + } + + if(!period) + period = DefaultPeriod; /* not minimum */ + if(period < MinimumPeriod || period > 5000000) + return AUDCLNT_E_INVALID_DEVICE_PERIOD; + if(duration > 20000000) /* the smaller the period, the lower this limit */ + return AUDCLNT_E_BUFFER_SIZE_ERROR; + if(flags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK){ + if(duration != period) + return AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL; + FIXME("EXCLUSIVE mode with EVENTCALLBACK\n"); + return AUDCLNT_E_DEVICE_IN_USE; + }else{ + if( duration < 8 * period) + duration = 8 * period; /* may grow above 2s */ + } + } + + EnterCriticalSection(&g_sessions_lock); + + if(This->stream){ + LeaveCriticalSection(&g_sessions_lock); + return AUDCLNT_E_ALREADY_INITIALIZED; + } + + params.name = NULL; + params.device = This->device_name; + params.flow = This->dataflow; + params.share = mode; + params.flags = flags; + params.duration = duration; + params.period = period; + params.fmt = fmt; + params.channel_count = NULL; + params.stream = &stream; + + UNIX_CALL(create_stream, ¶ms); + if(FAILED(params.result)){ + LeaveCriticalSection(&g_sessions_lock); + return params.result; + } + + This->flags = flags; + This->channel_count = fmt->nChannels; + This->period_ms = period / 10000; + + This->vols = HeapAlloc(GetProcessHeap(), 0, This->channel_count * sizeof(float)); + if(!This->vols){ + params.result = E_OUTOFMEMORY; + goto end; + } + + for(i = 0; i < This->channel_count; ++i) + This->vols[i] = 1.f; + + params.result = get_audio_session(sessionguid, This->parent, fmt->nChannels, &This->session); + if(FAILED(params.result)) goto end; + + list_add_tail(&This->session->clients, &This->entry); + +end: + if(FAILED(params.result)){ + release_params.stream = stream; + UNIX_CALL(release_stream, &release_params); + HeapFree(GetProcessHeap(), 0, This->vols); + This->vols = NULL; + }else{ + This->stream = stream; + set_stream_volumes(This, -1); + } + + LeaveCriticalSection(&g_sessions_lock); + + return params.result; +} + +static HRESULT WINAPI AudioClient_GetBufferSize(IAudioClient3 *iface, + UINT32 *frames) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct get_buffer_size_params params; + + TRACE("(%p)->(%p)\n", This, frames); + + if(!frames) + return E_POINTER; + + if(!This->stream) + return AUDCLNT_E_NOT_INITIALIZED; + + params.stream = This->stream; + params.frames = frames; + UNIX_CALL(get_buffer_size, ¶ms); + return params.result; +} + +static HRESULT WINAPI AudioClient_GetStreamLatency(IAudioClient3 *iface, + REFERENCE_TIME *out) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct get_latency_params params; + + TRACE("(%p)->(%p)\n", This, out); + + if(!out) + return E_POINTER; + + if(!This->stream) + return AUDCLNT_E_NOT_INITIALIZED; + + params.stream = This->stream; + params.latency = out; + UNIX_CALL(get_latency, ¶ms); + return params.result; +} + +static HRESULT WINAPI AudioClient_GetCurrentPadding(IAudioClient3 *iface, + UINT32 *numpad) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct get_current_padding_params params; + + TRACE("(%p)->(%p)\n", This, numpad); + + if(!numpad) + return E_POINTER; + + if(!This->stream) + return AUDCLNT_E_NOT_INITIALIZED; + + params.stream = This->stream; + params.padding = numpad; + UNIX_CALL(get_current_padding, ¶ms); + return params.result; +} + +static HRESULT WINAPI AudioClient_IsFormatSupported(IAudioClient3 *iface, + AUDCLNT_SHAREMODE mode, const WAVEFORMATEX *pwfx, + WAVEFORMATEX **outpwfx) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct is_format_supported_params params; + + TRACE("(%p)->(%x, %p, %p)\n", This, mode, pwfx, outpwfx); + if(pwfx) dump_fmt(pwfx); + + params.device = This->device_name; + params.flow = This->dataflow; + params.share = mode; + params.fmt_in = pwfx; + params.fmt_out = NULL; + + if(outpwfx){ + *outpwfx = NULL; + if(mode == AUDCLNT_SHAREMODE_SHARED) + params.fmt_out = CoTaskMemAlloc(sizeof(*params.fmt_out)); + } + UNIX_CALL(is_format_supported, ¶ms); + + if(params.result == S_FALSE) + *outpwfx = ¶ms.fmt_out->Format; + else + CoTaskMemFree(params.fmt_out); + + return params.result; +} + +static HRESULT WINAPI AudioClient_GetMixFormat(IAudioClient3 *iface, + WAVEFORMATEX **pwfx) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct get_mix_format_params params; + + TRACE("(%p)->(%p)\n", This, pwfx); + + if(!pwfx) + return E_POINTER; + *pwfx = NULL; + + params.device = This->device_name; + params.flow = This->dataflow; + params.fmt = CoTaskMemAlloc(sizeof(WAVEFORMATEXTENSIBLE)); + if(!params.fmt) + return E_OUTOFMEMORY; + + UNIX_CALL(get_mix_format, ¶ms); + + if(SUCCEEDED(params.result)){ + *pwfx = ¶ms.fmt->Format; + dump_fmt(*pwfx); + }else + CoTaskMemFree(params.fmt); + + return params.result; +} + +static HRESULT WINAPI AudioClient_GetDevicePeriod(IAudioClient3 *iface, + REFERENCE_TIME *defperiod, REFERENCE_TIME *minperiod) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + + TRACE("(%p)->(%p, %p)\n", This, defperiod, minperiod); + + if(!defperiod && !minperiod) + return E_POINTER; + + if(defperiod) + *defperiod = DefaultPeriod; + if(minperiod) + *minperiod = MinimumPeriod; + + return S_OK; +} + +void CALLBACK ca_period_cb(void *user, BOOLEAN timer) +{ + ACImpl *This = user; + + if(This->event) + SetEvent(This->event); +} + +static HRESULT WINAPI AudioClient_Start(IAudioClient3 *iface) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct start_params params; + + TRACE("(%p)\n", This); + + if(!This->stream) + return AUDCLNT_E_NOT_INITIALIZED; + + if((This->flags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK) && !This->event) + return AUDCLNT_E_EVENTHANDLE_NOT_SET; + + params.stream = This->stream; + UNIX_CALL(start, ¶ms); + + if(SUCCEEDED(params.result)){ + if(This->event && !This->timer){ + if(!CreateTimerQueueTimer(&This->timer, g_timer_q, ca_period_cb, This, 0, + This->period_ms, WT_EXECUTEINTIMERTHREAD)){ + This->timer = NULL; + IAudioClient3_Stop(iface); + WARN("Unable to create timer: %lu\n", GetLastError()); + return E_OUTOFMEMORY; + } + } + } + return params.result; +} + +static HRESULT WINAPI AudioClient_Stop(IAudioClient3 *iface) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct stop_params params; + + TRACE("(%p)\n", This); + + if(!This->stream) + return AUDCLNT_E_NOT_INITIALIZED; + + params.stream = This->stream; + UNIX_CALL(stop, ¶ms); + return params.result; +} + +static HRESULT WINAPI AudioClient_Reset(IAudioClient3 *iface) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct reset_params params; + + TRACE("(%p)\n", This); + + if(!This->stream) + return AUDCLNT_E_NOT_INITIALIZED; + + params.stream = This->stream; + UNIX_CALL(reset, ¶ms); + return params.result; +} + +static HRESULT WINAPI AudioClient_SetEventHandle(IAudioClient3 *iface, + HANDLE event) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + HRESULT hr = S_OK; + + TRACE("(%p)->(%p)\n", This, event); + + if(!event) + return E_INVALIDARG; + + if(!This->stream) + return AUDCLNT_E_NOT_INITIALIZED; + + EnterCriticalSection(&g_sessions_lock); + + if(!(This->flags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK)) + hr = AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED; + else if(This->event){ + FIXME("called twice\n"); + hr = HRESULT_FROM_WIN32(ERROR_INVALID_NAME); + }else + This->event = event; + + LeaveCriticalSection(&g_sessions_lock); + + return hr; +} + +static HRESULT WINAPI AudioClient_GetService(IAudioClient3 *iface, REFIID riid, + void **ppv) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + HRESULT hr; + + TRACE("(%p)->(%s, %p)\n", This, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + if(!This->stream) + return AUDCLNT_E_NOT_INITIALIZED; + + EnterCriticalSection(&g_sessions_lock); + + if(IsEqualIID(riid, &IID_IAudioRenderClient)){ + if(This->dataflow != eRender){ + hr = AUDCLNT_E_WRONG_ENDPOINT_TYPE; + goto end; + } + IAudioRenderClient_AddRef(&This->IAudioRenderClient_iface); + *ppv = &This->IAudioRenderClient_iface; + }else if(IsEqualIID(riid, &IID_IAudioCaptureClient)){ + if(This->dataflow != eCapture){ + hr = AUDCLNT_E_WRONG_ENDPOINT_TYPE; + goto end; + } + IAudioCaptureClient_AddRef(&This->IAudioCaptureClient_iface); + *ppv = &This->IAudioCaptureClient_iface; + }else if(IsEqualIID(riid, &IID_IAudioClock)){ + IAudioClock_AddRef(&This->IAudioClock_iface); + *ppv = &This->IAudioClock_iface; + }else if(IsEqualIID(riid, &IID_IAudioStreamVolume)){ + IAudioStreamVolume_AddRef(&This->IAudioStreamVolume_iface); + *ppv = &This->IAudioStreamVolume_iface; + }else if(IsEqualIID(riid, &IID_IAudioSessionControl)){ + if(!This->session_wrapper){ + This->session_wrapper = AudioSessionWrapper_Create(This); + if(!This->session_wrapper){ + hr = E_OUTOFMEMORY; + goto end; + } + }else + IAudioSessionControl2_AddRef(&This->session_wrapper->IAudioSessionControl2_iface); + + *ppv = &This->session_wrapper->IAudioSessionControl2_iface; + }else if(IsEqualIID(riid, &IID_IChannelAudioVolume)){ + if(!This->session_wrapper){ + This->session_wrapper = AudioSessionWrapper_Create(This); + if(!This->session_wrapper){ + hr = E_OUTOFMEMORY; + goto end; + } + }else + IChannelAudioVolume_AddRef(&This->session_wrapper->IChannelAudioVolume_iface); + + *ppv = &This->session_wrapper->IChannelAudioVolume_iface; + }else if(IsEqualIID(riid, &IID_ISimpleAudioVolume)){ + if(!This->session_wrapper){ + This->session_wrapper = AudioSessionWrapper_Create(This); + if(!This->session_wrapper){ + hr = E_OUTOFMEMORY; + goto end; + } + }else + ISimpleAudioVolume_AddRef(&This->session_wrapper->ISimpleAudioVolume_iface); + + *ppv = &This->session_wrapper->ISimpleAudioVolume_iface; + } + + if(*ppv) hr = S_OK; + else{ + FIXME("stub %s\n", debugstr_guid(riid)); + hr = E_NOINTERFACE; + } + +end: + LeaveCriticalSection(&g_sessions_lock); + return hr; +} + +static HRESULT WINAPI AudioClient_IsOffloadCapable(IAudioClient3 *iface, + AUDIO_STREAM_CATEGORY category, BOOL *offload_capable) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + + TRACE("(%p)->(0x%x, %p)\n", This, category, offload_capable); + + if(!offload_capable) + return E_INVALIDARG; + + *offload_capable = FALSE; + + return S_OK; +} + +static HRESULT WINAPI AudioClient_SetClientProperties(IAudioClient3 *iface, + const AudioClientProperties *prop) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + const Win8AudioClientProperties *legacy_prop = (const Win8AudioClientProperties *)prop; + + TRACE("(%p)->(%p)\n", This, prop); + + if(!legacy_prop) + return E_POINTER; + + if(legacy_prop->cbSize == sizeof(AudioClientProperties)){ + TRACE("{ bIsOffload: %u, eCategory: 0x%x, Options: 0x%x }\n", + legacy_prop->bIsOffload, + legacy_prop->eCategory, + prop->Options); + }else if(legacy_prop->cbSize == sizeof(Win8AudioClientProperties)){ + TRACE("{ bIsOffload: %u, eCategory: 0x%x }\n", + legacy_prop->bIsOffload, + legacy_prop->eCategory); + }else{ + WARN("Unsupported Size = %d\n", legacy_prop->cbSize); + return E_INVALIDARG; + } + + + if(legacy_prop->bIsOffload) + return AUDCLNT_E_ENDPOINT_OFFLOAD_NOT_CAPABLE; + + return S_OK; +} + +static HRESULT WINAPI AudioClient_GetBufferSizeLimits(IAudioClient3 *iface, + const WAVEFORMATEX *format, BOOL event_driven, REFERENCE_TIME *min_duration, + REFERENCE_TIME *max_duration) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + + FIXME("(%p)->(%p, %u, %p, %p)\n", This, format, event_driven, min_duration, max_duration); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioClient_GetSharedModeEnginePeriod(IAudioClient3 *iface, + const WAVEFORMATEX *format, UINT32 *default_period_frames, UINT32 *unit_period_frames, + UINT32 *min_period_frames, UINT32 *max_period_frames) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + + FIXME("(%p)->(%p, %p, %p, %p, %p)\n", This, format, default_period_frames, unit_period_frames, + min_period_frames, max_period_frames); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioClient_GetCurrentSharedModeEnginePeriod(IAudioClient3 *iface, + WAVEFORMATEX **cur_format, UINT32 *cur_period_frames) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + + FIXME("(%p)->(%p, %p)\n", This, cur_format, cur_period_frames); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioClient_InitializeSharedAudioStream(IAudioClient3 *iface, + DWORD flags, UINT32 period_frames, const WAVEFORMATEX *format, + const GUID *session_guid) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + + FIXME("(%p)->(0x%lx, %u, %p, %s)\n", This, flags, period_frames, format, debugstr_guid(session_guid)); + + return E_NOTIMPL; +} + +static const IAudioClient3Vtbl AudioClient3_Vtbl = +{ + AudioClient_QueryInterface, + AudioClient_AddRef, + AudioClient_Release, + AudioClient_Initialize, + AudioClient_GetBufferSize, + AudioClient_GetStreamLatency, + AudioClient_GetCurrentPadding, + AudioClient_IsFormatSupported, + AudioClient_GetMixFormat, + AudioClient_GetDevicePeriod, + AudioClient_Start, + AudioClient_Stop, + AudioClient_Reset, + AudioClient_SetEventHandle, + AudioClient_GetService, + AudioClient_IsOffloadCapable, + AudioClient_SetClientProperties, + AudioClient_GetBufferSizeLimits, + AudioClient_GetSharedModeEnginePeriod, + AudioClient_GetCurrentSharedModeEnginePeriod, + AudioClient_InitializeSharedAudioStream, +}; + +static HRESULT WINAPI AudioRenderClient_QueryInterface( + IAudioRenderClient *iface, REFIID riid, void **ppv) +{ + ACImpl *This = impl_from_IAudioRenderClient(iface); + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + if(IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IAudioRenderClient)) + *ppv = iface; + else if(IsEqualIID(riid, &IID_IMarshal)) + return IUnknown_QueryInterface(This->pUnkFTMarshal, riid, ppv); + + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioRenderClient_AddRef(IAudioRenderClient *iface) +{ + ACImpl *This = impl_from_IAudioRenderClient(iface); + return AudioClient_AddRef(&This->IAudioClient3_iface); +} + +static ULONG WINAPI AudioRenderClient_Release(IAudioRenderClient *iface) +{ + ACImpl *This = impl_from_IAudioRenderClient(iface); + return AudioClient_Release(&This->IAudioClient3_iface); +} + +static HRESULT WINAPI AudioRenderClient_GetBuffer(IAudioRenderClient *iface, + UINT32 frames, BYTE **data) +{ + ACImpl *This = impl_from_IAudioRenderClient(iface); + struct get_render_buffer_params params; + + TRACE("(%p)->(%u, %p)\n", This, frames, data); + + if(!data) + return E_POINTER; + *data = NULL; + + params.stream = This->stream; + params.frames = frames; + params.data = data; + UNIX_CALL(get_render_buffer, ¶ms); + return params.result; +} + +static HRESULT WINAPI AudioRenderClient_ReleaseBuffer( + IAudioRenderClient *iface, UINT32 frames, DWORD flags) +{ + ACImpl *This = impl_from_IAudioRenderClient(iface); + struct release_render_buffer_params params; + + TRACE("(%p)->(%u, %lx)\n", This, frames, flags); + + params.stream = This->stream; + params.written_frames = frames; + params.flags = flags; + UNIX_CALL(release_render_buffer, ¶ms); + return params.result; +} + +static const IAudioRenderClientVtbl AudioRenderClient_Vtbl = { + AudioRenderClient_QueryInterface, + AudioRenderClient_AddRef, + AudioRenderClient_Release, + AudioRenderClient_GetBuffer, + AudioRenderClient_ReleaseBuffer +}; + +static HRESULT WINAPI AudioCaptureClient_QueryInterface( + IAudioCaptureClient *iface, REFIID riid, void **ppv) +{ + ACImpl *This = impl_from_IAudioCaptureClient(iface); + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + if(IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IAudioCaptureClient)) + *ppv = iface; + else if(IsEqualIID(riid, &IID_IMarshal)) + return IUnknown_QueryInterface(This->pUnkFTMarshal, riid, ppv); + + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioCaptureClient_AddRef(IAudioCaptureClient *iface) +{ + ACImpl *This = impl_from_IAudioCaptureClient(iface); + return IAudioClient3_AddRef(&This->IAudioClient3_iface); +} + +static ULONG WINAPI AudioCaptureClient_Release(IAudioCaptureClient *iface) +{ + ACImpl *This = impl_from_IAudioCaptureClient(iface); + return IAudioClient3_Release(&This->IAudioClient3_iface); +} + +static HRESULT WINAPI AudioCaptureClient_GetBuffer(IAudioCaptureClient *iface, + BYTE **data, UINT32 *frames, DWORD *flags, UINT64 *devpos, + UINT64 *qpcpos) +{ + ACImpl *This = impl_from_IAudioCaptureClient(iface); + struct get_capture_buffer_params params; + + TRACE("(%p)->(%p, %p, %p, %p, %p)\n", This, data, frames, flags, + devpos, qpcpos); + + if(!data) + return E_POINTER; + + *data = NULL; + + if(!frames || !flags) + return E_POINTER; + + params.stream = This->stream; + params.data = data; + params.frames = frames; + params.flags = (UINT *)flags; + params.devpos = devpos; + params.qpcpos = qpcpos; + UNIX_CALL(get_capture_buffer, ¶ms); + return params.result; +} + +static HRESULT WINAPI AudioCaptureClient_ReleaseBuffer( + IAudioCaptureClient *iface, UINT32 done) +{ + ACImpl *This = impl_from_IAudioCaptureClient(iface); + struct release_capture_buffer_params params; + + TRACE("(%p)->(%u)\n", This, done); + + params.stream = This->stream; + params.done = done; + UNIX_CALL(release_capture_buffer, ¶ms); + return params.result; +} + +static HRESULT WINAPI AudioCaptureClient_GetNextPacketSize( + IAudioCaptureClient *iface, UINT32 *frames) +{ + ACImpl *This = impl_from_IAudioCaptureClient(iface); + struct get_next_packet_size_params params; + + TRACE("(%p)->(%p)\n", This, frames); + + if(!frames) + return E_POINTER; + + params.stream = This->stream; + params.frames = frames; + UNIX_CALL(get_next_packet_size, ¶ms); + return params.result; +} + +static const IAudioCaptureClientVtbl AudioCaptureClient_Vtbl = +{ + AudioCaptureClient_QueryInterface, + AudioCaptureClient_AddRef, + AudioCaptureClient_Release, + AudioCaptureClient_GetBuffer, + AudioCaptureClient_ReleaseBuffer, + AudioCaptureClient_GetNextPacketSize +}; + +static HRESULT WINAPI AudioClock_QueryInterface(IAudioClock *iface, + REFIID riid, void **ppv) +{ + ACImpl *This = impl_from_IAudioClock(iface); + + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + if(IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IAudioClock)) + *ppv = iface; + else if(IsEqualIID(riid, &IID_IAudioClock2)) + *ppv = &This->IAudioClock2_iface; + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioClock_AddRef(IAudioClock *iface) +{ + ACImpl *This = impl_from_IAudioClock(iface); + return IAudioClient3_AddRef(&This->IAudioClient3_iface); +} + +static ULONG WINAPI AudioClock_Release(IAudioClock *iface) +{ + ACImpl *This = impl_from_IAudioClock(iface); + return IAudioClient3_Release(&This->IAudioClient3_iface); +} + +static HRESULT WINAPI AudioClock_GetFrequency(IAudioClock *iface, UINT64 *freq) +{ + ACImpl *This = impl_from_IAudioClock(iface); + struct get_frequency_params params; + + TRACE("(%p)->(%p)\n", This, freq); + + params.stream = This->stream; + params.freq = freq; + UNIX_CALL(get_frequency, ¶ms); + return params.result; +} + +static HRESULT WINAPI AudioClock_GetPosition(IAudioClock *iface, UINT64 *pos, + UINT64 *qpctime) +{ + ACImpl *This = impl_from_IAudioClock(iface); + struct get_position_params params; + + TRACE("(%p)->(%p, %p)\n", This, pos, qpctime); + + if(!pos) + return E_POINTER; + + params.stream = This->stream; + params.pos = pos; + params.qpctime = qpctime; + UNIX_CALL(get_position, ¶ms); + return params.result; +} + +static HRESULT WINAPI AudioClock_GetCharacteristics(IAudioClock *iface, + DWORD *chars) +{ + ACImpl *This = impl_from_IAudioClock(iface); + + TRACE("(%p)->(%p)\n", This, chars); + + if(!chars) + return E_POINTER; + + *chars = AUDIOCLOCK_CHARACTERISTIC_FIXED_FREQ; + + return S_OK; +} + +static const IAudioClockVtbl AudioClock_Vtbl = +{ + AudioClock_QueryInterface, + AudioClock_AddRef, + AudioClock_Release, + AudioClock_GetFrequency, + AudioClock_GetPosition, + AudioClock_GetCharacteristics +}; + +static HRESULT WINAPI AudioClock2_QueryInterface(IAudioClock2 *iface, + REFIID riid, void **ppv) +{ + ACImpl *This = impl_from_IAudioClock2(iface); + return IAudioClock_QueryInterface(&This->IAudioClock_iface, riid, ppv); +} + +static ULONG WINAPI AudioClock2_AddRef(IAudioClock2 *iface) +{ + ACImpl *This = impl_from_IAudioClock2(iface); + return IAudioClient3_AddRef(&This->IAudioClient3_iface); +} + +static ULONG WINAPI AudioClock2_Release(IAudioClock2 *iface) +{ + ACImpl *This = impl_from_IAudioClock2(iface); + return IAudioClient3_Release(&This->IAudioClient3_iface); +} + +static HRESULT WINAPI AudioClock2_GetDevicePosition(IAudioClock2 *iface, + UINT64 *pos, UINT64 *qpctime) +{ + ACImpl *This = impl_from_IAudioClock2(iface); + + FIXME("(%p)->(%p, %p)\n", This, pos, qpctime); + + return E_NOTIMPL; +} + +static const IAudioClock2Vtbl AudioClock2_Vtbl = +{ + AudioClock2_QueryInterface, + AudioClock2_AddRef, + AudioClock2_Release, + AudioClock2_GetDevicePosition +}; + +static AudioSessionWrapper *AudioSessionWrapper_Create(ACImpl *client) +{ + AudioSessionWrapper *ret; + + ret = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, + sizeof(AudioSessionWrapper)); + if(!ret) + return NULL; + + ret->IAudioSessionControl2_iface.lpVtbl = &AudioSessionControl2_Vtbl; + ret->ISimpleAudioVolume_iface.lpVtbl = &SimpleAudioVolume_Vtbl; + ret->IChannelAudioVolume_iface.lpVtbl = &ChannelAudioVolume_Vtbl; + + ret->ref = 1; + + ret->client = client; + if(client){ + ret->session = client->session; + IAudioClient3_AddRef(&client->IAudioClient3_iface); + } + + return ret; +} + +static HRESULT WINAPI AudioSessionControl_QueryInterface( + IAudioSessionControl2 *iface, REFIID riid, void **ppv) +{ + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + if(IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IAudioSessionControl) || + IsEqualIID(riid, &IID_IAudioSessionControl2)) + *ppv = iface; + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioSessionControl_AddRef(IAudioSessionControl2 *iface) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + ULONG ref; + ref = InterlockedIncrement(&This->ref); + TRACE("(%p) Refcount now %lu\n", This, ref); + return ref; +} + +static ULONG WINAPI AudioSessionControl_Release(IAudioSessionControl2 *iface) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + ULONG ref; + + EnterCriticalSection(&g_sessions_lock); + + ref = InterlockedDecrement(&This->ref); + TRACE("(%p) Refcount now %lu\n", This, ref); + if(!ref){ + if(This->client){ + This->client->session_wrapper = NULL; + AudioClient_Release(&This->client->IAudioClient3_iface); + } + HeapFree(GetProcessHeap(), 0, This); + } + + LeaveCriticalSection(&g_sessions_lock); + return ref; +} + +static HRESULT WINAPI AudioSessionControl_GetState(IAudioSessionControl2 *iface, + AudioSessionState *state) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + struct is_started_params params; + ACImpl *client; + + TRACE("(%p)->(%p)\n", This, state); + + if(!state) + return NULL_PTR_ERR; + + EnterCriticalSection(&g_sessions_lock); + + if(list_empty(&This->session->clients)){ + *state = AudioSessionStateExpired; + LeaveCriticalSection(&g_sessions_lock); + return S_OK; + } + + LIST_FOR_EACH_ENTRY(client, &This->session->clients, ACImpl, entry){ + params.stream = client->stream; + UNIX_CALL(is_started, ¶ms); + if(params.result == S_OK){ + *state = AudioSessionStateActive; + LeaveCriticalSection(&g_sessions_lock); + return S_OK; + } + } + + LeaveCriticalSection(&g_sessions_lock); + + *state = AudioSessionStateInactive; + + return S_OK; +} + +static HRESULT WINAPI AudioSessionControl_GetDisplayName( + IAudioSessionControl2 *iface, WCHAR **name) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, name); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_SetDisplayName( + IAudioSessionControl2 *iface, const WCHAR *name, const GUID *session) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p, %s) - stub\n", This, name, debugstr_guid(session)); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_GetIconPath( + IAudioSessionControl2 *iface, WCHAR **path) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, path); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_SetIconPath( + IAudioSessionControl2 *iface, const WCHAR *path, const GUID *session) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p, %s) - stub\n", This, path, debugstr_guid(session)); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_GetGroupingParam( + IAudioSessionControl2 *iface, GUID *group) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, group); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_SetGroupingParam( + IAudioSessionControl2 *iface, const GUID *group, const GUID *session) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%s, %s) - stub\n", This, debugstr_guid(group), + debugstr_guid(session)); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_RegisterAudioSessionNotification( + IAudioSessionControl2 *iface, IAudioSessionEvents *events) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, events); + + return S_OK; +} + +static HRESULT WINAPI AudioSessionControl_UnregisterAudioSessionNotification( + IAudioSessionControl2 *iface, IAudioSessionEvents *events) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, events); + + return S_OK; +} + +static HRESULT WINAPI AudioSessionControl_GetSessionIdentifier( + IAudioSessionControl2 *iface, WCHAR **id) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, id); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_GetSessionInstanceIdentifier( + IAudioSessionControl2 *iface, WCHAR **id) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, id); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_GetProcessId( + IAudioSessionControl2 *iface, DWORD *pid) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + TRACE("(%p)->(%p)\n", This, pid); + + if(!pid) + return E_POINTER; + + *pid = GetCurrentProcessId(); + + return S_OK; +} + +static HRESULT WINAPI AudioSessionControl_IsSystemSoundsSession( + IAudioSessionControl2 *iface) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + TRACE("(%p)\n", This); + + return S_FALSE; +} + +static HRESULT WINAPI AudioSessionControl_SetDuckingPreference( + IAudioSessionControl2 *iface, BOOL optout) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + TRACE("(%p)->(%d)\n", This, optout); + + return S_OK; +} + +static const IAudioSessionControl2Vtbl AudioSessionControl2_Vtbl = +{ + AudioSessionControl_QueryInterface, + AudioSessionControl_AddRef, + AudioSessionControl_Release, + AudioSessionControl_GetState, + AudioSessionControl_GetDisplayName, + AudioSessionControl_SetDisplayName, + AudioSessionControl_GetIconPath, + AudioSessionControl_SetIconPath, + AudioSessionControl_GetGroupingParam, + AudioSessionControl_SetGroupingParam, + AudioSessionControl_RegisterAudioSessionNotification, + AudioSessionControl_UnregisterAudioSessionNotification, + AudioSessionControl_GetSessionIdentifier, + AudioSessionControl_GetSessionInstanceIdentifier, + AudioSessionControl_GetProcessId, + AudioSessionControl_IsSystemSoundsSession, + AudioSessionControl_SetDuckingPreference +}; + +static HRESULT WINAPI SimpleAudioVolume_QueryInterface( + ISimpleAudioVolume *iface, REFIID riid, void **ppv) +{ + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + if(IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_ISimpleAudioVolume)) + *ppv = iface; + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI SimpleAudioVolume_AddRef(ISimpleAudioVolume *iface) +{ + AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface); + return AudioSessionControl_AddRef(&This->IAudioSessionControl2_iface); +} + +static ULONG WINAPI SimpleAudioVolume_Release(ISimpleAudioVolume *iface) +{ + AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface); + return AudioSessionControl_Release(&This->IAudioSessionControl2_iface); +} + +static HRESULT WINAPI SimpleAudioVolume_SetMasterVolume( + ISimpleAudioVolume *iface, float level, const GUID *context) +{ + AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface); + AudioSession *session = This->session; + ACImpl *client; + + TRACE("(%p)->(%f, %s)\n", session, level, wine_dbgstr_guid(context)); + + if(level < 0.f || level > 1.f) + return E_INVALIDARG; + + if(context) + FIXME("Notifications not supported yet\n"); + + EnterCriticalSection(&g_sessions_lock); + + session->master_vol = level; + + LIST_FOR_EACH_ENTRY(client, &session->clients, ACImpl, entry) + set_stream_volumes(client, -1); + + LeaveCriticalSection(&g_sessions_lock); + + return S_OK; +} + +static HRESULT WINAPI SimpleAudioVolume_GetMasterVolume( + ISimpleAudioVolume *iface, float *level) +{ + AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface); + AudioSession *session = This->session; + + TRACE("(%p)->(%p)\n", session, level); + + if(!level) + return NULL_PTR_ERR; + + *level = session->master_vol; + + return S_OK; +} + +static HRESULT WINAPI SimpleAudioVolume_SetMute(ISimpleAudioVolume *iface, + BOOL mute, const GUID *context) +{ + AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface); + AudioSession *session = This->session; + ACImpl *client; + + TRACE("(%p)->(%u, %s)\n", session, mute, debugstr_guid(context)); + + if(context) + FIXME("Notifications not supported yet\n"); + + EnterCriticalSection(&g_sessions_lock); + + session->mute = mute; + + LIST_FOR_EACH_ENTRY(client, &session->clients, ACImpl, entry) + set_stream_volumes(client, -1); + + LeaveCriticalSection(&g_sessions_lock); + + return S_OK; +} + +static HRESULT WINAPI SimpleAudioVolume_GetMute(ISimpleAudioVolume *iface, + BOOL *mute) +{ + AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface); + AudioSession *session = This->session; + + TRACE("(%p)->(%p)\n", session, mute); + + if(!mute) + return NULL_PTR_ERR; + + *mute = session->mute; + + return S_OK; +} + +static const ISimpleAudioVolumeVtbl SimpleAudioVolume_Vtbl = +{ + SimpleAudioVolume_QueryInterface, + SimpleAudioVolume_AddRef, + SimpleAudioVolume_Release, + SimpleAudioVolume_SetMasterVolume, + SimpleAudioVolume_GetMasterVolume, + SimpleAudioVolume_SetMute, + SimpleAudioVolume_GetMute +}; + +static HRESULT WINAPI AudioStreamVolume_QueryInterface( + IAudioStreamVolume *iface, REFIID riid, void **ppv) +{ + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + if(IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IAudioStreamVolume)) + *ppv = iface; + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioStreamVolume_AddRef(IAudioStreamVolume *iface) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + return IAudioClient3_AddRef(&This->IAudioClient3_iface); +} + +static ULONG WINAPI AudioStreamVolume_Release(IAudioStreamVolume *iface) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + return IAudioClient3_Release(&This->IAudioClient3_iface); +} + +static HRESULT WINAPI AudioStreamVolume_GetChannelCount( + IAudioStreamVolume *iface, UINT32 *out) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + + TRACE("(%p)->(%p)\n", This, out); + + if(!out) + return E_POINTER; + + *out = This->channel_count; + + return S_OK; +} + +static HRESULT WINAPI AudioStreamVolume_SetChannelVolume( + IAudioStreamVolume *iface, UINT32 index, float level) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + + TRACE("(%p)->(%d, %f)\n", This, index, level); + + if(level < 0.f || level > 1.f) + return E_INVALIDARG; + + if(index >= This->channel_count) + return E_INVALIDARG; + + EnterCriticalSection(&g_sessions_lock); + + This->vols[index] = level; + + WARN("CoreAudio doesn't support per-channel volume control\n"); + set_stream_volumes(This, index); + + LeaveCriticalSection(&g_sessions_lock); + + return S_OK; +} + +static HRESULT WINAPI AudioStreamVolume_GetChannelVolume( + IAudioStreamVolume *iface, UINT32 index, float *level) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + + TRACE("(%p)->(%d, %p)\n", This, index, level); + + if(!level) + return E_POINTER; + + if(index >= This->channel_count) + return E_INVALIDARG; + + *level = This->vols[index]; + + return S_OK; +} + +static HRESULT WINAPI AudioStreamVolume_SetAllVolumes( + IAudioStreamVolume *iface, UINT32 count, const float *levels) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + UINT32 i; + + TRACE("(%p)->(%d, %p)\n", This, count, levels); + + if(!levels) + return E_POINTER; + + if(count != This->channel_count) + return E_INVALIDARG; + + EnterCriticalSection(&g_sessions_lock); + + for(i = 0; i < count; ++i) + This->vols[i] = levels[i]; + + set_stream_volumes(This, -1); + + LeaveCriticalSection(&g_sessions_lock); + + return S_OK; +} + +static HRESULT WINAPI AudioStreamVolume_GetAllVolumes( + IAudioStreamVolume *iface, UINT32 count, float *levels) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + UINT32 i; + + TRACE("(%p)->(%d, %p)\n", This, count, levels); + + if(!levels) + return E_POINTER; + + if(count != This->channel_count) + return E_INVALIDARG; + + EnterCriticalSection(&g_sessions_lock); + + for(i = 0; i < count; ++i) + levels[i] = This->vols[i]; + + LeaveCriticalSection(&g_sessions_lock); + + return S_OK; +} + +static const IAudioStreamVolumeVtbl AudioStreamVolume_Vtbl = +{ + AudioStreamVolume_QueryInterface, + AudioStreamVolume_AddRef, + AudioStreamVolume_Release, + AudioStreamVolume_GetChannelCount, + AudioStreamVolume_SetChannelVolume, + AudioStreamVolume_GetChannelVolume, + AudioStreamVolume_SetAllVolumes, + AudioStreamVolume_GetAllVolumes +}; + +static HRESULT WINAPI ChannelAudioVolume_QueryInterface( + IChannelAudioVolume *iface, REFIID riid, void **ppv) +{ + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + if(IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IChannelAudioVolume)) + *ppv = iface; + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI ChannelAudioVolume_AddRef(IChannelAudioVolume *iface) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + return AudioSessionControl_AddRef(&This->IAudioSessionControl2_iface); +} + +static ULONG WINAPI ChannelAudioVolume_Release(IChannelAudioVolume *iface) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + return AudioSessionControl_Release(&This->IAudioSessionControl2_iface); +} + +static HRESULT WINAPI ChannelAudioVolume_GetChannelCount( + IChannelAudioVolume *iface, UINT32 *out) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + AudioSession *session = This->session; + + TRACE("(%p)->(%p)\n", session, out); + + if(!out) + return NULL_PTR_ERR; + + *out = session->channel_count; + + return S_OK; +} + +static HRESULT WINAPI ChannelAudioVolume_SetChannelVolume( + IChannelAudioVolume *iface, UINT32 index, float level, + const GUID *context) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + AudioSession *session = This->session; + ACImpl *client; + + TRACE("(%p)->(%d, %f, %s)\n", session, index, level, + wine_dbgstr_guid(context)); + + if(level < 0.f || level > 1.f) + return E_INVALIDARG; + + if(index >= session->channel_count) + return E_INVALIDARG; + + if(context) + FIXME("Notifications not supported yet\n"); + + EnterCriticalSection(&g_sessions_lock); + + session->channel_vols[index] = level; + + WARN("CoreAudio doesn't support per-channel volume control\n"); + LIST_FOR_EACH_ENTRY(client, &session->clients, ACImpl, entry) + set_stream_volumes(client, index); + + LeaveCriticalSection(&g_sessions_lock); + + return S_OK; +} + +static HRESULT WINAPI ChannelAudioVolume_GetChannelVolume( + IChannelAudioVolume *iface, UINT32 index, float *level) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + AudioSession *session = This->session; + + TRACE("(%p)->(%d, %p)\n", session, index, level); + + if(!level) + return NULL_PTR_ERR; + + if(index >= session->channel_count) + return E_INVALIDARG; + + *level = session->channel_vols[index]; + + return S_OK; +} + +static HRESULT WINAPI ChannelAudioVolume_SetAllVolumes( + IChannelAudioVolume *iface, UINT32 count, const float *levels, + const GUID *context) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + AudioSession *session = This->session; + ACImpl *client; + UINT32 i; + + TRACE("(%p)->(%d, %p, %s)\n", session, count, levels, + wine_dbgstr_guid(context)); + + if(!levels) + return NULL_PTR_ERR; + + if(count != session->channel_count) + return E_INVALIDARG; + + if(context) + FIXME("Notifications not supported yet\n"); + + EnterCriticalSection(&g_sessions_lock); + + for(i = 0; i < count; ++i) + session->channel_vols[i] = levels[i]; + + LIST_FOR_EACH_ENTRY(client, &session->clients, ACImpl, entry) + set_stream_volumes(client, -1); + + LeaveCriticalSection(&g_sessions_lock); + + return S_OK; +} + +static HRESULT WINAPI ChannelAudioVolume_GetAllVolumes( + IChannelAudioVolume *iface, UINT32 count, float *levels) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + AudioSession *session = This->session; + int i; + + TRACE("(%p)->(%d, %p)\n", session, count, levels); + + if(!levels) + return NULL_PTR_ERR; + + if(count != session->channel_count) + return E_INVALIDARG; + + for(i = 0; i < count; ++i) + levels[i] = session->channel_vols[i]; + + return S_OK; +} + +static const IChannelAudioVolumeVtbl ChannelAudioVolume_Vtbl = +{ + ChannelAudioVolume_QueryInterface, + ChannelAudioVolume_AddRef, + ChannelAudioVolume_Release, + ChannelAudioVolume_GetChannelCount, + ChannelAudioVolume_SetChannelVolume, + ChannelAudioVolume_GetChannelVolume, + ChannelAudioVolume_SetAllVolumes, + ChannelAudioVolume_GetAllVolumes +}; + +static HRESULT WINAPI AudioSessionManager_QueryInterface(IAudioSessionManager2 *iface, + REFIID riid, void **ppv) +{ + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + if(IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IAudioSessionManager) || + IsEqualIID(riid, &IID_IAudioSessionManager2)) + *ppv = iface; + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioSessionManager_AddRef(IAudioSessionManager2 *iface) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + ULONG ref; + ref = InterlockedIncrement(&This->ref); + TRACE("(%p) Refcount now %lu\n", This, ref); + return ref; +} + +static ULONG WINAPI AudioSessionManager_Release(IAudioSessionManager2 *iface) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + ULONG ref; + ref = InterlockedDecrement(&This->ref); + TRACE("(%p) Refcount now %lu\n", This, ref); + if(!ref) + HeapFree(GetProcessHeap(), 0, This); + return ref; +} + +static HRESULT WINAPI AudioSessionManager_GetAudioSessionControl( + IAudioSessionManager2 *iface, const GUID *session_guid, DWORD flags, + IAudioSessionControl **out) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + AudioSession *session; + AudioSessionWrapper *wrapper; + HRESULT hr; + + TRACE("(%p)->(%s, %lx, %p)\n", This, debugstr_guid(session_guid), + flags, out); + + hr = get_audio_session(session_guid, This->device, 0, &session); + if(FAILED(hr)) + return hr; + + wrapper = AudioSessionWrapper_Create(NULL); + if(!wrapper) + return E_OUTOFMEMORY; + + wrapper->session = session; + + *out = (IAudioSessionControl*)&wrapper->IAudioSessionControl2_iface; + + return S_OK; +} + +static HRESULT WINAPI AudioSessionManager_GetSimpleAudioVolume( + IAudioSessionManager2 *iface, const GUID *session_guid, DWORD flags, + ISimpleAudioVolume **out) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + AudioSession *session; + AudioSessionWrapper *wrapper; + HRESULT hr; + + TRACE("(%p)->(%s, %lx, %p)\n", This, debugstr_guid(session_guid), + flags, out); + + hr = get_audio_session(session_guid, This->device, 0, &session); + if(FAILED(hr)) + return hr; + + wrapper = AudioSessionWrapper_Create(NULL); + if(!wrapper) + return E_OUTOFMEMORY; + + wrapper->session = session; + + *out = &wrapper->ISimpleAudioVolume_iface; + + return S_OK; +} + +static HRESULT WINAPI AudioSessionManager_GetSessionEnumerator( + IAudioSessionManager2 *iface, IAudioSessionEnumerator **out) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + FIXME("(%p)->(%p) - stub\n", This, out); + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionManager_RegisterSessionNotification( + IAudioSessionManager2 *iface, IAudioSessionNotification *notification) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + FIXME("(%p)->(%p) - stub\n", This, notification); + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionManager_UnregisterSessionNotification( + IAudioSessionManager2 *iface, IAudioSessionNotification *notification) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + FIXME("(%p)->(%p) - stub\n", This, notification); + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionManager_RegisterDuckNotification( + IAudioSessionManager2 *iface, const WCHAR *session_id, + IAudioVolumeDuckNotification *notification) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + FIXME("(%p)->(%p) - stub\n", This, notification); + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionManager_UnregisterDuckNotification( + IAudioSessionManager2 *iface, + IAudioVolumeDuckNotification *notification) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + FIXME("(%p)->(%p) - stub\n", This, notification); + return E_NOTIMPL; +} + +static const IAudioSessionManager2Vtbl AudioSessionManager2_Vtbl = +{ + AudioSessionManager_QueryInterface, + AudioSessionManager_AddRef, + AudioSessionManager_Release, + AudioSessionManager_GetAudioSessionControl, + AudioSessionManager_GetSimpleAudioVolume, + AudioSessionManager_GetSessionEnumerator, + AudioSessionManager_RegisterSessionNotification, + AudioSessionManager_UnregisterSessionNotification, + AudioSessionManager_RegisterDuckNotification, + AudioSessionManager_UnregisterDuckNotification +}; + +HRESULT WINAPI AUDDRV_GetAudioSessionManager(IMMDevice *device, + IAudioSessionManager2 **out) +{ + SessionMgr *This; + + This = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(SessionMgr)); + if(!This) + return E_OUTOFMEMORY; + + This->IAudioSessionManager2_iface.lpVtbl = &AudioSessionManager2_Vtbl; + This->device = device; + This->ref = 1; + + *out = &This->IAudioSessionManager2_iface; + + return S_OK; +} diff --git a/pkgs/osu-wine/audio-revert/winecoreaudio.drv/unixlib.h b/pkgs/osu-wine/audio-revert/winecoreaudio.drv/unixlib.h new file mode 100644 index 0000000..932a772 --- /dev/null +++ b/pkgs/osu-wine/audio-revert/winecoreaudio.drv/unixlib.h @@ -0,0 +1,36 @@ +/* + * Unixlib header file for winecoreaudio driver. + * + * Copyright 2021 Huw Davies + * + * 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 "../mmdevapi/unixlib.h" + +NTSTATUS unix_midi_init( void * ); +NTSTATUS unix_midi_release( void * ); +NTSTATUS unix_midi_out_message( void * ); +NTSTATUS unix_midi_in_message( void * ); +NTSTATUS unix_midi_notify_wait( void * ); + +#ifdef _WIN64 +NTSTATUS unix_wow64_midi_init(void *args); +NTSTATUS unix_wow64_midi_out_message(void *args); +NTSTATUS unix_wow64_midi_in_message(void *args); +NTSTATUS unix_wow64_midi_notify_wait(void *args); +#endif + +#define UNIX_CALL( func, params ) WINE_UNIX_CALL( func, params ) diff --git a/pkgs/osu-wine/audio-revert/winecoreaudio.drv/winecoreaudio.drv.spec b/pkgs/osu-wine/audio-revert/winecoreaudio.drv/winecoreaudio.drv.spec new file mode 100644 index 0000000..9d9f781 --- /dev/null +++ b/pkgs/osu-wine/audio-revert/winecoreaudio.drv/winecoreaudio.drv.spec @@ -0,0 +1,10 @@ +# WinMM driver functions +@ stdcall -private DriverProc(long long long long long) CoreAudio_DriverProc +@ stdcall -private midMessage(long long long long long) CoreAudio_midMessage +@ stdcall -private modMessage(long long long long long) CoreAudio_modMessage + +# MMDevAPI driver functions +@ stdcall -private GetPriority() AUDDRV_GetPriority +@ stdcall -private GetEndpointIDs(long ptr ptr ptr ptr) AUDDRV_GetEndpointIDs +@ stdcall -private GetAudioEndpoint(ptr ptr ptr) AUDDRV_GetAudioEndpoint +@ stdcall -private GetAudioSessionManager(ptr ptr) AUDDRV_GetAudioSessionManager diff --git a/pkgs/osu-wine/audio-revert/wineoss.drv/Makefile.in b/pkgs/osu-wine/audio-revert/wineoss.drv/Makefile.in new file mode 100644 index 0000000..a453388 --- /dev/null +++ b/pkgs/osu-wine/audio-revert/wineoss.drv/Makefile.in @@ -0,0 +1,14 @@ +MODULE = wineoss.drv +UNIXLIB = wineoss.so +IMPORTS = uuid ole32 user32 advapi32 +DELAYIMPORTS = winmm +UNIX_LIBS = $(OSS4_LIBS) $(PTHREAD_LIBS) +UNIX_CFLAGS = $(OSS4_CFLAGS) + +SOURCES = \ + midi.c \ + midipatch.c \ + mmaux.c \ + mmdevdrv.c \ + oss.c \ + ossmidi.c diff --git a/pkgs/osu-wine/audio-revert/wineoss.drv/midi.c b/pkgs/osu-wine/audio-revert/wineoss.drv/midi.c new file mode 100644 index 0000000..ca842bb --- /dev/null +++ b/pkgs/osu-wine/audio-revert/wineoss.drv/midi.c @@ -0,0 +1,176 @@ +/* + * MIDI driver for OSS (PE-side) + * + * Copyright 1994 Martin Ayotte + * Copyright 1998 Luiz Otavio L. Zorzella (init procedures) + * Copyright 1998, 1999 Eric POUECH + * Copyright 2022 Huw Davies + * + * 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 + */ + +/* TODO: + * + use better instrument definition for OPL/2 (midiPatch.c) or + * use existing instrument definition (from playmidi or kmid) + * with a .winerc option + * + have a look at OPL/3 ? + * + implement asynchronous playback of MidiHdr + * + implement STREAM'ed MidiHdr (question: how shall we share the + * code between the midiStream functions in MMSYSTEM/WINMM and + * the code for the low level driver) + * + use a more accurate read mechanism than the one of snooping on + * timers (like select on fd) + */ + +#include + +#include "windef.h" +#include "winbase.h" +#include "wingdi.h" +#include "winuser.h" +#include "winternl.h" +#include "mmddk.h" +#include "audioclient.h" + +#include "wine/debug.h" +#include "wine/unixlib.h" + +#include "unixlib.h" + +WINE_DEFAULT_DEBUG_CHANNEL(midi); + +/*======================================================================* + * Low level MIDI implementation * + *======================================================================*/ + +static void notify_client(struct notify_context *notify) +{ + TRACE("dev_id = %d msg = %d param1 = %04IX param2 = %04IX\n", + notify->dev_id, notify->msg, notify->param_1, notify->param_2); + + DriverCallback(notify->callback, notify->flags, notify->device, notify->msg, + notify->instance, notify->param_1, notify->param_2); +} + +/*======================================================================* + * MIDI entry points * + *======================================================================*/ + +/************************************************************************** + * midMessage (WINEOSS.@) + */ +DWORD WINAPI OSS_midMessage(UINT wDevID, UINT wMsg, DWORD_PTR dwUser, + DWORD_PTR dwParam1, DWORD_PTR dwParam2) +{ + struct midi_in_message_params params; + struct notify_context notify; + UINT err; + + TRACE("(%04X, %04X, %08IX, %08IX, %08IX);\n", + wDevID, wMsg, dwUser, dwParam1, dwParam2); + + params.dev_id = wDevID; + params.msg = wMsg; + params.user = dwUser; + params.param_1 = dwParam1; + params.param_2 = dwParam2; + params.err = &err; + params.notify = ¬ify; + + do + { + OSS_CALL(midi_in_message, ¶ms); + if ((!err || err == ERROR_RETRY) && notify.send_notify) notify_client(¬ify); + } while (err == ERROR_RETRY); + + return err; +} + +/************************************************************************** + * modMessage (WINEOSS.@) + */ +DWORD WINAPI OSS_modMessage(UINT wDevID, UINT wMsg, DWORD_PTR dwUser, + DWORD_PTR dwParam1, DWORD_PTR dwParam2) +{ + struct midi_out_message_params params; + struct notify_context notify; + UINT err; + + TRACE("(%04X, %04X, %08IX, %08IX, %08IX);\n", + wDevID, wMsg, dwUser, dwParam1, dwParam2); + + params.dev_id = wDevID; + params.msg = wMsg; + params.user = dwUser; + params.param_1 = dwParam1; + params.param_2 = dwParam2; + params.err = &err; + params.notify = ¬ify; + + OSS_CALL(midi_out_message, ¶ms); + + if (!err && notify.send_notify) notify_client(¬ify); + + return err; +} + +static DWORD WINAPI notify_thread(void *p) +{ + struct midi_notify_wait_params params; + struct notify_context notify; + BOOL quit; + + params.notify = ¬ify; + params.quit = &quit; + + while (1) + { + OSS_CALL(midi_notify_wait, ¶ms); + if (quit) break; + if (notify.send_notify) notify_client(¬ify); + } + return 0; +} + +/************************************************************************** + * DriverProc (WINEOSS.1) + */ +LRESULT CALLBACK OSS_DriverProc(DWORD_PTR dwDevID, HDRVR hDriv, UINT wMsg, + LPARAM dwParam1, LPARAM dwParam2) +{ + TRACE("(%08IX, %p, %08X, %08IX, %08IX)\n", + dwDevID, hDriv, wMsg, dwParam1, dwParam2); + + switch(wMsg) { + case DRV_LOAD: + CloseHandle(CreateThread(NULL, 0, notify_thread, NULL, 0, NULL)); + return 1; + case DRV_FREE: + OSS_CALL(midi_release, NULL); + return 1; + case DRV_OPEN: + case DRV_CLOSE: + case DRV_ENABLE: + case DRV_DISABLE: + case DRV_QUERYCONFIGURE: + case DRV_CONFIGURE: + return 1; + case DRV_INSTALL: + case DRV_REMOVE: + return DRV_SUCCESS; + default: + return 0; + } +} diff --git a/pkgs/osu-wine/audio-revert/wineoss.drv/midipatch.c b/pkgs/osu-wine/audio-revert/wineoss.drv/midipatch.c new file mode 100644 index 0000000..4ff4862 --- /dev/null +++ b/pkgs/osu-wine/audio-revert/wineoss.drv/midipatch.c @@ -0,0 +1,293 @@ +/* -*- tab-width: 8; c-basic-offset: 4 -*- */ + +/* + * FM patches for wine MIDI driver + * + * Copyright 1999 Eric Pouech + * + * 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 + */ + +/* + * Eric POUECH : MIDI FM patches for GM instruments + */ + +#if 0 +#pragma makedep unix +#endif + +#define NOT_DEFINED 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + +const unsigned char midiFMInstrumentPatches[128 * 16] = { +/* 0 Acoustic Grand Piano */ 0x21, 0x11, 0x4c, 0x00, 0xf1, 0xf2, 0x63, 0x72, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 1 Bright Acoustic Piano */ 0x01, 0x11, 0x4f, 0x00, 0xf1, 0xd2, 0x53, 0x74, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 2 Electric Grand Piano */ 0x01, 0x01, 0x4f, 0x04, 0xf1, 0xd2, 0x50, 0x7c, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 3 Honky-Tonk Piano */ 0x81, 0x13, 0x9d, 0x00, 0xf2, 0xf2, 0x51, 0xf1, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 4 Rhodes Piano */ 0x01, 0x01, 0x4f, 0x04, 0xf1, 0xd2, 0x50, 0x7c, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 5 Chorused Piano */ 0x01, 0x11, 0x4d, 0x00, 0xf1, 0xd2, 0x60, 0x7b, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 6 Harpsichord */ 0x32, 0x16, 0x87, 0x80, 0xa1, 0x7d, 0x10, 0x33, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 7 Clavinet */ 0x13, 0x08, 0x80, 0x00, 0xfb, 0xe8, 0xff, 0xff, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 8 Celesta */ 0x14, 0x04, 0x07, 0x00, 0x93, 0xb6, 0x73, 0x62, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 9 Glockenspiel */ 0x07, 0x12, 0x4f, 0x00, 0xf2, 0xf2, 0x60, 0x72, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 10 Music Box */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 11 Vibraphone */ 0x44, 0x60, 0x53, 0x80, 0xf5, 0xfd, 0x33, 0x25, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 12 Marimba */ 0x05, 0x01, 0x4e, 0x00, 0xda, 0xf9, 0x25, 0x15, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 13 Xylophone */ 0x11, 0x31, 0x2d, 0x00, 0xc8, 0xf5, 0x2f, 0xf5, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 14 Tubular Bells */ 0x03, 0x17, 0x4f, 0x03, 0xf1, 0xf2, 0x53, 0x74, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 15 Dulcimer */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 16 Hammond Organ */ 0x72, 0x71, 0xcd, 0x80, 0x91, 0x91, 0x2a, 0x2a, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 17 Percussive Organ */ 0x0c, 0x00, 0x00, 0x00, 0xf8, 0xd6, 0xb5, 0x4f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 18 Rock Organ */ 0x72, 0x70, 0xce, 0x80, 0x9f, 0x94, 0x12, 0x11, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 19 Church Organ */ 0xa5, 0xb1, 0xd2, 0x80, 0x81, 0xf1, 0x03, 0x05, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 20 Reed Organ */ 0x3e, 0xb1, 0x29, 0x80, 0xfb, 0xa0, 0xf0, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 21 Accordion */ 0x24, 0x31, 0x4f, 0x00, 0xf2, 0x52, 0x0b, 0x0b, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 22 Harmonica */ 0x22, 0xf2, 0x8f, 0x40, 0x41, 0x61, 0x03, 0x05, 0x02, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 23 Tango Accordion */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 24 Acoustic Nylon Guitar */ 0x01, 0x01, 0x11, 0x00, 0xf2, 0xf5, 0x1f, 0x88, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 25 Acoustic Steel Guitar */ 0x01, 0xa1, 0x46, 0x03, 0xf1, 0x31, 0x83, 0x86, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 26 Electric Jazz Guitar */ 0x03, 0x11, 0x5e, 0x00, 0x85, 0xd2, 0x51, 0x71, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 27 Electric Clean Guitar */ 0x32, 0x16, 0x87, 0x80, 0xa1, 0x7d, 0x10, 0x33, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 28 Electric Muted Guitar */ 0x13, 0x11, 0x96, 0x80, 0xff, 0xff, 0x21, 0x03, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 29 Overdriven Guitar */ 0x07, 0x14, 0x8f, 0x80, 0x82, 0x82, 0x7d, 0x7d, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 30 Distortion Guitar */ 0x05, 0x01, 0x8f, 0x80, 0xda, 0xf9, 0x15, 0x14, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 31 Guitar Harmonics */ 0xc3, 0x01, 0x05, 0x0d, 0x91, 0xf1, 0x1f, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 32 Acoustic Bass */ 0x21, 0x01, 0x2a, 0x00, 0xf2, 0xf5, 0x1f, 0x88, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 33 Electric Bass Fingered */ 0x01, 0x21, 0x15, 0x80, 0x25, 0x65, 0x2f, 0x6c, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 34 Electric Bass Picked */ 0x01, 0x01, 0x1d, 0x00, 0xf2, 0xf5, 0xef, 0x78, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 35 Fretless Bass */ 0x30, 0x21, 0x1e, 0x00, 0xf2, 0xf5, 0xef, 0x78, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 36 Slap Bass 1 */ 0x20, 0x21, 0x40, 0x00, 0x7b, 0x75, 0x04, 0x72, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 37 Slap Bass 2 */ 0x20, 0x21, 0x40, 0x00, 0x7b, 0xf5, 0x04, 0x72, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 38 Synth Bass 1 */ 0x41, 0x91, 0x83, 0x00, 0x65, 0x32, 0x05, 0x74, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 39 Synth Bass 2 */ 0x30, 0xb1, 0x88, 0x80, 0xd5, 0x61, 0x19, 0x1b, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 40 Violin */ 0x72, 0x62, 0x1c, 0x05, 0x51, 0x52, 0x03, 0x13, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 41 Viola */ 0x70, 0x71, 0xd0, 0x80, 0x52, 0x31, 0x11, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 42 Cello */ 0x70, 0x71, 0xc5, 0x80, 0x52, 0x31, 0x11, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 43 Contrabass */ 0x01, 0x00, 0x00, 0x00, 0x94, 0x83, 0xb6, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 44 Tremolo Strings */ 0x71, 0xa1, 0x8b, 0x40, 0x71, 0x42, 0x11, 0x15, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 45 Pizzicato Strings */ 0xf2, 0xe1, 0x40, 0x80, 0xf5, 0xfd, 0xa8, 0xad, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 46 Orchestral Harp */ 0x21, 0x11, 0x11, 0x00, 0xa3, 0xc4, 0x43, 0x22, 0x02, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 47 Timpani */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 48 String Ensemble 1 */ 0xe1, 0x21, 0x4f, 0x00, 0xc1, 0x32, 0xd3, 0x74, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 49 String Ensemble 2 */ 0xe1, 0x21, 0x4f, 0x00, 0xb1, 0x12, 0xd3, 0x74, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 50 Synth Strings 1 */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 51 Synth Strings 2 */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 52 Choir Aahs */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 53 Voice oohs */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 54 Synth Voice */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 55 Orchestra Hit */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 56 Trumpet */ 0x31, 0xa1, 0x1c, 0x80, 0x41, 0x92, 0x0b, 0x3b, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 57 Trombone */ 0x21, 0xa1, 0x18, 0x80, 0x53, 0x52, 0x1d, 0x3b, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 58 Tuba */ 0x21, 0x21, 0x19, 0x80, 0x43, 0x85, 0x8c, 0x2f, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 59 Muted Trumpet */ 0x31, 0xa1, 0x1c, 0x80, 0x41, 0x92, 0x0b, 0x3b, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 60 French Horn */ 0x21, 0x21, 0x9f, 0x80, 0x53, 0xaa, 0x5a, 0x1a, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 61 Brass Section */ 0x21, 0x21, 0x16, 0x00, 0x71, 0x81, 0xae, 0x9e, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 62 Synth Brass 1 */ 0x61, 0x60, 0x1c, 0x00, 0x71, 0x81, 0xae, 0x2e, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 63 Synth Brass 2 */ 0x21, 0x21, 0x8e, 0x80, 0xbb, 0x90, 0x29, 0x0a, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 64 Soprano Sax */ 0x01, 0x12, 0x4f, 0x00, 0x71, 0x52, 0x53, 0x7c, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 65 Alto Sax */ 0x01, 0x13, 0x4f, 0x00, 0x71, 0x62, 0x53, 0x84, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 66 Tenor Sax */ 0x01, 0x13, 0x8d, 0x00, 0x51, 0x52, 0x53, 0x7c, 0x01, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 67 Baritone Sax */ 0x01, 0x12, 0x4f, 0x00, 0x71, 0x22, 0x53, 0x7c, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 68 Oboe */ 0x71, 0x62, 0xc5, 0x05, 0x6e, 0x8b, 0x17, 0x0e, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 69 English Horn */ 0xe1, 0xe4, 0x23, 0x00, 0x71, 0x82, 0xae, 0x9e, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 70 Bassoon */ 0x30, 0xb1, 0xcd, 0x80, 0xd5, 0x61, 0x19, 0x1b, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 71 Clarinet */ 0x32, 0xa1, 0x1c, 0x80, 0x51, 0x82, 0x15, 0x45, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 72 Piccolo */ 0xe4, 0xe4, 0x0f, 0x00, 0x70, 0x60, 0x0f, 0x9f, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 73 Flute */ 0xe1, 0x61, 0x27, 0x80, 0x53, 0x53, 0x8a, 0x57, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 74 Recorder */ 0x61, 0x61, 0x27, 0x80, 0x74, 0x65, 0x8f, 0x2a, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 75 Pan Flute */ 0xe0, 0xa1, 0xec, 0x00, 0x6e, 0x65, 0x8f, 0x2a, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 76 Bottle Blow */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 77 Shakuhashi */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 78 Whistle */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 79 Ocarina */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 80 Synth lead 1 - Sq wave lead */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 81 Synth lead 2 - Sawtooth Wave */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 82 Synth lead 3 - Caliope lead */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 83 Synth lead 4 - Chiff lead */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 84 Synth lead 5 - Charang */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 85 Synth lead 6 - Solo Synth Voice */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 86 Synth lead 7 - Bright Saw Wave */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 87 Synth lead 8 - Brass and Lead */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 88 Synth pad 1 - Fantasia Pad */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 89 Synth pad 2 - Warm Pad */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 90 Synth pad 3 - Poly Synth Pad */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 91 Synth pad 4 - Space Voice Pad */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 92 Synth pad 5 - Bowed Glass Pad */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 93 Synth pad 6 - Metal Pad */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 94 Synth pad 7 - Halo Pad */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 95 Synth pad 8 - Sweep Pad */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 96 Synth SFX 1 - Ice Rain */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 97 Synth SFX 2 - Soundtrack */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 98 Synth SFX 3 - Crystal */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 99 Synth SFX 4 - Atmosphere */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 100 Synth SFX 5 - Brightness */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 101 Synth SFX 6 - Goblin */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 102 Synth SFX 7 - Echo drops */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 103 Synth SFX 8 - Star Theme */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 104 Sitar */ 0x01, 0x08, 0x40, 0x00, 0xf2, 0xf2, 0x54, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 105 Banjo */ 0x31, 0x16, 0x87, 0x80, 0xa1, 0x7d, 0x11, 0x43, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 106 Shamisen */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 107 Koto */ 0x0e, 0x02, 0x40, 0x00, 0x09, 0xf7, 0x53, 0x94, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 108 Kalimba */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 109 Bagpipe */ 0x31, 0x22, 0x43, 0x05, 0x6e, 0x8b, 0x17, 0x0c, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 110 Fiddle */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 111 Shanai */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 112 Tinkle Bell */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 113 Agogo */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 114 Steel Drums */ 0x00, 0x00, 0x0b, 0x00, 0xa8, 0xd6, 0x4c, 0x4f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 115 Woodblock */ 0x02, 0x11, 0x4f, 0x00, 0x71, 0x52, 0x53, 0x7c, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 116 Taiko Drum */ 0x12, 0x02, 0x0b, 0x00, 0x95, 0xd4, 0x4c, 0xdd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 117 Melodic Tom */ 0x01, 0x02, 0x00, 0x00, 0xfa, 0xda, 0xbf, 0xbf, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 118 Synth Drum */ 0x06, 0x00, 0x00, 0x00, 0xf0, 0xf6, 0xf0, 0xb4, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 119 Reverse Cymbal */ 0x64, 0x03, 0x00, 0x40, 0xb2, 0x97, 0x82, 0xd4, 0x02, 0x01, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 120 Guitar Fret Noise */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 121 Breath Noise */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 122 Seashore */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 123 Bird Tweet */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 124 Telephone Ring */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 125 Helicopter */ 0xf0, 0xe2, 0x00, 0xc0, 0x1e, 0x11, 0x11, 0x11, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 126 Applause */ 0x07, 0x01, 0x87, 0x80, 0xf0, 0xf0, 0x05, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 127 Gunshot */ 0x0c, 0x50, 0x00, 0x21, 0xf8, 0x09, 0xb6, 0x04, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +const unsigned char midiFMDrumsPatches[16 * 128] = { +/* 1 Not defined */ NOT_DEFINED +/* 2 Not defined */ NOT_DEFINED +/* 3 Not defined */ NOT_DEFINED +/* 4 Not defined */ NOT_DEFINED +/* 5 Not defined */ NOT_DEFINED +/* 6 Not defined */ NOT_DEFINED +/* 7 Not defined */ NOT_DEFINED +/* 8 Not defined */ NOT_DEFINED +/* 9 Not defined */ NOT_DEFINED +/* 10 Not defined */ NOT_DEFINED +/* 11 Not defined */ NOT_DEFINED +/* 12 Not defined */ NOT_DEFINED +/* 13 Not defined */ NOT_DEFINED +/* 14 Not defined */ NOT_DEFINED +/* 15 Not defined */ NOT_DEFINED +/* 16 Not defined */ NOT_DEFINED +/* 17 Not defined */ NOT_DEFINED +/* 18 Not defined */ NOT_DEFINED +/* 19 Not defined */ NOT_DEFINED +/* 20 Not defined */ NOT_DEFINED +/* 21 Not defined */ NOT_DEFINED +/* 22 Not defined */ NOT_DEFINED +/* 23 Not defined */ NOT_DEFINED +/* 24 Not defined */ NOT_DEFINED +/* 25 Not defined */ NOT_DEFINED +/* 26 Not defined */ NOT_DEFINED +/* 27 Not defined */ NOT_DEFINED +/* 28 Not defined */ NOT_DEFINED +/* 29 Not defined */ NOT_DEFINED +/* 30 Not defined */ NOT_DEFINED +/* 31 Not defined */ NOT_DEFINED +/* 32 Not defined */ NOT_DEFINED +/* 33 Not defined */ NOT_DEFINED +/* 34 Not defined */ NOT_DEFINED +/* 35 Acoustic Bass Drum */ 0x00, 0x00, 0x0d, 0x00, 0xe8, 0xa5, 0xef, 0xff, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 36 Bass Drum 1 */ 0x00, 0x00, 0x0b, 0x00, 0xa8, 0xd6, 0x4c, 0x4f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* 37 Side Stick */ NOT_DEFINED +/* 38 Acoustic Snare */ 0x2e, 0x02, 0x0a, 0x1b, 0xff, 0xf6, 0x0f, 0x4a, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, /* snare */ +/* 39 Hand Clap */ NOT_DEFINED +/* 40 Electric Snare */ 0x0c, 0xd0, 0x00, 0x00, 0xc7, 0x70, 0xb4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* rksnare */ +/* 41 Low Floor Tom */ NOT_DEFINED +/* 42 Closed Hi-Hat */ 0x64, 0x03, 0x02, 0x40, 0xb2, 0x97, 0xa2, 0xd4, 0x02, 0x01, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, /* cymbal */ +/* 43 High Floor Tom */ NOT_DEFINED +/* 44 Pedal Hi-Hat */ NOT_DEFINED +/* 45 Low Tom */ 0x01, 0x02, 0x00, 0x00, 0xfa, 0xda, 0xbf, 0xbf, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, /* tom */ +/* 46 Open Hi-Hat */ NOT_DEFINED +/* 47 Low-Mid Tom */ 0x02, 0x30, 0x00, 0x00, 0xc8, 0xe0, 0x97, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* tom2 */ +/* 48 Hi-Mid Tom */ NOT_DEFINED +/* 49 Crash Cymbal 1 */ NOT_DEFINED +/* 50 High Tom */ NOT_DEFINED +/* 51 Ride Cymbal 1 */ 0x64, 0x03, 0x00, 0x40, 0xb2, 0x97, 0x82, 0xd4, 0x02, 0x01, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, /* bcymbal */ +/* 52 Chinese Cymbal */ NOT_DEFINED +/* 53 Ride Bell */ NOT_DEFINED +/* 54 Tambourine */ NOT_DEFINED +/* 55 Splash Cymbal */ NOT_DEFINED +/* 56 Cowbell */ NOT_DEFINED +/* 57 Crash Cymbal 2 */ NOT_DEFINED +/* 58 Vibrasl */ NOT_DEFINED +/* 59 Ride Cymbal */ NOT_DEFINED +/* 60 Hi Bon */ NOT_DEFINED +/* 61 Low Bon */ NOT_DEFINED +/* 62 Mute Hi Con */ NOT_DEFINED +/* 63 Open Hi Con */ NOT_DEFINED +/* 64 Low Con */ NOT_DEFINED +/* 65 High Timba */ NOT_DEFINED +/* 66 Low Timba */ NOT_DEFINED +/* 67 High Ago */ NOT_DEFINED +/* 68 Low Ago */ NOT_DEFINED +/* 69 Caba */ NOT_DEFINED +/* 70 Marac */ NOT_DEFINED +/* 71 Short Whist */ NOT_DEFINED +/* 72 Long Whist */ NOT_DEFINED +/* 73 Short Gui */ NOT_DEFINED +/* 74 Long Gui */ NOT_DEFINED +/* 75 Clav */ 0x13, 0x08, 0x80, 0x00, 0xfb, 0xe8, 0xff, 0xff, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, /* claves */ +/* 76 Hi Wood Blo */ NOT_DEFINED +/* 77 Low Wood Blo */ NOT_DEFINED +/* 78 Mute Cui */ NOT_DEFINED +/* 79 Open Cui */ NOT_DEFINED +/* 80 Mute Triang */ NOT_DEFINED +/* 81 Open Triang */ 0x26, 0x1e, 0x03, 0x00, 0xe0, 0xff, 0xf0, 0x31, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, /* triangle */ +/* 82 Not defined */ NOT_DEFINED +/* 83 Not defined */ NOT_DEFINED +/* 84 Not defined */ NOT_DEFINED +/* 85 Not defined */ NOT_DEFINED +/* 86 Not defined */ NOT_DEFINED +/* 87 Not defined */ NOT_DEFINED +/* 88 Not defined */ NOT_DEFINED +/* 89 Not defined */ NOT_DEFINED +/* 90 Not defined */ NOT_DEFINED +/* 91 Not defined */ NOT_DEFINED +/* 92 Not defined */ NOT_DEFINED +/* 93 Not defined */ NOT_DEFINED +/* 94 Not defined */ NOT_DEFINED +/* 95 Not defined */ NOT_DEFINED +/* 96 Not defined */ NOT_DEFINED +/* 97 Not defined */ NOT_DEFINED +/* 98 Not defined */ NOT_DEFINED +/* 99 Not defined */ NOT_DEFINED +/* 100 Not defined */ NOT_DEFINED +/* 101 Not defined */ NOT_DEFINED +/* 102 Not defined */ NOT_DEFINED +/* 103 Not defined */ NOT_DEFINED +/* 104 Not defined */ NOT_DEFINED +/* 105 Not defined */ NOT_DEFINED +/* 106 Not defined */ NOT_DEFINED +/* 107 Not defined */ NOT_DEFINED +/* 108 Not defined */ NOT_DEFINED +/* 109 Not defined */ NOT_DEFINED +/* 110 Not defined */ NOT_DEFINED +/* 111 Not defined */ NOT_DEFINED +/* 112 Not defined */ NOT_DEFINED +/* 113 Not defined */ NOT_DEFINED +/* 114 Not defined */ NOT_DEFINED +/* 115 Not defined */ NOT_DEFINED +/* 116 Not defined */ NOT_DEFINED +/* 117 Not defined */ NOT_DEFINED +/* 118 Not defined */ NOT_DEFINED +/* 119 Not defined */ NOT_DEFINED +/* 120 Not defined */ NOT_DEFINED +/* 121 Not defined */ NOT_DEFINED +/* 122 Not defined */ NOT_DEFINED +/* 123 Not defined */ NOT_DEFINED +/* 124 Not defined */ NOT_DEFINED +/* 125 Not defined */ NOT_DEFINED +/* 126 Not defined */ NOT_DEFINED +/* 127 Not defined */ NOT_DEFINED +/* 128 Not defined */ NOT_DEFINED +}; diff --git a/pkgs/osu-wine/audio-revert/wineoss.drv/mmaux.c b/pkgs/osu-wine/audio-revert/wineoss.drv/mmaux.c new file mode 100644 index 0000000..f86ec6f --- /dev/null +++ b/pkgs/osu-wine/audio-revert/wineoss.drv/mmaux.c @@ -0,0 +1,57 @@ +/* + * Sample AUXILIARY Wine Driver + * + * Copyright 1994 Martin Ayotte + * + * 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 + +#include "windef.h" +#include "winbase.h" +#include "mmddk.h" +#include "audioclient.h" +#include "winternl.h" + +#include "wine/debug.h" +#include "wine/unixlib.h" + +#include "unixlib.h" + +WINE_DEFAULT_DEBUG_CHANNEL(mmaux); + +/************************************************************************** + * auxMessage (WINEOSS.2) + */ +DWORD WINAPI OSS_auxMessage(UINT wDevID, UINT wMsg, DWORD_PTR dwUser, + DWORD_PTR dwParam1, DWORD_PTR dwParam2) +{ + struct aux_message_params params; + UINT err; + + TRACE("(%04X, %04X, %08IX, %08IX, %08IX);\n", + wDevID, wMsg, dwUser, dwParam1, dwParam2); + + params.dev_id = wDevID; + params.msg = wMsg; + params.user = dwUser; + params.param_1 = dwParam1; + params.param_2 = dwParam2; + params.err = &err; + OSS_CALL(aux_message, ¶ms); + + return err; +} diff --git a/pkgs/osu-wine/audio-revert/wineoss.drv/mmdevdrv.c b/pkgs/osu-wine/audio-revert/wineoss.drv/mmdevdrv.c new file mode 100644 index 0000000..7411f87 --- /dev/null +++ b/pkgs/osu-wine/audio-revert/wineoss.drv/mmdevdrv.c @@ -0,0 +1,2385 @@ +/* + * Copyright 2011 Andrew Eikum for CodeWeavers + * 2022 Huw Davies + * + * 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 + */ + +#define COBJMACROS +#include + +#include "windef.h" +#include "winbase.h" +#include "winternl.h" +#include "winnls.h" +#include "winreg.h" + +#include "ole2.h" +#include "mmdeviceapi.h" +#include "devpkey.h" +#include "dshow.h" +#include "dsound.h" + +#include "initguid.h" +#include "endpointvolume.h" +#include "audiopolicy.h" +#include "audioclient.h" + +#include "wine/debug.h" +#include "wine/list.h" +#include "wine/unixlib.h" + +#include "unixlib.h" + +WINE_DEFAULT_DEBUG_CHANNEL(oss); + +#define NULL_PTR_ERR MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, RPC_X_NULL_REF_POINTER) + +static const REFERENCE_TIME DefaultPeriod = 100000; +static const REFERENCE_TIME MinimumPeriod = 50000; + +struct ACImpl; +typedef struct ACImpl ACImpl; + +typedef struct _AudioSession { + GUID guid; + struct list clients; + + IMMDevice *device; + + float master_vol; + UINT32 channel_count; + float *channel_vols; + BOOL mute; + + struct list entry; +} AudioSession; + +typedef struct _AudioSessionWrapper { + IAudioSessionControl2 IAudioSessionControl2_iface; + IChannelAudioVolume IChannelAudioVolume_iface; + ISimpleAudioVolume ISimpleAudioVolume_iface; + + LONG ref; + + ACImpl *client; + AudioSession *session; +} AudioSessionWrapper; + +struct ACImpl { + IAudioClient3 IAudioClient3_iface; + IAudioRenderClient IAudioRenderClient_iface; + IAudioCaptureClient IAudioCaptureClient_iface; + IAudioClock IAudioClock_iface; + IAudioClock2 IAudioClock2_iface; + IAudioStreamVolume IAudioStreamVolume_iface; + + LONG ref; + + IMMDevice *parent; + IUnknown *pUnkFTMarshal; + + EDataFlow dataflow; + float *vols; + UINT32 channel_count; + stream_handle stream; + + HANDLE timer_thread; + + AudioSession *session; + AudioSessionWrapper *session_wrapper; + + struct list entry; + + /* Keep at end */ + char devnode[0]; +}; + +typedef struct _SessionMgr { + IAudioSessionManager2 IAudioSessionManager2_iface; + + LONG ref; + + IMMDevice *device; +} SessionMgr; + +typedef struct _OSSDevice { + struct list entry; + EDataFlow flow; + GUID guid; + char devnode[0]; +} OSSDevice; + +static struct list g_devices = LIST_INIT(g_devices); + +static const WCHAR drv_key_devicesW[] = {'S','o','f','t','w','a','r','e','\\', + 'W','i','n','e','\\','D','r','i','v','e','r','s','\\', + 'w','i','n','e','o','s','s','.','d','r','v','\\','d','e','v','i','c','e','s',0}; +static const WCHAR guidW[] = {'g','u','i','d',0}; + +static CRITICAL_SECTION g_sessions_lock; +static CRITICAL_SECTION_DEBUG g_sessions_lock_debug = +{ + 0, 0, &g_sessions_lock, + { &g_sessions_lock_debug.ProcessLocksList, &g_sessions_lock_debug.ProcessLocksList }, + 0, 0, { (DWORD_PTR)(__FILE__ ": g_sessions_lock") } +}; +static CRITICAL_SECTION g_sessions_lock = { &g_sessions_lock_debug, -1, 0, 0, 0, 0 }; +static struct list g_sessions = LIST_INIT(g_sessions); + +static AudioSessionWrapper *AudioSessionWrapper_Create(ACImpl *client); + +static const IAudioClient3Vtbl AudioClient3_Vtbl; +static const IAudioRenderClientVtbl AudioRenderClient_Vtbl; +static const IAudioCaptureClientVtbl AudioCaptureClient_Vtbl; +static const IAudioSessionControl2Vtbl AudioSessionControl2_Vtbl; +static const ISimpleAudioVolumeVtbl SimpleAudioVolume_Vtbl; +static const IAudioClockVtbl AudioClock_Vtbl; +static const IAudioClock2Vtbl AudioClock2_Vtbl; +static const IAudioStreamVolumeVtbl AudioStreamVolume_Vtbl; +static const IChannelAudioVolumeVtbl ChannelAudioVolume_Vtbl; +static const IAudioSessionManager2Vtbl AudioSessionManager2_Vtbl; + +static inline ACImpl *impl_from_IAudioClient3(IAudioClient3 *iface) +{ + return CONTAINING_RECORD(iface, ACImpl, IAudioClient3_iface); +} + +static inline ACImpl *impl_from_IAudioRenderClient(IAudioRenderClient *iface) +{ + return CONTAINING_RECORD(iface, ACImpl, IAudioRenderClient_iface); +} + +static inline ACImpl *impl_from_IAudioCaptureClient(IAudioCaptureClient *iface) +{ + return CONTAINING_RECORD(iface, ACImpl, IAudioCaptureClient_iface); +} + +static inline AudioSessionWrapper *impl_from_IAudioSessionControl2(IAudioSessionControl2 *iface) +{ + return CONTAINING_RECORD(iface, AudioSessionWrapper, IAudioSessionControl2_iface); +} + +static inline AudioSessionWrapper *impl_from_ISimpleAudioVolume(ISimpleAudioVolume *iface) +{ + return CONTAINING_RECORD(iface, AudioSessionWrapper, ISimpleAudioVolume_iface); +} + +static inline AudioSessionWrapper *impl_from_IChannelAudioVolume(IChannelAudioVolume *iface) +{ + return CONTAINING_RECORD(iface, AudioSessionWrapper, IChannelAudioVolume_iface); +} + +static inline ACImpl *impl_from_IAudioClock(IAudioClock *iface) +{ + return CONTAINING_RECORD(iface, ACImpl, IAudioClock_iface); +} + +static inline ACImpl *impl_from_IAudioClock2(IAudioClock2 *iface) +{ + return CONTAINING_RECORD(iface, ACImpl, IAudioClock2_iface); +} + +static inline ACImpl *impl_from_IAudioStreamVolume(IAudioStreamVolume *iface) +{ + return CONTAINING_RECORD(iface, ACImpl, IAudioStreamVolume_iface); +} + +static inline SessionMgr *impl_from_IAudioSessionManager2(IAudioSessionManager2 *iface) +{ + return CONTAINING_RECORD(iface, SessionMgr, IAudioSessionManager2_iface); +} + +BOOL WINAPI DllMain(HINSTANCE dll, DWORD reason, void *reserved) +{ + switch (reason) + { + case DLL_PROCESS_ATTACH: + if(__wine_init_unix_call()) return FALSE; + break; + + case DLL_PROCESS_DETACH: + if (!reserved) + { + OSSDevice *iter, *iter2; + + DeleteCriticalSection(&g_sessions_lock); + + LIST_FOR_EACH_ENTRY_SAFE(iter, iter2, &g_devices, OSSDevice, entry){ + HeapFree(GetProcessHeap(), 0, iter); + } + } + break; + } + return TRUE; +} + +int WINAPI AUDDRV_GetPriority(void) +{ + struct test_connect_params params; + + params.name = NULL; + + OSS_CALL(test_connect, ¶ms); + + return params.priority; +} + +static HRESULT stream_release(stream_handle stream, HANDLE timer_thread) +{ + struct release_stream_params params; + + params.stream = stream; + params.timer_thread = timer_thread; + OSS_CALL(release_stream, ¶ms); + + return params.result; +} + +static DWORD WINAPI timer_thread(void *user) +{ + struct timer_loop_params params; + struct ACImpl *This = user; + + params.stream = This->stream; + OSS_CALL(timer_loop, ¶ms); + + return 0; +} + +static void set_device_guid(EDataFlow flow, HKEY drv_key, const WCHAR *key_name, + GUID *guid) +{ + HKEY key; + BOOL opened = FALSE; + LONG lr; + + if(!drv_key){ + lr = RegCreateKeyExW(HKEY_CURRENT_USER, drv_key_devicesW, 0, NULL, 0, KEY_WRITE, + NULL, &drv_key, NULL); + if(lr != ERROR_SUCCESS){ + ERR("RegCreateKeyEx(drv_key) failed: %lu\n", lr); + return; + } + opened = TRUE; + } + + lr = RegCreateKeyExW(drv_key, key_name, 0, NULL, 0, KEY_WRITE, + NULL, &key, NULL); + if(lr != ERROR_SUCCESS){ + ERR("RegCreateKeyEx(%s) failed: %lu\n", wine_dbgstr_w(key_name), lr); + goto exit; + } + + lr = RegSetValueExW(key, guidW, 0, REG_BINARY, (BYTE*)guid, + sizeof(GUID)); + if(lr != ERROR_SUCCESS) + ERR("RegSetValueEx(%s\\guid) failed: %lu\n", wine_dbgstr_w(key_name), lr); + + RegCloseKey(key); +exit: + if(opened) + RegCloseKey(drv_key); +} + +static void get_device_guid(EDataFlow flow, const char *device, GUID *guid) +{ + HKEY key = NULL, dev_key; + DWORD type, size = sizeof(*guid); + WCHAR key_name[256]; + + if(flow == eCapture) + key_name[0] = '1'; + else + key_name[0] = '0'; + key_name[1] = ','; + MultiByteToWideChar(CP_UNIXCP, 0, device, -1, key_name + 2, ARRAY_SIZE(key_name) - 2); + + if(RegOpenKeyExW(HKEY_CURRENT_USER, drv_key_devicesW, 0, KEY_WRITE|KEY_READ, &key) == ERROR_SUCCESS){ + if(RegOpenKeyExW(key, key_name, 0, KEY_READ, &dev_key) == ERROR_SUCCESS){ + if(RegQueryValueExW(dev_key, guidW, 0, &type, + (BYTE*)guid, &size) == ERROR_SUCCESS){ + if(type == REG_BINARY){ + RegCloseKey(dev_key); + RegCloseKey(key); + return; + } + ERR("Invalid type for device %s GUID: %lu; ignoring and overwriting\n", + wine_dbgstr_w(key_name), type); + } + RegCloseKey(dev_key); + } + } + + CoCreateGuid(guid); + + set_device_guid(flow, key, key_name, guid); + + if(key) + RegCloseKey(key); +} + +static void set_stream_volumes(ACImpl *This) +{ + struct set_volumes_params params; + + params.stream = This->stream; + params.master_volume = (This->session->mute ? 0.0f : This->session->master_vol); + params.volumes = This->vols; + params.session_volumes = This->session->channel_vols; + params.channel = 0; + OSS_CALL(set_volumes, ¶ms); +} + +static const OSSDevice *get_ossdevice_from_guid(const GUID *guid) +{ + OSSDevice *dev_item; + LIST_FOR_EACH_ENTRY(dev_item, &g_devices, OSSDevice, entry) + if(IsEqualGUID(guid, &dev_item->guid)) + return dev_item; + return NULL; +} + +static void device_add(OSSDevice *oss_dev) +{ + if(get_ossdevice_from_guid(&oss_dev->guid)) /* already in list */ + HeapFree(GetProcessHeap(), 0, oss_dev); + else + list_add_tail(&g_devices, &oss_dev->entry); +} + +HRESULT WINAPI AUDDRV_GetEndpointIDs(EDataFlow flow, WCHAR ***ids_out, GUID **guids_out, + UINT *num, UINT *def_index) +{ + struct get_endpoint_ids_params params; + GUID *guids = NULL; + WCHAR **ids = NULL; + unsigned int i; + + TRACE("%d %p %p %p %p\n", flow, ids, guids, num, def_index); + + params.flow = flow; + params.size = 1000; + params.endpoints = NULL; + do{ + HeapFree(GetProcessHeap(), 0, params.endpoints); + params.endpoints = HeapAlloc(GetProcessHeap(), 0, params.size); + OSS_CALL(get_endpoint_ids, ¶ms); + }while(params.result == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)); + + if(FAILED(params.result)) goto end; + + ids = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, params.num * sizeof(*ids)); + guids = HeapAlloc(GetProcessHeap(), 0, params.num * sizeof(*guids)); + if(!ids || !guids){ + params.result = E_OUTOFMEMORY; + goto end; + } + + for(i = 0; i < params.num; i++){ + WCHAR *name = (WCHAR *)((char *)params.endpoints + params.endpoints[i].name); + char *device = (char *)params.endpoints + params.endpoints[i].device; + unsigned int name_size = (wcslen(name) + 1) * sizeof(WCHAR); + unsigned int dev_size = strlen(device) + 1; + OSSDevice *oss_dev; + + ids[i] = HeapAlloc(GetProcessHeap(), 0, name_size); + oss_dev = HeapAlloc(GetProcessHeap(), 0, offsetof(OSSDevice, devnode[dev_size])); + if(!ids[i] || !oss_dev){ + HeapFree(GetProcessHeap(), 0, oss_dev); + params.result = E_OUTOFMEMORY; + goto end; + } + memcpy(ids[i], name, name_size); + get_device_guid(flow, device, guids + i); + + oss_dev->flow = flow; + oss_dev->guid = guids[i]; + memcpy(oss_dev->devnode, device, dev_size); + device_add(oss_dev); + } + *def_index = params.default_idx; + +end: + HeapFree(GetProcessHeap(), 0, params.endpoints); + if(FAILED(params.result)){ + HeapFree(GetProcessHeap(), 0, guids); + if(ids){ + for(i = 0; i < params.num; i++) + HeapFree(GetProcessHeap(), 0, ids[i]); + HeapFree(GetProcessHeap(), 0, ids); + } + }else{ + *ids_out = ids; + *guids_out = guids; + *num = params.num; + } + + return params.result; +} + +HRESULT WINAPI AUDDRV_GetAudioEndpoint(GUID *guid, IMMDevice *dev, + IAudioClient **out) +{ + ACImpl *This; + const OSSDevice *oss_dev; + HRESULT hr; + int len; + + TRACE("%s %p %p\n", debugstr_guid(guid), dev, out); + + oss_dev = get_ossdevice_from_guid(guid); + if(!oss_dev){ + WARN("Unknown GUID: %s\n", debugstr_guid(guid)); + return AUDCLNT_E_DEVICE_INVALIDATED; + } + len = strlen(oss_dev->devnode); + This = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, offsetof(ACImpl, devnode[len + 1])); + if(!This) + return E_OUTOFMEMORY; + + hr = CoCreateFreeThreadedMarshaler((IUnknown *)&This->IAudioClient3_iface, &This->pUnkFTMarshal); + if (FAILED(hr)) { + HeapFree(GetProcessHeap(), 0, This); + return hr; + } + + This->dataflow = oss_dev->flow; + strcpy(This->devnode, oss_dev->devnode); + + This->IAudioClient3_iface.lpVtbl = &AudioClient3_Vtbl; + This->IAudioRenderClient_iface.lpVtbl = &AudioRenderClient_Vtbl; + This->IAudioCaptureClient_iface.lpVtbl = &AudioCaptureClient_Vtbl; + This->IAudioClock_iface.lpVtbl = &AudioClock_Vtbl; + This->IAudioClock2_iface.lpVtbl = &AudioClock2_Vtbl; + This->IAudioStreamVolume_iface.lpVtbl = &AudioStreamVolume_Vtbl; + + This->parent = dev; + IMMDevice_AddRef(This->parent); + + *out = (IAudioClient *)&This->IAudioClient3_iface; + IAudioClient3_AddRef(&This->IAudioClient3_iface); + + return S_OK; +} + +static HRESULT WINAPI AudioClient_QueryInterface(IAudioClient3 *iface, + REFIID riid, void **ppv) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + if(IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IAudioClient) || + IsEqualIID(riid, &IID_IAudioClient2) || + IsEqualIID(riid, &IID_IAudioClient3)) + *ppv = iface; + else if(IsEqualIID(riid, &IID_IMarshal)) + return IUnknown_QueryInterface(This->pUnkFTMarshal, riid, ppv); + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioClient_AddRef(IAudioClient3 *iface) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + ULONG ref; + ref = InterlockedIncrement(&This->ref); + TRACE("(%p) Refcount now %lu\n", This, ref); + return ref; +} + +static ULONG WINAPI AudioClient_Release(IAudioClient3 *iface) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + ULONG ref; + + ref = InterlockedDecrement(&This->ref); + TRACE("(%p) Refcount now %lu\n", This, ref); + if(!ref){ + IAudioClient3_Stop(iface); + IMMDevice_Release(This->parent); + IUnknown_Release(This->pUnkFTMarshal); + if(This->session){ + EnterCriticalSection(&g_sessions_lock); + list_remove(&This->entry); + LeaveCriticalSection(&g_sessions_lock); + } + HeapFree(GetProcessHeap(), 0, This->vols); + if(This->stream) + stream_release(This->stream, This->timer_thread); + HeapFree(GetProcessHeap(), 0, This); + } + return ref; +} + +static void dump_fmt(const WAVEFORMATEX *fmt) +{ + TRACE("wFormatTag: 0x%x (", fmt->wFormatTag); + switch(fmt->wFormatTag){ + case WAVE_FORMAT_PCM: + TRACE("WAVE_FORMAT_PCM"); + break; + case WAVE_FORMAT_IEEE_FLOAT: + TRACE("WAVE_FORMAT_IEEE_FLOAT"); + break; + case WAVE_FORMAT_EXTENSIBLE: + TRACE("WAVE_FORMAT_EXTENSIBLE"); + break; + default: + TRACE("Unknown"); + break; + } + TRACE(")\n"); + + TRACE("nChannels: %u\n", fmt->nChannels); + TRACE("nSamplesPerSec: %lu\n", fmt->nSamplesPerSec); + TRACE("nAvgBytesPerSec: %lu\n", fmt->nAvgBytesPerSec); + TRACE("nBlockAlign: %u\n", fmt->nBlockAlign); + TRACE("wBitsPerSample: %u\n", fmt->wBitsPerSample); + TRACE("cbSize: %u\n", fmt->cbSize); + + if(fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE){ + WAVEFORMATEXTENSIBLE *fmtex = (void*)fmt; + TRACE("dwChannelMask: %08lx\n", fmtex->dwChannelMask); + TRACE("Samples: %04x\n", fmtex->Samples.wReserved); + TRACE("SubFormat: %s\n", wine_dbgstr_guid(&fmtex->SubFormat)); + } +} + +static void session_init_vols(AudioSession *session, UINT channels) +{ + if(session->channel_count < channels){ + UINT i; + + if(session->channel_vols) + session->channel_vols = HeapReAlloc(GetProcessHeap(), 0, + session->channel_vols, sizeof(float) * channels); + else + session->channel_vols = HeapAlloc(GetProcessHeap(), 0, + sizeof(float) * channels); + if(!session->channel_vols) + return; + + for(i = session->channel_count; i < channels; ++i) + session->channel_vols[i] = 1.f; + + session->channel_count = channels; + } +} + +static AudioSession *create_session(const GUID *guid, IMMDevice *device, + UINT num_channels) +{ + AudioSession *ret; + + ret = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(AudioSession)); + if(!ret) + return NULL; + + memcpy(&ret->guid, guid, sizeof(GUID)); + + ret->device = device; + + list_init(&ret->clients); + + list_add_head(&g_sessions, &ret->entry); + + session_init_vols(ret, num_channels); + + ret->master_vol = 1.f; + + return ret; +} + +/* if channels == 0, then this will return or create a session with + * matching dataflow and GUID. otherwise, channels must also match */ +static HRESULT get_audio_session(const GUID *sessionguid, + IMMDevice *device, UINT channels, AudioSession **out) +{ + AudioSession *session; + + if(!sessionguid || IsEqualGUID(sessionguid, &GUID_NULL)){ + *out = create_session(&GUID_NULL, device, channels); + if(!*out) + return E_OUTOFMEMORY; + + return S_OK; + } + + *out = NULL; + LIST_FOR_EACH_ENTRY(session, &g_sessions, AudioSession, entry){ + if(session->device == device && + IsEqualGUID(sessionguid, &session->guid)){ + session_init_vols(session, channels); + *out = session; + break; + } + } + + if(!*out){ + *out = create_session(sessionguid, device, channels); + if(!*out) + return E_OUTOFMEMORY; + } + + return S_OK; +} + +static HRESULT WINAPI AudioClient_Initialize(IAudioClient3 *iface, + AUDCLNT_SHAREMODE mode, DWORD flags, REFERENCE_TIME duration, + REFERENCE_TIME period, const WAVEFORMATEX *fmt, + const GUID *sessionguid) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct create_stream_params params; + stream_handle stream; + unsigned int i; + + TRACE("(%p)->(%x, %lx, %s, %s, %p, %s)\n", This, mode, flags, + wine_dbgstr_longlong(duration), wine_dbgstr_longlong(period), fmt, debugstr_guid(sessionguid)); + + if(!fmt) + return E_POINTER; + + dump_fmt(fmt); + + if(mode != AUDCLNT_SHAREMODE_SHARED && mode != AUDCLNT_SHAREMODE_EXCLUSIVE) + return E_INVALIDARG; + + if(flags & ~(AUDCLNT_STREAMFLAGS_CROSSPROCESS | + AUDCLNT_STREAMFLAGS_LOOPBACK | + AUDCLNT_STREAMFLAGS_EVENTCALLBACK | + AUDCLNT_STREAMFLAGS_NOPERSIST | + AUDCLNT_STREAMFLAGS_RATEADJUST | + AUDCLNT_SESSIONFLAGS_EXPIREWHENUNOWNED | + AUDCLNT_SESSIONFLAGS_DISPLAY_HIDE | + AUDCLNT_SESSIONFLAGS_DISPLAY_HIDEWHENEXPIRED | + AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY | + AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM)){ + FIXME("Unknown flags: %08lx\n", flags); + return E_INVALIDARG; + } + + if(mode == AUDCLNT_SHAREMODE_SHARED){ + period = DefaultPeriod; + if( duration < 3 * period) + duration = 3 * period; + }else{ + if(!period) + period = DefaultPeriod; /* not minimum */ + if(period < MinimumPeriod || period > 5000000) + return AUDCLNT_E_INVALID_DEVICE_PERIOD; + if(duration > 20000000) /* the smaller the period, the lower this limit */ + return AUDCLNT_E_BUFFER_SIZE_ERROR; + if(flags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK){ + if(duration != period) + return AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL; + FIXME("EXCLUSIVE mode with EVENTCALLBACK\n"); + return AUDCLNT_E_DEVICE_IN_USE; + }else{ + if( duration < 8 * period) + duration = 8 * period; /* may grow above 2s */ + } + } + + EnterCriticalSection(&g_sessions_lock); + + if(This->stream){ + LeaveCriticalSection(&g_sessions_lock); + return AUDCLNT_E_ALREADY_INITIALIZED; + } + + params.name = NULL; + params.device = This->devnode; + params.flow = This->dataflow; + params.share = mode; + params.flags = flags; + params.duration = duration; + params.period = period; + params.fmt = fmt; + params.channel_count = NULL; + params.stream = &stream; + + OSS_CALL(create_stream, ¶ms); + if(FAILED(params.result)){ + LeaveCriticalSection(&g_sessions_lock); + return params.result; + } + + This->channel_count = fmt->nChannels; + This->vols = HeapAlloc(GetProcessHeap(), 0, This->channel_count * sizeof(float)); + if(!This->vols){ + params.result = E_OUTOFMEMORY; + goto exit; + } + for(i = 0; i < This->channel_count; ++i) + This->vols[i] = 1.f; + + params.result = get_audio_session(sessionguid, This->parent, This->channel_count, + &This->session); + +exit: + if(FAILED(params.result)){ + stream_release(stream, NULL); + HeapFree(GetProcessHeap(), 0, This->vols); + This->vols = NULL; + } else { + list_add_tail(&This->session->clients, &This->entry); + This->stream = stream; + set_stream_volumes(This); + } + + LeaveCriticalSection(&g_sessions_lock); + + return params.result; +} + +static HRESULT WINAPI AudioClient_GetBufferSize(IAudioClient3 *iface, + UINT32 *frames) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct get_buffer_size_params params; + + TRACE("(%p)->(%p)\n", This, frames); + + if(!frames) + return E_POINTER; + + if(!This->stream) + return AUDCLNT_E_NOT_INITIALIZED; + + params.stream = This->stream; + params.frames = frames; + + OSS_CALL(get_buffer_size, ¶ms); + TRACE("buffer size: %u\n", *frames); + + return params.result; +} + +static HRESULT WINAPI AudioClient_GetStreamLatency(IAudioClient3 *iface, + REFERENCE_TIME *latency) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct get_latency_params params; + + TRACE("(%p)->(%p)\n", This, latency); + + if(!latency) + return E_POINTER; + + if(!This->stream) + return AUDCLNT_E_NOT_INITIALIZED; + + params.stream = This->stream; + params.latency = latency; + OSS_CALL(get_latency, ¶ms); + + return params.result; +} + +static HRESULT WINAPI AudioClient_GetCurrentPadding(IAudioClient3 *iface, + UINT32 *numpad) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct get_current_padding_params params; + + TRACE("(%p)->(%p)\n", This, numpad); + + if(!numpad) + return E_POINTER; + + if(!This->stream) + return AUDCLNT_E_NOT_INITIALIZED; + + params.stream = This->stream; + params.padding = numpad; + OSS_CALL(get_current_padding, ¶ms); + TRACE("padding: %u\n", *numpad); + + return params.result; +} + +static HRESULT WINAPI AudioClient_IsFormatSupported(IAudioClient3 *iface, + AUDCLNT_SHAREMODE mode, const WAVEFORMATEX *fmt, + WAVEFORMATEX **out) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct is_format_supported_params params; + + TRACE("(%p)->(%x, %p, %p)\n", This, mode, fmt, out); + if(fmt) dump_fmt(fmt); + + params.device = This->devnode; + params.flow = This->dataflow; + params.share = mode; + params.fmt_in = fmt; + params.fmt_out = NULL; + + if(out){ + *out = NULL; + if(mode == AUDCLNT_SHAREMODE_SHARED) + params.fmt_out = CoTaskMemAlloc(sizeof(*params.fmt_out)); + } + OSS_CALL(is_format_supported, ¶ms); + + if(params.result == S_FALSE) + *out = ¶ms.fmt_out->Format; + else + CoTaskMemFree(params.fmt_out); + + return params.result; +} + +static HRESULT WINAPI AudioClient_GetMixFormat(IAudioClient3 *iface, + WAVEFORMATEX **pwfx) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct get_mix_format_params params; + + TRACE("(%p)->(%p)\n", This, pwfx); + + if(!pwfx) + return E_POINTER; + *pwfx = NULL; + + params.device = This->devnode; + params.flow = This->dataflow; + params.fmt = CoTaskMemAlloc(sizeof(WAVEFORMATEXTENSIBLE)); + if(!params.fmt) + return E_OUTOFMEMORY; + + OSS_CALL(get_mix_format, ¶ms); + + if(SUCCEEDED(params.result)){ + *pwfx = ¶ms.fmt->Format; + dump_fmt(*pwfx); + } else + CoTaskMemFree(params.fmt); + + return params.result; +} + +static HRESULT WINAPI AudioClient_GetDevicePeriod(IAudioClient3 *iface, + REFERENCE_TIME *defperiod, REFERENCE_TIME *minperiod) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + + TRACE("(%p)->(%p, %p)\n", This, defperiod, minperiod); + + if(!defperiod && !minperiod) + return E_POINTER; + + if(defperiod) + *defperiod = DefaultPeriod; + if(minperiod) + *minperiod = MinimumPeriod; + + return S_OK; +} + +static HRESULT WINAPI AudioClient_Start(IAudioClient3 *iface) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct start_params params; + + TRACE("(%p)\n", This); + + EnterCriticalSection(&g_sessions_lock); + + if(!This->stream){ + LeaveCriticalSection(&g_sessions_lock); + return AUDCLNT_E_NOT_INITIALIZED; + } + + params.stream = This->stream; + OSS_CALL(start, ¶ms); + + if(SUCCEEDED(params.result) && !This->timer_thread){ + This->timer_thread = CreateThread(NULL, 0, timer_thread, This, 0, NULL); + SetThreadPriority(This->timer_thread, THREAD_PRIORITY_TIME_CRITICAL); + } + + LeaveCriticalSection(&g_sessions_lock); + + return params.result; +} + +static HRESULT WINAPI AudioClient_Stop(IAudioClient3 *iface) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct stop_params params; + + TRACE("(%p)\n", This); + + if(!This->stream) + return AUDCLNT_E_NOT_INITIALIZED; + + params.stream = This->stream; + OSS_CALL(stop, ¶ms); + + return params.result; +} + +static HRESULT WINAPI AudioClient_Reset(IAudioClient3 *iface) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct reset_params params; + + TRACE("(%p)\n", This); + + if(!This->stream) + return AUDCLNT_E_NOT_INITIALIZED; + + params.stream = This->stream; + OSS_CALL(reset, ¶ms); + + return params.result; +} + +static HRESULT WINAPI AudioClient_SetEventHandle(IAudioClient3 *iface, + HANDLE event) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + struct set_event_handle_params params; + + TRACE("(%p)->(%p)\n", This, event); + + if(!event) + return E_INVALIDARG; + + if(!This->stream) + return AUDCLNT_E_NOT_INITIALIZED; + + params.stream = This->stream; + params.event = event; + OSS_CALL(set_event_handle, ¶ms); + + return params.result; +} + +static HRESULT WINAPI AudioClient_GetService(IAudioClient3 *iface, REFIID riid, + void **ppv) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + + TRACE("(%p)->(%s, %p)\n", This, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + EnterCriticalSection(&g_sessions_lock); + + if(!This->stream){ + LeaveCriticalSection(&g_sessions_lock); + return AUDCLNT_E_NOT_INITIALIZED; + } + + if(IsEqualIID(riid, &IID_IAudioRenderClient)){ + if(This->dataflow != eRender){ + LeaveCriticalSection(&g_sessions_lock); + return AUDCLNT_E_WRONG_ENDPOINT_TYPE; + } + IAudioRenderClient_AddRef(&This->IAudioRenderClient_iface); + *ppv = &This->IAudioRenderClient_iface; + }else if(IsEqualIID(riid, &IID_IAudioCaptureClient)){ + if(This->dataflow != eCapture){ + LeaveCriticalSection(&g_sessions_lock); + return AUDCLNT_E_WRONG_ENDPOINT_TYPE; + } + IAudioCaptureClient_AddRef(&This->IAudioCaptureClient_iface); + *ppv = &This->IAudioCaptureClient_iface; + }else if(IsEqualIID(riid, &IID_IAudioClock)){ + IAudioClock_AddRef(&This->IAudioClock_iface); + *ppv = &This->IAudioClock_iface; + }else if(IsEqualIID(riid, &IID_IAudioStreamVolume)){ + IAudioStreamVolume_AddRef(&This->IAudioStreamVolume_iface); + *ppv = &This->IAudioStreamVolume_iface; + }else if(IsEqualIID(riid, &IID_IAudioSessionControl)){ + if(!This->session_wrapper){ + This->session_wrapper = AudioSessionWrapper_Create(This); + if(!This->session_wrapper){ + LeaveCriticalSection(&g_sessions_lock); + return E_OUTOFMEMORY; + } + }else + IAudioSessionControl2_AddRef(&This->session_wrapper->IAudioSessionControl2_iface); + + *ppv = &This->session_wrapper->IAudioSessionControl2_iface; + }else if(IsEqualIID(riid, &IID_IChannelAudioVolume)){ + if(!This->session_wrapper){ + This->session_wrapper = AudioSessionWrapper_Create(This); + if(!This->session_wrapper){ + LeaveCriticalSection(&g_sessions_lock); + return E_OUTOFMEMORY; + } + }else + IChannelAudioVolume_AddRef(&This->session_wrapper->IChannelAudioVolume_iface); + + *ppv = &This->session_wrapper->IChannelAudioVolume_iface; + }else if(IsEqualIID(riid, &IID_ISimpleAudioVolume)){ + if(!This->session_wrapper){ + This->session_wrapper = AudioSessionWrapper_Create(This); + if(!This->session_wrapper){ + LeaveCriticalSection(&g_sessions_lock); + return E_OUTOFMEMORY; + } + }else + ISimpleAudioVolume_AddRef(&This->session_wrapper->ISimpleAudioVolume_iface); + + *ppv = &This->session_wrapper->ISimpleAudioVolume_iface; + } + + if(*ppv){ + LeaveCriticalSection(&g_sessions_lock); + return S_OK; + } + + LeaveCriticalSection(&g_sessions_lock); + + FIXME("stub %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static HRESULT WINAPI AudioClient_IsOffloadCapable(IAudioClient3 *iface, + AUDIO_STREAM_CATEGORY category, BOOL *offload_capable) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + + TRACE("(%p)->(0x%x, %p)\n", This, category, offload_capable); + + if(!offload_capable) + return E_INVALIDARG; + + *offload_capable = FALSE; + + return S_OK; +} + +static HRESULT WINAPI AudioClient_SetClientProperties(IAudioClient3 *iface, + const AudioClientProperties *prop) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + const Win8AudioClientProperties *legacy_prop = (const Win8AudioClientProperties *)prop; + + TRACE("(%p)->(%p)\n", This, prop); + + if(!legacy_prop) + return E_POINTER; + + if(legacy_prop->cbSize == sizeof(AudioClientProperties)){ + TRACE("{ bIsOffload: %u, eCategory: 0x%x, Options: 0x%x }\n", + legacy_prop->bIsOffload, + legacy_prop->eCategory, + prop->Options); + }else if(legacy_prop->cbSize == sizeof(Win8AudioClientProperties)){ + TRACE("{ bIsOffload: %u, eCategory: 0x%x }\n", + legacy_prop->bIsOffload, + legacy_prop->eCategory); + }else{ + WARN("Unsupported Size = %d\n", legacy_prop->cbSize); + return E_INVALIDARG; + } + + + if(legacy_prop->bIsOffload) + return AUDCLNT_E_ENDPOINT_OFFLOAD_NOT_CAPABLE; + + return S_OK; +} + +static HRESULT WINAPI AudioClient_GetBufferSizeLimits(IAudioClient3 *iface, + const WAVEFORMATEX *format, BOOL event_driven, REFERENCE_TIME *min_duration, + REFERENCE_TIME *max_duration) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + + FIXME("(%p)->(%p, %u, %p, %p)\n", This, format, event_driven, min_duration, max_duration); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioClient_GetSharedModeEnginePeriod(IAudioClient3 *iface, + const WAVEFORMATEX *format, UINT32 *default_period_frames, UINT32 *unit_period_frames, + UINT32 *min_period_frames, UINT32 *max_period_frames) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + + FIXME("(%p)->(%p, %p, %p, %p, %p)\n", This, format, default_period_frames, unit_period_frames, + min_period_frames, max_period_frames); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioClient_GetCurrentSharedModeEnginePeriod(IAudioClient3 *iface, + WAVEFORMATEX **cur_format, UINT32 *cur_period_frames) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + + FIXME("(%p)->(%p, %p)\n", This, cur_format, cur_period_frames); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioClient_InitializeSharedAudioStream(IAudioClient3 *iface, + DWORD flags, UINT32 period_frames, const WAVEFORMATEX *format, + const GUID *session_guid) +{ + ACImpl *This = impl_from_IAudioClient3(iface); + + FIXME("(%p)->(0x%lx, %u, %p, %s)\n", This, flags, period_frames, format, debugstr_guid(session_guid)); + + return E_NOTIMPL; +} + +static const IAudioClient3Vtbl AudioClient3_Vtbl = +{ + AudioClient_QueryInterface, + AudioClient_AddRef, + AudioClient_Release, + AudioClient_Initialize, + AudioClient_GetBufferSize, + AudioClient_GetStreamLatency, + AudioClient_GetCurrentPadding, + AudioClient_IsFormatSupported, + AudioClient_GetMixFormat, + AudioClient_GetDevicePeriod, + AudioClient_Start, + AudioClient_Stop, + AudioClient_Reset, + AudioClient_SetEventHandle, + AudioClient_GetService, + AudioClient_IsOffloadCapable, + AudioClient_SetClientProperties, + AudioClient_GetBufferSizeLimits, + AudioClient_GetSharedModeEnginePeriod, + AudioClient_GetCurrentSharedModeEnginePeriod, + AudioClient_InitializeSharedAudioStream, +}; + +static HRESULT WINAPI AudioRenderClient_QueryInterface( + IAudioRenderClient *iface, REFIID riid, void **ppv) +{ + ACImpl *This = impl_from_IAudioRenderClient(iface); + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + if(IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IAudioRenderClient)) + *ppv = iface; + else if(IsEqualIID(riid, &IID_IMarshal)) + return IUnknown_QueryInterface(This->pUnkFTMarshal, riid, ppv); + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioRenderClient_AddRef(IAudioRenderClient *iface) +{ + ACImpl *This = impl_from_IAudioRenderClient(iface); + return AudioClient_AddRef(&This->IAudioClient3_iface); +} + +static ULONG WINAPI AudioRenderClient_Release(IAudioRenderClient *iface) +{ + ACImpl *This = impl_from_IAudioRenderClient(iface); + return AudioClient_Release(&This->IAudioClient3_iface); +} + +static HRESULT WINAPI AudioRenderClient_GetBuffer(IAudioRenderClient *iface, + UINT32 frames, BYTE **data) +{ + ACImpl *This = impl_from_IAudioRenderClient(iface); + struct get_render_buffer_params params; + + TRACE("(%p)->(%u, %p)\n", This, frames, data); + + if(!data) + return E_POINTER; + + *data = NULL; + + params.stream = This->stream; + params.frames = frames; + params.data = data; + OSS_CALL(get_render_buffer, ¶ms); + + return params.result; +} + +static HRESULT WINAPI AudioRenderClient_ReleaseBuffer( + IAudioRenderClient *iface, UINT32 written_frames, DWORD flags) +{ + ACImpl *This = impl_from_IAudioRenderClient(iface); + struct release_render_buffer_params params; + + TRACE("(%p)->(%u, %lx)\n", This, written_frames, flags); + + params.stream = This->stream; + params.written_frames = written_frames; + params.flags = flags; + OSS_CALL(release_render_buffer, ¶ms); + + return params.result; +} + +static const IAudioRenderClientVtbl AudioRenderClient_Vtbl = { + AudioRenderClient_QueryInterface, + AudioRenderClient_AddRef, + AudioRenderClient_Release, + AudioRenderClient_GetBuffer, + AudioRenderClient_ReleaseBuffer +}; + +static HRESULT WINAPI AudioCaptureClient_QueryInterface( + IAudioCaptureClient *iface, REFIID riid, void **ppv) +{ + ACImpl *This = impl_from_IAudioCaptureClient(iface); + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + if(IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IAudioCaptureClient)) + *ppv = iface; + else if(IsEqualIID(riid, &IID_IMarshal)) + return IUnknown_QueryInterface(This->pUnkFTMarshal, riid, ppv); + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioCaptureClient_AddRef(IAudioCaptureClient *iface) +{ + ACImpl *This = impl_from_IAudioCaptureClient(iface); + return IAudioClient3_AddRef(&This->IAudioClient3_iface); +} + +static ULONG WINAPI AudioCaptureClient_Release(IAudioCaptureClient *iface) +{ + ACImpl *This = impl_from_IAudioCaptureClient(iface); + return IAudioClient3_Release(&This->IAudioClient3_iface); +} + +static HRESULT WINAPI AudioCaptureClient_GetBuffer(IAudioCaptureClient *iface, + BYTE **data, UINT32 *frames, DWORD *flags, UINT64 *devpos, + UINT64 *qpcpos) +{ + ACImpl *This = impl_from_IAudioCaptureClient(iface); + struct get_capture_buffer_params params; + + TRACE("(%p)->(%p, %p, %p, %p, %p)\n", This, data, frames, flags, + devpos, qpcpos); + + if(!data) + return E_POINTER; + + *data = NULL; + + if(!frames || !flags) + return E_POINTER; + + params.stream = This->stream; + params.data = data; + params.frames = frames; + params.flags = (UINT*)flags; + params.devpos = devpos; + params.qpcpos = qpcpos; + OSS_CALL(get_capture_buffer, ¶ms); + + return params.result; +} + +static HRESULT WINAPI AudioCaptureClient_ReleaseBuffer( + IAudioCaptureClient *iface, UINT32 done) +{ + ACImpl *This = impl_from_IAudioCaptureClient(iface); + struct release_capture_buffer_params params; + + TRACE("(%p)->(%u)\n", This, done); + + params.stream = This->stream; + params.done = done; + OSS_CALL(release_capture_buffer, ¶ms); + + return params.result; +} + +static HRESULT WINAPI AudioCaptureClient_GetNextPacketSize( + IAudioCaptureClient *iface, UINT32 *frames) +{ + ACImpl *This = impl_from_IAudioCaptureClient(iface); + struct get_next_packet_size_params params; + + TRACE("(%p)->(%p)\n", This, frames); + + if(!frames) + return E_POINTER; + + params.stream = This->stream; + params.frames = frames; + OSS_CALL(get_next_packet_size, ¶ms); + + return params.result; +} + +static const IAudioCaptureClientVtbl AudioCaptureClient_Vtbl = +{ + AudioCaptureClient_QueryInterface, + AudioCaptureClient_AddRef, + AudioCaptureClient_Release, + AudioCaptureClient_GetBuffer, + AudioCaptureClient_ReleaseBuffer, + AudioCaptureClient_GetNextPacketSize +}; + +static HRESULT WINAPI AudioClock_QueryInterface(IAudioClock *iface, + REFIID riid, void **ppv) +{ + ACImpl *This = impl_from_IAudioClock(iface); + + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + if(IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IAudioClock)) + *ppv = iface; + else if(IsEqualIID(riid, &IID_IAudioClock2)) + *ppv = &This->IAudioClock2_iface; + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioClock_AddRef(IAudioClock *iface) +{ + ACImpl *This = impl_from_IAudioClock(iface); + return IAudioClient3_AddRef(&This->IAudioClient3_iface); +} + +static ULONG WINAPI AudioClock_Release(IAudioClock *iface) +{ + ACImpl *This = impl_from_IAudioClock(iface); + return IAudioClient3_Release(&This->IAudioClient3_iface); +} + +static HRESULT WINAPI AudioClock_GetFrequency(IAudioClock *iface, UINT64 *freq) +{ + ACImpl *This = impl_from_IAudioClock(iface); + struct get_frequency_params params; + + TRACE("(%p)->(%p)\n", This, freq); + + params.stream = This->stream; + params.freq = freq; + OSS_CALL(get_frequency, ¶ms); + + return params.result; +} + +static HRESULT WINAPI AudioClock_GetPosition(IAudioClock *iface, UINT64 *pos, + UINT64 *qpctime) +{ + ACImpl *This = impl_from_IAudioClock(iface); + struct get_position_params params; + + TRACE("(%p)->(%p, %p)\n", This, pos, qpctime); + + if(!pos) + return E_POINTER; + + params.stream = This->stream; + params.device = FALSE; + params.pos = pos; + params.qpctime = qpctime; + OSS_CALL(get_position, ¶ms); + + return params.result; +} + +static HRESULT WINAPI AudioClock_GetCharacteristics(IAudioClock *iface, + DWORD *chars) +{ + ACImpl *This = impl_from_IAudioClock(iface); + + TRACE("(%p)->(%p)\n", This, chars); + + if(!chars) + return E_POINTER; + + *chars = AUDIOCLOCK_CHARACTERISTIC_FIXED_FREQ; + + return S_OK; +} + +static const IAudioClockVtbl AudioClock_Vtbl = +{ + AudioClock_QueryInterface, + AudioClock_AddRef, + AudioClock_Release, + AudioClock_GetFrequency, + AudioClock_GetPosition, + AudioClock_GetCharacteristics +}; + +static HRESULT WINAPI AudioClock2_QueryInterface(IAudioClock2 *iface, + REFIID riid, void **ppv) +{ + ACImpl *This = impl_from_IAudioClock2(iface); + return IAudioClock_QueryInterface(&This->IAudioClock_iface, riid, ppv); +} + +static ULONG WINAPI AudioClock2_AddRef(IAudioClock2 *iface) +{ + ACImpl *This = impl_from_IAudioClock2(iface); + return IAudioClient3_AddRef(&This->IAudioClient3_iface); +} + +static ULONG WINAPI AudioClock2_Release(IAudioClock2 *iface) +{ + ACImpl *This = impl_from_IAudioClock2(iface); + return IAudioClient3_Release(&This->IAudioClient3_iface); +} + +static HRESULT WINAPI AudioClock2_GetDevicePosition(IAudioClock2 *iface, + UINT64 *pos, UINT64 *qpctime) +{ + ACImpl *This = impl_from_IAudioClock2(iface); + + FIXME("(%p)->(%p, %p)\n", This, pos, qpctime); + + return E_NOTIMPL; +} + +static const IAudioClock2Vtbl AudioClock2_Vtbl = +{ + AudioClock2_QueryInterface, + AudioClock2_AddRef, + AudioClock2_Release, + AudioClock2_GetDevicePosition +}; + +static AudioSessionWrapper *AudioSessionWrapper_Create(ACImpl *client) +{ + AudioSessionWrapper *ret; + + ret = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, + sizeof(AudioSessionWrapper)); + if(!ret) + return NULL; + + ret->IAudioSessionControl2_iface.lpVtbl = &AudioSessionControl2_Vtbl; + ret->ISimpleAudioVolume_iface.lpVtbl = &SimpleAudioVolume_Vtbl; + ret->IChannelAudioVolume_iface.lpVtbl = &ChannelAudioVolume_Vtbl; + + ret->ref = 1; + + ret->client = client; + if(client){ + ret->session = client->session; + AudioClient_AddRef(&client->IAudioClient3_iface); + } + + return ret; +} + +static HRESULT WINAPI AudioSessionControl_QueryInterface( + IAudioSessionControl2 *iface, REFIID riid, void **ppv) +{ + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + if(IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IAudioSessionControl) || + IsEqualIID(riid, &IID_IAudioSessionControl2)) + *ppv = iface; + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioSessionControl_AddRef(IAudioSessionControl2 *iface) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + ULONG ref; + ref = InterlockedIncrement(&This->ref); + TRACE("(%p) Refcount now %lu\n", This, ref); + return ref; +} + +static ULONG WINAPI AudioSessionControl_Release(IAudioSessionControl2 *iface) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + ULONG ref; + ref = InterlockedDecrement(&This->ref); + TRACE("(%p) Refcount now %lu\n", This, ref); + if(!ref){ + if(This->client){ + EnterCriticalSection(&g_sessions_lock); + This->client->session_wrapper = NULL; + LeaveCriticalSection(&g_sessions_lock); + AudioClient_Release(&This->client->IAudioClient3_iface); + } + HeapFree(GetProcessHeap(), 0, This); + } + return ref; +} + +static HRESULT WINAPI AudioSessionControl_GetState(IAudioSessionControl2 *iface, + AudioSessionState *state) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + struct is_started_params params; + ACImpl *client; + + TRACE("(%p)->(%p)\n", This, state); + + if(!state) + return NULL_PTR_ERR; + + EnterCriticalSection(&g_sessions_lock); + + if(list_empty(&This->session->clients)){ + *state = AudioSessionStateExpired; + LeaveCriticalSection(&g_sessions_lock); + return S_OK; + } + + LIST_FOR_EACH_ENTRY(client, &This->session->clients, ACImpl, entry){ + params.stream = client->stream; + OSS_CALL(is_started, ¶ms); + if(params.result == S_OK){ + *state = AudioSessionStateActive; + LeaveCriticalSection(&g_sessions_lock); + return S_OK; + } + } + + LeaveCriticalSection(&g_sessions_lock); + + *state = AudioSessionStateInactive; + + return S_OK; +} + +static HRESULT WINAPI AudioSessionControl_GetDisplayName( + IAudioSessionControl2 *iface, WCHAR **name) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, name); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_SetDisplayName( + IAudioSessionControl2 *iface, const WCHAR *name, const GUID *session) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p, %s) - stub\n", This, name, debugstr_guid(session)); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_GetIconPath( + IAudioSessionControl2 *iface, WCHAR **path) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, path); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_SetIconPath( + IAudioSessionControl2 *iface, const WCHAR *path, const GUID *session) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p, %s) - stub\n", This, path, debugstr_guid(session)); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_GetGroupingParam( + IAudioSessionControl2 *iface, GUID *group) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, group); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_SetGroupingParam( + IAudioSessionControl2 *iface, const GUID *group, const GUID *session) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%s, %s) - stub\n", This, debugstr_guid(group), + debugstr_guid(session)); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_RegisterAudioSessionNotification( + IAudioSessionControl2 *iface, IAudioSessionEvents *events) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, events); + + return S_OK; +} + +static HRESULT WINAPI AudioSessionControl_UnregisterAudioSessionNotification( + IAudioSessionControl2 *iface, IAudioSessionEvents *events) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, events); + + return S_OK; +} + +static HRESULT WINAPI AudioSessionControl_GetSessionIdentifier( + IAudioSessionControl2 *iface, WCHAR **id) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, id); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_GetSessionInstanceIdentifier( + IAudioSessionControl2 *iface, WCHAR **id) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, id); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_GetProcessId( + IAudioSessionControl2 *iface, DWORD *pid) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + TRACE("(%p)->(%p)\n", This, pid); + + if(!pid) + return E_POINTER; + + *pid = GetCurrentProcessId(); + + return S_OK; +} + +static HRESULT WINAPI AudioSessionControl_IsSystemSoundsSession( + IAudioSessionControl2 *iface) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + TRACE("(%p)\n", This); + + return S_FALSE; +} + +static HRESULT WINAPI AudioSessionControl_SetDuckingPreference( + IAudioSessionControl2 *iface, BOOL optout) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + TRACE("(%p)->(%d)\n", This, optout); + + return S_OK; +} + +static const IAudioSessionControl2Vtbl AudioSessionControl2_Vtbl = +{ + AudioSessionControl_QueryInterface, + AudioSessionControl_AddRef, + AudioSessionControl_Release, + AudioSessionControl_GetState, + AudioSessionControl_GetDisplayName, + AudioSessionControl_SetDisplayName, + AudioSessionControl_GetIconPath, + AudioSessionControl_SetIconPath, + AudioSessionControl_GetGroupingParam, + AudioSessionControl_SetGroupingParam, + AudioSessionControl_RegisterAudioSessionNotification, + AudioSessionControl_UnregisterAudioSessionNotification, + AudioSessionControl_GetSessionIdentifier, + AudioSessionControl_GetSessionInstanceIdentifier, + AudioSessionControl_GetProcessId, + AudioSessionControl_IsSystemSoundsSession, + AudioSessionControl_SetDuckingPreference +}; + +static HRESULT WINAPI SimpleAudioVolume_QueryInterface( + ISimpleAudioVolume *iface, REFIID riid, void **ppv) +{ + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + if(IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_ISimpleAudioVolume)) + *ppv = iface; + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI SimpleAudioVolume_AddRef(ISimpleAudioVolume *iface) +{ + AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface); + return AudioSessionControl_AddRef(&This->IAudioSessionControl2_iface); +} + +static ULONG WINAPI SimpleAudioVolume_Release(ISimpleAudioVolume *iface) +{ + AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface); + return AudioSessionControl_Release(&This->IAudioSessionControl2_iface); +} + +static HRESULT WINAPI SimpleAudioVolume_SetMasterVolume( + ISimpleAudioVolume *iface, float level, const GUID *context) +{ + AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface); + AudioSession *session = This->session; + ACImpl *client; + + TRACE("(%p)->(%f, %s)\n", session, level, wine_dbgstr_guid(context)); + + if(level < 0.f || level > 1.f) + return E_INVALIDARG; + + if(context) + FIXME("Notifications not supported yet\n"); + + EnterCriticalSection(&g_sessions_lock); + + session->master_vol = level; + + TRACE("OSS doesn't support setting volume\n"); + LIST_FOR_EACH_ENTRY(client, &session->clients, ACImpl, entry) + set_stream_volumes(client); + + LeaveCriticalSection(&g_sessions_lock); + + return S_OK; +} + +static HRESULT WINAPI SimpleAudioVolume_GetMasterVolume( + ISimpleAudioVolume *iface, float *level) +{ + AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface); + AudioSession *session = This->session; + + TRACE("(%p)->(%p)\n", session, level); + + if(!level) + return NULL_PTR_ERR; + + *level = session->master_vol; + + return S_OK; +} + +static HRESULT WINAPI SimpleAudioVolume_SetMute(ISimpleAudioVolume *iface, + BOOL mute, const GUID *context) +{ + AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface); + AudioSession *session = This->session; + ACImpl *client; + + TRACE("(%p)->(%u, %s)\n", session, mute, debugstr_guid(context)); + + EnterCriticalSection(&g_sessions_lock); + + session->mute = mute; + + LIST_FOR_EACH_ENTRY(client, &session->clients, ACImpl, entry) + set_stream_volumes(client); + + LeaveCriticalSection(&g_sessions_lock); + + return S_OK; +} + +static HRESULT WINAPI SimpleAudioVolume_GetMute(ISimpleAudioVolume *iface, + BOOL *mute) +{ + AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface); + AudioSession *session = This->session; + + TRACE("(%p)->(%p)\n", session, mute); + + if(!mute) + return NULL_PTR_ERR; + + *mute = This->session->mute; + + return S_OK; +} + +static const ISimpleAudioVolumeVtbl SimpleAudioVolume_Vtbl = +{ + SimpleAudioVolume_QueryInterface, + SimpleAudioVolume_AddRef, + SimpleAudioVolume_Release, + SimpleAudioVolume_SetMasterVolume, + SimpleAudioVolume_GetMasterVolume, + SimpleAudioVolume_SetMute, + SimpleAudioVolume_GetMute +}; + +static HRESULT WINAPI AudioStreamVolume_QueryInterface( + IAudioStreamVolume *iface, REFIID riid, void **ppv) +{ + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + if(IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IAudioStreamVolume)) + *ppv = iface; + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioStreamVolume_AddRef(IAudioStreamVolume *iface) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + return IAudioClient3_AddRef(&This->IAudioClient3_iface); +} + +static ULONG WINAPI AudioStreamVolume_Release(IAudioStreamVolume *iface) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + return IAudioClient3_Release(&This->IAudioClient3_iface); +} + +static HRESULT WINAPI AudioStreamVolume_GetChannelCount( + IAudioStreamVolume *iface, UINT32 *out) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + + TRACE("(%p)->(%p)\n", This, out); + + if(!out) + return E_POINTER; + + *out = This->channel_count; + + return S_OK; +} + +static HRESULT WINAPI AudioStreamVolume_SetChannelVolume( + IAudioStreamVolume *iface, UINT32 index, float level) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + + TRACE("(%p)->(%d, %f)\n", This, index, level); + + if(level < 0.f || level > 1.f) + return E_INVALIDARG; + + if(index >= This->channel_count) + return E_INVALIDARG; + + EnterCriticalSection(&g_sessions_lock); + + This->vols[index] = level; + + TRACE("OSS doesn't support setting volume\n"); + set_stream_volumes(This); + + LeaveCriticalSection(&g_sessions_lock); + + return S_OK; +} + +static HRESULT WINAPI AudioStreamVolume_GetChannelVolume( + IAudioStreamVolume *iface, UINT32 index, float *level) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + + TRACE("(%p)->(%d, %p)\n", This, index, level); + + if(!level) + return E_POINTER; + + if(index >= This->channel_count) + return E_INVALIDARG; + + *level = This->vols[index]; + + return S_OK; +} + +static HRESULT WINAPI AudioStreamVolume_SetAllVolumes( + IAudioStreamVolume *iface, UINT32 count, const float *levels) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + int i; + + TRACE("(%p)->(%d, %p)\n", This, count, levels); + + if(!levels) + return E_POINTER; + + if(count != This->channel_count) + return E_INVALIDARG; + + EnterCriticalSection(&g_sessions_lock); + + for(i = 0; i < count; ++i) + This->vols[i] = levels[i]; + + TRACE("OSS doesn't support setting volume\n"); + set_stream_volumes(This); + + LeaveCriticalSection(&g_sessions_lock); + + return S_OK; +} + +static HRESULT WINAPI AudioStreamVolume_GetAllVolumes( + IAudioStreamVolume *iface, UINT32 count, float *levels) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + int i; + + TRACE("(%p)->(%d, %p)\n", This, count, levels); + + if(!levels) + return E_POINTER; + + if(count != This->channel_count) + return E_INVALIDARG; + + EnterCriticalSection(&g_sessions_lock); + + for(i = 0; i < count; ++i) + levels[i] = This->vols[i]; + + LeaveCriticalSection(&g_sessions_lock); + + return S_OK; +} + +static const IAudioStreamVolumeVtbl AudioStreamVolume_Vtbl = +{ + AudioStreamVolume_QueryInterface, + AudioStreamVolume_AddRef, + AudioStreamVolume_Release, + AudioStreamVolume_GetChannelCount, + AudioStreamVolume_SetChannelVolume, + AudioStreamVolume_GetChannelVolume, + AudioStreamVolume_SetAllVolumes, + AudioStreamVolume_GetAllVolumes +}; + +static HRESULT WINAPI ChannelAudioVolume_QueryInterface( + IChannelAudioVolume *iface, REFIID riid, void **ppv) +{ + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + if(IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IChannelAudioVolume)) + *ppv = iface; + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI ChannelAudioVolume_AddRef(IChannelAudioVolume *iface) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + return AudioSessionControl_AddRef(&This->IAudioSessionControl2_iface); +} + +static ULONG WINAPI ChannelAudioVolume_Release(IChannelAudioVolume *iface) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + return AudioSessionControl_Release(&This->IAudioSessionControl2_iface); +} + +static HRESULT WINAPI ChannelAudioVolume_GetChannelCount( + IChannelAudioVolume *iface, UINT32 *out) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + AudioSession *session = This->session; + + TRACE("(%p)->(%p)\n", session, out); + + if(!out) + return NULL_PTR_ERR; + + *out = session->channel_count; + + return S_OK; +} + +static HRESULT WINAPI ChannelAudioVolume_SetChannelVolume( + IChannelAudioVolume *iface, UINT32 index, float level, + const GUID *context) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + AudioSession *session = This->session; + ACImpl *client; + + TRACE("(%p)->(%d, %f, %s)\n", session, index, level, + wine_dbgstr_guid(context)); + + if(level < 0.f || level > 1.f) + return E_INVALIDARG; + + if(index >= session->channel_count) + return E_INVALIDARG; + + if(context) + FIXME("Notifications not supported yet\n"); + + EnterCriticalSection(&g_sessions_lock); + + session->channel_vols[index] = level; + + TRACE("OSS doesn't support setting volume\n"); + LIST_FOR_EACH_ENTRY(client, &session->clients, ACImpl, entry) + set_stream_volumes(client); + + LeaveCriticalSection(&g_sessions_lock); + + return S_OK; +} + +static HRESULT WINAPI ChannelAudioVolume_GetChannelVolume( + IChannelAudioVolume *iface, UINT32 index, float *level) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + AudioSession *session = This->session; + + TRACE("(%p)->(%d, %p)\n", session, index, level); + + if(!level) + return NULL_PTR_ERR; + + if(index >= session->channel_count) + return E_INVALIDARG; + + *level = session->channel_vols[index]; + + return S_OK; +} + +static HRESULT WINAPI ChannelAudioVolume_SetAllVolumes( + IChannelAudioVolume *iface, UINT32 count, const float *levels, + const GUID *context) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + AudioSession *session = This->session; + ACImpl *client; + int i; + + TRACE("(%p)->(%d, %p, %s)\n", session, count, levels, + wine_dbgstr_guid(context)); + + if(!levels) + return NULL_PTR_ERR; + + if(count != session->channel_count) + return E_INVALIDARG; + + if(context) + FIXME("Notifications not supported yet\n"); + + EnterCriticalSection(&g_sessions_lock); + + for(i = 0; i < count; ++i) + session->channel_vols[i] = levels[i]; + + TRACE("OSS doesn't support setting volume\n"); + LIST_FOR_EACH_ENTRY(client, &session->clients, ACImpl, entry) + set_stream_volumes(client); + + LeaveCriticalSection(&g_sessions_lock); + + return S_OK; +} + +static HRESULT WINAPI ChannelAudioVolume_GetAllVolumes( + IChannelAudioVolume *iface, UINT32 count, float *levels) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + AudioSession *session = This->session; + int i; + + TRACE("(%p)->(%d, %p)\n", session, count, levels); + + if(!levels) + return NULL_PTR_ERR; + + if(count != session->channel_count) + return E_INVALIDARG; + + for(i = 0; i < count; ++i) + levels[i] = session->channel_vols[i]; + + return S_OK; +} + +static const IChannelAudioVolumeVtbl ChannelAudioVolume_Vtbl = +{ + ChannelAudioVolume_QueryInterface, + ChannelAudioVolume_AddRef, + ChannelAudioVolume_Release, + ChannelAudioVolume_GetChannelCount, + ChannelAudioVolume_SetChannelVolume, + ChannelAudioVolume_GetChannelVolume, + ChannelAudioVolume_SetAllVolumes, + ChannelAudioVolume_GetAllVolumes +}; + +static HRESULT WINAPI AudioSessionManager_QueryInterface(IAudioSessionManager2 *iface, + REFIID riid, void **ppv) +{ + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if(!ppv) + return E_POINTER; + *ppv = NULL; + + if(IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IAudioSessionManager) || + IsEqualIID(riid, &IID_IAudioSessionManager2)) + *ppv = iface; + if(*ppv){ + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioSessionManager_AddRef(IAudioSessionManager2 *iface) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + ULONG ref; + ref = InterlockedIncrement(&This->ref); + TRACE("(%p) Refcount now %lu\n", This, ref); + return ref; +} + +static ULONG WINAPI AudioSessionManager_Release(IAudioSessionManager2 *iface) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + ULONG ref; + ref = InterlockedDecrement(&This->ref); + TRACE("(%p) Refcount now %lu\n", This, ref); + if(!ref) + HeapFree(GetProcessHeap(), 0, This); + return ref; +} + +static HRESULT WINAPI AudioSessionManager_GetAudioSessionControl( + IAudioSessionManager2 *iface, const GUID *session_guid, DWORD flags, + IAudioSessionControl **out) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + AudioSession *session; + AudioSessionWrapper *wrapper; + HRESULT hr; + + TRACE("(%p)->(%s, %lx, %p)\n", This, debugstr_guid(session_guid), + flags, out); + + hr = get_audio_session(session_guid, This->device, 0, &session); + if(FAILED(hr)) + return hr; + + wrapper = AudioSessionWrapper_Create(NULL); + if(!wrapper) + return E_OUTOFMEMORY; + + wrapper->session = session; + + *out = (IAudioSessionControl*)&wrapper->IAudioSessionControl2_iface; + + return S_OK; +} + +static HRESULT WINAPI AudioSessionManager_GetSimpleAudioVolume( + IAudioSessionManager2 *iface, const GUID *session_guid, DWORD flags, + ISimpleAudioVolume **out) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + AudioSession *session; + AudioSessionWrapper *wrapper; + HRESULT hr; + + TRACE("(%p)->(%s, %lx, %p)\n", This, debugstr_guid(session_guid), + flags, out); + + hr = get_audio_session(session_guid, This->device, 0, &session); + if(FAILED(hr)) + return hr; + + wrapper = AudioSessionWrapper_Create(NULL); + if(!wrapper) + return E_OUTOFMEMORY; + + wrapper->session = session; + + *out = &wrapper->ISimpleAudioVolume_iface; + + return S_OK; +} + +static HRESULT WINAPI AudioSessionManager_GetSessionEnumerator( + IAudioSessionManager2 *iface, IAudioSessionEnumerator **out) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + FIXME("(%p)->(%p) - stub\n", This, out); + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionManager_RegisterSessionNotification( + IAudioSessionManager2 *iface, IAudioSessionNotification *notification) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + FIXME("(%p)->(%p) - stub\n", This, notification); + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionManager_UnregisterSessionNotification( + IAudioSessionManager2 *iface, IAudioSessionNotification *notification) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + FIXME("(%p)->(%p) - stub\n", This, notification); + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionManager_RegisterDuckNotification( + IAudioSessionManager2 *iface, const WCHAR *session_id, + IAudioVolumeDuckNotification *notification) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + FIXME("(%p)->(%p) - stub\n", This, notification); + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionManager_UnregisterDuckNotification( + IAudioSessionManager2 *iface, + IAudioVolumeDuckNotification *notification) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + FIXME("(%p)->(%p) - stub\n", This, notification); + return E_NOTIMPL; +} + +static const IAudioSessionManager2Vtbl AudioSessionManager2_Vtbl = +{ + AudioSessionManager_QueryInterface, + AudioSessionManager_AddRef, + AudioSessionManager_Release, + AudioSessionManager_GetAudioSessionControl, + AudioSessionManager_GetSimpleAudioVolume, + AudioSessionManager_GetSessionEnumerator, + AudioSessionManager_RegisterSessionNotification, + AudioSessionManager_UnregisterSessionNotification, + AudioSessionManager_RegisterDuckNotification, + AudioSessionManager_UnregisterDuckNotification +}; + +HRESULT WINAPI AUDDRV_GetAudioSessionManager(IMMDevice *device, + IAudioSessionManager2 **out) +{ + SessionMgr *This; + + This = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(SessionMgr)); + if(!This) + return E_OUTOFMEMORY; + + This->IAudioSessionManager2_iface.lpVtbl = &AudioSessionManager2_Vtbl; + This->device = device; + This->ref = 1; + + *out = &This->IAudioSessionManager2_iface; + + return S_OK; +} diff --git a/pkgs/osu-wine/audio-revert/wineoss.drv/oss.c b/pkgs/osu-wine/audio-revert/wineoss.drv/oss.c new file mode 100644 index 0000000..4bc8bc2 --- /dev/null +++ b/pkgs/osu-wine/audio-revert/wineoss.drv/oss.c @@ -0,0 +1,2055 @@ +/* + * OSS driver (unixlib) + * + * Copyright 2011 Andrew Eikum for CodeWeavers + * 2022 Huw Davies + * + * 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 + */ + +#if 0 +#pragma makedep unix +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "winternl.h" +#include "initguid.h" +#include "audioclient.h" +#include "mmddk.h" + +#include "wine/debug.h" +#include "wine/unixlib.h" + +#include "unixlib.h" + +struct oss_stream +{ + WAVEFORMATEX *fmt; + EDataFlow flow; + UINT flags; + AUDCLNT_SHAREMODE share; + HANDLE event; + + int fd; + + BOOL playing, mute, please_quit; + UINT64 written_frames, last_pos_frames; + UINT32 period_frames, bufsize_frames, held_frames, tmp_buffer_frames, in_oss_frames; + UINT32 oss_bufsize_bytes, lcl_offs_frames; /* offs into local_buffer where valid data starts */ + REFERENCE_TIME period; + + BYTE *local_buffer, *tmp_buffer; + INT32 getbuf_last; /* <0 when using tmp_buffer */ + + pthread_mutex_t lock; +}; + +WINE_DEFAULT_DEBUG_CHANNEL(oss); + +/* copied from kernelbase */ +static int muldiv( int a, int b, int c ) +{ + LONGLONG ret; + + if (!c) return -1; + + /* We want to deal with a positive divisor to simplify the logic. */ + if (c < 0) + { + a = -a; + c = -c; + } + + /* If the result is positive, we "add" to round. else, we subtract to round. */ + if ((a < 0 && b < 0) || (a >= 0 && b >= 0)) + ret = (((LONGLONG)a * b) + (c / 2)) / c; + else + ret = (((LONGLONG)a * b) - (c / 2)) / c; + + if (ret > 2147483647 || ret < -2147483647) return -1; + return ret; +} + +static void oss_lock(struct oss_stream *stream) +{ + pthread_mutex_lock(&stream->lock); +} + +static void oss_unlock(struct oss_stream *stream) +{ + pthread_mutex_unlock(&stream->lock); +} + +static NTSTATUS oss_unlock_result(struct oss_stream *stream, + HRESULT *result, HRESULT value) +{ + *result = value; + oss_unlock(stream); + return STATUS_SUCCESS; +} + +static struct oss_stream *handle_get_stream(stream_handle h) +{ + return (struct oss_stream *)(UINT_PTR)h; +} + +static NTSTATUS oss_test_connect(void *args) +{ + struct test_connect_params *params = args; + int mixer_fd; + oss_sysinfo sysinfo; + + /* Attempt to determine if we are running on OSS or ALSA's OSS + * compatibility layer. There is no official way to do that, so just check + * for validity as best as possible, without rejecting valid OSS + * implementations. */ + + mixer_fd = open("/dev/mixer", O_RDONLY, 0); + if(mixer_fd < 0){ + TRACE("Priority_Unavailable: open failed\n"); + params->priority = Priority_Unavailable; + return STATUS_SUCCESS; + } + + sysinfo.version[0] = 0xFF; + sysinfo.versionnum = ~0; + if(ioctl(mixer_fd, SNDCTL_SYSINFO, &sysinfo) < 0){ + TRACE("Priority_Unavailable: ioctl failed\n"); + close(mixer_fd); + params->priority = Priority_Unavailable; + return STATUS_SUCCESS; + } + + close(mixer_fd); + + if(sysinfo.version[0] < '4' || sysinfo.version[0] > '9'){ + TRACE("Priority_Low: sysinfo.version[0]: %x\n", sysinfo.version[0]); + params->priority = Priority_Low; + return STATUS_SUCCESS; + } + if(sysinfo.versionnum & 0x80000000){ + TRACE("Priority_Low: sysinfo.versionnum: %x\n", sysinfo.versionnum); + params->priority = Priority_Low; + return STATUS_SUCCESS; + } + + TRACE("Priority_Preferred: Seems like valid OSS!\n"); + + params->priority = Priority_Preferred; + return STATUS_SUCCESS; +} + +/* dst must be large enough to hold devnode */ +static void oss_clean_devnode(char *dest, const char *devnode) +{ + const char *dot, *slash; + size_t len; + + strcpy(dest, devnode); + dot = strrchr(dest, '.'); + if(!dot) + return; + + slash = strrchr(dest, '/'); + if(slash && dot < slash) + return; + + len = dot - dest; + dest[len] = '\0'; +} + +static int open_device(const char *device, EDataFlow flow) +{ + int flags = ((flow == eRender) ? O_WRONLY : O_RDONLY) | O_NONBLOCK; + + return open(device, flags, 0); +} + +static void get_default_device(EDataFlow flow, char device[OSS_DEVNODE_SIZE]) +{ + int fd, err; + oss_audioinfo ai; + + device[0] = '\0'; + fd = open_device("/dev/dsp", flow); + if(fd < 0){ + WARN("Couldn't open default device!\n"); + return; + } + + ai.dev = -1; + if((err = ioctl(fd, SNDCTL_ENGINEINFO, &ai)) < 0){ + WARN("SNDCTL_ENGINEINFO failed: %d (%s)\n", err, strerror(errno)); + close(fd); + return; + } + close(fd); + + TRACE("Default devnode: %s\n", ai.devnode); + oss_clean_devnode(device, ai.devnode); + return; +} + +static NTSTATUS oss_get_endpoint_ids(void *args) +{ + struct get_endpoint_ids_params *params = args; + oss_sysinfo sysinfo; + oss_audioinfo ai; + static int print_once = 0; + static const WCHAR outW[] = {'O','u','t',':',' ',0}; + static const WCHAR inW[] = {'I','n',':',' ',0}; + struct endpoint_info + { + WCHAR name[ARRAY_SIZE(ai.name) + ARRAY_SIZE(outW)]; + char device[OSS_DEVNODE_SIZE]; + } *info; + unsigned int i, j, num, needed, name_len, device_len, offset, default_idx = 0; + char default_device[OSS_DEVNODE_SIZE]; + struct endpoint *endpoint; + int mixer_fd; + + mixer_fd = open("/dev/mixer", O_RDONLY, 0); + if(mixer_fd < 0){ + ERR("OSS /dev/mixer doesn't seem to exist\n"); + params->result = AUDCLNT_E_SERVICE_NOT_RUNNING; + return STATUS_SUCCESS; + } + + if(ioctl(mixer_fd, SNDCTL_SYSINFO, &sysinfo) < 0){ + close(mixer_fd); + if(errno == EINVAL){ + ERR("OSS version too old, need at least OSSv4\n"); + params->result = AUDCLNT_E_SERVICE_NOT_RUNNING; + return STATUS_SUCCESS; + } + + ERR("Error getting SNDCTL_SYSINFO: %d (%s)\n", errno, strerror(errno)); + params->result = E_FAIL; + return STATUS_SUCCESS; + } + + if(!print_once){ + TRACE("OSS sysinfo:\n"); + TRACE("product: %s\n", sysinfo.product); + TRACE("version: %s\n", sysinfo.version); + TRACE("versionnum: %x\n", sysinfo.versionnum); + TRACE("numaudios: %d\n", sysinfo.numaudios); + TRACE("nummixers: %d\n", sysinfo.nummixers); + TRACE("numcards: %d\n", sysinfo.numcards); + TRACE("numaudioengines: %d\n", sysinfo.numaudioengines); + print_once = 1; + } + + if(sysinfo.numaudios <= 0){ + WARN("No audio devices!\n"); + close(mixer_fd); + params->result = AUDCLNT_E_SERVICE_NOT_RUNNING; + return STATUS_SUCCESS; + } + + info = malloc(sysinfo.numaudios * sizeof(*info)); + if(!info){ + close(mixer_fd); + params->result = E_OUTOFMEMORY; + return STATUS_SUCCESS; + } + + get_default_device(params->flow, default_device); + + num = 0; + for(i = 0; i < sysinfo.numaudios; ++i){ + char devnode[OSS_DEVNODE_SIZE]; + int fd, prefix_len; + const WCHAR *prefix; + + memset(&ai, 0, sizeof(ai)); + ai.dev = i; + if(ioctl(mixer_fd, SNDCTL_AUDIOINFO, &ai) < 0){ + WARN("Error getting AUDIOINFO for dev %d: %d (%s)\n", i, errno, + strerror(errno)); + continue; + } + + oss_clean_devnode(devnode, ai.devnode); + + /* check for duplicates */ + for(j = 0; j < num; j++) + if(!strcmp(devnode, info[j].device)) + break; + if(j < num) + continue; + + fd = open_device(devnode, params->flow); + if(fd < 0){ + WARN("Opening device \"%s\" failed, pretending it doesn't exist: %d (%s)\n", + devnode, errno, strerror(errno)); + continue; + } + close(fd); + + if((params->flow == eCapture && !(ai.caps & PCM_CAP_INPUT)) || + (params->flow == eRender && !(ai.caps & PCM_CAP_OUTPUT))) + continue; + + strcpy(info[num].device, devnode); + + if(params->flow == eRender){ + prefix = outW; + prefix_len = ARRAY_SIZE(outW) - 1; + }else{ + prefix = inW; + prefix_len = ARRAY_SIZE(inW) - 1; + } + memcpy(info[num].name, prefix, prefix_len * sizeof(WCHAR)); + ntdll_umbstowcs(ai.name, strlen(ai.name) + 1, info[num].name + prefix_len, + ARRAY_SIZE(info[num].name) - prefix_len); + if(!strcmp(default_device, info[num].device)) + default_idx = num; + num++; + } + close(mixer_fd); + + offset = needed = num * sizeof(*params->endpoints); + endpoint = params->endpoints; + + for(i = 0; i < num; i++){ + name_len = wcslen(info[i].name) + 1; + device_len = strlen(info[i].device) + 1; + needed += name_len * sizeof(WCHAR) + ((device_len + 1) & ~1); + + if(needed <= params->size){ + endpoint->name = offset; + memcpy((char *)params->endpoints + offset, info[i].name, name_len * sizeof(WCHAR)); + offset += name_len * sizeof(WCHAR); + endpoint->device = offset; + memcpy((char *)params->endpoints + offset, info[i].device, device_len); + offset += (device_len + 1) & ~1; + endpoint++; + } + } + free(info); + + params->num = num; + params->default_idx = default_idx; + + if(needed > params->size){ + params->size = needed; + params->result = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + } else + params->result = S_OK; + + return STATUS_SUCCESS; +} + +static UINT get_channel_mask(unsigned int channels) +{ + switch(channels){ + case 0: + return 0; + case 1: + return KSAUDIO_SPEAKER_MONO; + case 2: + return KSAUDIO_SPEAKER_STEREO; + case 3: + return KSAUDIO_SPEAKER_STEREO | SPEAKER_LOW_FREQUENCY; + case 4: + return KSAUDIO_SPEAKER_QUAD; /* not _SURROUND */ + case 5: + return KSAUDIO_SPEAKER_QUAD | SPEAKER_LOW_FREQUENCY; + case 6: + return KSAUDIO_SPEAKER_5POINT1; /* not 5POINT1_SURROUND */ + case 7: + return KSAUDIO_SPEAKER_5POINT1 | SPEAKER_BACK_CENTER; + case 8: + return KSAUDIO_SPEAKER_7POINT1_SURROUND; /* Vista deprecates 7POINT1 */ + } + FIXME("Unknown speaker configuration: %u\n", channels); + return 0; +} + +static int get_oss_format(const WAVEFORMATEX *fmt) +{ + WAVEFORMATEXTENSIBLE *fmtex = (WAVEFORMATEXTENSIBLE*)fmt; + + if(fmt->wFormatTag == WAVE_FORMAT_PCM || + (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + IsEqualGUID(&fmtex->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM))){ + switch(fmt->wBitsPerSample){ + case 8: + return AFMT_U8; + case 16: + return AFMT_S16_LE; + case 24: + return AFMT_S24_LE; + case 32: + return AFMT_S32_LE; + } + return -1; + } + +#ifdef AFMT_FLOAT + if(fmt->wFormatTag == WAVE_FORMAT_IEEE_FLOAT || + (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + IsEqualGUID(&fmtex->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))){ + if(fmt->wBitsPerSample != 32) + return -1; + + return AFMT_FLOAT; + } +#endif + + return -1; +} + +static WAVEFORMATEXTENSIBLE *clone_format(const WAVEFORMATEX *fmt) +{ + WAVEFORMATEXTENSIBLE *ret; + size_t size; + + if(fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE) + size = sizeof(WAVEFORMATEXTENSIBLE); + else + size = sizeof(WAVEFORMATEX); + + ret = malloc(size); + if(!ret) + return NULL; + + memcpy(ret, fmt, size); + + ret->Format.cbSize = size - sizeof(WAVEFORMATEX); + + return ret; +} + +static HRESULT setup_oss_device(AUDCLNT_SHAREMODE share, int fd, + const WAVEFORMATEX *fmt, WAVEFORMATEXTENSIBLE *out) +{ + const WAVEFORMATEXTENSIBLE *fmtex = (const WAVEFORMATEXTENSIBLE *)fmt; + int tmp, oss_format; + double tenth; + HRESULT ret = S_OK; + WAVEFORMATEXTENSIBLE *closest; + + tmp = oss_format = get_oss_format(fmt); + if(oss_format < 0) + return AUDCLNT_E_UNSUPPORTED_FORMAT; + if(ioctl(fd, SNDCTL_DSP_SETFMT, &tmp) < 0){ + WARN("SETFMT failed: %d (%s)\n", errno, strerror(errno)); + return E_FAIL; + } + if(tmp != oss_format){ + TRACE("Format unsupported by this OSS version: %x\n", oss_format); + return AUDCLNT_E_UNSUPPORTED_FORMAT; + } + + if(fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + (fmtex->Format.nAvgBytesPerSec == 0 || + fmtex->Format.nBlockAlign == 0 || + fmtex->Samples.wValidBitsPerSample > fmtex->Format.wBitsPerSample)) + return E_INVALIDARG; + + if(fmt->nChannels == 0) + return AUDCLNT_E_UNSUPPORTED_FORMAT; + + closest = clone_format(fmt); + if(!closest) + return E_OUTOFMEMORY; + + tmp = fmt->nSamplesPerSec; + if(ioctl(fd, SNDCTL_DSP_SPEED, &tmp) < 0){ + WARN("SPEED failed: %d (%s)\n", errno, strerror(errno)); + free(closest); + return E_FAIL; + } + tenth = fmt->nSamplesPerSec * 0.1; + if(tmp > fmt->nSamplesPerSec + tenth || tmp < fmt->nSamplesPerSec - tenth){ + ret = S_FALSE; + closest->Format.nSamplesPerSec = tmp; + } + + tmp = fmt->nChannels; + if(ioctl(fd, SNDCTL_DSP_CHANNELS, &tmp) < 0){ + WARN("CHANNELS failed: %d (%s)\n", errno, strerror(errno)); + free(closest); + return E_FAIL; + } + if(tmp != fmt->nChannels){ + ret = S_FALSE; + closest->Format.nChannels = tmp; + } + + if(closest->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) + closest->dwChannelMask = get_channel_mask(closest->Format.nChannels); + + if(fmt->nBlockAlign != fmt->nChannels * fmt->wBitsPerSample / 8 || + fmt->nAvgBytesPerSec != fmt->nBlockAlign * fmt->nSamplesPerSec || + (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + fmtex->Samples.wValidBitsPerSample < fmtex->Format.wBitsPerSample)) + ret = S_FALSE; + + if(share == AUDCLNT_SHAREMODE_EXCLUSIVE && + fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE){ + if(fmtex->dwChannelMask == 0 || fmtex->dwChannelMask & SPEAKER_RESERVED) + ret = S_FALSE; + } + + if(ret == S_FALSE && !out) + ret = AUDCLNT_E_UNSUPPORTED_FORMAT; + + if(ret == S_FALSE && out){ + closest->Format.nBlockAlign = + closest->Format.nChannels * closest->Format.wBitsPerSample / 8; + closest->Format.nAvgBytesPerSec = + closest->Format.nBlockAlign * closest->Format.nSamplesPerSec; + if(closest->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) + closest->Samples.wValidBitsPerSample = closest->Format.wBitsPerSample; + memcpy(out, closest, closest->Format.cbSize + sizeof(WAVEFORMATEX)); + } + free(closest); + + TRACE("returning: %08x\n", (unsigned)ret); + return ret; +} + +static ULONG_PTR zero_bits(void) +{ +#ifdef _WIN64 + return !NtCurrentTeb()->WowTebOffset ? 0 : 0x7fffffff; +#else + return 0; +#endif +} + +static NTSTATUS oss_create_stream(void *args) +{ + struct create_stream_params *params = args; + WAVEFORMATEXTENSIBLE *fmtex; + struct oss_stream *stream; + oss_audioinfo ai; + SIZE_T size; + + stream = calloc(1, sizeof(*stream)); + if(!stream){ + params->result = E_OUTOFMEMORY; + return STATUS_SUCCESS; + } + + stream->flow = params->flow; + pthread_mutex_init(&stream->lock, NULL); + + stream->fd = open_device(params->device, params->flow); + if(stream->fd < 0){ + WARN("Unable to open device %s: %d (%s)\n", params->device, errno, strerror(errno)); + params->result = AUDCLNT_E_DEVICE_INVALIDATED; + goto exit; + } + + ai.dev = -1; + if(ioctl(stream->fd, SNDCTL_ENGINEINFO, &ai) < 0){ + WARN("Unable to get audio info for device %s: %d (%s)\n", params->device, errno, strerror(errno)); + params->result = E_FAIL; + goto exit; + } + + TRACE("OSS audioinfo:\n"); + TRACE("devnode: %s\n", ai.devnode); + TRACE("name: %s\n", ai.name); + TRACE("busy: %x\n", ai.busy); + TRACE("caps: %x\n", ai.caps); + TRACE("iformats: %x\n", ai.iformats); + TRACE("oformats: %x\n", ai.oformats); + TRACE("enabled: %d\n", ai.enabled); + TRACE("min_rate: %d\n", ai.min_rate); + TRACE("max_rate: %d\n", ai.max_rate); + TRACE("min_channels: %d\n", ai.min_channels); + TRACE("max_channels: %d\n", ai.max_channels); + + params->result = setup_oss_device(params->share, stream->fd, params->fmt, NULL); + if(FAILED(params->result)) + goto exit; + + fmtex = clone_format(params->fmt); + if(!fmtex){ + params->result = E_OUTOFMEMORY; + goto exit; + } + stream->fmt = &fmtex->Format; + + stream->period = params->period; + stream->period_frames = muldiv(params->fmt->nSamplesPerSec, params->period, 10000000); + + stream->bufsize_frames = muldiv(params->duration, params->fmt->nSamplesPerSec, 10000000); + if(params->share == AUDCLNT_SHAREMODE_EXCLUSIVE) + stream->bufsize_frames -= stream->bufsize_frames % stream->period_frames; + size = stream->bufsize_frames * params->fmt->nBlockAlign; + if(NtAllocateVirtualMemory(GetCurrentProcess(), (void **)&stream->local_buffer, zero_bits(), + &size, MEM_COMMIT, PAGE_READWRITE)){ + params->result = E_OUTOFMEMORY; + goto exit; + } + + stream->share = params->share; + stream->flags = params->flags; + stream->oss_bufsize_bytes = 0; + +exit: + if(FAILED(params->result)){ + if(stream->fd >= 0) close(stream->fd); + if(stream->local_buffer){ + size = 0; + NtFreeVirtualMemory(GetCurrentProcess(), (void **)&stream->local_buffer, &size, MEM_RELEASE); + } + pthread_mutex_destroy(&stream->lock); + free(stream->fmt); + free(stream); + }else{ + *params->stream = (stream_handle)(UINT_PTR)stream; + } + + return STATUS_SUCCESS; +} + +static NTSTATUS oss_release_stream(void *args) +{ + struct release_stream_params *params = args; + struct oss_stream *stream = handle_get_stream(params->stream); + SIZE_T size; + + if(params->timer_thread){ + stream->please_quit = TRUE; + NtWaitForSingleObject(params->timer_thread, FALSE, NULL); + NtClose(params->timer_thread); + } + + close(stream->fd); + if(stream->local_buffer){ + size = 0; + NtFreeVirtualMemory(GetCurrentProcess(), (void **)&stream->local_buffer, &size, MEM_RELEASE); + } + if(stream->tmp_buffer){ + size = 0; + NtFreeVirtualMemory(GetCurrentProcess(), (void **)&stream->tmp_buffer, &size, MEM_RELEASE); + } + free(stream->fmt); + pthread_mutex_destroy(&stream->lock); + free(stream); + + params->result = S_OK; + return STATUS_SUCCESS; +} + +static NTSTATUS oss_start(void *args) +{ + struct start_params *params = args; + struct oss_stream *stream = handle_get_stream(params->stream); + + oss_lock(stream); + + if((stream->flags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK) && !stream->event) + return oss_unlock_result(stream, ¶ms->result, AUDCLNT_E_EVENTHANDLE_NOT_SET); + + if(stream->playing) + return oss_unlock_result(stream, ¶ms->result, AUDCLNT_E_NOT_STOPPED); + + stream->playing = TRUE; + + return oss_unlock_result(stream, ¶ms->result, S_OK); +} + +static NTSTATUS oss_stop(void *args) +{ + struct stop_params *params = args; + struct oss_stream *stream = handle_get_stream(params->stream); + + oss_lock(stream); + + if(!stream->playing) + return oss_unlock_result(stream, ¶ms->result, S_FALSE); + + stream->playing = FALSE; + stream->in_oss_frames = 0; + + return oss_unlock_result(stream, ¶ms->result, S_OK); +} + +static NTSTATUS oss_reset(void *args) +{ + struct reset_params *params = args; + struct oss_stream *stream = handle_get_stream(params->stream); + + oss_lock(stream); + + if(stream->playing) + return oss_unlock_result(stream, ¶ms->result, AUDCLNT_E_NOT_STOPPED); + + if(stream->getbuf_last) + return oss_unlock_result(stream, ¶ms->result, AUDCLNT_E_BUFFER_OPERATION_PENDING); + + if(stream->flow == eRender){ + stream->written_frames = 0; + stream->last_pos_frames = 0; + }else{ + stream->written_frames += stream->held_frames; + } + stream->held_frames = 0; + stream->lcl_offs_frames = 0; + stream->in_oss_frames = 0; + + return oss_unlock_result(stream, ¶ms->result, S_OK); +} + +static void silence_buffer(struct oss_stream *stream, BYTE *buffer, UINT32 frames) +{ + WAVEFORMATEXTENSIBLE *fmtex = (WAVEFORMATEXTENSIBLE*)stream->fmt; + if((stream->fmt->wFormatTag == WAVE_FORMAT_PCM || + (stream->fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + IsEqualGUID(&fmtex->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM))) && + stream->fmt->wBitsPerSample == 8) + memset(buffer, 128, frames * stream->fmt->nBlockAlign); + else + memset(buffer, 0, frames * stream->fmt->nBlockAlign); +} + +static void oss_write_data(struct oss_stream *stream) +{ + ssize_t written_bytes; + UINT32 written_frames, in_oss_frames, write_limit, max_period, write_offs_frames, new_frames; + SIZE_T to_write_frames, to_write_bytes, advanced; + audio_buf_info bi; + BYTE *buf; + + if(ioctl(stream->fd, SNDCTL_DSP_GETOSPACE, &bi) < 0){ + WARN("GETOSPACE failed: %d (%s)\n", errno, strerror(errno)); + return; + } + + max_period = max(bi.fragsize / stream->fmt->nBlockAlign, stream->period_frames); + + if(bi.bytes > stream->oss_bufsize_bytes){ + TRACE("New buffer size (%u) is larger than old buffer size (%u)\n", + bi.bytes, stream->oss_bufsize_bytes); + stream->oss_bufsize_bytes = bi.bytes; + in_oss_frames = 0; + }else + in_oss_frames = (stream->oss_bufsize_bytes - bi.bytes) / stream->fmt->nBlockAlign; + + if(in_oss_frames > stream->in_oss_frames){ + TRACE("Capping reported frames from %u to %u\n", + in_oss_frames, stream->in_oss_frames); + in_oss_frames = stream->in_oss_frames; + } + + write_limit = 0; + while(write_limit + in_oss_frames < max_period * 3) + write_limit += max_period; + if(write_limit == 0) + return; + + /* vvvvvv - in_oss_frames + * [--xxxxxxxxxx] + * [xxxxxxxxxx--] + * ^^^^^^^^^^ - held_frames + * ^ - lcl_offs_frames + */ + advanced = stream->in_oss_frames - in_oss_frames; + if(advanced > stream->held_frames) + advanced = stream->held_frames; + stream->lcl_offs_frames += advanced; + stream->lcl_offs_frames %= stream->bufsize_frames; + stream->held_frames -= advanced; + stream->in_oss_frames = in_oss_frames; + TRACE("advanced by %lu, lcl_offs: %u, held: %u, in_oss: %u\n", + advanced, stream->lcl_offs_frames, stream->held_frames, stream->in_oss_frames); + + + if(stream->held_frames == stream->in_oss_frames) + return; + + write_offs_frames = (stream->lcl_offs_frames + stream->in_oss_frames) % stream->bufsize_frames; + new_frames = stream->held_frames - stream->in_oss_frames; + + if(write_offs_frames + new_frames > stream->bufsize_frames) + to_write_frames = stream->bufsize_frames - write_offs_frames; + else + to_write_frames = new_frames; + + to_write_frames = min(to_write_frames, write_limit); + to_write_bytes = to_write_frames * stream->fmt->nBlockAlign; + TRACE("going to write %lu frames from %u (%lu of %u)\n", to_write_frames, + write_offs_frames, to_write_frames + write_offs_frames, + stream->bufsize_frames); + + buf = stream->local_buffer + write_offs_frames * stream->fmt->nBlockAlign; + + if(stream->mute) + silence_buffer(stream, buf, to_write_frames); + + written_bytes = write(stream->fd, buf, to_write_bytes); + if(written_bytes < 0){ + /* EAGAIN is OSS buffer full, log that too */ + WARN("write failed: %d (%s)\n", errno, strerror(errno)); + return; + } + written_frames = written_bytes / stream->fmt->nBlockAlign; + + stream->in_oss_frames += written_frames; + + if(written_frames < to_write_frames){ + /* OSS buffer probably full */ + return; + } + + if(new_frames > written_frames && written_frames < write_limit){ + /* wrapped and have some data back at the start to write */ + + to_write_frames = min(write_limit - written_frames, new_frames - written_frames); + to_write_bytes = to_write_frames * stream->fmt->nBlockAlign; + + if(stream->mute) + silence_buffer(stream, stream->local_buffer, to_write_frames); + + TRACE("wrapping to write %lu frames from beginning\n", to_write_frames); + + written_bytes = write(stream->fd, stream->local_buffer, to_write_bytes); + if(written_bytes < 0){ + WARN("write failed: %d (%s)\n", errno, strerror(errno)); + return; + } + written_frames = written_bytes / stream->fmt->nBlockAlign; + stream->in_oss_frames += written_frames; + } +} + +static void oss_read_data(struct oss_stream *stream) +{ + UINT64 pos, readable; + ssize_t nread; + + pos = (stream->held_frames + stream->lcl_offs_frames) % stream->bufsize_frames; + readable = (stream->bufsize_frames - pos) * stream->fmt->nBlockAlign; + + nread = read(stream->fd, stream->local_buffer + pos * stream->fmt->nBlockAlign, + readable); + if(nread < 0){ + WARN("read failed: %d (%s)\n", errno, strerror(errno)); + return; + } + + stream->held_frames += nread / stream->fmt->nBlockAlign; + + if(stream->held_frames > stream->bufsize_frames){ + WARN("Overflow of unread data\n"); + stream->lcl_offs_frames += stream->held_frames; + stream->lcl_offs_frames %= stream->bufsize_frames; + stream->held_frames = stream->bufsize_frames; + } +} + +static NTSTATUS oss_timer_loop(void *args) +{ + struct timer_loop_params *params = args; + struct oss_stream *stream = handle_get_stream(params->stream); + LARGE_INTEGER delay, now, next; + int adjust; + + oss_lock(stream); + + delay.QuadPart = -stream->period; + NtQueryPerformanceCounter(&now, NULL); + next.QuadPart = now.QuadPart + stream->period; + + while(!stream->please_quit){ + if(stream->playing){ + if(stream->flow == eRender && stream->held_frames) + oss_write_data(stream); + else if(stream->flow == eCapture) + oss_read_data(stream); + } + if(stream->event) + NtSetEvent(stream->event, NULL); + oss_unlock(stream); + + NtDelayExecution(FALSE, &delay); + + oss_lock(stream); + NtQueryPerformanceCounter(&now, NULL); + adjust = next.QuadPart - now.QuadPart; + if(adjust > stream->period / 2) + adjust = stream->period / 2; + else if(adjust < -stream->period / 2) + adjust = -stream->period / 2; + delay.QuadPart = -(stream->period + adjust); + next.QuadPart += stream->period; + } + + oss_unlock(stream); + + return STATUS_SUCCESS; +} + +static NTSTATUS oss_get_render_buffer(void *args) +{ + struct get_render_buffer_params *params = args; + struct oss_stream *stream = handle_get_stream(params->stream); + UINT32 write_pos, frames = params->frames; + BYTE **data = params->data; + SIZE_T size; + + oss_lock(stream); + + if(stream->getbuf_last) + return oss_unlock_result(stream, ¶ms->result, AUDCLNT_E_OUT_OF_ORDER); + + if(!frames) + return oss_unlock_result(stream, ¶ms->result, S_OK); + + if(stream->held_frames + frames > stream->bufsize_frames) + return oss_unlock_result(stream, ¶ms->result, AUDCLNT_E_BUFFER_TOO_LARGE); + + write_pos = + (stream->lcl_offs_frames + stream->held_frames) % stream->bufsize_frames; + if(write_pos + frames > stream->bufsize_frames){ + if(stream->tmp_buffer_frames < frames){ + if(stream->tmp_buffer){ + size = 0; + NtFreeVirtualMemory(GetCurrentProcess(), (void **)&stream->tmp_buffer, &size, MEM_RELEASE); + stream->tmp_buffer = NULL; + } + size = frames * stream->fmt->nBlockAlign; + if(NtAllocateVirtualMemory(GetCurrentProcess(), (void **)&stream->tmp_buffer, zero_bits(), + &size, MEM_COMMIT, PAGE_READWRITE)){ + stream->tmp_buffer_frames = 0; + return oss_unlock_result(stream, ¶ms->result, E_OUTOFMEMORY); + } + stream->tmp_buffer_frames = frames; + } + *data = stream->tmp_buffer; + stream->getbuf_last = -frames; + }else{ + *data = stream->local_buffer + write_pos * stream->fmt->nBlockAlign; + stream->getbuf_last = frames; + } + + silence_buffer(stream, *data, frames); + + return oss_unlock_result(stream, ¶ms->result, S_OK); +} + +static void oss_wrap_buffer(struct oss_stream *stream, BYTE *buffer, UINT32 written_frames) +{ + UINT32 write_offs_frames = + (stream->lcl_offs_frames + stream->held_frames) % stream->bufsize_frames; + UINT32 write_offs_bytes = write_offs_frames * stream->fmt->nBlockAlign; + UINT32 chunk_frames = stream->bufsize_frames - write_offs_frames; + UINT32 chunk_bytes = chunk_frames * stream->fmt->nBlockAlign; + UINT32 written_bytes = written_frames * stream->fmt->nBlockAlign; + + if(written_bytes <= chunk_bytes){ + memcpy(stream->local_buffer + write_offs_bytes, buffer, written_bytes); + }else{ + memcpy(stream->local_buffer + write_offs_bytes, buffer, chunk_bytes); + memcpy(stream->local_buffer, buffer + chunk_bytes, + written_bytes - chunk_bytes); + } +} + +static NTSTATUS oss_release_render_buffer(void *args) +{ + struct release_render_buffer_params *params = args; + struct oss_stream *stream = handle_get_stream(params->stream); + UINT32 written_frames = params->written_frames; + UINT flags = params->flags; + BYTE *buffer; + + oss_lock(stream); + + if(!written_frames){ + stream->getbuf_last = 0; + return oss_unlock_result(stream, ¶ms->result, S_OK); + } + + if(!stream->getbuf_last) + return oss_unlock_result(stream, ¶ms->result, AUDCLNT_E_OUT_OF_ORDER); + + if(written_frames > (stream->getbuf_last >= 0 ? stream->getbuf_last : -stream->getbuf_last)) + return oss_unlock_result(stream, ¶ms->result, AUDCLNT_E_INVALID_SIZE); + + if(stream->getbuf_last >= 0) + buffer = stream->local_buffer + stream->fmt->nBlockAlign * + ((stream->lcl_offs_frames + stream->held_frames) % stream->bufsize_frames); + else + buffer = stream->tmp_buffer; + + if(flags & AUDCLNT_BUFFERFLAGS_SILENT) + silence_buffer(stream, buffer, written_frames); + + if(stream->getbuf_last < 0) + oss_wrap_buffer(stream, buffer, written_frames); + + stream->held_frames += written_frames; + stream->written_frames += written_frames; + stream->getbuf_last = 0; + + return oss_unlock_result(stream, ¶ms->result, S_OK); +} + +static NTSTATUS oss_get_capture_buffer(void *args) +{ + struct get_capture_buffer_params *params = args; + struct oss_stream *stream = handle_get_stream(params->stream); + UINT64 *devpos = params->devpos, *qpcpos = params->qpcpos; + UINT32 *frames = params->frames; + UINT *flags = params->flags; + BYTE **data = params->data; + SIZE_T size; + + oss_lock(stream); + + if(stream->getbuf_last) + return oss_unlock_result(stream, ¶ms->result, AUDCLNT_E_OUT_OF_ORDER); + + if(stream->held_frames < stream->period_frames){ + *frames = 0; + return oss_unlock_result(stream, ¶ms->result, AUDCLNT_S_BUFFER_EMPTY); + } + + *flags = 0; + + *frames = stream->period_frames; + + if(stream->lcl_offs_frames + *frames > stream->bufsize_frames){ + UINT32 chunk_bytes, offs_bytes, frames_bytes; + if(stream->tmp_buffer_frames < *frames){ + if(stream->tmp_buffer){ + size = 0; + NtFreeVirtualMemory(GetCurrentProcess(), (void **)&stream->tmp_buffer, &size, MEM_RELEASE); + stream->tmp_buffer = NULL; + } + size = *frames * stream->fmt->nBlockAlign; + if(NtAllocateVirtualMemory(GetCurrentProcess(), (void **)&stream->tmp_buffer, zero_bits(), + &size, MEM_COMMIT, PAGE_READWRITE)){ + stream->tmp_buffer_frames = 0; + return oss_unlock_result(stream, ¶ms->result, E_OUTOFMEMORY); + } + stream->tmp_buffer_frames = *frames; + } + + *data = stream->tmp_buffer; + chunk_bytes = (stream->bufsize_frames - stream->lcl_offs_frames) * + stream->fmt->nBlockAlign; + offs_bytes = stream->lcl_offs_frames * stream->fmt->nBlockAlign; + frames_bytes = *frames * stream->fmt->nBlockAlign; + memcpy(stream->tmp_buffer, stream->local_buffer + offs_bytes, chunk_bytes); + memcpy(stream->tmp_buffer + chunk_bytes, stream->local_buffer, + frames_bytes - chunk_bytes); + }else + *data = stream->local_buffer + + stream->lcl_offs_frames * stream->fmt->nBlockAlign; + + stream->getbuf_last = *frames; + + if(devpos) + *devpos = stream->written_frames; + if(qpcpos){ + LARGE_INTEGER stamp, freq; + NtQueryPerformanceCounter(&stamp, &freq); + *qpcpos = (stamp.QuadPart * (INT64)10000000) / freq.QuadPart; + } + + return oss_unlock_result(stream, ¶ms->result, *frames ? S_OK : AUDCLNT_S_BUFFER_EMPTY); +} + +static NTSTATUS oss_release_capture_buffer(void *args) +{ + struct release_capture_buffer_params *params = args; + struct oss_stream *stream = handle_get_stream(params->stream); + UINT32 done = params->done; + + oss_lock(stream); + + if(!done){ + stream->getbuf_last = 0; + return oss_unlock_result(stream, ¶ms->result, S_OK); + } + + if(!stream->getbuf_last) + return oss_unlock_result(stream, ¶ms->result, AUDCLNT_E_OUT_OF_ORDER); + + if(stream->getbuf_last != done) + return oss_unlock_result(stream, ¶ms->result, AUDCLNT_E_INVALID_SIZE); + + stream->written_frames += done; + stream->held_frames -= done; + stream->lcl_offs_frames += done; + stream->lcl_offs_frames %= stream->bufsize_frames; + stream->getbuf_last = 0; + + return oss_unlock_result(stream, ¶ms->result, S_OK); +} + +static NTSTATUS oss_is_format_supported(void *args) +{ + struct is_format_supported_params *params = args; + int fd; + + params->result = S_OK; + + if(!params->fmt_in || (params->share == AUDCLNT_SHAREMODE_SHARED && !params->fmt_out)) + params->result = E_POINTER; + else if(params->share != AUDCLNT_SHAREMODE_SHARED && params->share != AUDCLNT_SHAREMODE_EXCLUSIVE) + params->result = E_INVALIDARG; + else if(params->fmt_in->wFormatTag == WAVE_FORMAT_EXTENSIBLE && + params->fmt_in->cbSize < sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX)) + params->result = E_INVALIDARG; + if(FAILED(params->result)) + return STATUS_SUCCESS; + + fd = open_device(params->device, params->flow); + if(fd < 0){ + WARN("Unable to open device %s: %d (%s)\n", params->device, errno, strerror(errno)); + params->result = AUDCLNT_E_DEVICE_INVALIDATED; + return STATUS_SUCCESS; + } + params->result = setup_oss_device(params->share, fd, params->fmt_in, params->fmt_out); + close(fd); + + return STATUS_SUCCESS; +} + +static NTSTATUS oss_get_mix_format(void *args) +{ + struct get_mix_format_params *params = args; + WAVEFORMATEXTENSIBLE *fmt = params->fmt; + oss_audioinfo ai; + int formats, fd; + + if(params->flow != eRender && params->flow != eCapture){ + params->result = E_UNEXPECTED; + return STATUS_SUCCESS; + } + + fd = open_device(params->device, params->flow); + if(fd < 0){ + WARN("Unable to open device %s: %d (%s)\n", params->device, errno, strerror(errno)); + params->result = AUDCLNT_E_DEVICE_INVALIDATED; + return STATUS_SUCCESS; + } + + ai.dev = -1; + if(ioctl(fd, SNDCTL_ENGINEINFO, &ai) < 0){ + WARN("Unable to get audio info for device %s: %d (%s)\n", params->device, errno, strerror(errno)); + close(fd); + params->result = E_FAIL; + return STATUS_SUCCESS; + } + close(fd); + + if(params->flow == eRender) + formats = ai.oformats; + else + formats = ai.iformats; + + fmt->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + if(formats & AFMT_S16_LE){ + fmt->Format.wBitsPerSample = 16; + fmt->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; +#ifdef AFMT_FLOAT + }else if(formats & AFMT_FLOAT){ + fmt->Format.wBitsPerSample = 32; + fmt->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; +#endif + }else if(formats & AFMT_U8){ + fmt->Format.wBitsPerSample = 8; + fmt->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + }else if(formats & AFMT_S32_LE){ + fmt->Format.wBitsPerSample = 32; + fmt->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + }else if(formats & AFMT_S24_LE){ + fmt->Format.wBitsPerSample = 24; + fmt->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + }else{ + WARN("Didn't recognize any available OSS formats: %x\n", formats); + params->result = E_FAIL; + return STATUS_SUCCESS; + } + + /* some OSS drivers are buggy, so set reasonable defaults if + * the reported values seem wacky */ + fmt->Format.nChannels = max(ai.max_channels, ai.min_channels); + if(fmt->Format.nChannels == 0 || fmt->Format.nChannels > 8) + fmt->Format.nChannels = 2; + + /* For most hardware on Windows, users must choose a configuration with an even + * number of channels (stereo, quad, 5.1, 7.1). Users can then disable + * channels, but those channels are still reported to applications from + * GetMixFormat! Some applications behave badly if given an odd number of + * channels (e.g. 2.1). */ + if(fmt->Format.nChannels > 1 && (fmt->Format.nChannels & 0x1)) + { + if(fmt->Format.nChannels < ai.max_channels) + fmt->Format.nChannels += 1; + else + /* We could "fake" more channels and downmix the emulated channels, + * but at that point you really ought to tweak your OSS setup or + * just use PulseAudio. */ + WARN("Some Windows applications behave badly with an odd number of channels (%u)!\n", fmt->Format.nChannels); + } + + if(ai.max_rate == 0) + fmt->Format.nSamplesPerSec = 44100; + else + fmt->Format.nSamplesPerSec = min(ai.max_rate, 44100); + if(fmt->Format.nSamplesPerSec < ai.min_rate) + fmt->Format.nSamplesPerSec = ai.min_rate; + + fmt->dwChannelMask = get_channel_mask(fmt->Format.nChannels); + + fmt->Format.nBlockAlign = (fmt->Format.wBitsPerSample * + fmt->Format.nChannels) / 8; + fmt->Format.nAvgBytesPerSec = fmt->Format.nSamplesPerSec * + fmt->Format.nBlockAlign; + + fmt->Samples.wValidBitsPerSample = fmt->Format.wBitsPerSample; + fmt->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); + + params->result = S_OK; + return STATUS_SUCCESS; +} + +static NTSTATUS oss_get_buffer_size(void *args) +{ + struct get_buffer_size_params *params = args; + struct oss_stream *stream = handle_get_stream(params->stream); + + oss_lock(stream); + + *params->frames = stream->bufsize_frames; + + return oss_unlock_result(stream, ¶ms->result, S_OK); +} + +static NTSTATUS oss_get_latency(void *args) +{ + struct get_latency_params *params = args; + struct oss_stream *stream = handle_get_stream(params->stream); + + oss_lock(stream); + + /* pretend we process audio in Period chunks, so max latency includes + * the period time. Some native machines add .6666ms in shared mode. */ + *params->latency = stream->period + 6666; + + return oss_unlock_result(stream, ¶ms->result, S_OK); +} + +static NTSTATUS oss_get_current_padding(void *args) +{ + struct get_current_padding_params *params = args; + struct oss_stream *stream = handle_get_stream(params->stream); + + oss_lock(stream); + + *params->padding = stream->held_frames; + + return oss_unlock_result(stream, ¶ms->result, S_OK); +} + +static NTSTATUS oss_get_next_packet_size(void *args) +{ + struct get_next_packet_size_params *params = args; + struct oss_stream *stream = handle_get_stream(params->stream); + UINT32 *frames = params->frames; + + oss_lock(stream); + + *frames = stream->held_frames < stream->period_frames ? 0 : stream->period_frames; + + return oss_unlock_result(stream, ¶ms->result, S_OK); +} + +static NTSTATUS oss_get_frequency(void *args) +{ + struct get_frequency_params *params = args; + struct oss_stream *stream = handle_get_stream(params->stream); + UINT64 *freq = params->freq; + + oss_lock(stream); + + if(stream->share == AUDCLNT_SHAREMODE_SHARED) + *freq = (UINT64)stream->fmt->nSamplesPerSec * stream->fmt->nBlockAlign; + else + *freq = stream->fmt->nSamplesPerSec; + + return oss_unlock_result(stream, ¶ms->result, S_OK); +} + +static NTSTATUS oss_get_position(void *args) +{ + struct get_position_params *params = args; + struct oss_stream *stream = handle_get_stream(params->stream); + UINT64 *pos = params->pos, *qpctime = params->qpctime; + + oss_lock(stream); + + if(stream->flow == eRender){ + *pos = stream->written_frames - stream->held_frames; + if(*pos < stream->last_pos_frames) + *pos = stream->last_pos_frames; + }else if(stream->flow == eCapture){ + audio_buf_info bi; + UINT32 held; + + if(ioctl(stream->fd, SNDCTL_DSP_GETISPACE, &bi) < 0){ + TRACE("GETISPACE failed: %d (%s)\n", errno, strerror(errno)); + held = 0; + }else{ + if(bi.bytes <= bi.fragsize) + held = 0; + else + held = bi.bytes / stream->fmt->nBlockAlign; + } + + *pos = stream->written_frames + held; + } + + stream->last_pos_frames = *pos; + + TRACE("returning: %s\n", wine_dbgstr_longlong(*pos)); + if(stream->share == AUDCLNT_SHAREMODE_SHARED) + *pos *= stream->fmt->nBlockAlign; + + if(qpctime){ + LARGE_INTEGER stamp, freq; + NtQueryPerformanceCounter(&stamp, &freq); + *qpctime = (stamp.QuadPart * (INT64)10000000) / freq.QuadPart; + } + + return oss_unlock_result(stream, ¶ms->result, S_OK); +} + +static NTSTATUS oss_set_volumes(void *args) +{ + struct set_volumes_params *params = args; + struct oss_stream *stream = handle_get_stream(params->stream); + + oss_lock(stream); + stream->mute = !params->master_volume; + oss_unlock(stream); + + return STATUS_SUCCESS; +} + +static NTSTATUS oss_set_event_handle(void *args) +{ + struct set_event_handle_params *params = args; + struct oss_stream *stream = handle_get_stream(params->stream); + + oss_lock(stream); + + if(!(stream->flags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK)) + return oss_unlock_result(stream, ¶ms->result, AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED); + + if (stream->event){ + FIXME("called twice\n"); + return oss_unlock_result(stream, ¶ms->result, HRESULT_FROM_WIN32(ERROR_INVALID_NAME)); + } + + stream->event = params->event; + + return oss_unlock_result(stream, ¶ms->result, S_OK); +} + +static NTSTATUS oss_is_started(void *args) +{ + struct is_started_params *params = args; + struct oss_stream *stream = handle_get_stream(params->stream); + + oss_lock(stream); + + return oss_unlock_result(stream, ¶ms->result, stream->playing ? S_OK : S_FALSE); +} + +/* Aux driver */ + +static unsigned int num_aux; + +#define MIXER_DEV "/dev/mixer" + +static UINT aux_init(void) +{ + int mixer; + + TRACE("()\n"); + + if ((mixer = open(MIXER_DEV, O_RDWR)) < 0) + { + WARN("mixer device not available !\n"); + num_aux = 0; + } + else + { + close(mixer); + num_aux = 6; + } + return 0; +} + +static UINT aux_exit(void) +{ + TRACE("()\n"); + return 0; +} + +static UINT aux_get_devcaps(WORD dev_id, AUXCAPSW *caps, UINT size) +{ + int mixer, volume; + static const WCHAR ini[] = {'O','S','S',' ','A','u','x',' ','#','0',0}; + + TRACE("(%04X, %p, %u);\n", dev_id, caps, size); + if (caps == NULL) return MMSYSERR_NOTENABLED; + if (dev_id >= num_aux) return MMSYSERR_BADDEVICEID; + if ((mixer = open(MIXER_DEV, O_RDWR)) < 0) + { + WARN("mixer device not available !\n"); + return MMSYSERR_NOTENABLED; + } + if (ioctl(mixer, SOUND_MIXER_READ_LINE, &volume) == -1) + { + close(mixer); + WARN("unable to read mixer !\n"); + return MMSYSERR_NOTENABLED; + } + close(mixer); + caps->wMid = 0xAA; + caps->wPid = 0x55 + dev_id; + caps->vDriverVersion = 0x0100; + memcpy(caps->szPname, ini, sizeof(ini)); + caps->szPname[9] = '0' + dev_id; /* 6 at max */ + caps->wTechnology = (dev_id == 2) ? AUXCAPS_CDAUDIO : AUXCAPS_AUXIN; + caps->wReserved1 = 0; + caps->dwSupport = AUXCAPS_VOLUME | AUXCAPS_LRVOLUME; + + return MMSYSERR_NOERROR; +} + +static UINT aux_get_volume(WORD dev_id, UINT *vol) +{ + int mixer, volume, left, right, cmd; + + TRACE("(%04X, %p);\n", dev_id, vol); + if (vol == NULL) return MMSYSERR_NOTENABLED; + if ((mixer = open(MIXER_DEV, O_RDWR)) < 0) + { + WARN("mixer device not available !\n"); + return MMSYSERR_NOTENABLED; + } + switch(dev_id) + { + case 0: + TRACE("SOUND_MIXER_READ_PCM !\n"); + cmd = SOUND_MIXER_READ_PCM; + break; + case 1: + TRACE("SOUND_MIXER_READ_SYNTH !\n"); + cmd = SOUND_MIXER_READ_SYNTH; + break; + case 2: + TRACE("SOUND_MIXER_READ_CD !\n"); + cmd = SOUND_MIXER_READ_CD; + break; + case 3: + TRACE("SOUND_MIXER_READ_LINE !\n"); + cmd = SOUND_MIXER_READ_LINE; + break; + case 4: + TRACE("SOUND_MIXER_READ_MIC !\n"); + cmd = SOUND_MIXER_READ_MIC; + break; + case 5: + TRACE("SOUND_MIXER_READ_VOLUME !\n"); + cmd = SOUND_MIXER_READ_VOLUME; + break; + default: + WARN("invalid device id=%04X !\n", dev_id); + close(mixer); + return MMSYSERR_NOTENABLED; + } + if (ioctl(mixer, cmd, &volume) == -1) + { + WARN("unable to read mixer !\n"); + close(mixer); + return MMSYSERR_NOTENABLED; + } + close(mixer); + left = LOBYTE(LOWORD(volume)); + right = HIBYTE(LOWORD(volume)); + TRACE("left=%d right=%d !\n", left, right); + *vol = MAKELONG((left * 0xFFFFL) / 100, (right * 0xFFFFL) / 100); + return MMSYSERR_NOERROR; +} + +static UINT aux_set_volume(WORD dev_id, UINT vol) +{ + int mixer; + int volume, left, right; + int cmd; + + TRACE("(%04X, %08X);\n", dev_id, vol); + + left = (LOWORD(vol) * 100) >> 16; + right = (HIWORD(vol) * 100) >> 16; + volume = (right << 8) | left; + + if ((mixer = open(MIXER_DEV, O_RDWR)) < 0) + { + WARN("mixer device not available !\n"); + return MMSYSERR_NOTENABLED; + } + + switch(dev_id) + { + case 0: + TRACE("SOUND_MIXER_WRITE_PCM !\n"); + cmd = SOUND_MIXER_WRITE_PCM; + break; + case 1: + TRACE("SOUND_MIXER_WRITE_SYNTH !\n"); + cmd = SOUND_MIXER_WRITE_SYNTH; + break; + case 2: + TRACE("SOUND_MIXER_WRITE_CD !\n"); + cmd = SOUND_MIXER_WRITE_CD; + break; + case 3: + TRACE("SOUND_MIXER_WRITE_LINE !\n"); + cmd = SOUND_MIXER_WRITE_LINE; + break; + case 4: + TRACE("SOUND_MIXER_WRITE_MIC !\n"); + cmd = SOUND_MIXER_WRITE_MIC; + break; + case 5: + TRACE("SOUND_MIXER_WRITE_VOLUME !\n"); + cmd = SOUND_MIXER_WRITE_VOLUME; + break; + default: + WARN("invalid device id=%04X !\n", dev_id); + close(mixer); + return MMSYSERR_NOTENABLED; + } + if (ioctl(mixer, cmd, &volume) == -1) + { + WARN("unable to set mixer !\n"); + close(mixer); + return MMSYSERR_NOTENABLED; + } + close(mixer); + return MMSYSERR_NOERROR; +} + +static NTSTATUS oss_aux_message(void *args) +{ + struct aux_message_params *params = args; + + switch (params->msg) + { + case DRVM_INIT: + *params->err = aux_init(); + break; + case DRVM_EXIT: + *params->err = aux_exit(); + break; + case DRVM_ENABLE: + case DRVM_DISABLE: + /* FIXME: Pretend this is supported */ + *params->err = 0; + break; + case AUXDM_GETDEVCAPS: + *params->err = aux_get_devcaps(params->dev_id, (AUXCAPSW *)params->param_1, params->param_2); + break; + case AUXDM_GETNUMDEVS: + TRACE("return %d;\n", num_aux); + *params->err = num_aux; + break; + case AUXDM_GETVOLUME: + *params->err = aux_get_volume(params->dev_id, (UINT *)params->param_1); + break; + case AUXDM_SETVOLUME: + *params->err = aux_set_volume(params->dev_id, params->param_1); + break; + default: + WARN("unknown message !\n"); + *params->err = MMSYSERR_NOTSUPPORTED; + break; + } + + return STATUS_SUCCESS; +} + +unixlib_entry_t __wine_unix_call_funcs[] = +{ + NULL, + NULL, + NULL, + oss_get_endpoint_ids, + oss_create_stream, + oss_release_stream, + oss_start, + oss_stop, + oss_reset, + oss_timer_loop, + oss_get_render_buffer, + oss_release_render_buffer, + oss_get_capture_buffer, + oss_release_capture_buffer, + oss_is_format_supported, + oss_get_mix_format, + NULL, + oss_get_buffer_size, + oss_get_latency, + oss_get_current_padding, + oss_get_next_packet_size, + oss_get_frequency, + oss_get_position, + oss_set_volumes, + oss_set_event_handle, + oss_test_connect, + oss_is_started, + NULL, + NULL, + oss_midi_release, + oss_midi_out_message, + oss_midi_in_message, + oss_midi_notify_wait, + oss_aux_message, +}; + +#ifdef _WIN64 + +typedef UINT PTR32; + +static NTSTATUS oss_wow64_test_connect(void *args) +{ + struct + { + PTR32 name; + enum driver_priority priority; + } *params32 = args; + struct test_connect_params params = + { + .name = ULongToPtr(params32->name), + }; + oss_test_connect(¶ms); + params32->priority = params.priority; + return STATUS_SUCCESS; +} + +static NTSTATUS oss_wow64_get_endpoint_ids(void *args) +{ + struct + { + EDataFlow flow; + PTR32 endpoints; + unsigned int size; + HRESULT result; + unsigned int num; + unsigned int default_idx; + } *params32 = args; + struct get_endpoint_ids_params params = + { + .flow = params32->flow, + .endpoints = ULongToPtr(params32->endpoints), + .size = params32->size + }; + oss_get_endpoint_ids(¶ms); + params32->size = params.size; + params32->result = params.result; + params32->num = params.num; + params32->default_idx = params.default_idx; + return STATUS_SUCCESS; +} + +static NTSTATUS oss_wow64_create_stream(void *args) +{ + struct + { + PTR32 name; + PTR32 device; + EDataFlow flow; + AUDCLNT_SHAREMODE share; + UINT flags; + REFERENCE_TIME duration; + REFERENCE_TIME period; + PTR32 fmt; + HRESULT result; + PTR32 channel_count; + PTR32 stream; + } *params32 = args; + struct create_stream_params params = + { + .name = ULongToPtr(params32->name), + .device = ULongToPtr(params32->device), + .flow = params32->flow, + .share = params32->share, + .flags = params32->flags, + .duration = params32->duration, + .period = params32->period, + .fmt = ULongToPtr(params32->fmt), + .channel_count = ULongToPtr(params32->channel_count), + .stream = ULongToPtr(params32->stream) + }; + oss_create_stream(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + +static NTSTATUS oss_wow64_release_stream(void *args) +{ + struct + { + stream_handle stream; + PTR32 timer_thread; + HRESULT result; + } *params32 = args; + struct release_stream_params params = + { + .stream = params32->stream, + .timer_thread = ULongToHandle(params32->timer_thread) + }; + oss_release_stream(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + +static NTSTATUS oss_wow64_get_render_buffer(void *args) +{ + struct + { + stream_handle stream; + UINT32 frames; + HRESULT result; + PTR32 data; + } *params32 = args; + BYTE *data = NULL; + struct get_render_buffer_params params = + { + .stream = params32->stream, + .frames = params32->frames, + .data = &data + }; + oss_get_render_buffer(¶ms); + params32->result = params.result; + *(unsigned int *)ULongToPtr(params32->data) = PtrToUlong(data); + return STATUS_SUCCESS; +} + +static NTSTATUS oss_wow64_get_capture_buffer(void *args) +{ + struct + { + stream_handle stream; + HRESULT result; + PTR32 data; + PTR32 frames; + PTR32 flags; + PTR32 devpos; + PTR32 qpcpos; + } *params32 = args; + BYTE *data = NULL; + struct get_capture_buffer_params params = + { + .stream = params32->stream, + .data = &data, + .frames = ULongToPtr(params32->frames), + .flags = ULongToPtr(params32->flags), + .devpos = ULongToPtr(params32->devpos), + .qpcpos = ULongToPtr(params32->qpcpos) + }; + oss_get_capture_buffer(¶ms); + params32->result = params.result; + *(unsigned int *)ULongToPtr(params32->data) = PtrToUlong(data); + return STATUS_SUCCESS; +}; + +static NTSTATUS oss_wow64_is_format_supported(void *args) +{ + struct + { + PTR32 device; + EDataFlow flow; + AUDCLNT_SHAREMODE share; + PTR32 fmt_in; + PTR32 fmt_out; + HRESULT result; + } *params32 = args; + struct is_format_supported_params params = + { + .device = ULongToPtr(params32->device), + .flow = params32->flow, + .share = params32->share, + .fmt_in = ULongToPtr(params32->fmt_in), + .fmt_out = ULongToPtr(params32->fmt_out) + }; + oss_is_format_supported(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + +static NTSTATUS oss_wow64_get_mix_format(void *args) +{ + struct + { + PTR32 device; + EDataFlow flow; + PTR32 fmt; + HRESULT result; + } *params32 = args; + struct get_mix_format_params params = + { + .device = ULongToPtr(params32->device), + .flow = params32->flow, + .fmt = ULongToPtr(params32->fmt) + }; + oss_get_mix_format(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + +static NTSTATUS oss_wow64_get_buffer_size(void *args) +{ + struct + { + stream_handle stream; + HRESULT result; + PTR32 frames; + } *params32 = args; + struct get_buffer_size_params params = + { + .stream = params32->stream, + .frames = ULongToPtr(params32->frames) + }; + oss_get_buffer_size(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + +static NTSTATUS oss_wow64_get_latency(void *args) +{ + struct + { + stream_handle stream; + HRESULT result; + PTR32 latency; + } *params32 = args; + struct get_latency_params params = + { + .stream = params32->stream, + .latency = ULongToPtr(params32->latency) + }; + oss_get_latency(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + +static NTSTATUS oss_wow64_get_current_padding(void *args) +{ + struct + { + stream_handle stream; + HRESULT result; + PTR32 padding; + } *params32 = args; + struct get_current_padding_params params = + { + .stream = params32->stream, + .padding = ULongToPtr(params32->padding) + }; + oss_get_current_padding(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + +static NTSTATUS oss_wow64_get_next_packet_size(void *args) +{ + struct + { + stream_handle stream; + HRESULT result; + PTR32 frames; + } *params32 = args; + struct get_next_packet_size_params params = + { + .stream = params32->stream, + .frames = ULongToPtr(params32->frames) + }; + oss_get_next_packet_size(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + +static NTSTATUS oss_wow64_get_frequency(void *args) +{ + struct + { + stream_handle stream; + HRESULT result; + PTR32 freq; + } *params32 = args; + struct get_frequency_params params = + { + .stream = params32->stream, + .freq = ULongToPtr(params32->freq) + }; + oss_get_frequency(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + +static NTSTATUS oss_wow64_get_position(void *args) +{ + struct + { + stream_handle stream; + BOOL device; + HRESULT result; + PTR32 pos; + PTR32 qpctime; + } *params32 = args; + struct get_position_params params = + { + .stream = params32->stream, + .device = params32->device, + .pos = ULongToPtr(params32->pos), + .qpctime = ULongToPtr(params32->qpctime) + }; + oss_get_position(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + +static NTSTATUS oss_wow64_set_volumes(void *args) +{ + struct + { + stream_handle stream; + float master_volume; + PTR32 volumes; + PTR32 session_volumes; + int channel; + } *params32 = args; + struct set_volumes_params params = + { + .stream = params32->stream, + .master_volume = params32->master_volume, + .volumes = ULongToPtr(params32->volumes), + .session_volumes = ULongToPtr(params32->session_volumes), + .channel = params32->channel + }; + return oss_set_volumes(¶ms); +} + +static NTSTATUS oss_wow64_set_event_handle(void *args) +{ + struct + { + stream_handle stream; + PTR32 event; + HRESULT result; + } *params32 = args; + struct set_event_handle_params params = + { + .stream = params32->stream, + .event = ULongToHandle(params32->event) + }; + + oss_set_event_handle(¶ms); + params32->result = params.result; + return STATUS_SUCCESS; +} + +static NTSTATUS oss_wow64_aux_message(void *args) +{ + struct + { + UINT dev_id; + UINT msg; + UINT user; + UINT param_1; + UINT param_2; + PTR32 err; + } *params32 = args; + struct aux_message_params params = + { + .dev_id = params32->dev_id, + .msg = params32->msg, + .user = params32->user, + .param_1 = params32->param_1, + .param_2 = params32->param_2, + .err = ULongToPtr(params32->err), + }; + return oss_aux_message(¶ms); +} + +unixlib_entry_t __wine_unix_call_wow64_funcs[] = +{ + NULL, + NULL, + NULL, + oss_wow64_get_endpoint_ids, + oss_wow64_create_stream, + oss_wow64_release_stream, + oss_start, + oss_stop, + oss_reset, + oss_timer_loop, + oss_wow64_get_render_buffer, + oss_release_render_buffer, + oss_wow64_get_capture_buffer, + oss_release_capture_buffer, + oss_wow64_is_format_supported, + oss_wow64_get_mix_format, + NULL, + oss_wow64_get_buffer_size, + oss_wow64_get_latency, + oss_wow64_get_current_padding, + oss_wow64_get_next_packet_size, + oss_wow64_get_frequency, + oss_wow64_get_position, + oss_wow64_set_volumes, + oss_wow64_set_event_handle, + oss_wow64_test_connect, + oss_is_started, + NULL, + NULL, + oss_midi_release, + oss_wow64_midi_out_message, + oss_wow64_midi_in_message, + oss_wow64_midi_notify_wait, + oss_wow64_aux_message, +}; + +#endif /* _WIN64 */ diff --git a/pkgs/osu-wine/audio-revert/wineoss.drv/ossmidi.c b/pkgs/osu-wine/audio-revert/wineoss.drv/ossmidi.c new file mode 100644 index 0000000..158132c --- /dev/null +++ b/pkgs/osu-wine/audio-revert/wineoss.drv/ossmidi.c @@ -0,0 +1,2177 @@ +/* + * MIDI driver for OSS (unixlib) + * + * Copyright 1994 Martin Ayotte + * Copyright 1998 Luiz Otavio L. Zorzella (init procedures) + * Copyright 1998, 1999 Eric POUECH + * Copyright 2022 Huw Davies + * + * 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 + */ + +#if 0 +#pragma makedep unix +#endif + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "winternl.h" +#include "audioclient.h" +#include "mmddk.h" + +#include "wine/debug.h" +#include "wine/unixlib.h" + +#include "unixlib.h" + +struct midi_dest +{ + BOOL bEnabled; + MIDIOPENDESC midiDesc; + BYTE runningStatus; + WORD wFlags; + MIDIHDR *lpQueueHdr; + void *lpExtra; /* according to port type (MIDI, FM...), extra data when needed */ + MIDIOUTCAPSW caps; + int fd; +}; + +struct midi_src +{ + int state; /* -1 disabled, 0 is no recording started, 1 in recording, bit 2 set if in sys exclusive recording */ + MIDIOPENDESC midiDesc; + WORD wFlags; + MIDIHDR *lpQueueHdr; + unsigned char incoming[3]; + unsigned char incPrev; + char incLen; + UINT startTime; + MIDIINCAPSW caps; + int fd; +}; + +static pthread_mutex_t in_buffer_mutex = PTHREAD_MUTEX_INITIALIZER; + +static unsigned int num_dests, num_srcs, num_synths, seq_refs; +static struct midi_dest dests[MAX_MIDIOUTDRV]; +static struct midi_src srcs[MAX_MIDIINDRV]; +static int load_count; + +static unsigned int num_midi_in_started; +static int rec_cancel_pipe[2]; +static pthread_t rec_thread_id; + +static pthread_mutex_t notify_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t notify_read_cond = PTHREAD_COND_INITIALIZER; +static pthread_cond_t notify_write_cond = PTHREAD_COND_INITIALIZER; +static BOOL notify_quit; +#define NOTIFY_BUFFER_SIZE 64 + 1 /* + 1 for the sentinel */ +static struct notify_context notify_buffer[NOTIFY_BUFFER_SIZE]; +static struct notify_context *notify_read = notify_buffer, *notify_write = notify_buffer; + +typedef struct sVoice +{ + int note; /* 0 means not used */ + int channel; + unsigned cntMark : 30, + status : 2; +#define sVS_UNUSED 0 +#define sVS_PLAYING 1 +#define sVS_SUSTAINED 2 +} sVoice; + +typedef struct sChannel +{ + int program; + + int bender; + int benderRange; + /* controllers */ + int bank; /* CTL_BANK_SELECT */ + int volume; /* CTL_MAIN_VOLUME */ + int balance; /* CTL_BALANCE */ + int expression; /* CTL_EXPRESSION */ + int sustain; /* CTL_SUSTAIN */ + + unsigned char nrgPmtMSB; /* Non register Parameters */ + unsigned char nrgPmtLSB; + unsigned char regPmtMSB; /* Non register Parameters */ + unsigned char regPmtLSB; +} sChannel; + +typedef struct sFMextra +{ + unsigned counter; + int drumSetMask; + sChannel channel[16]; /* MIDI has only 16 channels */ + sVoice voice[1]; /* dyn allocated according to sound card */ + /* do not append fields below voice[1] since the size of this structure + * depends on the number of available voices on the FM synth... + */ +} sFMextra; + +#define IS_DRUM_CHANNEL(_xtra, _chn) ((_xtra)->drumSetMask & (1 << (_chn))) + +WINE_DEFAULT_DEBUG_CHANNEL(midi); + +static int oss_to_win_device_type(int type) +{ + /* MOD_MIDIPORT output port + * MOD_SYNTH generic internal synth + * MOD_SQSYNTH square wave internal synth + * MOD_FMSYNTH FM internal synth + * MOD_MAPPER MIDI mapper + * MOD_WAVETABLE hardware wavetable internal synth + * MOD_SWSYNTH software internal synth + */ + + /* FIXME Is this really the correct equivalence from UNIX to + Windows Sound type */ + + switch (type) + { + case SYNTH_TYPE_FM: return MOD_FMSYNTH; + case SYNTH_TYPE_SAMPLE: return MOD_SYNTH; + case SYNTH_TYPE_MIDI: return MOD_MIDIPORT; + default: + ERR("Cannot determine the type of this midi device. " + "Assuming FM Synth\n"); + return MOD_FMSYNTH; + } +} + +static void in_buffer_lock(void) +{ + pthread_mutex_lock(&in_buffer_mutex); +} + +static void in_buffer_unlock(void) +{ + pthread_mutex_unlock(&in_buffer_mutex); +} + +static uint64_t get_time_msec(void) +{ + struct timespec now = {0, 0}; + +#ifdef CLOCK_MONOTONIC_RAW + if (!clock_gettime(CLOCK_MONOTONIC_RAW, &now)) + return (uint64_t)now.tv_sec * 1000 + now.tv_nsec / 1000000; +#endif + clock_gettime(CLOCK_MONOTONIC, &now); + return (uint64_t)now.tv_sec * 1000 + now.tv_nsec / 1000000; +} + +/* + * notify buffer: The notification ring buffer is implemented so that + * there is always at least one unused sentinel before the current + * read position in order to allow detection of the full vs empty + * state. + */ +static struct notify_context *notify_buffer_next(struct notify_context *notify) +{ + if (++notify >= notify_buffer + ARRAY_SIZE(notify_buffer)) + notify = notify_buffer; + + return notify; +} + +static BOOL notify_buffer_empty(void) +{ + return notify_read == notify_write; +} + +static BOOL notify_buffer_full(void) +{ + return notify_buffer_next(notify_write) == notify_read; +} + +static BOOL notify_buffer_add(struct notify_context *notify) +{ + if (notify_buffer_full()) return FALSE; + + *notify_write = *notify; + notify_write = notify_buffer_next(notify_write); + return TRUE; +} + +static BOOL notify_buffer_remove(struct notify_context *notify) +{ + if (notify_buffer_empty()) return FALSE; + + *notify = *notify_read; + notify_read = notify_buffer_next(notify_read); + return TRUE; +} + +static void notify_post(struct notify_context *notify) +{ + pthread_mutex_lock(¬ify_mutex); + + if (notify) + { + while (notify_buffer_full()) + pthread_cond_wait(¬ify_write_cond, ¬ify_mutex); + + notify_buffer_add(notify); + } + else notify_quit = TRUE; + pthread_cond_signal(¬ify_read_cond); + + pthread_mutex_unlock(¬ify_mutex); +} + +static void set_in_notify(struct notify_context *notify, struct midi_src *src, WORD dev_id, WORD msg, + UINT_PTR param_1, UINT_PTR param_2) +{ + notify->send_notify = TRUE; + notify->dev_id = dev_id; + notify->msg = msg; + notify->param_1 = param_1; + notify->param_2 = param_2; + notify->callback = src->midiDesc.dwCallback; + notify->flags = src->wFlags; + notify->device = src->midiDesc.hMidi; + notify->instance = src->midiDesc.dwInstance; +} + +static int seq_open(void) +{ + static int midi_warn = 1; + static int fd = -1; + + if (seq_refs <= 0) + { + const char* device = getenv("MIDIDEV"); + + if (!device) device = "/dev/sequencer"; + fd = open(device, O_RDWR, 0); + if (fd == -1) + { + if (midi_warn) + { + WARN("Can't open MIDI device '%s' ! (%s). If your program needs this (probably not): %s\n", + device, strerror(errno), + errno == ENOENT ? "create it ! (\"man MAKEDEV\" ?)" : + errno == ENODEV ? "load MIDI sequencer kernel driver !" : + errno == EACCES ? "grant access ! (\"man chmod\")" : ""); + } + midi_warn = 0; + return -1; + } + fcntl(fd, F_SETFD, 1); /* set close on exec flag */ + ioctl(fd, SNDCTL_SEQ_RESET); + } + seq_refs++; + return fd; +} + +static int seq_close(int fd) +{ + if (--seq_refs == 0) + close(fd); + + return 0; +} + +static UINT oss_midi_init(void) +{ + int i, status, synth_devs = 255, midi_devs = 255, fd, len; + struct synth_info sinfo; + struct midi_info minfo; + struct midi_dest *dest; + struct midi_src *src; + + TRACE("(%i)\n", load_count); + + if (load_count++) + return 1; + + /* try to open device */ + fd = seq_open(); + if (fd == -1) + return -1; + + /* find how many Synth devices are there in the system */ + status = ioctl(fd, SNDCTL_SEQ_NRSYNTHS, &synth_devs); + if (status == -1) + { + ERR("ioctl for nr synth failed.\n"); + seq_close(fd); + return -1; + } + + if (synth_devs > MAX_MIDIOUTDRV) + { + ERR("MAX_MIDIOUTDRV (%d) was enough for the number of devices (%d). " + "Some FM devices will not be available.\n", MAX_MIDIOUTDRV, synth_devs); + synth_devs = MAX_MIDIOUTDRV; + } + + for (i = 0, dest = dests; i < synth_devs; i++, dest++) + { + /* Manufac ID. We do not have access to this with soundcard.h + * Does not seem to be a problem, because in mmsystem.h only + * Microsoft's ID is listed. + */ + dest->caps.wMid = 0x00FF; + dest->caps.wPid = 0x0001; /* FIXME Product ID */ + /* Product Version. We simply say "1" */ + dest->caps.vDriverVersion = 0x001; + /* The following are mandatory for MOD_MIDIPORT */ + dest->caps.wChannelMask = 0xFFFF; + dest->caps.wVoices = 0; + dest->caps.wNotes = 0; + dest->caps.dwSupport = 0; + + sinfo.device = i; + status = ioctl(fd, SNDCTL_SYNTH_INFO, &sinfo); + if (status == -1) + { + char buf[255]; + + ERR("ioctl for synth info failed on %d, disabling it.\n", i); + + sprintf(buf, "Wine OSS Midi Out #%d disabled", i); + len = ntdll_umbstowcs(buf, strlen(buf) + 1, dest->caps.szPname, ARRAY_SIZE(dest->caps.szPname)); + dest->caps.szPname[len - 1] = '\0'; + dest->caps.wTechnology = MOD_MIDIPORT; + dest->bEnabled = FALSE; + } + else + { + len = ntdll_umbstowcs(sinfo.name, strlen(sinfo.name) + 1, dest->caps.szPname, ARRAY_SIZE(dest->caps.szPname)); + dest->caps.szPname[len - 1] = '\0'; + dest->caps.wTechnology = oss_to_win_device_type(sinfo.synth_type); + + if (dest->caps.wTechnology != MOD_MIDIPORT) + { + /* FIXME Do we have this information? + * Assuming the soundcards can handle + * MIDICAPS_VOLUME and MIDICAPS_LRVOLUME but + * not MIDICAPS_CACHE. + */ + dest->caps.dwSupport = MIDICAPS_VOLUME | MIDICAPS_LRVOLUME; + dest->caps.wVoices = sinfo.nr_voices; + + /* FIXME Is it possible to know the maximum + * number of simultaneous notes of a soundcard ? + * I believe we don't have this information, but + * it's probably equal or more than wVoices + */ + dest->caps.wNotes = sinfo.nr_voices; + } + dest->bEnabled = TRUE; + + /* We also have the information sinfo.synth_subtype, not used here + */ + if (sinfo.capabilities & SYNTH_CAP_INPUT) + FIXME("Synthesizer supports MIDI in. Not yet supported.\n"); + + TRACE("SynthOut[%d]\tOSS info: synth type=%d/%d capa=%x\n", + i, sinfo.synth_type, sinfo.synth_subtype, (unsigned)sinfo.capabilities); + } + + TRACE("SynthOut[%d]\tname='%s' techn=%d voices=%d notes=%d chnMsk=%04x support=%d\n", + i, wine_dbgstr_w(dest->caps.szPname), dest->caps.wTechnology, + dest->caps.wVoices, dest->caps.wNotes, dest->caps.wChannelMask, + (unsigned)dest->caps.dwSupport); + } + + /* find how many MIDI devices are there in the system */ + status = ioctl(fd, SNDCTL_SEQ_NRMIDIS, &midi_devs); + if (status == -1) + { + ERR("ioctl on nr midi failed.\n"); + midi_devs = 0; + goto wrapup; + } + + /* FIXME: the two restrictions below could be loosened in some cases */ + if (synth_devs + midi_devs > MAX_MIDIOUTDRV) + { + ERR("MAX_MIDIOUTDRV was not enough for the number of devices. " + "Some MIDI devices will not be available.\n"); + midi_devs = MAX_MIDIOUTDRV - synth_devs; + } + + if (midi_devs > MAX_MIDIINDRV) + { + ERR("MAX_MIDIINDRV (%d) was not enough for the number of devices (%d). " + "Some MIDI devices will not be available.\n", MAX_MIDIINDRV, midi_devs); + midi_devs = MAX_MIDIINDRV; + } + + dest = dests + synth_devs; + src = srcs; + for (i = 0; i < midi_devs; i++, dest++, src++) + { + minfo.device = i; + status = ioctl(fd, SNDCTL_MIDI_INFO, &minfo); + if (status == -1) WARN("ioctl on midi info for device %d failed.\n", i); + + /* Manufacturer ID. We do not have access to this with soundcard.h + Does not seem to be a problem, because in mmsystem.h only Microsoft's ID is listed + */ + dest->caps.wMid = 0x00FF; + dest->caps.wPid = 0x0001; /* FIXME Product ID */ + /* Product Version. We simply say "1" */ + dest->caps.vDriverVersion = 0x001; + if (status == -1) + { + char buf[255]; + + sprintf(buf, "Wine OSS Midi Out #%d disabled", synth_devs + i); + len = ntdll_umbstowcs(buf, strlen(buf) + 1, dest->caps.szPname, ARRAY_SIZE(dest->caps.szPname)); + dest->caps.szPname[len - 1] = '\0'; + dest->bEnabled = FALSE; + } + else + { + len = ntdll_umbstowcs(minfo.name, strlen(minfo.name) + 1, dest->caps.szPname, ARRAY_SIZE(dest->caps.szPname)); + dest->caps.szPname[len - 1] = '\0'; + dest->bEnabled = TRUE; + } + dest->caps.wTechnology = MOD_MIDIPORT; + dest->caps.wVoices = 0; + dest->caps.wNotes = 0; + dest->caps.wChannelMask = 0xFFFF; + dest->caps.dwSupport = 0; + + /* Manufac ID. We do not have access to this with soundcard.h + Does not seem to be a problem, because in mmsystem.h only + Microsoft's ID is listed */ + src->caps.wMid = 0x00FF; + src->caps.wPid = 0x0001; /* FIXME Product ID */ + /* Product Version. We simply say "1" */ + src->caps.vDriverVersion = 0x001; + if (status == -1) + { + char buf[ARRAY_SIZE(dest->caps.szPname)]; + + sprintf(buf, "Wine OSS Midi In #%d disabled", synth_devs + i); + len = ntdll_umbstowcs(buf, strlen(buf) + 1, src->caps.szPname, ARRAY_SIZE(src->caps.szPname)); + src->caps.szPname[len - 1] = '\0'; + src->state = -1; + } + else + { + len = ntdll_umbstowcs(minfo.name, strlen(minfo.name) + 1, src->caps.szPname, ARRAY_SIZE(src->caps.szPname)); + src->caps.szPname[len - 1] = '\0'; + src->state = 0; + } + src->caps.dwSupport = 0; /* mandatory with MIDIINCAPS */ + + TRACE("OSS info: midi[%d] dev-type=%d capa=%x\n" + "\tMidiOut[%d] name='%s' techn=%d voices=%d notes=%d chnMsk=%04x support=%d\n" + "\tMidiIn [%d] name='%s' support=%d\n", + i, minfo.dev_type, (unsigned)minfo.capabilities, + synth_devs + i, wine_dbgstr_w(dest->caps.szPname), dest->caps.wTechnology, + dest->caps.wVoices, dest->caps.wNotes, dest->caps.wChannelMask, (unsigned)dest->caps.dwSupport, + i, wine_dbgstr_w(src->caps.szPname), (unsigned)src->caps.dwSupport); + } + +wrapup: + /* windows does not seem to differentiate Synth from MIDI devices */ + num_synths = synth_devs; + num_dests = synth_devs + midi_devs; + num_srcs = midi_devs; + + /* close file and exit */ + seq_close(fd); + + return 0; +} + +static UINT midi_exit(void) +{ + TRACE("(%i)\n", load_count); + + if (--load_count) + return 1; + + return 0; +} + +NTSTATUS oss_midi_release(void *args) +{ + /* stop the notify_wait thread */ + notify_post(NULL); + + return STATUS_SUCCESS; +} + +/* FIXME: this is a bad idea, it's even not static... */ +SEQ_DEFINEBUF(1024); + +/* FIXME: this is not reentrant, not static - because of global variable + * _seqbuf and al. + */ +/************************************************************************** + * seqbuf_dump [internal] + * + * Used by SEQ_DUMPBUF to flush the buffer. + * + */ +void seqbuf_dump(void) +{ + int fd; + + /* The device is already open, but there's no way to pass the + fd to this function. Rather than rely on a global variable + we pretend to open the seq again. */ + fd = seq_open(); + if (_seqbufptr) + { + if (write(fd, _seqbuf, _seqbufptr) == -1) + { + WARN("Can't write data to sequencer %d, errno %d (%s)!\n", + fd, errno, strerror(errno)); + } + /* FIXME: In any case buffer is lost so that if many errors occur the buffer + * will not overrun */ + _seqbufptr = 0; + } + seq_close(fd); +} + +extern const unsigned char midiFMInstrumentPatches[16 * 128]; +extern const unsigned char midiFMDrumsPatches[16 * 128]; + +static int midi_out_fm_load(WORD dev_id, int fd) +{ + struct sbi_instrument sbi; + int i; + + sbi.device = dev_id; + sbi.key = FM_PATCH; + + memset(sbi.operators + 16, 0, 16); + for (i = 0; i < 128; i++) + { + sbi.channel = i; + memcpy(sbi.operators, midiFMInstrumentPatches + i * 16, 16); + + if (write(fd, &sbi, sizeof(sbi)) == -1) + { + WARN("Couldn't write patch for instrument %d, errno %d (%s)!\n", sbi.channel, errno, strerror(errno)); + return -1; + } + } + for (i = 0; i < 128; i++) + { + sbi.channel = 128 + i; + memcpy(sbi.operators, midiFMDrumsPatches + i * 16, 16); + + if (write(fd, &sbi, sizeof(sbi)) == -1) + { + WARN("Couldn't write patch for drum %d, errno %d (%s)!\n", sbi.channel, errno, strerror(errno)); + return -1; + } + } + return 0; +} + +static void midi_out_fm_reset(WORD dev_id) +{ + struct midi_dest *dest = dests + dev_id; + sFMextra *extra = dest->lpExtra; + sVoice *voice = extra->voice; + sChannel *channel = extra->channel; + int i; + + for (i = 0; i < dest->caps.wVoices; i++) + { + if (voice[i].status != sVS_UNUSED) + SEQ_STOP_NOTE(dev_id, i, voice[i].note, 64); + SEQ_KEY_PRESSURE(dev_id, i, 127, 0); + SEQ_CONTROL(dev_id, i, SEQ_VOLMODE, VOL_METHOD_LINEAR); + voice[i].note = 0; + voice[i].channel = -1; + voice[i].cntMark = 0; + voice[i].status = sVS_UNUSED; + } + for (i = 0; i < 16; i++) + { + channel[i].program = 0; + channel[i].bender = 8192; + channel[i].benderRange = 2; + channel[i].bank = 0; + channel[i].volume = 127; + channel[i].balance = 64; + channel[i].expression = 0; + channel[i].sustain = 0; + } + extra->counter = 0; + extra->drumSetMask = 1 << 9; /* channel 10 is normally drums, sometimes 16 also */ + SEQ_DUMPBUF(); +} + +static void set_out_notify(struct notify_context *notify, struct midi_dest *dest, WORD dev_id, WORD msg, + UINT_PTR param_1, UINT_PTR param_2) +{ + notify->send_notify = TRUE; + notify->dev_id = dev_id; + notify->msg = msg; + notify->param_1 = param_1; + notify->param_2 = param_2; + notify->callback = dest->midiDesc.dwCallback; + notify->flags = dest->wFlags; + notify->device = dest->midiDesc.hMidi; + notify->instance = dest->midiDesc.dwInstance; +} + +static UINT midi_out_open(WORD dev_id, MIDIOPENDESC *midi_desc, UINT flags, struct notify_context *notify) +{ + struct midi_dest *dest; + int fd = -1; + + TRACE("(%04X, %p, %08X);\n", dev_id, midi_desc, flags); + if (midi_desc == NULL) + { + WARN("Invalid Parameter !\n"); + return MMSYSERR_INVALPARAM; + } + if (dev_id >= num_dests) + { + TRACE("MAX_MIDIOUTDRV reached !\n"); + return MMSYSERR_BADDEVICEID; + } + dest = dests + dev_id; + if (dest->midiDesc.hMidi != 0) + { + WARN("device already open !\n"); + return MMSYSERR_ALLOCATED; + } + if (!dest->bEnabled) + { + WARN("device disabled !\n"); + return MIDIERR_NODEVICE; + } + if ((flags & ~CALLBACK_TYPEMASK) != 0) + { + WARN("bad flags\n"); + return MMSYSERR_INVALFLAG; + } + + dest->lpExtra = NULL; + + switch (dest->caps.wTechnology) + { + case MOD_FMSYNTH: + { + void *extra; + + extra = malloc(offsetof(struct sFMextra, voice[dest->caps.wVoices])); + if (!extra) + { + WARN("can't alloc extra data !\n"); + return MMSYSERR_NOMEM; + } + dest->lpExtra = extra; + fd = seq_open(); + if (fd < 0) + { + dest->lpExtra = NULL; + free(extra); + return MMSYSERR_ERROR; + } + if (midi_out_fm_load(dev_id, fd) < 0) + { + seq_close(fd); + dest->lpExtra = NULL; + free(extra); + return MMSYSERR_ERROR; + } + midi_out_fm_reset(dev_id); + break; + } + case MOD_MIDIPORT: + case MOD_SYNTH: + fd = seq_open(); + if (fd < 0) + return MMSYSERR_ALLOCATED; + break; + default: + WARN("Technology not supported (yet) %d !\n", dest->caps.wTechnology); + return MMSYSERR_NOTENABLED; + } + + dest->runningStatus = 0; + dest->wFlags = HIWORD(flags & CALLBACK_TYPEMASK); + + dest->lpQueueHdr= NULL; + dest->midiDesc = *midi_desc; + dest->fd = fd; + + set_out_notify(notify, dest, dev_id, MOM_OPEN, 0, 0); + TRACE("Successful !\n"); + return MMSYSERR_NOERROR; +} + +static UINT midi_out_close(WORD dev_id, struct notify_context *notify) +{ + struct midi_dest *dest; + + TRACE("(%04X);\n", dev_id); + + if (dev_id >= num_dests) + { + TRACE("MAX_MIDIOUTDRV reached !\n"); + return MMSYSERR_BADDEVICEID; + } + dest = dests + dev_id; + + if (dest->midiDesc.hMidi == 0) + { + WARN("device not opened !\n"); + return MMSYSERR_ERROR; + } + /* FIXME: should test that no pending buffer is still in the queue for + * playing */ + + if (dest->fd == -1) + { + WARN("can't close !\n"); + return MMSYSERR_ERROR; + } + + switch (dest->caps.wTechnology) + { + case MOD_FMSYNTH: + case MOD_SYNTH: + case MOD_MIDIPORT: + seq_close(dest->fd); + break; + default: + WARN("Technology not supported (yet) %d !\n", dest->caps.wTechnology); + return MMSYSERR_NOTENABLED; + } + + free(dest->lpExtra); + dest->lpExtra = NULL; + dest->fd = -1; + + set_out_notify(notify, dest, dev_id, MOM_CLOSE, 0, 0); + dest->midiDesc.hMidi = 0; + return MMSYSERR_NOERROR; +} + +static UINT midi_out_fm_data(WORD dev_id, UINT data) +{ + struct midi_dest *dest = dests + dev_id; + BYTE evt = LOBYTE(LOWORD(data)), d1, d2; + sFMextra *extra = dest->lpExtra; + sVoice *voice = extra->voice; + sChannel *channel = extra->channel; + int chn = (evt & 0x0F), i, nv; + + if (evt & 0x80) + { + d1 = HIBYTE(LOWORD(data)); + d2 = LOBYTE(HIWORD(data)); + if (evt < 0xF0) + dest->runningStatus = evt; + } + else if (dest->runningStatus) + { + evt = dest->runningStatus; + d1 = LOBYTE(LOWORD(data)); + d2 = HIBYTE(LOWORD(data)); + } + else + { + FIXME("ooch %x\n", data); + return MMSYSERR_NOERROR; + } + + /* FIXME: chorus depth controller is not used */ + + switch (evt & 0xF0) + { + case MIDI_NOTEOFF: + for (i = 0; i < dest->caps.wVoices; i++) + { + /* don't stop sustained notes */ + if (voice[i].status == sVS_PLAYING && voice[i].channel == chn && voice[i].note == d1) + { + voice[i].status = sVS_UNUSED; + SEQ_STOP_NOTE(dev_id, i, d1, d2); + } + } + break; + case MIDI_NOTEON: + if (d2 == 0) /* note off if velocity == 0 */ + { + for (i = 0; i < dest->caps.wVoices; i++) /* don't stop sustained notes */ + { + if (voice[i].status == sVS_PLAYING && voice[i].channel == chn && voice[i].note == d1) + { + voice[i].status = sVS_UNUSED; + SEQ_STOP_NOTE(dev_id, i, d1, 64); + } + } + break; + } + /* finding out in this order : + * - an empty voice + * - if replaying the same note on the same channel + * - the older voice (LRU) + */ + for (i = nv = 0; i < dest->caps.wVoices; i++) + { + if (voice[i].status == sVS_UNUSED || (voice[i].note == d1 && voice[i].channel == chn)) + { + nv = i; + break; + } + if (voice[i].cntMark < voice[0].cntMark) + nv = i; + } + TRACE("playing on voice=%d, pgm=%d, pan=0x%02X, vol=0x%02X, bender=0x%02X, note=0x%02X, vel=0x%02X\n", + nv, channel[chn].program, channel[chn].balance, channel[chn].volume, channel[chn].bender, d1, d2); + + SEQ_SET_PATCH(dev_id, nv, IS_DRUM_CHANNEL(extra, chn) ? + (128 + d1) : channel[chn].program); + SEQ_BENDER_RANGE(dev_id, nv, channel[chn].benderRange * 100); + SEQ_BENDER(dev_id, nv, channel[chn].bender); + SEQ_CONTROL(dev_id, nv, CTL_PAN, channel[chn].balance); + SEQ_CONTROL(dev_id, nv, CTL_EXPRESSION, channel[chn].expression); + SEQ_START_NOTE(dev_id, nv, d1, d2); + voice[nv].status = channel[chn].sustain ? sVS_SUSTAINED : sVS_PLAYING; + voice[nv].note = d1; + voice[nv].channel = chn; + voice[nv].cntMark = extra->counter++; + break; + case MIDI_KEY_PRESSURE: + for (i = 0; i < dest->caps.wVoices; i++) + if (voice[i].status != sVS_UNUSED && voice[i].channel == chn && voice[i].note == d1) + SEQ_KEY_PRESSURE(dev_id, i, d1, d2); + break; + case MIDI_CTL_CHANGE: + switch (d1) + { + case CTL_BANK_SELECT: channel[chn].bank = d2; break; + case CTL_MAIN_VOLUME: channel[chn].volume = d2; break; + case CTL_PAN: channel[chn].balance = d2; break; + case CTL_EXPRESSION: channel[chn].expression = d2; break; + case CTL_SUSTAIN: channel[chn].sustain = d2; + if (d2) + { + for (i = 0; i < dest->caps.wVoices; i++) + if (voice[i].status == sVS_PLAYING && voice[i].channel == chn) + voice[i].status = sVS_SUSTAINED; + } + else + { + for (i = 0; i < dest->caps.wVoices; i++) + { + if (voice[i].status == sVS_SUSTAINED && voice[i].channel == chn) + { + voice[i].status = sVS_UNUSED; + SEQ_STOP_NOTE(dev_id, i, voice[i].note, 64); + } + } + } + break; + case CTL_NONREG_PARM_NUM_LSB: channel[chn].nrgPmtLSB = d2; break; + case CTL_NONREG_PARM_NUM_MSB: channel[chn].nrgPmtMSB = d2; break; + case CTL_REGIST_PARM_NUM_LSB: channel[chn].regPmtLSB = d2; break; + case CTL_REGIST_PARM_NUM_MSB: channel[chn].regPmtMSB = d2; break; + case CTL_DATA_ENTRY: + switch ((channel[chn].regPmtMSB << 8) | channel[chn].regPmtLSB) + { + case 0x0000: + if (channel[chn].benderRange != d2) + { + channel[chn].benderRange = d2; + for (i = 0; i < dest->caps.wVoices; i++) + if (voice[i].channel == chn) + SEQ_BENDER_RANGE(dev_id, i, channel[chn].benderRange); + } + break; + + case 0x7F7F: + channel[chn].benderRange = 2; + for (i = 0; i < dest->caps.wVoices; i++) + if (voice[i].channel == chn) + SEQ_BENDER_RANGE(dev_id, i, channel[chn].benderRange); + break; + default: + TRACE("Data entry: regPmt=0x%02x%02x, nrgPmt=0x%02x%02x with %x\n", + channel[chn].regPmtMSB, channel[chn].regPmtLSB, + channel[chn].nrgPmtMSB, channel[chn].nrgPmtLSB, d2); + break; + } + break; + + case 0x78: /* all sounds off */ + /* FIXME: I don't know if I have to take care of the channel for this control? */ + for (i = 0; i < dest->caps.wVoices; i++) + { + if (voice[i].status != sVS_UNUSED && voice[i].channel == chn) + { + voice[i].status = sVS_UNUSED; + SEQ_STOP_NOTE(dev_id, i, voice[i].note, 64); + } + } + break; + case 0x7B: /* all notes off */ + /* FIXME: I don't know if I have to take care of the channel for this control? */ + for (i = 0; i < dest->caps.wVoices; i++) + { + if (voice[i].status == sVS_PLAYING && voice[i].channel == chn) + { + voice[i].status = sVS_UNUSED; + SEQ_STOP_NOTE(dev_id, i, voice[i].note, 64); + } + } + break; + default: + TRACE("Dropping MIDI control event 0x%02x(%02x) on channel %d\n", d1, d2, chn); + break; + } + break; + case MIDI_PGM_CHANGE: + channel[chn].program = d1; + break; + case MIDI_CHN_PRESSURE: + for (i = 0; i < dest->caps.wVoices; i++) + if (voice[i].status != sVS_UNUSED && voice[i].channel == chn) + SEQ_KEY_PRESSURE(dev_id, i, voice[i].note, d1); + + break; + case MIDI_PITCH_BEND: + channel[chn].bender = (d2 << 7) + d1; + for (i = 0; i < dest->caps.wVoices; i++) + if (voice[i].channel == chn) + SEQ_BENDER(dev_id, i, channel[chn].bender); + break; + case MIDI_SYSTEM_PREFIX: + switch (evt & 0x0F) + { + case 0x0F: /* Reset */ + midi_out_fm_reset(dev_id); + dest->runningStatus = 0; + break; + default: + WARN("Unsupported (yet) system event %02x\n", evt & 0x0F); + } + if (evt <= 0xF7) + dest->runningStatus = 0; + break; + default: + WARN("Internal error, shouldn't happen (event=%08x)\n", evt & 0xF0); + return MMSYSERR_NOTENABLED; + } + + SEQ_DUMPBUF(); + return MMSYSERR_NOERROR; +} + +static UINT midi_out_port_data(WORD dev_id, UINT data) +{ + struct midi_dest *dest = dests + dev_id; + BYTE evt = LOBYTE(LOWORD(data)), d1, d2; + int dev = dev_id - num_synths; + + if (dev < 0) + { + WARN("Internal error on devID (%u) !\n", dev_id); + return MIDIERR_NODEVICE; + } + + if (evt & 0x80) + { + d1 = HIBYTE(LOWORD(data)); + d2 = LOBYTE(HIWORD(data)); + } + else if (dest->runningStatus) + { + evt = dest->runningStatus; + d1 = LOBYTE(LOWORD(data)); + d2 = HIBYTE(LOWORD(data)); + } + else + { + FIXME("ooch %x\n", data); + return MMSYSERR_NOERROR; + } + + switch (evt & 0xF0) + { + case MIDI_NOTEOFF: + case MIDI_NOTEON: + case MIDI_KEY_PRESSURE: + case MIDI_CTL_CHANGE: + case MIDI_PITCH_BEND: + if (LOBYTE(LOWORD(data)) >= 0x80) + { + SEQ_MIDIOUT(dev, evt); + dest->runningStatus = evt; + } + SEQ_MIDIOUT(dev, d1); + SEQ_MIDIOUT(dev, d2); + break; + case MIDI_PGM_CHANGE: + case MIDI_CHN_PRESSURE: + if (LOBYTE(LOWORD(data)) >= 0x80) + { + SEQ_MIDIOUT(dev, evt); + dest->runningStatus = evt; + } + SEQ_MIDIOUT(dev, d1); + break; + case MIDI_SYSTEM_PREFIX: + switch (evt & 0x0F) + { + case 0x00: /* System Exclusive, don't do it on MODM_DATA, should require MODM_LONGDATA */ + case 0x04: /* Undefined. */ + case 0x05: /* Undefined. */ + case 0x07: /* End of Exclusive. */ + case 0x09: /* Undefined. */ + case 0x0D: /* Undefined. */ + break; + case 0x06: /* Tune Request */ + case 0x08: /* Timing Clock. */ + case 0x0A: /* Start. */ + case 0x0B: /* Continue */ + case 0x0C: /* Stop */ + case 0x0E: /* Active Sensing. */ + SEQ_MIDIOUT(dev, evt); + break; + case 0x0F: /* Reset */ + SEQ_MIDIOUT(dev, MIDI_SYSTEM_PREFIX); + SEQ_MIDIOUT(dev, 0x7e); + SEQ_MIDIOUT(dev, 0x7f); + SEQ_MIDIOUT(dev, 0x09); + SEQ_MIDIOUT(dev, 0x01); + SEQ_MIDIOUT(dev, 0xf7); + dest->runningStatus = 0; + break; + case 0x01: /* MTC Quarter frame */ + case 0x03: /* Song Select. */ + SEQ_MIDIOUT(dev, evt); + SEQ_MIDIOUT(dev, d1); + break; + case 0x02: /* Song Position Pointer. */ + SEQ_MIDIOUT(dev, evt); + SEQ_MIDIOUT(dev, d1); + SEQ_MIDIOUT(dev, d2); + } + if (evt <= 0xF7) /* System Exclusive, System Common Message */ + dest->runningStatus = 0; + break; + } + + SEQ_DUMPBUF(); + return MMSYSERR_NOERROR; +} + +static UINT midi_out_data(WORD dev_id, UINT data) +{ + struct midi_dest *dest; + + TRACE("(%04X, %08X);\n", dev_id, data); + + if (dev_id >= num_dests) return MMSYSERR_BADDEVICEID; + dest = dests + dev_id; + if (!dest->bEnabled) return MIDIERR_NODEVICE; + + if (dest->fd == -1) + { + WARN("can't play !\n"); + return MIDIERR_NODEVICE; + } + switch (dest->caps.wTechnology) + { + case MOD_FMSYNTH: + return midi_out_fm_data(dev_id, data); + case MOD_MIDIPORT: + return midi_out_port_data(dev_id, data); + } + + WARN("Technology not supported (yet) %d !\n", dest->caps.wTechnology); + return MMSYSERR_NOTENABLED; +} + +static UINT midi_out_long_data(WORD dev_id, MIDIHDR *hdr, UINT hdr_size, struct notify_context *notify) +{ + struct midi_dest *dest; + BYTE *data; + unsigned int count; + + TRACE("(%04X, %p, %08X);\n", dev_id, hdr, hdr_size); + + /* Note: MS doc does not say much about the dwBytesRecorded member of the MIDIHDR structure + * but it seems to be used only for midi input. + * Taking a look at the WAVEHDR structure (which is quite similar) confirms this assumption. + */ + + if (dev_id >= num_dests) return MMSYSERR_BADDEVICEID; + dest = dests + dev_id; + if (!dest->bEnabled) return MIDIERR_NODEVICE; + + if (dest->fd == -1) + { + WARN("can't play !\n"); + return MIDIERR_NODEVICE; + } + + data = (BYTE *)hdr->lpData; + + if (data == NULL) + return MIDIERR_UNPREPARED; + if (!(hdr->dwFlags & MHDR_PREPARED)) + return MIDIERR_UNPREPARED; + if (hdr->dwFlags & MHDR_INQUEUE) + return MIDIERR_STILLPLAYING; + hdr->dwFlags &= ~MHDR_DONE; + hdr->dwFlags |= MHDR_INQUEUE; + + /* FIXME: MS doc is not 100% clear. Will lpData only contain system exclusive + * data, or can it also contain raw MIDI data, to be split up and sent to + * modShortData() ? + * If the latter is true, then the following WARNing will fire up + */ + if (data[0] != 0xF0 || data[hdr->dwBufferLength - 1] != 0xF7) + WARN("The allegedly system exclusive buffer is not correct\n\tPlease report with MIDI file\n"); + + TRACE("dwBufferLength=%u !\n", (unsigned)hdr->dwBufferLength); + TRACE(" %02X %02X %02X ... %02X %02X %02X\n", + data[0], data[1], data[2], data[hdr->dwBufferLength - 3], + data[hdr->dwBufferLength - 2], data[hdr->dwBufferLength - 1]); + + switch (dest->caps.wTechnology) + { + case MOD_FMSYNTH: + /* FIXME: I don't think there is much to do here */ + break; + case MOD_MIDIPORT: + if (data[0] != 0xF0) + { + /* Send end of System Exclusive */ + SEQ_MIDIOUT(dev_id - num_synths, 0xF0); + WARN("Adding missing 0xF0 marker at the beginning of system exclusive byte stream\n"); + } + for (count = 0; count < hdr->dwBufferLength; count++) + SEQ_MIDIOUT(dev_id - num_synths, data[count]); + if (data[count - 1] != 0xF7) + { + /* Send end of System Exclusive */ + SEQ_MIDIOUT(dev_id - num_synths, 0xF7); + WARN("Adding missing 0xF7 marker at the end of system exclusive byte stream\n"); + } + SEQ_DUMPBUF(); + break; + default: + WARN("Technology not supported (yet) %d !\n", dest->caps.wTechnology); + return MMSYSERR_NOTENABLED; + } + + dest->runningStatus = 0; + hdr->dwFlags &= ~MHDR_INQUEUE; + hdr->dwFlags |= MHDR_DONE; + set_out_notify(notify, dest, dev_id, MOM_DONE, (UINT_PTR)hdr, 0); + return MMSYSERR_NOERROR; +} + +static UINT midi_out_prepare(WORD dev_id, MIDIHDR *hdr, UINT hdr_size) +{ + TRACE("(%04X, %p, %d);\n", dev_id, hdr, hdr_size); + + if (hdr_size < offsetof(MIDIHDR, dwOffset) || !hdr || !hdr->lpData) + return MMSYSERR_INVALPARAM; + if (hdr->dwFlags & MHDR_PREPARED) + return MMSYSERR_NOERROR; + + hdr->lpNext = 0; + hdr->dwFlags |= MHDR_PREPARED; + hdr->dwFlags &= ~(MHDR_DONE | MHDR_INQUEUE); + return MMSYSERR_NOERROR; +} + +static UINT midi_out_unprepare(WORD dev_id, MIDIHDR *hdr, UINT hdr_size) +{ + TRACE("(%04X, %p, %d);\n", dev_id, hdr, hdr_size); + + if (hdr_size < offsetof(MIDIHDR, dwOffset) || !hdr || !hdr->lpData) + return MMSYSERR_INVALPARAM; + if (!(hdr->dwFlags & MHDR_PREPARED)) + return MMSYSERR_NOERROR; + if (hdr->dwFlags & MHDR_INQUEUE) + return MIDIERR_STILLPLAYING; + + hdr->dwFlags &= ~MHDR_PREPARED; + return MMSYSERR_NOERROR; +} + +static UINT midi_out_get_devcaps(WORD dev_id, MIDIOUTCAPSW *caps, UINT size) +{ + TRACE("(%04X, %p, %08X);\n", dev_id, caps, size); + + if (dev_id >= num_dests) return MMSYSERR_BADDEVICEID; + if (!caps) return MMSYSERR_INVALPARAM; + + memcpy(caps, &dests[dev_id].caps, min(size, sizeof(*caps))); + + return MMSYSERR_NOERROR; +} + +static UINT midi_out_get_volume(WORD dev_id, UINT *volume) +{ + if (!volume) return MMSYSERR_INVALPARAM; + if (dev_id >= num_dests) return MMSYSERR_BADDEVICEID; + + *volume = 0xFFFFFFFF; + return (dests[dev_id].caps.dwSupport & MIDICAPS_VOLUME) ? 0 : MMSYSERR_NOTSUPPORTED; +} + +static UINT midi_out_reset(WORD dev_id) +{ + struct midi_dest *dest; + unsigned chn; + + TRACE("(%04X);\n", dev_id); + + if (dev_id >= num_dests) return MMSYSERR_BADDEVICEID; + dest = dests + dev_id; + if (!dest->bEnabled) return MIDIERR_NODEVICE; + + /* stop all notes */ + for (chn = 0; chn < 16; chn++) + { + /* turn off every note */ + midi_out_data(dev_id, 0x7800 | MIDI_CTL_CHANGE | chn); + /* remove sustain on all channels */ + midi_out_data(dev_id, (CTL_SUSTAIN << 8) | MIDI_CTL_CHANGE | chn); + } + dest->runningStatus = 0; + /* FIXME: the LongData buffers must also be returned to the app */ + return MMSYSERR_NOERROR; +} + +static void handle_sysex_data(struct midi_src *src, unsigned char value, UINT time) +{ + struct notify_context notify; + MIDIHDR *hdr; + BOOL done = FALSE; + + src->state |= 2; + src->incLen = 0; + + in_buffer_lock(); + + hdr = src->lpQueueHdr; + if (hdr) + { + BYTE *data = (BYTE *)hdr->lpData; + + data[hdr->dwBytesRecorded++] = value; + if (hdr->dwBytesRecorded == hdr->dwBufferLength) + done = TRUE; + } + + if (value == 0xf7) /* end */ + { + src->state &= ~2; + done = TRUE; + } + + if (done && hdr) + { + src->lpQueueHdr = hdr->lpNext; + hdr->dwFlags &= ~MHDR_INQUEUE; + hdr->dwFlags |= MHDR_DONE; + set_in_notify(¬ify, src, src - srcs, MIM_LONGDATA, (UINT_PTR)hdr, time); + notify_post(¬ify); + } + + in_buffer_unlock(); +} + +static void handle_regular_data(struct midi_src *src, unsigned char value, UINT time) +{ + struct notify_context notify; + UINT to_send = 0; + +#define IS_CMD(_x) (((_x) & 0x80) == 0x80) +#define IS_SYS_CMD(_x) (((_x) & 0xF0) == 0xF0) + + if (!IS_CMD(value) && src->incLen == 0) /* try to reuse old cmd */ + { + if (IS_CMD(src->incPrev) && !IS_SYS_CMD(src->incPrev)) + { + src->incoming[0] = src->incPrev; + src->incLen = 1; + } + else + { + /* FIXME: should generate MIM_ERROR notification */ + return; + } + } + src->incoming[(int)src->incLen++] = value; + if (src->incLen == 1 && !IS_SYS_CMD(src->incoming[0])) + /* store new cmd, just in case */ + src->incPrev = src->incoming[0]; + +#undef IS_CMD +#undef IS_SYS_CMD + + switch (src->incoming[0] & 0xF0) + { + case MIDI_NOTEOFF: + case MIDI_NOTEON: + case MIDI_KEY_PRESSURE: + case MIDI_CTL_CHANGE: + case MIDI_PITCH_BEND: + if (src->incLen == 3) + to_send = (src->incoming[2] << 16) | (src->incoming[1] << 8) | + src->incoming[0]; + break; + case MIDI_PGM_CHANGE: + case MIDI_CHN_PRESSURE: + if (src->incLen == 2) + to_send = (src->incoming[1] << 8) | src->incoming[0]; + break; + case MIDI_SYSTEM_PREFIX: + if (src->incLen == 1) + to_send = src->incoming[0]; + break; + } + + if (to_send) + { + src->incLen = 0; + set_in_notify(¬ify, src, src - srcs, MIM_DATA, to_send, time); + notify_post(¬ify); + } +} + +static void handle_midi_data(unsigned char *buffer, unsigned int len) +{ + unsigned int time = get_time_msec(), i; + struct midi_src *src; + unsigned char value; + WORD dev_id; + + for (i = 0; i < len; i += (buffer[i] & 0x80) ? 8 : 4) + { + if (buffer[i] != SEQ_MIDIPUTC) continue; + + dev_id = buffer[i + 2]; + value = buffer[i + 1]; + + if (dev_id >= num_srcs) continue; + src = srcs + dev_id; + if (src->state <= 0) continue; + + if (value == 0xf0 || src->state & 2) /* system exclusive */ + handle_sysex_data(src, value, time - src->startTime); + else + handle_regular_data(src, value, time - src->startTime); + } +} + +static void *rec_thread_proc(void *arg) +{ + int fd = PtrToLong(arg); + unsigned char buffer[256]; + int len; + struct pollfd pollfd[2]; + + pollfd[0].fd = rec_cancel_pipe[0]; + pollfd[0].events = POLLIN; + pollfd[1].fd = fd; + pollfd[1].events = POLLIN; + + while (1) + { + /* Check if an event is present */ + if (poll(pollfd, ARRAY_SIZE(pollfd), -1) <= 0) + continue; + + if (pollfd[0].revents & POLLIN) /* cancelled */ + break; + + len = read(fd, buffer, sizeof(buffer)); + + if (len > 0 && len % 4 == 0) + handle_midi_data(buffer, len); + } + return NULL; +} + +static UINT midi_in_open(WORD dev_id, MIDIOPENDESC *desc, UINT flags, struct notify_context *notify) +{ + struct midi_src *src; + int fd; + + TRACE("(%04X, %p, %08X);\n", dev_id, desc, flags); + + if (desc == NULL) + { + WARN("Invalid Parameter !\n"); + return MMSYSERR_INVALPARAM; + } + + /* FIXME : + * how to check that content of lpDesc is correct ? + */ + if (dev_id >= num_srcs) + { + WARN("wDevID too large (%u) !\n", dev_id); + return MMSYSERR_BADDEVICEID; + } + src = srcs + dev_id; + if (src->state == -1) + { + WARN("device disabled\n"); + return MIDIERR_NODEVICE; + } + if (src->midiDesc.hMidi != 0) + { + WARN("device already open !\n"); + return MMSYSERR_ALLOCATED; + } + if ((flags & MIDI_IO_STATUS) != 0) + { + WARN("No support for MIDI_IO_STATUS in dwFlags yet, ignoring it\n"); + flags &= ~MIDI_IO_STATUS; + } + if ((flags & ~CALLBACK_TYPEMASK) != 0) + { + FIXME("Bad flags\n"); + return MMSYSERR_INVALFLAG; + } + + fd = seq_open(); + if (fd < 0) + return MMSYSERR_ERROR; + + if (num_midi_in_started++ == 0) + { + pipe(rec_cancel_pipe); + if (pthread_create(&rec_thread_id, NULL, rec_thread_proc, LongToPtr(fd))) + { + close(rec_cancel_pipe[0]); + close(rec_cancel_pipe[1]); + num_midi_in_started = 0; + WARN("Couldn't create thread for midi-in\n"); + seq_close(fd); + return MMSYSERR_ERROR; + } + TRACE("Created thread for midi-in\n"); + } + + src->wFlags = HIWORD(flags & CALLBACK_TYPEMASK); + + src->lpQueueHdr = NULL; + src->midiDesc = *desc; + src->state = 0; + src->incLen = 0; + src->startTime = 0; + src->fd = fd; + + set_in_notify(notify, src, dev_id, MIM_OPEN, 0, 0); + return MMSYSERR_NOERROR; +} + +static UINT midi_in_close(WORD dev_id, struct notify_context *notify) +{ + struct midi_src *src; + + TRACE("(%04X);\n", dev_id); + + if (dev_id >= num_srcs) + { + WARN("dev_id too big (%u) !\n", dev_id); + return MMSYSERR_BADDEVICEID; + } + src = srcs + dev_id; + if (src->midiDesc.hMidi == 0) + { + WARN("device not opened !\n"); + return MMSYSERR_ERROR; + } + if (src->lpQueueHdr != 0) + return MIDIERR_STILLPLAYING; + + if (src->fd == -1) + { + WARN("ooops !\n"); + return MMSYSERR_ERROR; + } + if (--num_midi_in_started == 0) + { + TRACE("Stopping thread for midi-in\n"); + write(rec_cancel_pipe[1], "x", 1); + pthread_join(rec_thread_id, NULL); + close(rec_cancel_pipe[0]); + close(rec_cancel_pipe[1]); + TRACE("Stopped thread for midi-in\n"); + } + seq_close(src->fd); + src->fd = -1; + + set_in_notify(notify, src, dev_id, MIM_CLOSE, 0, 0); + src->midiDesc.hMidi = 0; + + return MMSYSERR_NOERROR; +} + +static UINT midi_in_add_buffer(WORD dev_id, MIDIHDR *hdr, UINT hdr_size) +{ + struct midi_src *src; + MIDIHDR **next; + + TRACE("(%04X, %p, %d);\n", dev_id, hdr, hdr_size); + + if (dev_id >= num_srcs) return MMSYSERR_BADDEVICEID; + src = srcs + dev_id; + if (src->state == -1) return MIDIERR_NODEVICE; + + if (!hdr || hdr_size < offsetof(MIDIHDR, dwOffset) || !hdr->dwBufferLength) + return MMSYSERR_INVALPARAM; + if (hdr->dwFlags & MHDR_INQUEUE) return MIDIERR_STILLPLAYING; + if (!(hdr->dwFlags & MHDR_PREPARED)) return MIDIERR_UNPREPARED; + + in_buffer_lock(); + + hdr->dwFlags &= ~WHDR_DONE; + hdr->dwFlags |= MHDR_INQUEUE; + hdr->dwBytesRecorded = 0; + hdr->lpNext = NULL; + + next = &src->lpQueueHdr; + while (*next) next = &(*next)->lpNext; + *next = hdr; + + in_buffer_unlock(); + + return MMSYSERR_NOERROR; +} + +static UINT midi_in_prepare(WORD dev_id, MIDIHDR *hdr, UINT hdr_size) +{ + TRACE("(%04X, %p, %d);\n", dev_id, hdr, hdr_size); + + if (hdr_size < offsetof(MIDIHDR, dwOffset) || !hdr || !hdr->lpData) + return MMSYSERR_INVALPARAM; + if (hdr->dwFlags & MHDR_PREPARED) + return MMSYSERR_NOERROR; + + hdr->lpNext = NULL; + hdr->dwFlags |= MHDR_PREPARED; + hdr->dwFlags &= ~(MHDR_DONE | MHDR_INQUEUE); + + return MMSYSERR_NOERROR; +} + +static UINT midi_in_unprepare(WORD dev_id, MIDIHDR *hdr, UINT hdr_size) +{ + TRACE("(%04X, %p, %d);\n", dev_id, hdr, hdr_size); + + if (hdr_size < offsetof(MIDIHDR, dwOffset) || !hdr || !hdr->lpData) + return MMSYSERR_INVALPARAM; + if (!(hdr->dwFlags & MHDR_PREPARED)) + return MMSYSERR_NOERROR; + if (hdr->dwFlags & MHDR_INQUEUE) + return MIDIERR_STILLPLAYING; + + hdr->dwFlags &= ~MHDR_PREPARED; + + return MMSYSERR_NOERROR; +} + +static UINT midi_in_get_devcaps(WORD dev_id, MIDIINCAPSW *caps, UINT size) +{ + TRACE("(%04X, %p, %08X);\n", dev_id, caps, size); + + if (dev_id >= num_srcs) return MMSYSERR_BADDEVICEID; + if (!caps) return MMSYSERR_INVALPARAM; + + memcpy(caps, &srcs[dev_id].caps, min(size, sizeof(*caps))); + + return MMSYSERR_NOERROR; +} + +static UINT midi_in_start(WORD dev_id) +{ + struct midi_src *src; + + TRACE("(%04X);\n", dev_id); + + if (dev_id >= num_srcs) return MMSYSERR_BADDEVICEID; + src = srcs + dev_id; + if (src->state == -1) return MIDIERR_NODEVICE; + + src->state = 1; + src->startTime = get_time_msec(); + return MMSYSERR_NOERROR; +} + +static UINT midi_in_stop(WORD dev_id) +{ + struct midi_src *src; + + TRACE("(%04X);\n", dev_id); + + if (dev_id >= num_srcs) return MMSYSERR_BADDEVICEID; + src = srcs + dev_id; + if (src->state == -1) return MIDIERR_NODEVICE; + + src->state = 0; + return MMSYSERR_NOERROR; +} + +static UINT midi_in_reset(WORD dev_id, struct notify_context *notify) +{ + UINT cur_time = get_time_msec(); + UINT err = MMSYSERR_NOERROR; + struct midi_src *src; + MIDIHDR *hdr; + + TRACE("(%04X);\n", dev_id); + + if (dev_id >= num_srcs) return MMSYSERR_BADDEVICEID; + src = srcs + dev_id; + if (src->state == -1) return MIDIERR_NODEVICE; + + in_buffer_lock(); + + if (src->lpQueueHdr) + { + hdr = src->lpQueueHdr; + src->lpQueueHdr = hdr->lpNext; + hdr->dwFlags &= ~MHDR_INQUEUE; + hdr->dwFlags |= MHDR_DONE; + set_in_notify(notify, src, dev_id, MIM_LONGDATA, (UINT_PTR)hdr, cur_time - src->startTime); + if (src->lpQueueHdr) err = ERROR_RETRY; /* ask the client to call again */ + } + + in_buffer_unlock(); + + return err; +} + +NTSTATUS oss_midi_out_message(void *args) +{ + struct midi_out_message_params *params = args; + + params->notify->send_notify = FALSE; + + switch (params->msg) + { + case DRVM_INIT: + *params->err = oss_midi_init(); + break; + case DRVM_EXIT: + *params->err = midi_exit(); + break; + case DRVM_ENABLE: + case DRVM_DISABLE: + /* FIXME: Pretend this is supported */ + *params->err = MMSYSERR_NOERROR; + break; + case MODM_OPEN: + *params->err = midi_out_open(params->dev_id, (MIDIOPENDESC *)params->param_1, params->param_2, params->notify); + break; + case MODM_CLOSE: + *params->err = midi_out_close(params->dev_id, params->notify); + break; + case MODM_DATA: + *params->err = midi_out_data(params->dev_id, params->param_1); + break; + case MODM_LONGDATA: + *params->err = midi_out_long_data(params->dev_id, (MIDIHDR *)params->param_1, params->param_2, params->notify); + break; + case MODM_PREPARE: + *params->err = midi_out_prepare(params->dev_id, (MIDIHDR *)params->param_1, params->param_2); + break; + case MODM_UNPREPARE: + *params->err = midi_out_unprepare(params->dev_id, (MIDIHDR *)params->param_1, params->param_2); + break; + case MODM_GETDEVCAPS: + *params->err = midi_out_get_devcaps(params->dev_id, (MIDIOUTCAPSW *)params->param_1, params->param_2); + break; + case MODM_GETNUMDEVS: + *params->err = num_dests; + break; + case MODM_GETVOLUME: + *params->err = midi_out_get_volume(params->dev_id, (UINT *)params->param_1); + break; + case MODM_SETVOLUME: + *params->err = 0; + break; + case MODM_RESET: + *params->err = midi_out_reset(params->dev_id); + break; + default: + TRACE("Unsupported message\n"); + *params->err = MMSYSERR_NOTSUPPORTED; + } + + return STATUS_SUCCESS; +} + +NTSTATUS oss_midi_in_message(void *args) +{ + struct midi_in_message_params *params = args; + + params->notify->send_notify = FALSE; + + switch (params->msg) + { + case DRVM_INIT: + *params->err = oss_midi_init(); + break; + case DRVM_EXIT: + *params->err = midi_exit(); + break; + case DRVM_ENABLE: + case DRVM_DISABLE: + /* FIXME: Pretend this is supported */ + *params->err = MMSYSERR_NOERROR; + break; + case MIDM_OPEN: + *params->err = midi_in_open(params->dev_id, (MIDIOPENDESC *)params->param_1, params->param_2, params->notify); + break; + case MIDM_CLOSE: + *params->err = midi_in_close(params->dev_id, params->notify); + break; + case MIDM_ADDBUFFER: + *params->err = midi_in_add_buffer(params->dev_id, (MIDIHDR *)params->param_1, params->param_2); + break; + case MIDM_PREPARE: + *params->err = midi_in_prepare(params->dev_id, (MIDIHDR *)params->param_1, params->param_2); + break; + case MIDM_UNPREPARE: + *params->err = midi_in_unprepare(params->dev_id, (MIDIHDR *)params->param_1, params->param_2); + break; + case MIDM_GETDEVCAPS: + *params->err = midi_in_get_devcaps(params->dev_id, (MIDIINCAPSW *)params->param_1, params->param_2); + break; + case MIDM_GETNUMDEVS: + *params->err = num_srcs; + break; + case MIDM_START: + *params->err = midi_in_start(params->dev_id); + break; + case MIDM_STOP: + *params->err = midi_in_stop(params->dev_id); + break; + case MIDM_RESET: + *params->err = midi_in_reset(params->dev_id, params->notify); + break; + default: + TRACE("Unsupported message\n"); + *params->err = MMSYSERR_NOTSUPPORTED; + } + + return STATUS_SUCCESS; +} + +NTSTATUS oss_midi_notify_wait(void *args) +{ + struct midi_notify_wait_params *params = args; + + pthread_mutex_lock(¬ify_mutex); + + while (!notify_quit && notify_buffer_empty()) + pthread_cond_wait(¬ify_read_cond, ¬ify_mutex); + + *params->quit = notify_quit; + if (!notify_quit) + { + notify_buffer_remove(params->notify); + pthread_cond_signal(¬ify_write_cond); + } + pthread_mutex_unlock(¬ify_mutex); + + return STATUS_SUCCESS; +} + +#ifdef _WIN64 + +typedef UINT PTR32; + +struct notify_context32 +{ + BOOL send_notify; + WORD dev_id; + WORD msg; + UINT param_1; + UINT param_2; + UINT callback; + UINT flags; + PTR32 device; + UINT instance; +}; + +static void notify_to_notify32(struct notify_context32 *notify32, + const struct notify_context *notify) +{ + notify32->send_notify = notify->send_notify; + notify32->dev_id = notify->dev_id; + notify32->msg = notify->msg; + notify32->param_1 = notify->param_1; + notify32->param_2 = notify->param_2; + notify32->callback = notify->callback; + notify32->flags = notify->flags; + notify32->device = PtrToUlong(notify->device); + notify32->instance = notify->instance; +} + +struct midi_open_desc32 +{ + PTR32 hMidi; + UINT dwCallback; + UINT dwInstance; + UINT dnDevNode; + UINT cIds; + MIDIOPENSTRMID rgIds; +}; + +struct midi_hdr32 +{ + PTR32 lpData; + UINT dwBufferLength; + UINT dwBytesRecorded; + UINT dwUser; + UINT dwFlags; + PTR32 lpNext; + UINT reserved; + UINT dwOffset; + UINT dwReserved[8]; +}; + +static UINT wow64_midi_out_prepare(WORD dev_id, struct midi_hdr32 *hdr, UINT hdr_size) +{ + TRACE("(%04X, %p, %d);\n", dev_id, hdr, hdr_size); + + if (hdr_size < offsetof(struct midi_hdr32, dwOffset) || !hdr || !hdr->lpData) + return MMSYSERR_INVALPARAM; + if (hdr->dwFlags & MHDR_PREPARED) + return MMSYSERR_NOERROR; + + hdr->lpNext = 0; + hdr->dwFlags |= MHDR_PREPARED; + hdr->dwFlags &= ~(MHDR_DONE | MHDR_INQUEUE); + return MMSYSERR_NOERROR; +} + +static UINT wow64_midi_out_unprepare(WORD dev_id, struct midi_hdr32 *hdr, UINT hdr_size) +{ + TRACE("(%04X, %p, %d);\n", dev_id, hdr, hdr_size); + + if (hdr_size < offsetof(struct midi_hdr32, dwOffset) || !hdr || !hdr->lpData) + return MMSYSERR_INVALPARAM; + if (!(hdr->dwFlags & MHDR_PREPARED)) + return MMSYSERR_NOERROR; + if (hdr->dwFlags & MHDR_INQUEUE) + return MIDIERR_STILLPLAYING; + + hdr->dwFlags &= ~MHDR_PREPARED; + return MMSYSERR_NOERROR; +} + +NTSTATUS oss_wow64_midi_out_message(void *args) +{ + struct + { + UINT dev_id; + UINT msg; + UINT user; + UINT param_1; + UINT param_2; + PTR32 err; + PTR32 notify; + } *params32 = args; + struct notify_context32 *notify32 = ULongToPtr(params32->notify); + struct midi_open_desc32 *desc32; + struct midi_hdr32 *hdr32; + struct notify_context notify; + MIDIOPENDESC open_desc; + MIDIHDR hdr; + struct midi_out_message_params params = + { + .dev_id = params32->dev_id, + .msg = params32->msg, + .user = params32->user, + .param_1 = params32->param_1, + .param_2 = params32->param_2, + .err = ULongToPtr(params32->err), + .notify = ¬ify + }; + notify32->send_notify = FALSE; + + switch (params32->msg) + { + case MODM_OPEN: + desc32 = ULongToPtr(params32->param_1); + + open_desc.hMidi = ULongToPtr(desc32->hMidi); + open_desc.dwCallback = desc32->dwCallback; + open_desc.dwInstance = desc32->dwInstance; + open_desc.dnDevNode = desc32->dnDevNode; + open_desc.cIds = desc32->cIds; + open_desc.rgIds.dwStreamID = desc32->rgIds.dwStreamID; + open_desc.rgIds.wDeviceID = desc32->rgIds.wDeviceID; + + params.param_1 = (UINT_PTR)&open_desc; + break; + + case MODM_LONGDATA: + hdr32 = ULongToPtr(params32->param_1); + + memset(&hdr, 0, sizeof(hdr)); + hdr.lpData = ULongToPtr(hdr32->lpData); + hdr.dwBufferLength = hdr32->dwBufferLength; + hdr.dwFlags = hdr32->dwFlags; + + params.param_1 = (UINT_PTR)&hdr; + params.param_2 = sizeof(hdr); + break; + + case MODM_PREPARE: /* prepare and unprepare are easier to handle explicitly */ + hdr32 = ULongToPtr(params32->param_1); + + *params.err = wow64_midi_out_prepare(params32->dev_id, hdr32, params32->param_2); + return STATUS_SUCCESS; + + case MODM_UNPREPARE: + hdr32 = ULongToPtr(params32->param_1); + + *params.err = wow64_midi_out_unprepare(params32->dev_id, hdr32, params32->param_2); + return STATUS_SUCCESS; + } + + oss_midi_out_message(¶ms); + + switch (params32->msg) + { + case MODM_LONGDATA: + hdr32 = ULongToPtr(params32->param_1); + + hdr32->dwFlags = hdr.dwFlags; + break; + } + + if (notify.send_notify) + { + notify_to_notify32(notify32, ¬ify); + + if (notify.msg == MOM_DONE) + notify32->param_1 = params32->param_1; /* restore the 32-bit hdr */ + } + return STATUS_SUCCESS; +} + +static UINT wow64_midi_in_prepare(WORD dev_id, struct midi_hdr32 *hdr, UINT hdr_size) +{ + TRACE("(%04X, %p, %d);\n", dev_id, hdr, hdr_size); + + if (hdr_size < offsetof(struct midi_hdr32, dwOffset) || !hdr || !hdr->lpData) + return MMSYSERR_INVALPARAM; + if (hdr->dwFlags & MHDR_PREPARED) + return MMSYSERR_NOERROR; + + hdr->lpNext = 0; + hdr->dwFlags |= MHDR_PREPARED; + hdr->dwFlags &= ~(MHDR_DONE | MHDR_INQUEUE); + + return MMSYSERR_NOERROR; +} + +static UINT wow64_midi_in_unprepare(WORD dev_id, struct midi_hdr32 *hdr, UINT hdr_size) +{ + TRACE("(%04X, %p, %d);\n", dev_id, hdr, hdr_size); + + if (hdr_size < offsetof(struct midi_hdr32, dwOffset) || !hdr || !hdr->lpData) + return MMSYSERR_INVALPARAM; + if (!(hdr->dwFlags & MHDR_PREPARED)) + return MMSYSERR_NOERROR; + if (hdr->dwFlags & MHDR_INQUEUE) + return MIDIERR_STILLPLAYING; + + hdr->dwFlags &= ~MHDR_PREPARED; + + return MMSYSERR_NOERROR; +} + +NTSTATUS oss_wow64_midi_in_message(void *args) +{ + struct + { + UINT dev_id; + UINT msg; + UINT user; + UINT param_1; + UINT param_2; + PTR32 err; + PTR32 notify; + } *params32 = args; + struct notify_context32 *notify32 = ULongToPtr(params32->notify); + struct midi_open_desc32 *desc32; + struct midi_hdr32 *hdr32; + struct notify_context notify; + MIDIOPENDESC open_desc; + MIDIHDR *hdr = NULL; + struct midi_in_message_params params = + { + .dev_id = params32->dev_id, + .msg = params32->msg, + .user = params32->user, + .param_1 = params32->param_1, + .param_2 = params32->param_2, + .err = ULongToPtr(params32->err), + .notify = ¬ify + }; + notify32->send_notify = FALSE; + + switch (params32->msg) + { + case MIDM_OPEN: + desc32 = ULongToPtr(params32->param_1); + + open_desc.hMidi = ULongToPtr(desc32->hMidi); + open_desc.dwCallback = desc32->dwCallback; + open_desc.dwInstance = desc32->dwInstance; + open_desc.dnDevNode = desc32->dnDevNode; + open_desc.cIds = desc32->cIds; + open_desc.rgIds.dwStreamID = desc32->rgIds.dwStreamID; + open_desc.rgIds.wDeviceID = desc32->rgIds.wDeviceID; + + params.param_1 = (UINT_PTR)&open_desc; + break; + + case MIDM_ADDBUFFER: + hdr32 = ULongToPtr(params32->param_1); + + hdr = calloc(1, sizeof(*hdr)); + hdr->lpData = ULongToPtr(hdr32->lpData); + hdr->dwBufferLength = hdr32->dwBufferLength; + hdr->dwFlags = hdr32->dwFlags; + hdr->dwReserved[7] = params32->param_1; /* keep hdr32 for MIM_LONGDATA notification */ + + params.param_1 = (UINT_PTR)hdr; + params.param_2 = sizeof(*hdr); + break; + + case MIDM_PREPARE: /* prepare and unprepare are easier to handle explicitly */ + hdr32 = ULongToPtr(params32->param_1); + + *params.err = wow64_midi_in_prepare(params32->dev_id, hdr32, params32->param_2); + return STATUS_SUCCESS; + + case MIDM_UNPREPARE: + hdr32 = ULongToPtr(params32->param_1); + + *params.err = wow64_midi_in_unprepare(params32->dev_id, hdr32, params32->param_2); + return STATUS_SUCCESS; + } + + oss_midi_in_message(¶ms); + + switch (params32->msg) + { + case MIDM_ADDBUFFER: + hdr32 = ULongToPtr(params32->param_1); + + if (!*params.err) + { + hdr32->dwFlags = hdr->dwFlags; + hdr32->dwBytesRecorded = hdr->dwBytesRecorded; + hdr32->lpNext = 0; + } + else + free(hdr); + break; + } + + if (notify.send_notify) + { + notify_to_notify32(notify32, ¬ify); + + if (notify.msg == MIM_LONGDATA) + { + hdr = (MIDIHDR *)notify.param_1; + notify32->param_1 = hdr->dwReserved[7]; + hdr32 = ULongToPtr(notify32->param_1); + hdr32->dwBytesRecorded = hdr->dwBytesRecorded; + hdr32->dwFlags = hdr->dwFlags; + free(hdr); + } + } + return STATUS_SUCCESS; +} + +NTSTATUS oss_wow64_midi_notify_wait(void *args) +{ + struct + { + PTR32 quit; + PTR32 notify; + } *params32 = args; + struct notify_context32 *notify32 = ULongToPtr(params32->notify); + struct midi_hdr32 *hdr32; + struct notify_context notify; + MIDIHDR *hdr; + struct midi_notify_wait_params params = + { + .quit = ULongToPtr(params32->quit), + .notify = ¬ify + }; + notify32->send_notify = FALSE; + + oss_midi_notify_wait(¶ms); + + if (!*params.quit && notify.send_notify) + { + notify_to_notify32(notify32, ¬ify); + + if (notify.msg == MIM_LONGDATA) + { + hdr = (MIDIHDR *)notify.param_1; + notify32->param_1 = hdr->dwReserved[7]; + hdr32 = ULongToPtr(notify32->param_1); + hdr32->dwBytesRecorded = hdr->dwBytesRecorded; + hdr32->dwFlags = hdr->dwFlags; + free(hdr); + } + } + return STATUS_SUCCESS; +} + +#endif /* _WIN64 */ diff --git a/pkgs/osu-wine/audio-revert/wineoss.drv/unixlib.h b/pkgs/osu-wine/audio-revert/wineoss.drv/unixlib.h new file mode 100644 index 0000000..54482a1 --- /dev/null +++ b/pkgs/osu-wine/audio-revert/wineoss.drv/unixlib.h @@ -0,0 +1,32 @@ +/* + * Copyright 2022 Huw Davies + * + * 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 "../mmdevapi/unixlib.h" + +NTSTATUS oss_midi_release(void *args); +NTSTATUS oss_midi_out_message(void *args); +NTSTATUS oss_midi_in_message(void *args); +NTSTATUS oss_midi_notify_wait(void *args); + +#ifdef _WIN64 +NTSTATUS oss_wow64_midi_out_message(void *args); +NTSTATUS oss_wow64_midi_in_message(void *args); +NTSTATUS oss_wow64_midi_notify_wait(void *args); +#endif + +#define OSS_CALL(func, params) WINE_UNIX_CALL(func, params) diff --git a/pkgs/osu-wine/audio-revert/wineoss.drv/wineoss.drv.spec b/pkgs/osu-wine/audio-revert/wineoss.drv/wineoss.drv.spec new file mode 100644 index 0000000..fe6cc51 --- /dev/null +++ b/pkgs/osu-wine/audio-revert/wineoss.drv/wineoss.drv.spec @@ -0,0 +1,11 @@ +# WinMM driver functions +@ stdcall -private DriverProc(long long long long long) OSS_DriverProc +@ stdcall -private auxMessage(long long long long long) OSS_auxMessage +@ stdcall -private midMessage(long long long long long) OSS_midMessage +@ stdcall -private modMessage(long long long long long) OSS_modMessage + +# MMDevAPI driver functions +@ stdcall -private GetPriority() AUDDRV_GetPriority +@ stdcall -private GetEndpointIDs(long ptr ptr ptr ptr) AUDDRV_GetEndpointIDs +@ stdcall -private GetAudioEndpoint(ptr ptr ptr) AUDDRV_GetAudioEndpoint +@ stdcall -private GetAudioSessionManager(ptr ptr) AUDDRV_GetAudioSessionManager diff --git a/pkgs/osu-wine/audio-revert/winepulse.drv/Makefile.in b/pkgs/osu-wine/audio-revert/winepulse.drv/Makefile.in new file mode 100644 index 0000000..4a9b417 --- /dev/null +++ b/pkgs/osu-wine/audio-revert/winepulse.drv/Makefile.in @@ -0,0 +1,9 @@ +MODULE = winepulse.drv +IMPORTS = dxguid uuid winmm user32 advapi32 ole32 +UNIX_LIBS = $(PULSE_LIBS) $(PTHREAD_LIBS) +UNIX_CFLAGS = $(PULSE_CFLAGS) + +EXTRADLLFLAGS = -mcygwin + +SOURCES = \ + mmdevdrv.c diff --git a/pkgs/osu-wine/audio-revert/winepulse.drv/mmdevdrv.c b/pkgs/osu-wine/audio-revert/winepulse.drv/mmdevdrv.c new file mode 100644 index 0000000..2ca2350 --- /dev/null +++ b/pkgs/osu-wine/audio-revert/winepulse.drv/mmdevdrv.c @@ -0,0 +1,5663 @@ +/* + * Copyright 2011-2012 Maarten Lankhorst + * Copyright 2010-2011 Maarten Lankhorst for CodeWeavers + * Copyright 2011 Andrew Eikum for CodeWeavers + * + * 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 + */ + +#define NONAMELESSUNION +#define COBJMACROS +#undef WINE_UNIX_LIB + +#include "config.h" +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "windef.h" +#include "winbase.h" +#include "winnls.h" +#include "winreg.h" +#include "wine/debug.h" +#include "wine/unicode.h" +#include "wine/list.h" + +#include "ole2.h" +#include "dshow.h" +#include "dsound.h" +#include "propsys.h" +#include "propkey.h" + +#include "initguid.h" +#include "propkeydef.h" +#include "ks.h" +#include "ksmedia.h" +#include "mmdeviceapi.h" +#include "audioclient.h" +#include "endpointvolume.h" +#include "audiopolicy.h" + +WINE_DEFAULT_DEBUG_CHANNEL(pulse); + +#define NULL_PTR_ERR MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, RPC_X_NULL_REF_POINTER) + +/* From */ +enum DriverPriority { + Priority_Unavailable = 0, + Priority_Low, + Priority_Neutral, + Priority_Preferred +}; + +static pa_context *pulse_ctx; +static pa_mainloop *pulse_ml; + +static HANDLE pulse_thread; +static pthread_mutex_t pulse_lock; +static pthread_cond_t pulse_cond = PTHREAD_COND_INITIALIZER; +static struct list g_sessions = LIST_INIT(g_sessions); + +static UINT g_phys_speakers_mask = 0; + +/* Mixer format + period times */ +static WAVEFORMATEXTENSIBLE pulse_fmt[2]; +static REFERENCE_TIME pulse_min_period[2], pulse_def_period[2]; + +static GUID pulse_render_guid = +{ 0xfd47d9cc, 0x4218, 0x4135, { 0x9c, 0xe2, 0x0c, 0x19, 0x5c, 0x87, 0x40, 0x5b } }; +static GUID pulse_capture_guid = +{ 0x25da76d0, 0x033c, 0x4235, { 0x90, 0x02, 0x19, 0xf4, 0x88, 0x94, 0xac, 0x6f } }; + +static UINT8 mult_alaw_sample(UINT8, float); +static UINT8 mult_ulaw_sample(UINT8, float); + +BOOL WINAPI DllMain(HINSTANCE dll, DWORD reason, void *reserved) +{ + if (reason == DLL_PROCESS_ATTACH) { + pthread_mutexattr_t attr; + + DisableThreadLibraryCalls(dll); + + pthread_mutexattr_init(&attr); + pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT); + + if (pthread_mutex_init(&pulse_lock, &attr) != 0) + pthread_mutex_init(&pulse_lock, NULL); + } else if (reason == DLL_PROCESS_DETACH) { + if (pulse_thread) + SetThreadPriority(pulse_thread, 0); + if (pulse_ctx) { + pa_context_disconnect(pulse_ctx); + pa_context_unref(pulse_ctx); + } + if (pulse_ml) + pa_mainloop_quit(pulse_ml, 0); + if (pulse_thread) { + WaitForSingleObject(pulse_thread, INFINITE); + CloseHandle(pulse_thread); + } + } + return TRUE; +} + +typedef struct ACImpl ACImpl; + +typedef struct _AudioSession { + GUID guid; + struct list clients; + + IMMDevice *device; + + float master_vol; + UINT32 channel_count; + float *channel_vols; + BOOL mute; + + struct list entry; +} AudioSession; + +typedef struct _AudioSessionWrapper { + IAudioSessionControl2 IAudioSessionControl2_iface; + IChannelAudioVolume IChannelAudioVolume_iface; + ISimpleAudioVolume ISimpleAudioVolume_iface; + + LONG ref; + + ACImpl *client; + AudioSession *session; +} AudioSessionWrapper; + +typedef struct _ACPacket { + struct list entry; + UINT64 qpcpos; + BYTE *data; + UINT32 discont; +} ACPacket; + +struct ACImpl { + IAudioClient IAudioClient_iface; + IAudioRenderClient IAudioRenderClient_iface; + IAudioCaptureClient IAudioCaptureClient_iface; + IAudioClock IAudioClock_iface; + IAudioClock2 IAudioClock2_iface; + IAudioStreamVolume IAudioStreamVolume_iface; + IUnknown *marshal; + IMMDevice *parent; + struct list entry; + float vol[PA_CHANNELS_MAX]; + + LONG ref; + EDataFlow dataflow; + DWORD flags; + AUDCLNT_SHAREMODE share; + HANDLE event; + + INT32 locked; + UINT32 bufsize_frames, bufsize_bytes, capture_period, pad, started, peek_ofs, wri_offs_bytes, lcl_offs_bytes; + UINT32 tmp_buffer_bytes, held_bytes, peek_len, peek_buffer_len; + BYTE *local_buffer, *tmp_buffer, *peek_buffer; + void *locked_ptr; + + pa_stream *stream; + pa_sample_spec ss; + pa_channel_map map; + pa_buffer_attr attr; + + INT64 clock_lastpos, clock_written; + + AudioSession *session; + AudioSessionWrapper *session_wrapper; + struct list packet_free_head; + struct list packet_filled_head; +}; + +static const WCHAR defaultW[] = {'P','u','l','s','e','a','u','d','i','o',0}; + +static const IAudioClientVtbl AudioClient_Vtbl; +static const IAudioRenderClientVtbl AudioRenderClient_Vtbl; +static const IAudioCaptureClientVtbl AudioCaptureClient_Vtbl; +static const IAudioSessionControl2Vtbl AudioSessionControl2_Vtbl; +static const ISimpleAudioVolumeVtbl SimpleAudioVolume_Vtbl; +static const IChannelAudioVolumeVtbl ChannelAudioVolume_Vtbl; +static const IAudioClockVtbl AudioClock_Vtbl; +static const IAudioClock2Vtbl AudioClock2_Vtbl; +static const IAudioStreamVolumeVtbl AudioStreamVolume_Vtbl; + +static AudioSessionWrapper *AudioSessionWrapper_Create(ACImpl *client); + +static inline ACImpl *impl_from_IAudioClient(IAudioClient *iface) +{ + return CONTAINING_RECORD(iface, ACImpl, IAudioClient_iface); +} + +static inline ACImpl *impl_from_IAudioRenderClient(IAudioRenderClient *iface) +{ + return CONTAINING_RECORD(iface, ACImpl, IAudioRenderClient_iface); +} + +static inline ACImpl *impl_from_IAudioCaptureClient(IAudioCaptureClient *iface) +{ + return CONTAINING_RECORD(iface, ACImpl, IAudioCaptureClient_iface); +} + +static inline AudioSessionWrapper *impl_from_IAudioSessionControl2(IAudioSessionControl2 *iface) +{ + return CONTAINING_RECORD(iface, AudioSessionWrapper, IAudioSessionControl2_iface); +} + +static inline AudioSessionWrapper *impl_from_ISimpleAudioVolume(ISimpleAudioVolume *iface) +{ + return CONTAINING_RECORD(iface, AudioSessionWrapper, ISimpleAudioVolume_iface); +} + +static inline AudioSessionWrapper *impl_from_IChannelAudioVolume(IChannelAudioVolume *iface) +{ + return CONTAINING_RECORD(iface, AudioSessionWrapper, IChannelAudioVolume_iface); +} + +static inline ACImpl *impl_from_IAudioClock(IAudioClock *iface) +{ + return CONTAINING_RECORD(iface, ACImpl, IAudioClock_iface); +} + +static inline ACImpl *impl_from_IAudioClock2(IAudioClock2 *iface) +{ + return CONTAINING_RECORD(iface, ACImpl, IAudioClock2_iface); +} + +static inline ACImpl *impl_from_IAudioStreamVolume(IAudioStreamVolume *iface) +{ + return CONTAINING_RECORD(iface, ACImpl, IAudioStreamVolume_iface); +} + +/* Following pulseaudio design here, mainloop has the lock taken whenever + * it is handling something for pulse, and the lock is required whenever + * doing any pa_* call that can affect the state in any way + * + * pa_cond_wait is used when waiting on results, because the mainloop needs + * the same lock taken to affect the state + * + * This is basically the same as the pa_threaded_mainloop implementation, + * but that cannot be used because it uses pthread_create directly + * + * pa_threaded_mainloop_(un)lock -> pthread_mutex_(un)lock + * pa_threaded_mainloop_signal -> pthread_cond_broadcast + * pa_threaded_mainloop_wait -> pthread_cond_wait + */ + +static int pulse_poll_func(struct pollfd *ufds, unsigned long nfds, int timeout, void *userdata) { + int r; + pthread_mutex_unlock(&pulse_lock); + r = poll(ufds, nfds, timeout); + pthread_mutex_lock(&pulse_lock); + return r; +} + +static DWORD CALLBACK pulse_mainloop_thread(void *tmp) { + int ret; + pulse_ml = pa_mainloop_new(); + pa_mainloop_set_poll_func(pulse_ml, pulse_poll_func, NULL); + pthread_mutex_lock(&pulse_lock); + pthread_cond_broadcast(&pulse_cond); + pa_mainloop_run(pulse_ml, &ret); + pthread_mutex_unlock(&pulse_lock); + pa_mainloop_free(pulse_ml); + return ret; +} + +static void pulse_contextcallback(pa_context *c, void *userdata) +{ + switch (pa_context_get_state(c)) { + default: + FIXME("Unhandled state: %i\n", pa_context_get_state(c)); + return; + + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + case PA_CONTEXT_TERMINATED: + TRACE("State change to %i\n", pa_context_get_state(c)); + return; + + case PA_CONTEXT_READY: + TRACE("Ready\n"); + break; + + case PA_CONTEXT_FAILED: + WARN("Context failed: %s\n", pa_strerror(pa_context_errno(c))); + break; + } + pthread_cond_broadcast(&pulse_cond); +} + +static void pulse_stream_state(pa_stream *s, void *user) +{ + pa_stream_state_t state = pa_stream_get_state(s); + TRACE("Stream state changed to %i\n", state); + pthread_cond_broadcast(&pulse_cond); +} + +static const enum pa_channel_position pulse_pos_from_wfx[] = { + PA_CHANNEL_POSITION_FRONT_LEFT, + PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_FRONT_CENTER, + PA_CHANNEL_POSITION_LFE, + PA_CHANNEL_POSITION_REAR_LEFT, + PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER, + PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER, + PA_CHANNEL_POSITION_REAR_CENTER, + PA_CHANNEL_POSITION_SIDE_LEFT, + PA_CHANNEL_POSITION_SIDE_RIGHT, + PA_CHANNEL_POSITION_TOP_CENTER, + PA_CHANNEL_POSITION_TOP_FRONT_LEFT, + PA_CHANNEL_POSITION_TOP_FRONT_CENTER, + PA_CHANNEL_POSITION_TOP_FRONT_RIGHT, + PA_CHANNEL_POSITION_TOP_REAR_LEFT, + PA_CHANNEL_POSITION_TOP_REAR_CENTER, + PA_CHANNEL_POSITION_TOP_REAR_RIGHT +}; + +static DWORD pulse_channel_map_to_channel_mask(const pa_channel_map *map) +{ + int i; + DWORD mask = 0; + + for (i = 0; i < map->channels; ++i) { + switch (map->map[i]) { + default: FIXME("Unhandled channel %s\n", pa_channel_position_to_string(map->map[i])); break; + case PA_CHANNEL_POSITION_FRONT_LEFT: mask |= SPEAKER_FRONT_LEFT; break; + case PA_CHANNEL_POSITION_MONO: + case PA_CHANNEL_POSITION_FRONT_CENTER: mask |= SPEAKER_FRONT_CENTER; break; + case PA_CHANNEL_POSITION_FRONT_RIGHT: mask |= SPEAKER_FRONT_RIGHT; break; + case PA_CHANNEL_POSITION_REAR_LEFT: mask |= SPEAKER_BACK_LEFT; break; + case PA_CHANNEL_POSITION_REAR_CENTER: mask |= SPEAKER_BACK_CENTER; break; + case PA_CHANNEL_POSITION_REAR_RIGHT: mask |= SPEAKER_BACK_RIGHT; break; + case PA_CHANNEL_POSITION_LFE: mask |= SPEAKER_LOW_FREQUENCY; break; + case PA_CHANNEL_POSITION_SIDE_LEFT: mask |= SPEAKER_SIDE_LEFT; break; + case PA_CHANNEL_POSITION_SIDE_RIGHT: mask |= SPEAKER_SIDE_RIGHT; break; + case PA_CHANNEL_POSITION_TOP_CENTER: mask |= SPEAKER_TOP_CENTER; break; + case PA_CHANNEL_POSITION_TOP_FRONT_LEFT: mask |= SPEAKER_TOP_FRONT_LEFT; break; + case PA_CHANNEL_POSITION_TOP_FRONT_CENTER: mask |= SPEAKER_TOP_FRONT_CENTER; break; + case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: mask |= SPEAKER_TOP_FRONT_RIGHT; break; + case PA_CHANNEL_POSITION_TOP_REAR_LEFT: mask |= SPEAKER_TOP_BACK_LEFT; break; + case PA_CHANNEL_POSITION_TOP_REAR_CENTER: mask |= SPEAKER_TOP_BACK_CENTER; break; + case PA_CHANNEL_POSITION_TOP_REAR_RIGHT: mask |= SPEAKER_TOP_BACK_RIGHT; break; + case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: mask |= SPEAKER_FRONT_LEFT_OF_CENTER; break; + case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: mask |= SPEAKER_FRONT_RIGHT_OF_CENTER; break; + } + } + + return mask; +} + +/* For most hardware on Windows, users must choose a configuration with an even + * number of channels (stereo, quad, 5.1, 7.1). Users can then disable + * channels, but those channels are still reported to applications from + * GetMixFormat! Some applications behave badly if given an odd number of + * channels (e.g. 2.1). Here, we find the nearest configuration that Windows + * would report for a given channel layout. */ +static void convert_channel_map(const pa_channel_map *pa_map, WAVEFORMATEXTENSIBLE *fmt) +{ + DWORD pa_mask = pulse_channel_map_to_channel_mask(pa_map); + + TRACE("got mask for PA: 0x%x\n", pa_mask); + + if (pa_map->channels == 1) + { + fmt->Format.nChannels = 1; + fmt->dwChannelMask = pa_mask; + return; + } + + /* compare against known configurations and find smallest configuration + * which is a superset of the given speakers */ + + if (pa_map->channels <= 2 && + (pa_mask & ~KSAUDIO_SPEAKER_STEREO) == 0) + { + fmt->Format.nChannels = 2; + fmt->dwChannelMask = KSAUDIO_SPEAKER_STEREO; + return; + } + + if (pa_map->channels <= 4 && + (pa_mask & ~KSAUDIO_SPEAKER_QUAD) == 0) + { + fmt->Format.nChannels = 4; + fmt->dwChannelMask = KSAUDIO_SPEAKER_QUAD; + return; + } + + if (pa_map->channels <= 4 && + (pa_mask & ~KSAUDIO_SPEAKER_SURROUND) == 0) + { + fmt->Format.nChannels = 4; + fmt->dwChannelMask = KSAUDIO_SPEAKER_SURROUND; + return; + } + + if (pa_map->channels <= 6 && + (pa_mask & ~KSAUDIO_SPEAKER_5POINT1) == 0) + { + fmt->Format.nChannels = 6; + fmt->dwChannelMask = KSAUDIO_SPEAKER_5POINT1; + return; + } + + if (pa_map->channels <= 6 && + (pa_mask & ~KSAUDIO_SPEAKER_5POINT1_SURROUND) == 0) + { + fmt->Format.nChannels = 6; + fmt->dwChannelMask = KSAUDIO_SPEAKER_5POINT1_SURROUND; + return; + } + + if (pa_map->channels <= 8 && + (pa_mask & ~KSAUDIO_SPEAKER_7POINT1) == 0) + { + fmt->Format.nChannels = 8; + fmt->dwChannelMask = KSAUDIO_SPEAKER_7POINT1; + return; + } + + if (pa_map->channels <= 8 && + (pa_mask & ~KSAUDIO_SPEAKER_7POINT1_SURROUND) == 0) + { + fmt->Format.nChannels = 8; + fmt->dwChannelMask = KSAUDIO_SPEAKER_7POINT1_SURROUND; + return; + } + + /* oddball format, report truthfully */ + fmt->Format.nChannels = pa_map->channels; + fmt->dwChannelMask = pa_mask; +} + +static void pulse_probe_settings(int render, WAVEFORMATEXTENSIBLE *fmt) { + WAVEFORMATEX *wfx = &fmt->Format; + pa_stream *stream; + pa_channel_map map; + pa_sample_spec ss; + pa_buffer_attr attr; + int ret; + unsigned int length = 0; + + pa_channel_map_init_auto(&map, 2, PA_CHANNEL_MAP_ALSA); + ss.rate = 48000; + ss.format = PA_SAMPLE_FLOAT32LE; + ss.channels = map.channels; + + attr.maxlength = -1; + attr.tlength = -1; + attr.minreq = attr.fragsize = pa_frame_size(&ss); + attr.prebuf = 0; + + stream = pa_stream_new(pulse_ctx, "format test stream", &ss, &map); + if (stream) + pa_stream_set_state_callback(stream, pulse_stream_state, NULL); + if (!stream) + ret = -1; + else if (render) + ret = pa_stream_connect_playback(stream, NULL, &attr, + PA_STREAM_START_CORKED|PA_STREAM_FIX_RATE|PA_STREAM_FIX_CHANNELS|PA_STREAM_EARLY_REQUESTS, NULL, NULL); + else + ret = pa_stream_connect_record(stream, NULL, &attr, PA_STREAM_START_CORKED|PA_STREAM_FIX_RATE|PA_STREAM_FIX_CHANNELS|PA_STREAM_EARLY_REQUESTS); + if (ret >= 0) { + while (pa_mainloop_iterate(pulse_ml, 1, &ret) >= 0 && + pa_stream_get_state(stream) == PA_STREAM_CREATING) + {} + if (pa_stream_get_state(stream) == PA_STREAM_READY) { + ss = *pa_stream_get_sample_spec(stream); + map = *pa_stream_get_channel_map(stream); + if (render) + length = pa_stream_get_buffer_attr(stream)->minreq; + else + length = pa_stream_get_buffer_attr(stream)->fragsize; + pa_stream_disconnect(stream); + while (pa_mainloop_iterate(pulse_ml, 1, &ret) >= 0 && + pa_stream_get_state(stream) == PA_STREAM_READY) + {} + } + } + + if (stream) + pa_stream_unref(stream); + + if (length) + pulse_def_period[!render] = pulse_min_period[!render] = pa_bytes_to_usec(10 * length, &ss); + + const char* penv = getenv("STAGING_AUDIO_PERIOD"); + if (penv) { + int val = atoi(penv); + pulse_def_period[!render] = pulse_min_period[!render] = val; + printf("Staging audio period set to %d.\n", val); + } + + wfx->wFormatTag = WAVE_FORMAT_EXTENSIBLE; + wfx->cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); + + convert_channel_map(&map, fmt); + + wfx->wBitsPerSample = 8 * pa_sample_size_of_format(ss.format); + wfx->nSamplesPerSec = ss.rate; + wfx->nBlockAlign = wfx->nChannels * wfx->wBitsPerSample / 8; + wfx->nAvgBytesPerSec = wfx->nSamplesPerSec * wfx->nBlockAlign; + if (ss.format != PA_SAMPLE_S24_32LE) + fmt->Samples.wValidBitsPerSample = wfx->wBitsPerSample; + else + fmt->Samples.wValidBitsPerSample = 24; + if (ss.format == PA_SAMPLE_FLOAT32LE) + fmt->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + else + fmt->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; +} + +static HRESULT pulse_connect(void) +{ + int len; + WCHAR path[MAX_PATH], *name; + char *str; + + if (!pulse_thread) + { + if (!(pulse_thread = CreateThread(NULL, 0, pulse_mainloop_thread, NULL, 0, NULL))) + { + ERR("Failed to create mainloop thread.\n"); + return E_FAIL; + } + SetThreadPriority(pulse_thread, THREAD_PRIORITY_TIME_CRITICAL); + pthread_cond_wait(&pulse_cond, &pulse_lock); + } + + if (pulse_ctx && PA_CONTEXT_IS_GOOD(pa_context_get_state(pulse_ctx))) + return S_OK; + if (pulse_ctx) + pa_context_unref(pulse_ctx); + + GetModuleFileNameW(NULL, path, ARRAY_SIZE(path)); + name = strrchrW(path, '\\'); + if (!name) + name = path; + else + name++; + len = WideCharToMultiByte(CP_UNIXCP, 0, name, -1, NULL, 0, NULL, NULL); + str = pa_xmalloc(len); + WideCharToMultiByte(CP_UNIXCP, 0, name, -1, str, len, NULL, NULL); + TRACE("Name: %s\n", str); + pulse_ctx = pa_context_new(pa_mainloop_get_api(pulse_ml), str); + pa_xfree(str); + if (!pulse_ctx) { + ERR("Failed to create context\n"); + return E_FAIL; + } + + pa_context_set_state_callback(pulse_ctx, pulse_contextcallback, NULL); + + TRACE("libpulse protocol version: %u. API Version %u\n", pa_context_get_protocol_version(pulse_ctx), PA_API_VERSION); + if (pa_context_connect(pulse_ctx, NULL, 0, NULL) < 0) + goto fail; + + /* Wait for connection */ + while (pthread_cond_wait(&pulse_cond, &pulse_lock)) { + pa_context_state_t state = pa_context_get_state(pulse_ctx); + + if (state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) + goto fail; + + if (state == PA_CONTEXT_READY) + break; + } + + TRACE("Connected to server %s with protocol version: %i.\n", + pa_context_get_server(pulse_ctx), + pa_context_get_server_protocol_version(pulse_ctx)); + return S_OK; + +fail: + pa_context_unref(pulse_ctx); + pulse_ctx = NULL; + return E_FAIL; +} + +/* For default PulseAudio render device, OR together all of the + * PKEY_AudioEndpoint_PhysicalSpeakers values of the sinks. */ +static void pulse_phys_speakers_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) +{ + if (i) + g_phys_speakers_mask |= pulse_channel_map_to_channel_mask(&i->channel_map); +} + +/* some poorly-behaved applications call audio functions during DllMain, so we + * have to do as much as possible without creating a new thread. this function + * sets up a synchronous connection to verify the server is running and query + * static data. */ +static HRESULT pulse_test_connect(void) +{ + int len, ret; + WCHAR path[MAX_PATH], *name; + char *str; + pa_operation *o; + + pulse_ml = pa_mainloop_new(); + + pa_mainloop_set_poll_func(pulse_ml, pulse_poll_func, NULL); + + GetModuleFileNameW(NULL, path, ARRAY_SIZE(path)); + name = strrchrW(path, '\\'); + if (!name) + name = path; + else + name++; + len = WideCharToMultiByte(CP_UNIXCP, 0, name, -1, NULL, 0, NULL, NULL); + str = pa_xmalloc(len); + WideCharToMultiByte(CP_UNIXCP, 0, name, -1, str, len, NULL, NULL); + TRACE("Name: %s\n", str); + pulse_ctx = pa_context_new(pa_mainloop_get_api(pulse_ml), str); + pa_xfree(str); + if (!pulse_ctx) { + ERR("Failed to create context\n"); + pa_mainloop_free(pulse_ml); + pulse_ml = NULL; + return E_FAIL; + } + + pa_context_set_state_callback(pulse_ctx, pulse_contextcallback, NULL); + + TRACE("libpulse protocol version: %u. API Version %u\n", pa_context_get_protocol_version(pulse_ctx), PA_API_VERSION); + if (pa_context_connect(pulse_ctx, NULL, 0, NULL) < 0) + goto fail; + + /* Wait for connection */ + while (pa_mainloop_iterate(pulse_ml, 1, &ret) >= 0) { + pa_context_state_t state = pa_context_get_state(pulse_ctx); + + if (state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) + goto fail; + + if (state == PA_CONTEXT_READY) + break; + } + + if (pa_context_get_state(pulse_ctx) != PA_CONTEXT_READY) + goto fail; + + TRACE("Test-connected to server %s with protocol version: %i.\n", + pa_context_get_server(pulse_ctx), + pa_context_get_server_protocol_version(pulse_ctx)); + + pulse_probe_settings(1, &pulse_fmt[0]); + pulse_probe_settings(0, &pulse_fmt[1]); + + g_phys_speakers_mask = 0; + o = pa_context_get_sink_info_list(pulse_ctx, &pulse_phys_speakers_cb, NULL); + if (o) { + while (pa_mainloop_iterate(pulse_ml, 1, &ret) >= 0 && + pa_operation_get_state(o) == PA_OPERATION_RUNNING) + {} + pa_operation_unref(o); + } + + pa_context_unref(pulse_ctx); + pulse_ctx = NULL; + pa_mainloop_free(pulse_ml); + pulse_ml = NULL; + + return S_OK; + +fail: + pa_context_unref(pulse_ctx); + pulse_ctx = NULL; + pa_mainloop_free(pulse_ml); + pulse_ml = NULL; + + return E_FAIL; +} + +static HRESULT pulse_stream_valid(ACImpl *This) { + if (!This->stream) + return AUDCLNT_E_NOT_INITIALIZED; + if (pa_stream_get_state(This->stream) != PA_STREAM_READY) + return AUDCLNT_E_DEVICE_INVALIDATED; + return S_OK; +} + +static void silence_buffer(pa_sample_format_t format, BYTE *buffer, UINT32 bytes) +{ + memset(buffer, format == PA_SAMPLE_U8 ? 0x80 : 0, bytes); +} + +static void pulse_free_noop(void *buf) +{ +} + +enum write_buffer_flags +{ + WINEPULSE_WRITE_NOFREE = 0x01, + WINEPULSE_WRITE_SILENT = 0x02 +}; + +static int write_buffer(const ACImpl *This, BYTE *buffer, UINT32 bytes, + enum write_buffer_flags flags) +{ + float vol[PA_CHANNELS_MAX]; + BOOL adjust = FALSE; + UINT32 i, channels; + BYTE *end; + + if (!bytes) return 0; + if (This->session->mute || (flags & WINEPULSE_WRITE_SILENT)) + { + silence_buffer(This->ss.format, buffer, bytes); + goto write; + } + + /* Adjust the buffer based on the volume for each channel */ + channels = This->ss.channels; + for (i = 0; i < channels; i++) + { + vol[i] = This->vol[i] * This->session->master_vol * This->session->channel_vols[i]; + adjust |= vol[i] != 1.0f; + } + if (!adjust) goto write; + + end = buffer + bytes; + switch (This->ss.format) + { +#ifndef WORDS_BIGENDIAN +#define PROCESS_BUFFER(type) do \ +{ \ + type *p = (type*)buffer; \ + do \ + { \ + for (i = 0; i < channels; i++) \ + p[i] = p[i] * vol[i]; \ + p += i; \ + } while ((BYTE*)p != end); \ +} while (0) + case PA_SAMPLE_S16LE: + PROCESS_BUFFER(INT16); + break; + case PA_SAMPLE_S32LE: + PROCESS_BUFFER(INT32); + break; + case PA_SAMPLE_FLOAT32LE: + PROCESS_BUFFER(float); + break; +#undef PROCESS_BUFFER + case PA_SAMPLE_S24_32LE: + { + UINT32 *p = (UINT32*)buffer; + do + { + for (i = 0; i < channels; i++) + { + p[i] = (INT32)((INT32)(p[i] << 8) * vol[i]); + p[i] >>= 8; + } + p += i; + } while ((BYTE*)p != end); + break; + } + case PA_SAMPLE_S24LE: + { + /* do it 12 bytes at a time until it is no longer possible */ + UINT32 *q = (UINT32*)buffer; + BYTE *p; + + i = 0; + while (end - (BYTE*)q >= 12) + { + UINT32 v[4], k; + v[0] = q[0] << 8; + v[1] = q[1] << 16 | (q[0] >> 16 & ~0xff); + v[2] = q[2] << 24 | (q[1] >> 8 & ~0xff); + v[3] = q[2] & ~0xff; + for (k = 0; k < 4; k++) + { + v[k] = (INT32)((INT32)v[k] * vol[i]); + if (++i == channels) i = 0; + } + *q++ = v[0] >> 8 | (v[1] & ~0xff) << 16; + *q++ = v[1] >> 16 | (v[2] & ~0xff) << 8; + *q++ = v[2] >> 24 | (v[3] & ~0xff); + } + p = (BYTE*)q; + while (p != end) + { + UINT32 v = (INT32)((INT32)(p[0] << 8 | p[1] << 16 | p[2] << 24) * vol[i]); + *p++ = v >> 8 & 0xff; + *p++ = v >> 16 & 0xff; + *p++ = v >> 24; + if (++i == channels) i = 0; + } + break; + } +#endif + case PA_SAMPLE_U8: + { + UINT8 *p = (UINT8*)buffer; + do + { + for (i = 0; i < channels; i++) + p[i] = (int)((p[i] - 128) * vol[i]) + 128; + p += i; + } while ((BYTE*)p != end); + break; + } + case PA_SAMPLE_ALAW: + { + UINT8 *p = (UINT8*)buffer; + do + { + for (i = 0; i < channels; i++) + p[i] = mult_alaw_sample(p[i], vol[i]); + p += i; + } while ((BYTE*)p != end); + break; + } + case PA_SAMPLE_ULAW: + { + UINT8 *p = (UINT8*)buffer; + do + { + for (i = 0; i < channels; i++) + p[i] = mult_ulaw_sample(p[i], vol[i]); + p += i; + } while ((BYTE*)p != end); + break; + } + default: + TRACE("Unhandled format %i, not adjusting volume.\n", This->ss.format); + break; + } + +write: + return pa_stream_write(This->stream, buffer, bytes, + (flags & WINEPULSE_WRITE_NOFREE) ? pulse_free_noop : NULL, + 0, PA_SEEK_RELATIVE); +} + +static void dump_attr(const pa_buffer_attr *attr) { + TRACE("maxlength: %u\n", attr->maxlength); + TRACE("minreq: %u\n", attr->minreq); + TRACE("fragsize: %u\n", attr->fragsize); + TRACE("tlength: %u\n", attr->tlength); + TRACE("prebuf: %u\n", attr->prebuf); +} + +static void pulse_op_cb(pa_stream *s, int success, void *user) { + TRACE("Success: %i\n", success); + *(int*)user = success; + pthread_cond_broadcast(&pulse_cond); +} + +static void pulse_attr_update(pa_stream *s, void *user) { + const pa_buffer_attr *attr = pa_stream_get_buffer_attr(s); + TRACE("New attributes or device moved:\n"); + dump_attr(attr); +} + +/* Here's the buffer setup: + * + * vvvvvvvv sent to HW already + * vvvvvvvv in Pulse buffer but rewindable + * [dddddddddddddddd] Pulse buffer + * [dddddddddddddddd--------] mmdevapi buffer + * ^^^^^^^^^^^^^^^^ pad + * ^ lcl_offs_bytes + * ^^^^^^^^^ held_bytes + * ^ wri_offs_bytes + * + * GetCurrentPadding is pad + * + * During pulse_wr_callback, we decrement pad, fill Pulse buffer, and move + * lcl_offs forward + * + * During Stop, we flush the Pulse buffer + */ +static void pulse_wr_callback(pa_stream *s, size_t bytes, void *userdata) +{ + ACImpl *This = userdata; + UINT32 oldpad = This->pad; + + if(This->local_buffer){ + UINT32 to_write; + BYTE *buf = This->local_buffer + This->lcl_offs_bytes; + + if(This->pad > bytes){ + This->clock_written += bytes; + This->pad -= bytes; + }else{ + This->clock_written += This->pad; + This->pad = 0; + } + + bytes = min(bytes, This->held_bytes); + + if(This->lcl_offs_bytes + bytes > This->bufsize_bytes){ + to_write = This->bufsize_bytes - This->lcl_offs_bytes; + TRACE("writing small chunk of %u bytes\n", to_write); + write_buffer(This, buf, to_write, 0); + This->held_bytes -= to_write; + to_write = bytes - to_write; + This->lcl_offs_bytes = 0; + buf = This->local_buffer; + }else + to_write = bytes; + + TRACE("writing main chunk of %u bytes\n", to_write); + write_buffer(This, buf, to_write, 0); + This->lcl_offs_bytes += to_write; + This->lcl_offs_bytes %= This->bufsize_bytes; + This->held_bytes -= to_write; + }else{ + if (bytes < This->bufsize_bytes) + This->pad = This->bufsize_bytes - bytes; + else + This->pad = 0; + + if (oldpad == This->pad) + return; + + assert(oldpad > This->pad); + + This->clock_written += oldpad - This->pad; + TRACE("New pad: %zu (-%zu)\n", This->pad / pa_frame_size(&This->ss), (oldpad - This->pad) / pa_frame_size(&This->ss)); + } + + if (This->event) + SetEvent(This->event); +} + +static void pulse_underflow_callback(pa_stream *s, void *userdata) +{ + WARN("Underflow\n"); +} + +/* Latency is periodically updated even when nothing is played, + * because of PA_STREAM_AUTO_TIMING_UPDATE so use it as timer + * + * Perfect for passing all tests :) + */ +static void pulse_latency_callback(pa_stream *s, void *userdata) +{ + ACImpl *This = userdata; + if (!This->pad && This->event) + SetEvent(This->event); +} + +static void pulse_started_callback(pa_stream *s, void *userdata) +{ + TRACE("(Re)started playing\n"); +} + +static void pulse_rd_loop(ACImpl *This, size_t bytes) +{ + while (bytes >= This->capture_period) { + ACPacket *p, *next; + LARGE_INTEGER stamp, freq; + BYTE *dst, *src; + size_t src_len, copy, rem = This->capture_period; + if (!(p = (ACPacket*)list_head(&This->packet_free_head))) { + p = (ACPacket*)list_head(&This->packet_filled_head); + if (!p->discont) { + next = (ACPacket*)p->entry.next; + next->discont = 1; + } else + p = (ACPacket*)list_tail(&This->packet_filled_head); + assert(This->pad == This->bufsize_bytes); + } else { + assert(This->pad < This->bufsize_bytes); + This->pad += This->capture_period; + assert(This->pad <= This->bufsize_bytes); + } + QueryPerformanceCounter(&stamp); + QueryPerformanceFrequency(&freq); + p->qpcpos = (stamp.QuadPart * (INT64)10000000) / freq.QuadPart; + p->discont = 0; + list_remove(&p->entry); + list_add_tail(&This->packet_filled_head, &p->entry); + + dst = p->data; + while (rem) { + if (This->peek_len) { + copy = min(rem, This->peek_len - This->peek_ofs); + + memcpy(dst, This->peek_buffer + This->peek_ofs, copy); + + rem -= copy; + dst += copy; + This->peek_ofs += copy; + if(This->peek_len == This->peek_ofs) + This->peek_len = 0; + } else { + pa_stream_peek(This->stream, (const void**)&src, &src_len); + + copy = min(rem, src_len); + + memcpy(dst, src, rem); + + dst += copy; + rem -= copy; + + if (copy < src_len) { + if (src_len > This->peek_buffer_len) { + HeapFree(GetProcessHeap(), 0, This->peek_buffer); + This->peek_buffer = HeapAlloc(GetProcessHeap(), 0, src_len); + This->peek_buffer_len = src_len; + } + + memcpy(This->peek_buffer, src + copy, src_len - copy); + This->peek_len = src_len - copy; + This->peek_ofs = 0; + } + + pa_stream_drop(This->stream); + } + } + + bytes -= This->capture_period; + } +} + +static void pulse_rd_drop(ACImpl *This, size_t bytes) +{ + while (bytes >= This->capture_period) { + size_t src_len, copy, rem = This->capture_period; + while (rem) { + const void *src; + pa_stream_peek(This->stream, &src, &src_len); + assert(src_len); + assert(This->peek_ofs < src_len); + src_len -= This->peek_ofs; + assert(src_len <= bytes); + + copy = rem; + if (copy > src_len) + copy = src_len; + + src_len -= copy; + rem -= copy; + + if (!src_len) { + This->peek_ofs = 0; + pa_stream_drop(This->stream); + } else + This->peek_ofs += copy; + bytes -= copy; + } + } +} + +static void pulse_rd_callback(pa_stream *s, size_t bytes, void *userdata) +{ + ACImpl *This = userdata; + + TRACE("Readable total: %zu, fragsize: %u\n", bytes, pa_stream_get_buffer_attr(s)->fragsize); + assert(bytes >= This->peek_ofs); + bytes -= This->peek_ofs; + if (bytes < This->capture_period) + return; + + if (This->started) + pulse_rd_loop(This, bytes); + else + pulse_rd_drop(This, bytes); + + if (This->event) + SetEvent(This->event); +} + +static HRESULT pulse_stream_connect(ACImpl *This, UINT32 period_bytes) { + int ret; + char buffer[64]; + static LONG number; + pa_buffer_attr attr; + if (This->stream) { + pa_stream_disconnect(This->stream); + while (pa_stream_get_state(This->stream) == PA_STREAM_READY) + pthread_cond_wait(&pulse_cond, &pulse_lock); + pa_stream_unref(This->stream); + } + ret = InterlockedIncrement(&number); + sprintf(buffer, "audio stream #%i", ret); + This->stream = pa_stream_new(pulse_ctx, buffer, &This->ss, &This->map); + + if (!This->stream) { + WARN("pa_stream_new returned error %i\n", pa_context_errno(pulse_ctx)); + return AUDCLNT_E_ENDPOINT_CREATE_FAILED; + } + + pa_stream_set_state_callback(This->stream, pulse_stream_state, This); + pa_stream_set_buffer_attr_callback(This->stream, pulse_attr_update, This); + pa_stream_set_moved_callback(This->stream, pulse_attr_update, This); + + /* PulseAudio will fill in correct values */ + attr.minreq = attr.fragsize = period_bytes; + attr.maxlength = attr.tlength = This->bufsize_bytes; + attr.prebuf = pa_frame_size(&This->ss); + dump_attr(&attr); + if (This->dataflow == eRender) + ret = pa_stream_connect_playback(This->stream, NULL, &attr, + PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_AUTO_TIMING_UPDATE|PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_EARLY_REQUESTS, NULL, NULL); + else + ret = pa_stream_connect_record(This->stream, NULL, &attr, + PA_STREAM_START_CORKED|PA_STREAM_START_UNMUTED|PA_STREAM_AUTO_TIMING_UPDATE|PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_EARLY_REQUESTS); + if (ret < 0) { + WARN("Returns %i\n", ret); + return AUDCLNT_E_ENDPOINT_CREATE_FAILED; + } + while (pa_stream_get_state(This->stream) == PA_STREAM_CREATING) + pthread_cond_wait(&pulse_cond, &pulse_lock); + if (pa_stream_get_state(This->stream) != PA_STREAM_READY) + return AUDCLNT_E_ENDPOINT_CREATE_FAILED; + + if (This->dataflow == eRender) { + pa_stream_set_write_callback(This->stream, pulse_wr_callback, This); + pa_stream_set_underflow_callback(This->stream, pulse_underflow_callback, This); + pa_stream_set_started_callback(This->stream, pulse_started_callback, This); + } else + pa_stream_set_read_callback(This->stream, pulse_rd_callback, This); + return S_OK; +} + +HRESULT WINAPI AUDDRV_GetEndpointIDs(EDataFlow flow, const WCHAR ***ids, GUID **keys, + UINT *num, UINT *def_index) +{ + WCHAR *id; + + TRACE("%d %p %p %p\n", flow, ids, num, def_index); + + *num = 1; + *def_index = 0; + + *ids = HeapAlloc(GetProcessHeap(), 0, sizeof(**ids)); + *keys = NULL; + if (!*ids) + return E_OUTOFMEMORY; + + (*ids)[0] = id = HeapAlloc(GetProcessHeap(), 0, sizeof(defaultW)); + *keys = HeapAlloc(GetProcessHeap(), 0, sizeof(**keys)); + if (!*keys || !id) { + HeapFree(GetProcessHeap(), 0, id); + HeapFree(GetProcessHeap(), 0, *keys); + HeapFree(GetProcessHeap(), 0, *ids); + *ids = NULL; + *keys = NULL; + return E_OUTOFMEMORY; + } + memcpy(id, defaultW, sizeof(defaultW)); + + if (flow == eRender) + (*keys)[0] = pulse_render_guid; + else + (*keys)[0] = pulse_capture_guid; + + return S_OK; +} + +int WINAPI AUDDRV_GetPriority(void) +{ + HRESULT hr; + pthread_mutex_lock(&pulse_lock); + hr = pulse_test_connect(); + pthread_mutex_unlock(&pulse_lock); + return SUCCEEDED(hr) ? Priority_Preferred : Priority_Unavailable; +} + +HRESULT WINAPI AUDDRV_GetAudioEndpoint(GUID *guid, IMMDevice *dev, IAudioClient **out) +{ + ACImpl *This; + int i; + EDataFlow dataflow; + HRESULT hr; + + TRACE("%s %p %p\n", debugstr_guid(guid), dev, out); + if (IsEqualGUID(guid, &pulse_render_guid)) + dataflow = eRender; + else if (IsEqualGUID(guid, &pulse_capture_guid)) + dataflow = eCapture; + else + return E_UNEXPECTED; + + *out = NULL; + + This = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*This)); + if (!This) + return E_OUTOFMEMORY; + + This->IAudioClient_iface.lpVtbl = &AudioClient_Vtbl; + This->IAudioRenderClient_iface.lpVtbl = &AudioRenderClient_Vtbl; + This->IAudioCaptureClient_iface.lpVtbl = &AudioCaptureClient_Vtbl; + This->IAudioClock_iface.lpVtbl = &AudioClock_Vtbl; + This->IAudioClock2_iface.lpVtbl = &AudioClock2_Vtbl; + This->IAudioStreamVolume_iface.lpVtbl = &AudioStreamVolume_Vtbl; + This->dataflow = dataflow; + This->parent = dev; + for (i = 0; i < PA_CHANNELS_MAX; ++i) + This->vol[i] = 1.f; + + hr = CoCreateFreeThreadedMarshaler((IUnknown*)&This->IAudioClient_iface, &This->marshal); + if (hr) { + HeapFree(GetProcessHeap(), 0, This); + return hr; + } + IMMDevice_AddRef(This->parent); + + *out = &This->IAudioClient_iface; + IAudioClient_AddRef(&This->IAudioClient_iface); + + return S_OK; +} + +static HRESULT WINAPI AudioClient_QueryInterface(IAudioClient *iface, + REFIID riid, void **ppv) +{ + ACImpl *This = impl_from_IAudioClient(iface); + + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if (!ppv) + return E_POINTER; + + *ppv = NULL; + if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IAudioClient)) + *ppv = iface; + if (*ppv) { + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + if (IsEqualIID(riid, &IID_IMarshal)) + return IUnknown_QueryInterface(This->marshal, riid, ppv); + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioClient_AddRef(IAudioClient *iface) +{ + ACImpl *This = impl_from_IAudioClient(iface); + ULONG ref; + ref = InterlockedIncrement(&This->ref); + TRACE("(%p) Refcount now %u\n", This, ref); + return ref; +} + +static ULONG WINAPI AudioClient_Release(IAudioClient *iface) +{ + ACImpl *This = impl_from_IAudioClient(iface); + ULONG ref; + ref = InterlockedDecrement(&This->ref); + TRACE("(%p) Refcount now %u\n", This, ref); + if (!ref) { + if (This->stream) { + pthread_mutex_lock(&pulse_lock); + if (PA_STREAM_IS_GOOD(pa_stream_get_state(This->stream))) { + pa_stream_disconnect(This->stream); + while (PA_STREAM_IS_GOOD(pa_stream_get_state(This->stream))) + pthread_cond_wait(&pulse_cond, &pulse_lock); + } + pa_stream_unref(This->stream); + This->stream = NULL; + list_remove(&This->entry); + pthread_mutex_unlock(&pulse_lock); + } + IUnknown_Release(This->marshal); + IMMDevice_Release(This->parent); + HeapFree(GetProcessHeap(), 0, This->tmp_buffer); + HeapFree(GetProcessHeap(), 0, This->peek_buffer); + HeapFree(GetProcessHeap(), 0, This->local_buffer); + HeapFree(GetProcessHeap(), 0, This); + } + return ref; +} + +static void dump_fmt(const WAVEFORMATEX *fmt) +{ + TRACE("wFormatTag: 0x%x (", fmt->wFormatTag); + switch(fmt->wFormatTag) { + case WAVE_FORMAT_PCM: + TRACE("WAVE_FORMAT_PCM"); + break; + case WAVE_FORMAT_IEEE_FLOAT: + TRACE("WAVE_FORMAT_IEEE_FLOAT"); + break; + case WAVE_FORMAT_EXTENSIBLE: + TRACE("WAVE_FORMAT_EXTENSIBLE"); + break; + default: + TRACE("Unknown"); + break; + } + TRACE(")\n"); + + TRACE("nChannels: %u\n", fmt->nChannels); + TRACE("nSamplesPerSec: %u\n", fmt->nSamplesPerSec); + TRACE("nAvgBytesPerSec: %u\n", fmt->nAvgBytesPerSec); + TRACE("nBlockAlign: %u\n", fmt->nBlockAlign); + TRACE("wBitsPerSample: %u\n", fmt->wBitsPerSample); + TRACE("cbSize: %u\n", fmt->cbSize); + + if (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { + WAVEFORMATEXTENSIBLE *fmtex = (void*)fmt; + TRACE("dwChannelMask: %08x\n", fmtex->dwChannelMask); + TRACE("Samples: %04x\n", fmtex->Samples.wReserved); + TRACE("SubFormat: %s\n", wine_dbgstr_guid(&fmtex->SubFormat)); + } +} + +static WAVEFORMATEX *clone_format(const WAVEFORMATEX *fmt) +{ + WAVEFORMATEX *ret; + size_t size; + + if (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE) + size = sizeof(WAVEFORMATEXTENSIBLE); + else + size = sizeof(WAVEFORMATEX); + + ret = CoTaskMemAlloc(size); + if (!ret) + return NULL; + + memcpy(ret, fmt, size); + + ret->cbSize = size - sizeof(WAVEFORMATEX); + + return ret; +} + +static DWORD get_channel_mask(unsigned int channels) +{ + switch(channels) { + case 0: + return 0; + case 1: + return KSAUDIO_SPEAKER_MONO; + case 2: + return KSAUDIO_SPEAKER_STEREO; + case 3: + return KSAUDIO_SPEAKER_STEREO | SPEAKER_LOW_FREQUENCY; + case 4: + return KSAUDIO_SPEAKER_QUAD; /* not _SURROUND */ + case 5: + return KSAUDIO_SPEAKER_QUAD | SPEAKER_LOW_FREQUENCY; + case 6: + return KSAUDIO_SPEAKER_5POINT1; /* not 5POINT1_SURROUND */ + case 7: + return KSAUDIO_SPEAKER_5POINT1 | SPEAKER_BACK_CENTER; + case 8: + return KSAUDIO_SPEAKER_7POINT1_SURROUND; /* Vista deprecates 7POINT1 */ + } + FIXME("Unknown speaker configuration: %u\n", channels); + return 0; +} + +static void session_init_vols(AudioSession *session, UINT channels) +{ + if (session->channel_count < channels) { + UINT i; + + if (session->channel_vols) + session->channel_vols = HeapReAlloc(GetProcessHeap(), 0, + session->channel_vols, sizeof(float) * channels); + else + session->channel_vols = HeapAlloc(GetProcessHeap(), 0, + sizeof(float) * channels); + if (!session->channel_vols) + return; + + for(i = session->channel_count; i < channels; ++i) + session->channel_vols[i] = 1.f; + + session->channel_count = channels; + } +} + +static AudioSession *create_session(const GUID *guid, IMMDevice *device, + UINT num_channels) +{ + AudioSession *ret; + + ret = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(AudioSession)); + if (!ret) + return NULL; + + memcpy(&ret->guid, guid, sizeof(GUID)); + + ret->device = device; + + list_init(&ret->clients); + + list_add_head(&g_sessions, &ret->entry); + + session_init_vols(ret, num_channels); + + ret->master_vol = 1.f; + + return ret; +} + +/* if channels == 0, then this will return or create a session with + * matching dataflow and GUID. otherwise, channels must also match */ +static HRESULT get_audio_session(const GUID *sessionguid, + IMMDevice *device, UINT channels, AudioSession **out) +{ + AudioSession *session; + + if (!sessionguid || IsEqualGUID(sessionguid, &GUID_NULL)) { + *out = create_session(&GUID_NULL, device, channels); + if (!*out) + return E_OUTOFMEMORY; + + return S_OK; + } + + *out = NULL; + LIST_FOR_EACH_ENTRY(session, &g_sessions, AudioSession, entry) { + if (session->device == device && + IsEqualGUID(sessionguid, &session->guid)) { + session_init_vols(session, channels); + *out = session; + break; + } + } + + if (!*out) { + *out = create_session(sessionguid, device, channels); + if (!*out) + return E_OUTOFMEMORY; + } + + return S_OK; +} + +static HRESULT pulse_spec_from_waveformat(ACImpl *This, const WAVEFORMATEX *fmt) +{ + pa_channel_map_init(&This->map); + This->ss.rate = fmt->nSamplesPerSec; + This->ss.format = PA_SAMPLE_INVALID; + + switch(fmt->wFormatTag) { + case WAVE_FORMAT_IEEE_FLOAT: + if (!fmt->nChannels || fmt->nChannels > 2 || fmt->wBitsPerSample != 32) + break; + This->ss.format = PA_SAMPLE_FLOAT32LE; + pa_channel_map_init_auto(&This->map, fmt->nChannels, PA_CHANNEL_MAP_ALSA); + break; + case WAVE_FORMAT_PCM: + if (!fmt->nChannels || fmt->nChannels > 2) + break; + if (fmt->wBitsPerSample == 8) + This->ss.format = PA_SAMPLE_U8; + else if (fmt->wBitsPerSample == 16) + This->ss.format = PA_SAMPLE_S16LE; + else + return AUDCLNT_E_UNSUPPORTED_FORMAT; + pa_channel_map_init_auto(&This->map, fmt->nChannels, PA_CHANNEL_MAP_ALSA); + break; + case WAVE_FORMAT_EXTENSIBLE: { + WAVEFORMATEXTENSIBLE *wfe = (WAVEFORMATEXTENSIBLE*)fmt; + DWORD mask = wfe->dwChannelMask; + DWORD i = 0, j; + if (fmt->cbSize != (sizeof(*wfe) - sizeof(*fmt)) && fmt->cbSize != sizeof(*wfe)) + break; + if (IsEqualGUID(&wfe->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) && + (!wfe->Samples.wValidBitsPerSample || wfe->Samples.wValidBitsPerSample == 32) && + fmt->wBitsPerSample == 32) + This->ss.format = PA_SAMPLE_FLOAT32LE; + else if (IsEqualGUID(&wfe->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM)) { + DWORD valid = wfe->Samples.wValidBitsPerSample; + if (!valid) + valid = fmt->wBitsPerSample; + if (!valid || valid > fmt->wBitsPerSample) + break; + switch (fmt->wBitsPerSample) { + case 8: + if (valid == 8) + This->ss.format = PA_SAMPLE_U8; + break; + case 16: + if (valid == 16) + This->ss.format = PA_SAMPLE_S16LE; + break; + case 24: + if (valid == 24) + This->ss.format = PA_SAMPLE_S24LE; + break; + case 32: + if (valid == 24) + This->ss.format = PA_SAMPLE_S24_32LE; + else if (valid == 32) + This->ss.format = PA_SAMPLE_S32LE; + break; + default: + return AUDCLNT_E_UNSUPPORTED_FORMAT; + } + } + This->map.channels = fmt->nChannels; + if (!mask || (mask & (SPEAKER_ALL|SPEAKER_RESERVED))) + mask = get_channel_mask(fmt->nChannels); + for (j = 0; j < ARRAY_SIZE(pulse_pos_from_wfx) && i < fmt->nChannels; ++j) { + if (mask & (1 << j)) + This->map.map[i++] = pulse_pos_from_wfx[j]; + } + + /* Special case for mono since pulse appears to map it differently */ + if (mask == SPEAKER_FRONT_CENTER) + This->map.map[0] = PA_CHANNEL_POSITION_MONO; + + if (i < fmt->nChannels || (mask & SPEAKER_RESERVED)) { + This->map.channels = 0; + ERR("Invalid channel mask: %i/%i and %x(%x)\n", i, fmt->nChannels, mask, wfe->dwChannelMask); + break; + } + break; + } + case WAVE_FORMAT_ALAW: + case WAVE_FORMAT_MULAW: + if (fmt->wBitsPerSample != 8) { + FIXME("Unsupported bpp %u for LAW\n", fmt->wBitsPerSample); + return AUDCLNT_E_UNSUPPORTED_FORMAT; + } + if (fmt->nChannels != 1 && fmt->nChannels != 2) { + FIXME("Unsupported channels %u for LAW\n", fmt->nChannels); + return AUDCLNT_E_UNSUPPORTED_FORMAT; + } + This->ss.format = fmt->wFormatTag == WAVE_FORMAT_MULAW ? PA_SAMPLE_ULAW : PA_SAMPLE_ALAW; + pa_channel_map_init_auto(&This->map, fmt->nChannels, PA_CHANNEL_MAP_ALSA); + break; + default: + WARN("Unhandled tag %x\n", fmt->wFormatTag); + return AUDCLNT_E_UNSUPPORTED_FORMAT; + } + This->ss.channels = This->map.channels; + if (!pa_channel_map_valid(&This->map) || This->ss.format == PA_SAMPLE_INVALID) { + ERR("Invalid format! Channel spec valid: %i, format: %i\n", pa_channel_map_valid(&This->map), This->ss.format); + return AUDCLNT_E_UNSUPPORTED_FORMAT; + } + return S_OK; +} + +static HRESULT WINAPI AudioClient_Initialize(IAudioClient *iface, + AUDCLNT_SHAREMODE mode, DWORD flags, REFERENCE_TIME duration, + REFERENCE_TIME period, const WAVEFORMATEX *fmt, + const GUID *sessionguid) +{ + ACImpl *This = impl_from_IAudioClient(iface); + HRESULT hr = S_OK; + UINT period_bytes; + + TRACE("(%p)->(%x, %x, %s, %s, %p, %s)\n", This, mode, flags, + wine_dbgstr_longlong(duration), wine_dbgstr_longlong(period), fmt, debugstr_guid(sessionguid)); + + if (!fmt) + return E_POINTER; + + if (mode != AUDCLNT_SHAREMODE_SHARED && mode != AUDCLNT_SHAREMODE_EXCLUSIVE) + return E_INVALIDARG; + if (mode == AUDCLNT_SHAREMODE_EXCLUSIVE) + return AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED; + + if (flags & ~(AUDCLNT_STREAMFLAGS_CROSSPROCESS | + AUDCLNT_STREAMFLAGS_LOOPBACK | + AUDCLNT_STREAMFLAGS_EVENTCALLBACK | + AUDCLNT_STREAMFLAGS_NOPERSIST | + AUDCLNT_STREAMFLAGS_RATEADJUST | + AUDCLNT_SESSIONFLAGS_EXPIREWHENUNOWNED | + AUDCLNT_SESSIONFLAGS_DISPLAY_HIDE | + AUDCLNT_SESSIONFLAGS_DISPLAY_HIDEWHENEXPIRED | + AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY | + AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM)) { + FIXME("Unknown flags: %08x\n", flags); + return E_INVALIDARG; + } + + pthread_mutex_lock(&pulse_lock); + + hr = pulse_connect(); + if (FAILED(hr)) { + pthread_mutex_unlock(&pulse_lock); + return hr; + } + + if (This->stream) { + pthread_mutex_unlock(&pulse_lock); + return AUDCLNT_E_ALREADY_INITIALIZED; + } + + hr = pulse_spec_from_waveformat(This, fmt); + TRACE("Obtaining format returns %08x\n", hr); + dump_fmt(fmt); + + if (FAILED(hr)) + goto exit; + + if (mode == AUDCLNT_SHAREMODE_SHARED) { + REFERENCE_TIME def = pulse_def_period[This->dataflow == eCapture]; + REFERENCE_TIME min = pulse_min_period[This->dataflow == eCapture]; + + /* Switch to low latency mode if below 2 default periods, + * which is 20 ms by default, this will increase the amount + * of interrupts but allows very low latency. In dsound I + * managed to get a total latency of ~8ms, which is well below + * default + */ + if (duration < 2 * def) + period = min; + else + period = def; + if (duration < 2 * period) + duration = 2 * period; + + /* Uh oh, really low latency requested.. */ + if (duration <= 2 * period) + period /= 2; + } + + const char* denv = getenv("STAGING_AUDIO_DURATION"); + if (denv) { + int val = atoi(denv); + duration = val; + printf("Staging audio duration set to %d.\n", val); + } + + period_bytes = pa_frame_size(&This->ss) * MulDiv(period, This->ss.rate, 10000000); + + if (duration < 20000000) + This->bufsize_frames = ceil((duration / 10000000.) * fmt->nSamplesPerSec); + else + This->bufsize_frames = 2 * fmt->nSamplesPerSec; + This->bufsize_bytes = This->bufsize_frames * pa_frame_size(&This->ss); + + This->share = mode; + This->flags = flags; + hr = pulse_stream_connect(This, period_bytes); + if (SUCCEEDED(hr)) { + UINT32 unalign; + const pa_buffer_attr *attr = pa_stream_get_buffer_attr(This->stream); + This->attr = *attr; + /* Update frames according to new size */ + dump_attr(attr); + if (This->dataflow == eRender) { + if (attr->tlength < This->bufsize_bytes) { + TRACE("PulseAudio buffer too small (%u < %u), using tmp buffer\n", attr->tlength, This->bufsize_bytes); + + This->local_buffer = HeapAlloc(GetProcessHeap(), 0, This->bufsize_bytes); + if(!This->local_buffer) + hr = E_OUTOFMEMORY; + } + } else { + UINT32 i, capture_packets; + + This->capture_period = period_bytes = attr->fragsize; + if ((unalign = This->bufsize_bytes % period_bytes)) + This->bufsize_bytes += period_bytes - unalign; + This->bufsize_frames = This->bufsize_bytes / pa_frame_size(&This->ss); + + capture_packets = This->bufsize_bytes / This->capture_period; + + This->local_buffer = HeapAlloc(GetProcessHeap(), 0, This->bufsize_bytes + capture_packets * sizeof(ACPacket)); + if (!This->local_buffer) + hr = E_OUTOFMEMORY; + else { + ACPacket *cur_packet = (ACPacket*)((char*)This->local_buffer + This->bufsize_bytes); + BYTE *data = This->local_buffer; + silence_buffer(This->ss.format, This->local_buffer, This->bufsize_bytes); + list_init(&This->packet_free_head); + list_init(&This->packet_filled_head); + for (i = 0; i < capture_packets; ++i, ++cur_packet) { + list_add_tail(&This->packet_free_head, &cur_packet->entry); + cur_packet->data = data; + data += This->capture_period; + } + assert(!This->capture_period || This->bufsize_bytes == This->capture_period * capture_packets); + assert(!capture_packets || data - This->bufsize_bytes == This->local_buffer); + } + } + } + if (SUCCEEDED(hr)) + hr = get_audio_session(sessionguid, This->parent, fmt->nChannels, &This->session); + if (SUCCEEDED(hr)) + list_add_tail(&This->session->clients, &This->entry); + +exit: + if (FAILED(hr)) { + HeapFree(GetProcessHeap(), 0, This->local_buffer); + This->local_buffer = NULL; + if (This->stream) { + pa_stream_disconnect(This->stream); + pa_stream_unref(This->stream); + This->stream = NULL; + } + } + pthread_mutex_unlock(&pulse_lock); + return hr; +} + +static HRESULT WINAPI AudioClient_GetBufferSize(IAudioClient *iface, + UINT32 *out) +{ + ACImpl *This = impl_from_IAudioClient(iface); + HRESULT hr; + + TRACE("(%p)->(%p)\n", This, out); + + if (!out) + return E_POINTER; + + pthread_mutex_lock(&pulse_lock); + hr = pulse_stream_valid(This); + if (SUCCEEDED(hr)) + *out = This->bufsize_frames; + pthread_mutex_unlock(&pulse_lock); + + return hr; +} + +static HRESULT WINAPI AudioClient_GetStreamLatency(IAudioClient *iface, + REFERENCE_TIME *latency) +{ + ACImpl *This = impl_from_IAudioClient(iface); + const pa_buffer_attr *attr; + REFERENCE_TIME lat; + HRESULT hr; + + TRACE("(%p)->(%p)\n", This, latency); + + if (!latency) + return E_POINTER; + + pthread_mutex_lock(&pulse_lock); + hr = pulse_stream_valid(This); + if (FAILED(hr)) { + pthread_mutex_unlock(&pulse_lock); + return hr; + } + attr = pa_stream_get_buffer_attr(This->stream); + if (This->dataflow == eRender){ + lat = attr->minreq / pa_frame_size(&This->ss); + lat += pulse_def_period[0]; + }else + lat = attr->fragsize / pa_frame_size(&This->ss); + *latency = 10000000; + *latency *= lat; + *latency /= This->ss.rate; + pthread_mutex_unlock(&pulse_lock); + TRACE("Latency: %u ms\n", (DWORD)(*latency / 10000)); + return S_OK; +} + +static void ACImpl_GetRenderPad(ACImpl *This, UINT32 *out) +{ + *out = This->pad / pa_frame_size(&This->ss); +} + +static void ACImpl_GetCapturePad(ACImpl *This, UINT32 *out) +{ + ACPacket *packet = This->locked_ptr; + if (!packet && !list_empty(&This->packet_filled_head)) { + packet = (ACPacket*)list_head(&This->packet_filled_head); + This->locked_ptr = packet; + list_remove(&packet->entry); + } + if (out) + *out = This->pad / pa_frame_size(&This->ss); +} + +static HRESULT WINAPI AudioClient_GetCurrentPadding(IAudioClient *iface, + UINT32 *out) +{ + ACImpl *This = impl_from_IAudioClient(iface); + HRESULT hr; + + TRACE("(%p)->(%p)\n", This, out); + + if (!out) + return E_POINTER; + + pthread_mutex_lock(&pulse_lock); + hr = pulse_stream_valid(This); + if (FAILED(hr)) { + pthread_mutex_unlock(&pulse_lock); + return hr; + } + + if (This->dataflow == eRender) + ACImpl_GetRenderPad(This, out); + else + ACImpl_GetCapturePad(This, out); + pthread_mutex_unlock(&pulse_lock); + + TRACE("%p Pad: %u ms (%u)\n", This, MulDiv(*out, 1000, This->ss.rate), *out); + return S_OK; +} + +static HRESULT WINAPI AudioClient_IsFormatSupported(IAudioClient *iface, + AUDCLNT_SHAREMODE mode, const WAVEFORMATEX *fmt, + WAVEFORMATEX **out) +{ + ACImpl *This = impl_from_IAudioClient(iface); + HRESULT hr = S_OK; + WAVEFORMATEX *closest = NULL; + BOOL exclusive; + + TRACE("(%p)->(%x, %p, %p)\n", This, mode, fmt, out); + + if (!fmt) + return E_POINTER; + + if (out) + *out = NULL; + + if (mode == AUDCLNT_SHAREMODE_EXCLUSIVE) { + exclusive = 1; + out = NULL; + } else if (mode == AUDCLNT_SHAREMODE_SHARED) { + exclusive = 0; + if (!out) + return E_POINTER; + } else + return E_INVALIDARG; + + if (fmt->nChannels == 0) + return AUDCLNT_E_UNSUPPORTED_FORMAT; + + closest = clone_format(fmt); + if (!closest) + return E_OUTOFMEMORY; + + dump_fmt(fmt); + + switch (fmt->wFormatTag) { + case WAVE_FORMAT_EXTENSIBLE: { + WAVEFORMATEXTENSIBLE *ext = (WAVEFORMATEXTENSIBLE*)closest; + + if ((fmt->cbSize != sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX) && + fmt->cbSize != sizeof(WAVEFORMATEXTENSIBLE)) || + fmt->nBlockAlign != fmt->wBitsPerSample / 8 * fmt->nChannels || + ext->Samples.wValidBitsPerSample > fmt->wBitsPerSample || + fmt->nAvgBytesPerSec != fmt->nBlockAlign * fmt->nSamplesPerSec) { + hr = E_INVALIDARG; + break; + } + + if (exclusive) { + UINT32 mask = 0, i, channels = 0; + + if (!(ext->dwChannelMask & (SPEAKER_ALL | SPEAKER_RESERVED))) { + for (i = 1; !(i & SPEAKER_RESERVED); i <<= 1) { + if (i & ext->dwChannelMask) { + mask |= i; + channels++; + } + } + + if (channels != fmt->nChannels || (ext->dwChannelMask & ~mask)) { + hr = AUDCLNT_E_UNSUPPORTED_FORMAT; + break; + } + } else { + hr = AUDCLNT_E_UNSUPPORTED_FORMAT; + break; + } + } + + if (IsEqualGUID(&ext->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) { + if (fmt->wBitsPerSample != 32) { + hr = E_INVALIDARG; + break; + } + + if (ext->Samples.wValidBitsPerSample != fmt->wBitsPerSample) { + hr = S_FALSE; + ext->Samples.wValidBitsPerSample = fmt->wBitsPerSample; + } + } else if (IsEqualGUID(&ext->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM)) { + if (!fmt->wBitsPerSample || fmt->wBitsPerSample > 32 || fmt->wBitsPerSample % 8) { + hr = E_INVALIDARG; + break; + } + + if (ext->Samples.wValidBitsPerSample != fmt->wBitsPerSample && + !(fmt->wBitsPerSample == 32 && + ext->Samples.wValidBitsPerSample == 24)) { + hr = S_FALSE; + ext->Samples.wValidBitsPerSample = fmt->wBitsPerSample; + break; + } + } else { + hr = AUDCLNT_E_UNSUPPORTED_FORMAT; + break; + } + + break; + } + + case WAVE_FORMAT_ALAW: + case WAVE_FORMAT_MULAW: + if (fmt->wBitsPerSample != 8) { + hr = E_INVALIDARG; + break; + } + /* Fall-through */ + case WAVE_FORMAT_IEEE_FLOAT: + if (fmt->wFormatTag == WAVE_FORMAT_IEEE_FLOAT && fmt->wBitsPerSample != 32) { + hr = E_INVALIDARG; + break; + } + /* Fall-through */ + case WAVE_FORMAT_PCM: + if (fmt->wFormatTag == WAVE_FORMAT_PCM && + (!fmt->wBitsPerSample || fmt->wBitsPerSample > 32 || fmt->wBitsPerSample % 8)) { + hr = E_INVALIDARG; + break; + } + + if (fmt->nChannels > 2) { + hr = AUDCLNT_E_UNSUPPORTED_FORMAT; + break; + } + /* + * fmt->cbSize, fmt->nBlockAlign and fmt->nAvgBytesPerSec seem to be + * ignored, invalid values are happily accepted. + */ + break; + default: + hr = AUDCLNT_E_UNSUPPORTED_FORMAT; + break; + } + + if (exclusive && hr != S_OK) { + hr = AUDCLNT_E_UNSUPPORTED_FORMAT; + CoTaskMemFree(closest); + } else if (hr != S_FALSE) + CoTaskMemFree(closest); + else + *out = closest; + + /* Winepulse does not currently support exclusive mode, if you know of an + * application that uses it, I will correct this.. + */ + if (hr == S_OK && exclusive) + return This->dataflow == eCapture ? AUDCLNT_E_UNSUPPORTED_FORMAT : AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED; + + TRACE("returning: %08x %p\n", hr, out ? *out : NULL); + return hr; +} + +static HRESULT WINAPI AudioClient_GetMixFormat(IAudioClient *iface, + WAVEFORMATEX **pwfx) +{ + ACImpl *This = impl_from_IAudioClient(iface); + WAVEFORMATEXTENSIBLE *fmt = &pulse_fmt[This->dataflow == eCapture]; + + TRACE("(%p)->(%p)\n", This, pwfx); + + if (!pwfx) + return E_POINTER; + + *pwfx = clone_format(&fmt->Format); + if (!*pwfx) + return E_OUTOFMEMORY; + dump_fmt(*pwfx); + return S_OK; +} + +static HRESULT WINAPI AudioClient_GetDevicePeriod(IAudioClient *iface, + REFERENCE_TIME *defperiod, REFERENCE_TIME *minperiod) +{ + ACImpl *This = impl_from_IAudioClient(iface); + + TRACE("(%p)->(%p, %p)\n", This, defperiod, minperiod); + + if (!defperiod && !minperiod) + return E_POINTER; + + if (defperiod) + *defperiod = pulse_def_period[This->dataflow == eCapture]; + if (minperiod) + *minperiod = pulse_min_period[This->dataflow == eCapture]; + + return S_OK; +} + +static HRESULT WINAPI AudioClient_Start(IAudioClient *iface) +{ + ACImpl *This = impl_from_IAudioClient(iface); + HRESULT hr = S_OK; + int success; + pa_operation *o; + + TRACE("(%p)\n", This); + + pthread_mutex_lock(&pulse_lock); + hr = pulse_stream_valid(This); + if (FAILED(hr)) { + pthread_mutex_unlock(&pulse_lock); + return hr; + } + + if ((This->flags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK) && !This->event) { + pthread_mutex_unlock(&pulse_lock); + return AUDCLNT_E_EVENTHANDLE_NOT_SET; + } + + if (This->started) { + pthread_mutex_unlock(&pulse_lock); + return AUDCLNT_E_NOT_STOPPED; + } + + if (pa_stream_is_corked(This->stream)) { + o = pa_stream_cork(This->stream, 0, pulse_op_cb, &success); + if (o) { + while(pa_operation_get_state(o) == PA_OPERATION_RUNNING) + pthread_cond_wait(&pulse_cond, &pulse_lock); + pa_operation_unref(o); + } else + success = 0; + if (!success) + hr = E_FAIL; + } + + if (SUCCEEDED(hr)) { + This->started = TRUE; + if (This->dataflow == eRender && This->event) + pa_stream_set_latency_update_callback(This->stream, pulse_latency_callback, This); + } + pthread_mutex_unlock(&pulse_lock); + return hr; +} + +static HRESULT WINAPI AudioClient_Stop(IAudioClient *iface) +{ + ACImpl *This = impl_from_IAudioClient(iface); + HRESULT hr = S_OK; + pa_operation *o; + int success; + + TRACE("(%p)\n", This); + + pthread_mutex_lock(&pulse_lock); + hr = pulse_stream_valid(This); + if (FAILED(hr)) { + pthread_mutex_unlock(&pulse_lock); + return hr; + } + + if (!This->started) { + pthread_mutex_unlock(&pulse_lock); + return S_FALSE; + } + + if (This->dataflow == eRender) { + o = pa_stream_cork(This->stream, 1, pulse_op_cb, &success); + if (o) { + while(pa_operation_get_state(o) == PA_OPERATION_RUNNING) + pthread_cond_wait(&pulse_cond, &pulse_lock); + pa_operation_unref(o); + } else + success = 0; + if (!success) + hr = E_FAIL; + } + if (SUCCEEDED(hr)) { + This->started = FALSE; + } + pthread_mutex_unlock(&pulse_lock); + return hr; +} + +static HRESULT WINAPI AudioClient_Reset(IAudioClient *iface) +{ + ACImpl *This = impl_from_IAudioClient(iface); + HRESULT hr = S_OK; + + TRACE("(%p)\n", This); + + pthread_mutex_lock(&pulse_lock); + hr = pulse_stream_valid(This); + if (FAILED(hr)) { + pthread_mutex_unlock(&pulse_lock); + return hr; + } + + if (This->started) { + pthread_mutex_unlock(&pulse_lock); + return AUDCLNT_E_NOT_STOPPED; + } + + if (This->locked) { + pthread_mutex_unlock(&pulse_lock); + return AUDCLNT_E_BUFFER_OPERATION_PENDING; + } + + if (This->dataflow == eRender) { + /* If there is still data in the render buffer it needs to be removed from the server */ + int success = 0; + if (This->pad) { + pa_operation *o = pa_stream_flush(This->stream, pulse_op_cb, &success); + if (o) { + while(pa_operation_get_state(o) == PA_OPERATION_RUNNING) + pthread_cond_wait(&pulse_cond, &pulse_lock); + pa_operation_unref(o); + } + } + if (success || !This->pad){ + This->clock_lastpos = This->clock_written = This->pad = 0; + This->wri_offs_bytes = This->lcl_offs_bytes = This->held_bytes = 0; + } + } else { + ACPacket *p; + This->clock_written += This->pad; + This->pad = 0; + + if ((p = This->locked_ptr)) { + This->locked_ptr = NULL; + list_add_tail(&This->packet_free_head, &p->entry); + } + list_move_tail(&This->packet_free_head, &This->packet_filled_head); + } + pthread_mutex_unlock(&pulse_lock); + + return hr; +} + +static HRESULT WINAPI AudioClient_SetEventHandle(IAudioClient *iface, + HANDLE event) +{ + ACImpl *This = impl_from_IAudioClient(iface); + HRESULT hr; + + TRACE("(%p)->(%p)\n", This, event); + + if (!event) + return E_INVALIDARG; + + pthread_mutex_lock(&pulse_lock); + hr = pulse_stream_valid(This); + if (FAILED(hr)) { + pthread_mutex_unlock(&pulse_lock); + return hr; + } + + if (!(This->flags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK)) + hr = AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED; + else if (This->event) + hr = HRESULT_FROM_WIN32(ERROR_INVALID_NAME); + else + This->event = event; + pthread_mutex_unlock(&pulse_lock); + return hr; +} + +static HRESULT WINAPI AudioClient_GetService(IAudioClient *iface, REFIID riid, + void **ppv) +{ + ACImpl *This = impl_from_IAudioClient(iface); + HRESULT hr; + + TRACE("(%p)->(%s, %p)\n", This, debugstr_guid(riid), ppv); + + if (!ppv) + return E_POINTER; + *ppv = NULL; + + pthread_mutex_lock(&pulse_lock); + hr = pulse_stream_valid(This); + pthread_mutex_unlock(&pulse_lock); + if (FAILED(hr)) + return hr; + + if (IsEqualIID(riid, &IID_IAudioRenderClient)) { + if (This->dataflow != eRender) + return AUDCLNT_E_WRONG_ENDPOINT_TYPE; + *ppv = &This->IAudioRenderClient_iface; + } else if (IsEqualIID(riid, &IID_IAudioCaptureClient)) { + if (This->dataflow != eCapture) + return AUDCLNT_E_WRONG_ENDPOINT_TYPE; + *ppv = &This->IAudioCaptureClient_iface; + } else if (IsEqualIID(riid, &IID_IAudioClock)) { + *ppv = &This->IAudioClock_iface; + } else if (IsEqualIID(riid, &IID_IAudioStreamVolume)) { + *ppv = &This->IAudioStreamVolume_iface; + } else if (IsEqualIID(riid, &IID_IAudioSessionControl) || + IsEqualIID(riid, &IID_IChannelAudioVolume) || + IsEqualIID(riid, &IID_ISimpleAudioVolume)) { + if (!This->session_wrapper) { + This->session_wrapper = AudioSessionWrapper_Create(This); + if (!This->session_wrapper) + return E_OUTOFMEMORY; + } + if (IsEqualIID(riid, &IID_IAudioSessionControl)) + *ppv = &This->session_wrapper->IAudioSessionControl2_iface; + else if (IsEqualIID(riid, &IID_IChannelAudioVolume)) + *ppv = &This->session_wrapper->IChannelAudioVolume_iface; + else if (IsEqualIID(riid, &IID_ISimpleAudioVolume)) + *ppv = &This->session_wrapper->ISimpleAudioVolume_iface; + } + + if (*ppv) { + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + FIXME("stub %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static const IAudioClientVtbl AudioClient_Vtbl = +{ + AudioClient_QueryInterface, + AudioClient_AddRef, + AudioClient_Release, + AudioClient_Initialize, + AudioClient_GetBufferSize, + AudioClient_GetStreamLatency, + AudioClient_GetCurrentPadding, + AudioClient_IsFormatSupported, + AudioClient_GetMixFormat, + AudioClient_GetDevicePeriod, + AudioClient_Start, + AudioClient_Stop, + AudioClient_Reset, + AudioClient_SetEventHandle, + AudioClient_GetService +}; + +static HRESULT WINAPI AudioRenderClient_QueryInterface( + IAudioRenderClient *iface, REFIID riid, void **ppv) +{ + ACImpl *This = impl_from_IAudioRenderClient(iface); + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if (!ppv) + return E_POINTER; + *ppv = NULL; + + if (IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IAudioRenderClient)) + *ppv = iface; + if (*ppv) { + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + if (IsEqualIID(riid, &IID_IMarshal)) + return IUnknown_QueryInterface(This->marshal, riid, ppv); + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioRenderClient_AddRef(IAudioRenderClient *iface) +{ + ACImpl *This = impl_from_IAudioRenderClient(iface); + return AudioClient_AddRef(&This->IAudioClient_iface); +} + +static ULONG WINAPI AudioRenderClient_Release(IAudioRenderClient *iface) +{ + ACImpl *This = impl_from_IAudioRenderClient(iface); + return AudioClient_Release(&This->IAudioClient_iface); +} + +static void alloc_tmp_buffer(ACImpl *This, UINT32 bytes) +{ + if(This->tmp_buffer_bytes >= bytes) + return; + + HeapFree(GetProcessHeap(), 0, This->tmp_buffer); + This->tmp_buffer = HeapAlloc(GetProcessHeap(), 0, bytes); + This->tmp_buffer_bytes = bytes; +} + +static HRESULT WINAPI AudioRenderClient_GetBuffer(IAudioRenderClient *iface, + UINT32 frames, BYTE **data) +{ + ACImpl *This = impl_from_IAudioRenderClient(iface); + size_t avail, req, bytes = frames * pa_frame_size(&This->ss); + UINT32 pad; + HRESULT hr = S_OK; + int ret = -1; + + TRACE("(%p)->(%u, %p)\n", This, frames, data); + + if (!data) + return E_POINTER; + *data = NULL; + + pthread_mutex_lock(&pulse_lock); + hr = pulse_stream_valid(This); + if (FAILED(hr) || This->locked) { + pthread_mutex_unlock(&pulse_lock); + return FAILED(hr) ? hr : AUDCLNT_E_OUT_OF_ORDER; + } + if (!frames) { + pthread_mutex_unlock(&pulse_lock); + return S_OK; + } + + ACImpl_GetRenderPad(This, &pad); + avail = This->bufsize_frames - pad; + if (avail < frames || bytes > This->bufsize_bytes) { + pthread_mutex_unlock(&pulse_lock); + WARN("Wanted to write %u, but only %zu available\n", frames, avail); + return AUDCLNT_E_BUFFER_TOO_LARGE; + } + + if(This->local_buffer){ + if(This->wri_offs_bytes + bytes > This->bufsize_bytes){ + alloc_tmp_buffer(This, bytes); + *data = This->tmp_buffer; + This->locked = -frames; + }else{ + *data = This->local_buffer + This->wri_offs_bytes; + This->locked = frames; + } + }else{ + req = bytes; + ret = pa_stream_begin_write(This->stream, &This->locked_ptr, &req); + if (ret < 0 || req < bytes) { + FIXME("%p Not using pulse locked data: %i %zu/%u %u/%u\n", This, ret, req/pa_frame_size(&This->ss), frames, pad, This->bufsize_frames); + if (ret >= 0) + pa_stream_cancel_write(This->stream); + alloc_tmp_buffer(This, bytes); + *data = This->tmp_buffer; + This->locked_ptr = NULL; + } else + *data = This->locked_ptr; + + This->locked = frames; + } + + silence_buffer(This->ss.format, *data, bytes); + + pthread_mutex_unlock(&pulse_lock); + + return hr; +} + +static void pulse_wrap_buffer(ACImpl *This, BYTE *buffer, UINT32 written_bytes) +{ + UINT32 chunk_bytes = This->bufsize_bytes - This->wri_offs_bytes; + + if(written_bytes <= chunk_bytes){ + memcpy(This->local_buffer + This->wri_offs_bytes, buffer, written_bytes); + }else{ + memcpy(This->local_buffer + This->wri_offs_bytes, buffer, chunk_bytes); + memcpy(This->local_buffer, buffer + chunk_bytes, + written_bytes - chunk_bytes); + } +} + +static HRESULT WINAPI AudioRenderClient_ReleaseBuffer( + IAudioRenderClient *iface, UINT32 written_frames, DWORD flags) +{ + ACImpl *This = impl_from_IAudioRenderClient(iface); + UINT32 written_bytes = written_frames * pa_frame_size(&This->ss); + + TRACE("(%p)->(%u, %x)\n", This, written_frames, flags); + + pthread_mutex_lock(&pulse_lock); + if (!This->locked || !written_frames) { + if (This->locked_ptr) + pa_stream_cancel_write(This->stream); + This->locked = 0; + This->locked_ptr = NULL; + pthread_mutex_unlock(&pulse_lock); + return written_frames ? AUDCLNT_E_OUT_OF_ORDER : S_OK; + } + + if (This->locked < written_frames) { + pthread_mutex_unlock(&pulse_lock); + return AUDCLNT_E_INVALID_SIZE; + } + + if(This->local_buffer){ + BYTE *buffer; + + if(This->locked >= 0) + buffer = This->local_buffer + This->wri_offs_bytes; + else + buffer = This->tmp_buffer; + + if(flags & AUDCLNT_BUFFERFLAGS_SILENT) + silence_buffer(This->ss.format, buffer, written_bytes); + + if(This->locked < 0) + pulse_wrap_buffer(This, buffer, written_bytes); + + This->wri_offs_bytes += written_bytes; + This->wri_offs_bytes %= This->bufsize_bytes; + + This->pad += written_bytes; + This->held_bytes += written_bytes; + + if(This->held_bytes == This->pad){ + int e; + UINT32 to_write = min(This->attr.tlength, written_bytes); + + /* nothing in PA, so send data immediately */ + + TRACE("pre-writing %u bytes\n", to_write); + + e = write_buffer(This, buffer, to_write, 0); + if(e) + ERR("pa_stream_write failed: 0x%x\n", e); + + This->lcl_offs_bytes += to_write; + This->lcl_offs_bytes %= This->bufsize_bytes; + This->held_bytes -= to_write; + } + + }else{ + enum write_buffer_flags wr_flags = 0; + + if (flags & AUDCLNT_BUFFERFLAGS_SILENT) wr_flags |= WINEPULSE_WRITE_SILENT; + if (!This->locked_ptr) wr_flags |= WINEPULSE_WRITE_NOFREE; + + write_buffer(This, This->locked_ptr ? This->locked_ptr : This->tmp_buffer, written_bytes, wr_flags); + This->pad += written_bytes; + } + + if (!pa_stream_is_corked(This->stream)) { + int success; + pa_operation *o; + o = pa_stream_trigger(This->stream, pulse_op_cb, &success); + if (o) { + while(pa_operation_get_state(o) == PA_OPERATION_RUNNING) + pthread_cond_wait(&pulse_cond, &pulse_lock); + pa_operation_unref(o); + } + } + + This->locked = 0; + This->locked_ptr = NULL; + TRACE("Released %u, pad %zu\n", written_frames, This->pad / pa_frame_size(&This->ss)); + assert(This->pad <= This->bufsize_bytes); + + pthread_mutex_unlock(&pulse_lock); + return S_OK; +} + +static const IAudioRenderClientVtbl AudioRenderClient_Vtbl = { + AudioRenderClient_QueryInterface, + AudioRenderClient_AddRef, + AudioRenderClient_Release, + AudioRenderClient_GetBuffer, + AudioRenderClient_ReleaseBuffer +}; + +static HRESULT WINAPI AudioCaptureClient_QueryInterface( + IAudioCaptureClient *iface, REFIID riid, void **ppv) +{ + ACImpl *This = impl_from_IAudioCaptureClient(iface); + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if (!ppv) + return E_POINTER; + *ppv = NULL; + + if (IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IAudioCaptureClient)) + *ppv = iface; + if (*ppv) { + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + if (IsEqualIID(riid, &IID_IMarshal)) + return IUnknown_QueryInterface(This->marshal, riid, ppv); + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioCaptureClient_AddRef(IAudioCaptureClient *iface) +{ + ACImpl *This = impl_from_IAudioCaptureClient(iface); + return IAudioClient_AddRef(&This->IAudioClient_iface); +} + +static ULONG WINAPI AudioCaptureClient_Release(IAudioCaptureClient *iface) +{ + ACImpl *This = impl_from_IAudioCaptureClient(iface); + return IAudioClient_Release(&This->IAudioClient_iface); +} + +static HRESULT WINAPI AudioCaptureClient_GetBuffer(IAudioCaptureClient *iface, + BYTE **data, UINT32 *frames, DWORD *flags, UINT64 *devpos, + UINT64 *qpcpos) +{ + ACImpl *This = impl_from_IAudioCaptureClient(iface); + HRESULT hr; + ACPacket *packet; + + TRACE("(%p)->(%p, %p, %p, %p, %p)\n", This, data, frames, flags, + devpos, qpcpos); + + if (!data) + return E_POINTER; + + *data = NULL; + + if (!frames || !flags) + return E_POINTER; + + pthread_mutex_lock(&pulse_lock); + hr = pulse_stream_valid(This); + if (FAILED(hr) || This->locked) { + pthread_mutex_unlock(&pulse_lock); + return FAILED(hr) ? hr : AUDCLNT_E_OUT_OF_ORDER; + } + + ACImpl_GetCapturePad(This, NULL); + if ((packet = This->locked_ptr)) { + *frames = This->capture_period / pa_frame_size(&This->ss); + *flags = 0; + if (packet->discont) + *flags |= AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY; + if (devpos) { + if (packet->discont) + *devpos = (This->clock_written + This->capture_period) / pa_frame_size(&This->ss); + else + *devpos = This->clock_written / pa_frame_size(&This->ss); + } + if (qpcpos) + *qpcpos = packet->qpcpos; + *data = packet->data; + } + else + *frames = 0; + This->locked = *frames; + pthread_mutex_unlock(&pulse_lock); + return *frames ? S_OK : AUDCLNT_S_BUFFER_EMPTY; +} + +static HRESULT WINAPI AudioCaptureClient_ReleaseBuffer( + IAudioCaptureClient *iface, UINT32 done) +{ + ACImpl *This = impl_from_IAudioCaptureClient(iface); + + TRACE("(%p)->(%u)\n", This, done); + + pthread_mutex_lock(&pulse_lock); + if (!This->locked && done) { + pthread_mutex_unlock(&pulse_lock); + return AUDCLNT_E_OUT_OF_ORDER; + } + if (done && This->locked != done) { + pthread_mutex_unlock(&pulse_lock); + return AUDCLNT_E_INVALID_SIZE; + } + if (done) { + ACPacket *packet = This->locked_ptr; + This->locked_ptr = NULL; + This->pad -= This->capture_period; + if (packet->discont) + This->clock_written += 2 * This->capture_period; + else + This->clock_written += This->capture_period; + list_add_tail(&This->packet_free_head, &packet->entry); + } + This->locked = 0; + pthread_mutex_unlock(&pulse_lock); + return S_OK; +} + +static HRESULT WINAPI AudioCaptureClient_GetNextPacketSize( + IAudioCaptureClient *iface, UINT32 *frames) +{ + ACImpl *This = impl_from_IAudioCaptureClient(iface); + + TRACE("(%p)->(%p)\n", This, frames); + if (!frames) + return E_POINTER; + + pthread_mutex_lock(&pulse_lock); + ACImpl_GetCapturePad(This, NULL); + if (This->locked_ptr) + *frames = This->capture_period / pa_frame_size(&This->ss); + else + *frames = 0; + pthread_mutex_unlock(&pulse_lock); + return S_OK; +} + +static const IAudioCaptureClientVtbl AudioCaptureClient_Vtbl = +{ + AudioCaptureClient_QueryInterface, + AudioCaptureClient_AddRef, + AudioCaptureClient_Release, + AudioCaptureClient_GetBuffer, + AudioCaptureClient_ReleaseBuffer, + AudioCaptureClient_GetNextPacketSize +}; + +static HRESULT WINAPI AudioClock_QueryInterface(IAudioClock *iface, + REFIID riid, void **ppv) +{ + ACImpl *This = impl_from_IAudioClock(iface); + + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if (!ppv) + return E_POINTER; + *ppv = NULL; + + if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IAudioClock)) + *ppv = iface; + else if (IsEqualIID(riid, &IID_IAudioClock2)) + *ppv = &This->IAudioClock2_iface; + if (*ppv) { + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + if (IsEqualIID(riid, &IID_IMarshal)) + return IUnknown_QueryInterface(This->marshal, riid, ppv); + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioClock_AddRef(IAudioClock *iface) +{ + ACImpl *This = impl_from_IAudioClock(iface); + return IAudioClient_AddRef(&This->IAudioClient_iface); +} + +static ULONG WINAPI AudioClock_Release(IAudioClock *iface) +{ + ACImpl *This = impl_from_IAudioClock(iface); + return IAudioClient_Release(&This->IAudioClient_iface); +} + +static HRESULT WINAPI AudioClock_GetFrequency(IAudioClock *iface, UINT64 *freq) +{ + ACImpl *This = impl_from_IAudioClock(iface); + HRESULT hr; + + TRACE("(%p)->(%p)\n", This, freq); + + pthread_mutex_lock(&pulse_lock); + hr = pulse_stream_valid(This); + if (SUCCEEDED(hr)) { + *freq = This->ss.rate; + if (This->share == AUDCLNT_SHAREMODE_SHARED) + *freq *= pa_frame_size(&This->ss); + } + pthread_mutex_unlock(&pulse_lock); + return hr; +} + +static HRESULT WINAPI AudioClock_GetPosition(IAudioClock *iface, UINT64 *pos, + UINT64 *qpctime) +{ + ACImpl *This = impl_from_IAudioClock(iface); + HRESULT hr; + + TRACE("(%p)->(%p, %p)\n", This, pos, qpctime); + + if (!pos) + return E_POINTER; + + pthread_mutex_lock(&pulse_lock); + hr = pulse_stream_valid(This); + if (FAILED(hr)) { + pthread_mutex_unlock(&pulse_lock); + return hr; + } + + *pos = This->clock_written; + + if (This->share == AUDCLNT_SHAREMODE_EXCLUSIVE) + *pos /= pa_frame_size(&This->ss); + + /* Make time never go backwards */ + if (*pos < This->clock_lastpos) + *pos = This->clock_lastpos; + else + This->clock_lastpos = *pos; + pthread_mutex_unlock(&pulse_lock); + + TRACE("%p Position: %u\n", This, (unsigned)*pos); + + if (qpctime) { + LARGE_INTEGER stamp, freq; + QueryPerformanceCounter(&stamp); + QueryPerformanceFrequency(&freq); + *qpctime = (stamp.QuadPart * (INT64)10000000) / freq.QuadPart; + } + + return S_OK; +} + +static HRESULT WINAPI AudioClock_GetCharacteristics(IAudioClock *iface, + DWORD *chars) +{ + ACImpl *This = impl_from_IAudioClock(iface); + + TRACE("(%p)->(%p)\n", This, chars); + + if (!chars) + return E_POINTER; + + *chars = AUDIOCLOCK_CHARACTERISTIC_FIXED_FREQ; + + return S_OK; +} + +static const IAudioClockVtbl AudioClock_Vtbl = +{ + AudioClock_QueryInterface, + AudioClock_AddRef, + AudioClock_Release, + AudioClock_GetFrequency, + AudioClock_GetPosition, + AudioClock_GetCharacteristics +}; + +static HRESULT WINAPI AudioClock2_QueryInterface(IAudioClock2 *iface, + REFIID riid, void **ppv) +{ + ACImpl *This = impl_from_IAudioClock2(iface); + return IAudioClock_QueryInterface(&This->IAudioClock_iface, riid, ppv); +} + +static ULONG WINAPI AudioClock2_AddRef(IAudioClock2 *iface) +{ + ACImpl *This = impl_from_IAudioClock2(iface); + return IAudioClient_AddRef(&This->IAudioClient_iface); +} + +static ULONG WINAPI AudioClock2_Release(IAudioClock2 *iface) +{ + ACImpl *This = impl_from_IAudioClock2(iface); + return IAudioClient_Release(&This->IAudioClient_iface); +} + +static HRESULT WINAPI AudioClock2_GetDevicePosition(IAudioClock2 *iface, + UINT64 *pos, UINT64 *qpctime) +{ + ACImpl *This = impl_from_IAudioClock2(iface); + HRESULT hr = AudioClock_GetPosition(&This->IAudioClock_iface, pos, qpctime); + if (SUCCEEDED(hr) && This->share == AUDCLNT_SHAREMODE_SHARED) + *pos /= pa_frame_size(&This->ss); + return hr; +} + +static const IAudioClock2Vtbl AudioClock2_Vtbl = +{ + AudioClock2_QueryInterface, + AudioClock2_AddRef, + AudioClock2_Release, + AudioClock2_GetDevicePosition +}; + +static HRESULT WINAPI AudioStreamVolume_QueryInterface( + IAudioStreamVolume *iface, REFIID riid, void **ppv) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if (!ppv) + return E_POINTER; + *ppv = NULL; + + if (IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IAudioStreamVolume)) + *ppv = iface; + if (*ppv) { + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + if (IsEqualIID(riid, &IID_IMarshal)) + return IUnknown_QueryInterface(This->marshal, riid, ppv); + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioStreamVolume_AddRef(IAudioStreamVolume *iface) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + return IAudioClient_AddRef(&This->IAudioClient_iface); +} + +static ULONG WINAPI AudioStreamVolume_Release(IAudioStreamVolume *iface) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + return IAudioClient_Release(&This->IAudioClient_iface); +} + +static HRESULT WINAPI AudioStreamVolume_GetChannelCount( + IAudioStreamVolume *iface, UINT32 *out) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + + TRACE("(%p)->(%p)\n", This, out); + + if (!out) + return E_POINTER; + + *out = This->ss.channels; + + return S_OK; +} + +struct pulse_info_cb_data { + UINT32 n; + float *levels; +}; + +static HRESULT WINAPI AudioStreamVolume_SetAllVolumes( + IAudioStreamVolume *iface, UINT32 count, const float *levels) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + HRESULT hr; + int i; + + TRACE("(%p)->(%d, %p)\n", This, count, levels); + + if (!levels) + return E_POINTER; + + if (count != This->ss.channels) + return E_INVALIDARG; + + pthread_mutex_lock(&pulse_lock); + hr = pulse_stream_valid(This); + if (FAILED(hr)) + goto out; + + for (i = 0; i < count; ++i) + This->vol[i] = levels[i]; + +out: + pthread_mutex_unlock(&pulse_lock); + return hr; +} + +static HRESULT WINAPI AudioStreamVolume_GetAllVolumes( + IAudioStreamVolume *iface, UINT32 count, float *levels) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + HRESULT hr; + int i; + + TRACE("(%p)->(%d, %p)\n", This, count, levels); + + if (!levels) + return E_POINTER; + + if (count != This->ss.channels) + return E_INVALIDARG; + + pthread_mutex_lock(&pulse_lock); + hr = pulse_stream_valid(This); + if (FAILED(hr)) + goto out; + + for (i = 0; i < count; ++i) + levels[i] = This->vol[i]; + +out: + pthread_mutex_unlock(&pulse_lock); + return hr; +} + +static HRESULT WINAPI AudioStreamVolume_SetChannelVolume( + IAudioStreamVolume *iface, UINT32 index, float level) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + HRESULT hr; + float volumes[PA_CHANNELS_MAX]; + + TRACE("(%p)->(%d, %f)\n", This, index, level); + + if (level < 0.f || level > 1.f) + return E_INVALIDARG; + + if (index >= This->ss.channels) + return E_INVALIDARG; + + hr = AudioStreamVolume_GetAllVolumes(iface, This->ss.channels, volumes); + volumes[index] = level; + if (SUCCEEDED(hr)) + hr = AudioStreamVolume_SetAllVolumes(iface, This->ss.channels, volumes); + return hr; +} + +static HRESULT WINAPI AudioStreamVolume_GetChannelVolume( + IAudioStreamVolume *iface, UINT32 index, float *level) +{ + ACImpl *This = impl_from_IAudioStreamVolume(iface); + float volumes[PA_CHANNELS_MAX]; + HRESULT hr; + + TRACE("(%p)->(%d, %p)\n", This, index, level); + + if (!level) + return E_POINTER; + + if (index >= This->ss.channels) + return E_INVALIDARG; + + hr = AudioStreamVolume_GetAllVolumes(iface, This->ss.channels, volumes); + if (SUCCEEDED(hr)) + *level = volumes[index]; + return hr; +} + +static const IAudioStreamVolumeVtbl AudioStreamVolume_Vtbl = +{ + AudioStreamVolume_QueryInterface, + AudioStreamVolume_AddRef, + AudioStreamVolume_Release, + AudioStreamVolume_GetChannelCount, + AudioStreamVolume_SetChannelVolume, + AudioStreamVolume_GetChannelVolume, + AudioStreamVolume_SetAllVolumes, + AudioStreamVolume_GetAllVolumes +}; + +static AudioSessionWrapper *AudioSessionWrapper_Create(ACImpl *client) +{ + AudioSessionWrapper *ret; + + ret = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, + sizeof(AudioSessionWrapper)); + if (!ret) + return NULL; + + ret->IAudioSessionControl2_iface.lpVtbl = &AudioSessionControl2_Vtbl; + ret->ISimpleAudioVolume_iface.lpVtbl = &SimpleAudioVolume_Vtbl; + ret->IChannelAudioVolume_iface.lpVtbl = &ChannelAudioVolume_Vtbl; + + ret->ref = !client; + + ret->client = client; + if (client) { + ret->session = client->session; + AudioClient_AddRef(&client->IAudioClient_iface); + } + + return ret; +} + +static HRESULT WINAPI AudioSessionControl_QueryInterface( + IAudioSessionControl2 *iface, REFIID riid, void **ppv) +{ + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if (!ppv) + return E_POINTER; + *ppv = NULL; + + if (IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IAudioSessionControl) || + IsEqualIID(riid, &IID_IAudioSessionControl2)) + *ppv = iface; + if (*ppv) { + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI AudioSessionControl_AddRef(IAudioSessionControl2 *iface) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + ULONG ref; + ref = InterlockedIncrement(&This->ref); + TRACE("(%p) Refcount now %u\n", This, ref); + return ref; +} + +static ULONG WINAPI AudioSessionControl_Release(IAudioSessionControl2 *iface) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + ULONG ref; + ref = InterlockedDecrement(&This->ref); + TRACE("(%p) Refcount now %u\n", This, ref); + if (!ref) { + if (This->client) { + This->client->session_wrapper = NULL; + AudioClient_Release(&This->client->IAudioClient_iface); + } + HeapFree(GetProcessHeap(), 0, This); + } + return ref; +} + +static HRESULT WINAPI AudioSessionControl_GetState(IAudioSessionControl2 *iface, + AudioSessionState *state) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + ACImpl *client; + + TRACE("(%p)->(%p)\n", This, state); + + if (!state) + return NULL_PTR_ERR; + + pthread_mutex_lock(&pulse_lock); + if (list_empty(&This->session->clients)) { + *state = AudioSessionStateExpired; + goto out; + } + LIST_FOR_EACH_ENTRY(client, &This->session->clients, ACImpl, entry) { + if (client->started) { + *state = AudioSessionStateActive; + goto out; + } + } + *state = AudioSessionStateInactive; + +out: + pthread_mutex_unlock(&pulse_lock); + return S_OK; +} + +static HRESULT WINAPI AudioSessionControl_GetDisplayName( + IAudioSessionControl2 *iface, WCHAR **name) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, name); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_SetDisplayName( + IAudioSessionControl2 *iface, const WCHAR *name, const GUID *session) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p, %s) - stub\n", This, name, debugstr_guid(session)); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_GetIconPath( + IAudioSessionControl2 *iface, WCHAR **path) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, path); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_SetIconPath( + IAudioSessionControl2 *iface, const WCHAR *path, const GUID *session) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p, %s) - stub\n", This, path, debugstr_guid(session)); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_GetGroupingParam( + IAudioSessionControl2 *iface, GUID *group) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, group); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_SetGroupingParam( + IAudioSessionControl2 *iface, const GUID *group, const GUID *session) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%s, %s) - stub\n", This, debugstr_guid(group), + debugstr_guid(session)); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_RegisterAudioSessionNotification( + IAudioSessionControl2 *iface, IAudioSessionEvents *events) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, events); + + return S_OK; +} + +static HRESULT WINAPI AudioSessionControl_UnregisterAudioSessionNotification( + IAudioSessionControl2 *iface, IAudioSessionEvents *events) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, events); + + return S_OK; +} + +static HRESULT WINAPI AudioSessionControl_GetSessionIdentifier( + IAudioSessionControl2 *iface, WCHAR **id) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, id); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_GetSessionInstanceIdentifier( + IAudioSessionControl2 *iface, WCHAR **id) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + FIXME("(%p)->(%p) - stub\n", This, id); + + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionControl_GetProcessId( + IAudioSessionControl2 *iface, DWORD *pid) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + TRACE("(%p)->(%p)\n", This, pid); + + if (!pid) + return E_POINTER; + + *pid = GetCurrentProcessId(); + + return S_OK; +} + +static HRESULT WINAPI AudioSessionControl_IsSystemSoundsSession( + IAudioSessionControl2 *iface) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + TRACE("(%p)\n", This); + + return S_FALSE; +} + +static HRESULT WINAPI AudioSessionControl_SetDuckingPreference( + IAudioSessionControl2 *iface, BOOL optout) +{ + AudioSessionWrapper *This = impl_from_IAudioSessionControl2(iface); + + TRACE("(%p)->(%d)\n", This, optout); + + return S_OK; +} + +static const IAudioSessionControl2Vtbl AudioSessionControl2_Vtbl = +{ + AudioSessionControl_QueryInterface, + AudioSessionControl_AddRef, + AudioSessionControl_Release, + AudioSessionControl_GetState, + AudioSessionControl_GetDisplayName, + AudioSessionControl_SetDisplayName, + AudioSessionControl_GetIconPath, + AudioSessionControl_SetIconPath, + AudioSessionControl_GetGroupingParam, + AudioSessionControl_SetGroupingParam, + AudioSessionControl_RegisterAudioSessionNotification, + AudioSessionControl_UnregisterAudioSessionNotification, + AudioSessionControl_GetSessionIdentifier, + AudioSessionControl_GetSessionInstanceIdentifier, + AudioSessionControl_GetProcessId, + AudioSessionControl_IsSystemSoundsSession, + AudioSessionControl_SetDuckingPreference +}; + +typedef struct _SessionMgr { + IAudioSessionManager2 IAudioSessionManager2_iface; + + LONG ref; + + IMMDevice *device; +} SessionMgr; + +static HRESULT WINAPI AudioSessionManager_QueryInterface(IAudioSessionManager2 *iface, + REFIID riid, void **ppv) +{ + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if (!ppv) + return E_POINTER; + *ppv = NULL; + + if (IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IAudioSessionManager) || + IsEqualIID(riid, &IID_IAudioSessionManager2)) + *ppv = iface; + if (*ppv) { + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static inline SessionMgr *impl_from_IAudioSessionManager2(IAudioSessionManager2 *iface) +{ + return CONTAINING_RECORD(iface, SessionMgr, IAudioSessionManager2_iface); +} + +static ULONG WINAPI AudioSessionManager_AddRef(IAudioSessionManager2 *iface) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + ULONG ref; + ref = InterlockedIncrement(&This->ref); + TRACE("(%p) Refcount now %u\n", This, ref); + return ref; +} + +static ULONG WINAPI AudioSessionManager_Release(IAudioSessionManager2 *iface) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + ULONG ref; + ref = InterlockedDecrement(&This->ref); + TRACE("(%p) Refcount now %u\n", This, ref); + if (!ref) + HeapFree(GetProcessHeap(), 0, This); + return ref; +} + +static HRESULT WINAPI AudioSessionManager_GetAudioSessionControl( + IAudioSessionManager2 *iface, const GUID *session_guid, DWORD flags, + IAudioSessionControl **out) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + AudioSession *session; + AudioSessionWrapper *wrapper; + HRESULT hr; + + TRACE("(%p)->(%s, %x, %p)\n", This, debugstr_guid(session_guid), + flags, out); + + hr = get_audio_session(session_guid, This->device, 0, &session); + if (FAILED(hr)) + return hr; + + wrapper = AudioSessionWrapper_Create(NULL); + if (!wrapper) + return E_OUTOFMEMORY; + + wrapper->session = session; + + *out = (IAudioSessionControl*)&wrapper->IAudioSessionControl2_iface; + + return S_OK; +} + +static HRESULT WINAPI AudioSessionManager_GetSimpleAudioVolume( + IAudioSessionManager2 *iface, const GUID *session_guid, DWORD flags, + ISimpleAudioVolume **out) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + AudioSession *session; + AudioSessionWrapper *wrapper; + HRESULT hr; + + TRACE("(%p)->(%s, %x, %p)\n", This, debugstr_guid(session_guid), + flags, out); + + hr = get_audio_session(session_guid, This->device, 0, &session); + if (FAILED(hr)) + return hr; + + wrapper = AudioSessionWrapper_Create(NULL); + if (!wrapper) + return E_OUTOFMEMORY; + + wrapper->session = session; + + *out = &wrapper->ISimpleAudioVolume_iface; + + return S_OK; +} + +static HRESULT WINAPI AudioSessionManager_GetSessionEnumerator( + IAudioSessionManager2 *iface, IAudioSessionEnumerator **out) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + FIXME("(%p)->(%p) - stub\n", This, out); + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionManager_RegisterSessionNotification( + IAudioSessionManager2 *iface, IAudioSessionNotification *notification) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + FIXME("(%p)->(%p) - stub\n", This, notification); + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionManager_UnregisterSessionNotification( + IAudioSessionManager2 *iface, IAudioSessionNotification *notification) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + FIXME("(%p)->(%p) - stub\n", This, notification); + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionManager_RegisterDuckNotification( + IAudioSessionManager2 *iface, const WCHAR *session_id, + IAudioVolumeDuckNotification *notification) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + FIXME("(%p)->(%p) - stub\n", This, notification); + return E_NOTIMPL; +} + +static HRESULT WINAPI AudioSessionManager_UnregisterDuckNotification( + IAudioSessionManager2 *iface, + IAudioVolumeDuckNotification *notification) +{ + SessionMgr *This = impl_from_IAudioSessionManager2(iface); + FIXME("(%p)->(%p) - stub\n", This, notification); + return E_NOTIMPL; +} + +static const IAudioSessionManager2Vtbl AudioSessionManager2_Vtbl = +{ + AudioSessionManager_QueryInterface, + AudioSessionManager_AddRef, + AudioSessionManager_Release, + AudioSessionManager_GetAudioSessionControl, + AudioSessionManager_GetSimpleAudioVolume, + AudioSessionManager_GetSessionEnumerator, + AudioSessionManager_RegisterSessionNotification, + AudioSessionManager_UnregisterSessionNotification, + AudioSessionManager_RegisterDuckNotification, + AudioSessionManager_UnregisterDuckNotification +}; + +static HRESULT WINAPI SimpleAudioVolume_QueryInterface( + ISimpleAudioVolume *iface, REFIID riid, void **ppv) +{ + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if (!ppv) + return E_POINTER; + *ppv = NULL; + + if (IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_ISimpleAudioVolume)) + *ppv = iface; + if (*ppv) { + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI SimpleAudioVolume_AddRef(ISimpleAudioVolume *iface) +{ + AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface); + return AudioSessionControl_AddRef(&This->IAudioSessionControl2_iface); +} + +static ULONG WINAPI SimpleAudioVolume_Release(ISimpleAudioVolume *iface) +{ + AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface); + return AudioSessionControl_Release(&This->IAudioSessionControl2_iface); +} + +static HRESULT WINAPI SimpleAudioVolume_SetMasterVolume( + ISimpleAudioVolume *iface, float level, const GUID *context) +{ + AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface); + AudioSession *session = This->session; + + TRACE("(%p)->(%f, %s)\n", session, level, wine_dbgstr_guid(context)); + + if (level < 0.f || level > 1.f) + return E_INVALIDARG; + + if (context) + FIXME("Notifications not supported yet\n"); + + TRACE("PulseAudio does not support session volume control\n"); + + pthread_mutex_lock(&pulse_lock); + session->master_vol = level; + pthread_mutex_unlock(&pulse_lock); + + return S_OK; +} + +static HRESULT WINAPI SimpleAudioVolume_GetMasterVolume( + ISimpleAudioVolume *iface, float *level) +{ + AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface); + AudioSession *session = This->session; + + TRACE("(%p)->(%p)\n", session, level); + + if (!level) + return NULL_PTR_ERR; + + *level = session->master_vol; + + return S_OK; +} + +static HRESULT WINAPI SimpleAudioVolume_SetMute(ISimpleAudioVolume *iface, + BOOL mute, const GUID *context) +{ + AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface); + AudioSession *session = This->session; + + TRACE("(%p)->(%u, %s)\n", session, mute, debugstr_guid(context)); + + if (context) + FIXME("Notifications not supported yet\n"); + + session->mute = mute; + + return S_OK; +} + +static HRESULT WINAPI SimpleAudioVolume_GetMute(ISimpleAudioVolume *iface, + BOOL *mute) +{ + AudioSessionWrapper *This = impl_from_ISimpleAudioVolume(iface); + AudioSession *session = This->session; + + TRACE("(%p)->(%p)\n", session, mute); + + if (!mute) + return NULL_PTR_ERR; + + *mute = session->mute; + + return S_OK; +} + +static const ISimpleAudioVolumeVtbl SimpleAudioVolume_Vtbl = +{ + SimpleAudioVolume_QueryInterface, + SimpleAudioVolume_AddRef, + SimpleAudioVolume_Release, + SimpleAudioVolume_SetMasterVolume, + SimpleAudioVolume_GetMasterVolume, + SimpleAudioVolume_SetMute, + SimpleAudioVolume_GetMute +}; + +static HRESULT WINAPI ChannelAudioVolume_QueryInterface( + IChannelAudioVolume *iface, REFIID riid, void **ppv) +{ + TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), ppv); + + if (!ppv) + return E_POINTER; + *ppv = NULL; + + if (IsEqualIID(riid, &IID_IUnknown) || + IsEqualIID(riid, &IID_IChannelAudioVolume)) + *ppv = iface; + if (*ppv) { + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("Unknown interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI ChannelAudioVolume_AddRef(IChannelAudioVolume *iface) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + return AudioSessionControl_AddRef(&This->IAudioSessionControl2_iface); +} + +static ULONG WINAPI ChannelAudioVolume_Release(IChannelAudioVolume *iface) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + return AudioSessionControl_Release(&This->IAudioSessionControl2_iface); +} + +static HRESULT WINAPI ChannelAudioVolume_GetChannelCount( + IChannelAudioVolume *iface, UINT32 *out) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + AudioSession *session = This->session; + + TRACE("(%p)->(%p)\n", session, out); + + if (!out) + return NULL_PTR_ERR; + + *out = session->channel_count; + + return S_OK; +} + +static HRESULT WINAPI ChannelAudioVolume_SetChannelVolume( + IChannelAudioVolume *iface, UINT32 index, float level, + const GUID *context) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + AudioSession *session = This->session; + + TRACE("(%p)->(%d, %f, %s)\n", session, index, level, + wine_dbgstr_guid(context)); + + if (level < 0.f || level > 1.f) + return E_INVALIDARG; + + if (index >= session->channel_count) + return E_INVALIDARG; + + if (context) + FIXME("Notifications not supported yet\n"); + + TRACE("PulseAudio does not support session volume control\n"); + + pthread_mutex_lock(&pulse_lock); + session->channel_vols[index] = level; + pthread_mutex_unlock(&pulse_lock); + + return S_OK; +} + +static HRESULT WINAPI ChannelAudioVolume_GetChannelVolume( + IChannelAudioVolume *iface, UINT32 index, float *level) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + AudioSession *session = This->session; + + TRACE("(%p)->(%d, %p)\n", session, index, level); + + if (!level) + return NULL_PTR_ERR; + + if (index >= session->channel_count) + return E_INVALIDARG; + + *level = session->channel_vols[index]; + + return S_OK; +} + +static HRESULT WINAPI ChannelAudioVolume_SetAllVolumes( + IChannelAudioVolume *iface, UINT32 count, const float *levels, + const GUID *context) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + AudioSession *session = This->session; + int i; + + TRACE("(%p)->(%d, %p, %s)\n", session, count, levels, + wine_dbgstr_guid(context)); + + if (!levels) + return NULL_PTR_ERR; + + if (count != session->channel_count) + return E_INVALIDARG; + + if (context) + FIXME("Notifications not supported yet\n"); + + TRACE("PulseAudio does not support session volume control\n"); + + pthread_mutex_lock(&pulse_lock); + for(i = 0; i < count; ++i) + session->channel_vols[i] = levels[i]; + pthread_mutex_unlock(&pulse_lock); + return S_OK; +} + +static HRESULT WINAPI ChannelAudioVolume_GetAllVolumes( + IChannelAudioVolume *iface, UINT32 count, float *levels) +{ + AudioSessionWrapper *This = impl_from_IChannelAudioVolume(iface); + AudioSession *session = This->session; + int i; + + TRACE("(%p)->(%d, %p)\n", session, count, levels); + + if (!levels) + return NULL_PTR_ERR; + + if (count != session->channel_count) + return E_INVALIDARG; + + for(i = 0; i < count; ++i) + levels[i] = session->channel_vols[i]; + + return S_OK; +} + +static const IChannelAudioVolumeVtbl ChannelAudioVolume_Vtbl = +{ + ChannelAudioVolume_QueryInterface, + ChannelAudioVolume_AddRef, + ChannelAudioVolume_Release, + ChannelAudioVolume_GetChannelCount, + ChannelAudioVolume_SetChannelVolume, + ChannelAudioVolume_GetChannelVolume, + ChannelAudioVolume_SetAllVolumes, + ChannelAudioVolume_GetAllVolumes +}; + +HRESULT WINAPI AUDDRV_GetAudioSessionManager(IMMDevice *device, + IAudioSessionManager2 **out) +{ + SessionMgr *This = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(SessionMgr)); + *out = NULL; + if (!This) + return E_OUTOFMEMORY; + This->IAudioSessionManager2_iface.lpVtbl = &AudioSessionManager2_Vtbl; + This->device = device; + This->ref = 1; + *out = &This->IAudioSessionManager2_iface; + return S_OK; +} + +HRESULT WINAPI AUDDRV_GetPropValue(GUID *guid, const PROPERTYKEY *prop, PROPVARIANT *out) +{ + TRACE("%s, (%s,%u), %p\n", wine_dbgstr_guid(guid), wine_dbgstr_guid(&prop->fmtid), prop->pid, out); + + if (IsEqualGUID(guid, &pulse_render_guid) && IsEqualPropertyKey(*prop, PKEY_AudioEndpoint_PhysicalSpeakers)) { + out->vt = VT_UI4; + out->ulVal = g_phys_speakers_mask; + + return out->ulVal ? S_OK : E_FAIL; + } + + return E_NOTIMPL; +} + + + +/********************************************************************** + * A-law and u-law sample manipulation functions + * Multiply with the given volume (vol must be between 0...1 inclusive) + * + * These were taken from PulseAudio's sources but adjusted to directly + * fit our usage (since we use floats directly) so they should be exact. + */ +static UINT8 mult_alaw_sample(UINT8 sample, float vol) +{ + static const float decode_to_13bits_float[1 << 8] = + { + -688.0f, -656.0f, -752.0f, -720.0f, -560.0f, -528.0f, -624.0f, -592.0f, + -944.0f, -912.0f, -1008.0f, -976.0f, -816.0f, -784.0f, -880.0f, -848.0f, + -344.0f, -328.0f, -376.0f, -360.0f, -280.0f, -264.0f, -312.0f, -296.0f, + -472.0f, -456.0f, -504.0f, -488.0f, -408.0f, -392.0f, -440.0f, -424.0f, + -2752.0f, -2624.0f, -3008.0f, -2880.0f, -2240.0f, -2112.0f, -2496.0f, -2368.0f, + -3776.0f, -3648.0f, -4032.0f, -3904.0f, -3264.0f, -3136.0f, -3520.0f, -3392.0f, + -1376.0f, -1312.0f, -1504.0f, -1440.0f, -1120.0f, -1056.0f, -1248.0f, -1184.0f, + -1888.0f, -1824.0f, -2016.0f, -1952.0f, -1632.0f, -1568.0f, -1760.0f, -1696.0f, + -43.0f, -41.0f, -47.0f, -45.0f, -35.0f, -33.0f, -39.0f, -37.0f, + -59.0f, -57.0f, -63.0f, -61.0f, -51.0f, -49.0f, -55.0f, -53.0f, + -11.0f, -9.0f, -15.0f, -13.0f, -3.0f, -1.0f, -7.0f, -5.0f, + -27.0f, -25.0f, -31.0f, -29.0f, -19.0f, -17.0f, -23.0f, -21.0f, + -172.0f, -164.0f, -188.0f, -180.0f, -140.0f, -132.0f, -156.0f, -148.0f, + -236.0f, -228.0f, -252.0f, -244.0f, -204.0f, -196.0f, -220.0f, -212.0f, + -86.0f, -82.0f, -94.0f, -90.0f, -70.0f, -66.0f, -78.0f, -74.0f, + -118.0f, -114.0f, -126.0f, -122.0f, -102.0f, -98.0f, -110.0f, -106.0f, + 688.0f, 656.0f, 752.0f, 720.0f, 560.0f, 528.0f, 624.0f, 592.0f, + 944.0f, 912.0f, 1008.0f, 976.0f, 816.0f, 784.0f, 880.0f, 848.0f, + 344.0f, 328.0f, 376.0f, 360.0f, 280.0f, 264.0f, 312.0f, 296.0f, + 472.0f, 456.0f, 504.0f, 488.0f, 408.0f, 392.0f, 440.0f, 424.0f, + 2752.0f, 2624.0f, 3008.0f, 2880.0f, 2240.0f, 2112.0f, 2496.0f, 2368.0f, + 3776.0f, 3648.0f, 4032.0f, 3904.0f, 3264.0f, 3136.0f, 3520.0f, 3392.0f, + 1376.0f, 1312.0f, 1504.0f, 1440.0f, 1120.0f, 1056.0f, 1248.0f, 1184.0f, + 1888.0f, 1824.0f, 2016.0f, 1952.0f, 1632.0f, 1568.0f, 1760.0f, 1696.0f, + 43.0f, 41.0f, 47.0f, 45.0f, 35.0f, 33.0f, 39.0f, 37.0f, + 59.0f, 57.0f, 63.0f, 61.0f, 51.0f, 49.0f, 55.0f, 53.0f, + 11.0f, 9.0f, 15.0f, 13.0f, 3.0f, 1.0f, 7.0f, 5.0f, + 27.0f, 25.0f, 31.0f, 29.0f, 19.0f, 17.0f, 23.0f, 21.0f, + 172.0f, 164.0f, 188.0f, 180.0f, 140.0f, 132.0f, 156.0f, 148.0f, + 236.0f, 228.0f, 252.0f, 244.0f, 204.0f, 196.0f, 220.0f, 212.0f, + 86.0f, 82.0f, 94.0f, 90.0f, 70.0f, 66.0f, 78.0f, 74.0f, + 118.0f, 114.0f, 126.0f, 122.0f, 102.0f, 98.0f, 110.0f, 106.0f + }; + + static const UINT8 encode[1 << 13] = + { + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, + 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, + 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, + 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, + 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, + 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x38, 0x38, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, + 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, + 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, + 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, + 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, + 0x3e, 0x3e, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, + 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, + 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, + 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, + 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x31, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, + 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, + 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, + 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, + 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, + 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x35, 0x35, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, + 0x6b, 0x6b, 0x6b, 0x6b, 0x6b, 0x6b, 0x6b, 0x6b, 0x68, 0x68, 0x68, 0x68, 0x68, + 0x68, 0x68, 0x68, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x6e, 0x6e, + 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, + 0x6f, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6d, 0x6d, 0x6d, 0x6d, + 0x6d, 0x6d, 0x6d, 0x6d, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, + 0x60, 0x60, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x67, 0x67, 0x67, 0x67, 0x67, 0x67, 0x67, 0x67, + 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x65, 0x65, 0x65, 0x65, 0x65, + 0x65, 0x65, 0x65, 0x7a, 0x7a, 0x7a, 0x7a, 0x7b, 0x7b, 0x7b, 0x7b, 0x78, 0x78, + 0x78, 0x78, 0x79, 0x79, 0x79, 0x79, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7c, 0x7c, 0x7c, 0x7c, 0x7d, 0x7d, 0x7d, 0x7d, 0x72, 0x72, 0x72, 0x72, + 0x73, 0x73, 0x73, 0x73, 0x70, 0x70, 0x70, 0x70, 0x71, 0x71, 0x71, 0x71, 0x76, + 0x76, 0x76, 0x76, 0x77, 0x77, 0x77, 0x77, 0x74, 0x74, 0x74, 0x74, 0x75, 0x75, + 0x75, 0x75, 0x4a, 0x4a, 0x4b, 0x4b, 0x48, 0x48, 0x49, 0x49, 0x4e, 0x4e, 0x4f, + 0x4f, 0x4c, 0x4c, 0x4d, 0x4d, 0x42, 0x42, 0x43, 0x43, 0x40, 0x40, 0x41, 0x41, + 0x46, 0x46, 0x47, 0x47, 0x44, 0x44, 0x45, 0x45, 0x5a, 0x5a, 0x5b, 0x5b, 0x58, + 0x58, 0x59, 0x59, 0x5e, 0x5e, 0x5f, 0x5f, 0x5c, 0x5c, 0x5d, 0x5d, 0x52, 0x52, + 0x53, 0x53, 0x50, 0x50, 0x51, 0x51, 0x56, 0x56, 0x57, 0x57, 0x54, 0x54, 0x55, + 0x55, 0xd5, 0xd5, 0xd4, 0xd4, 0xd7, 0xd7, 0xd6, 0xd6, 0xd1, 0xd1, 0xd0, 0xd0, + 0xd3, 0xd3, 0xd2, 0xd2, 0xdd, 0xdd, 0xdc, 0xdc, 0xdf, 0xdf, 0xde, 0xde, 0xd9, + 0xd9, 0xd8, 0xd8, 0xdb, 0xdb, 0xda, 0xda, 0xc5, 0xc5, 0xc4, 0xc4, 0xc7, 0xc7, + 0xc6, 0xc6, 0xc1, 0xc1, 0xc0, 0xc0, 0xc3, 0xc3, 0xc2, 0xc2, 0xcd, 0xcd, 0xcc, + 0xcc, 0xcf, 0xcf, 0xce, 0xce, 0xc9, 0xc9, 0xc8, 0xc8, 0xcb, 0xcb, 0xca, 0xca, + 0xf5, 0xf5, 0xf5, 0xf5, 0xf4, 0xf4, 0xf4, 0xf4, 0xf7, 0xf7, 0xf7, 0xf7, 0xf6, + 0xf6, 0xf6, 0xf6, 0xf1, 0xf1, 0xf1, 0xf1, 0xf0, 0xf0, 0xf0, 0xf0, 0xf3, 0xf3, + 0xf3, 0xf3, 0xf2, 0xf2, 0xf2, 0xf2, 0xfd, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc, + 0xfc, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xf9, 0xf9, 0xf9, 0xf9, + 0xf8, 0xf8, 0xf8, 0xf8, 0xfb, 0xfb, 0xfb, 0xfb, 0xfa, 0xfa, 0xfa, 0xfa, 0xe5, + 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, + 0xe4, 0xe4, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe6, 0xe6, 0xe6, + 0xe6, 0xe6, 0xe6, 0xe6, 0xe6, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, + 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, + 0xe3, 0xe3, 0xe3, 0xe2, 0xe2, 0xe2, 0xe2, 0xe2, 0xe2, 0xe2, 0xe2, 0xed, 0xed, + 0xed, 0xed, 0xed, 0xed, 0xed, 0xed, 0xec, 0xec, 0xec, 0xec, 0xec, 0xec, 0xec, + 0xec, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xee, 0xee, 0xee, 0xee, + 0xee, 0xee, 0xee, 0xee, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe8, + 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb, + 0xeb, 0xeb, 0xea, 0xea, 0xea, 0xea, 0xea, 0xea, 0xea, 0xea, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb4, 0xb4, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb7, 0xb7, 0xb7, 0xb7, + 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, + 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, + 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, + 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, + 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb3, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, + 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, + 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, + 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, + 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, + 0xb2, 0xb2, 0xb2, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, + 0xbd, 0xbd, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbc, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb8, 0xb8, + 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, + 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, + 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, + 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, + 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xba, 0xba, 0xba, 0xba, + 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, + 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, + 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, + 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, + 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa + }; + + return encode[(int)(vol * decode_to_13bits_float[sample]) + ARRAY_SIZE(encode) / 2]; +} + +static UINT8 mult_ulaw_sample(UINT8 sample, float vol) +{ + static const float decode_to_14bits_float[1 << 8] = + { + -8031.0f, -7775.0f, -7519.0f, -7263.0f, -7007.0f, -6751.0f, -6495.0f, -6239.0f, + -5983.0f, -5727.0f, -5471.0f, -5215.0f, -4959.0f, -4703.0f, -4447.0f, -4191.0f, + -3999.0f, -3871.0f, -3743.0f, -3615.0f, -3487.0f, -3359.0f, -3231.0f, -3103.0f, + -2975.0f, -2847.0f, -2719.0f, -2591.0f, -2463.0f, -2335.0f, -2207.0f, -2079.0f, + -1983.0f, -1919.0f, -1855.0f, -1791.0f, -1727.0f, -1663.0f, -1599.0f, -1535.0f, + -1471.0f, -1407.0f, -1343.0f, -1279.0f, -1215.0f, -1151.0f, -1087.0f, -1023.0f, + -975.0f, -943.0f, -911.0f, -879.0f, -847.0f, -815.0f, -783.0f, -751.0f, + -719.0f, -687.0f, -655.0f, -623.0f, -591.0f, -559.0f, -527.0f, -495.0f, + -471.0f, -455.0f, -439.0f, -423.0f, -407.0f, -391.0f, -375.0f, -359.0f, + -343.0f, -327.0f, -311.0f, -295.0f, -279.0f, -263.0f, -247.0f, -231.0f, + -219.0f, -211.0f, -203.0f, -195.0f, -187.0f, -179.0f, -171.0f, -163.0f, + -155.0f, -147.0f, -139.0f, -131.0f, -123.0f, -115.0f, -107.0f, -99.0f, + -93.0f, -89.0f, -85.0f, -81.0f, -77.0f, -73.0f, -69.0f, -65.0f, + -61.0f, -57.0f, -53.0f, -49.0f, -45.0f, -41.0f, -37.0f, -33.0f, + -30.0f, -28.0f, -26.0f, -24.0f, -22.0f, -20.0f, -18.0f, -16.0f, + -14.0f, -12.0f, -10.0f, -8.0f, -6.0f, -4.0f, -2.0f, 0.0f, + 8031.0f, 7775.0f, 7519.0f, 7263.0f, 7007.0f, 6751.0f, 6495.0f, 6239.0f, + 5983.0f, 5727.0f, 5471.0f, 5215.0f, 4959.0f, 4703.0f, 4447.0f, 4191.0f, + 3999.0f, 3871.0f, 3743.0f, 3615.0f, 3487.0f, 3359.0f, 3231.0f, 3103.0f, + 2975.0f, 2847.0f, 2719.0f, 2591.0f, 2463.0f, 2335.0f, 2207.0f, 2079.0f, + 1983.0f, 1919.0f, 1855.0f, 1791.0f, 1727.0f, 1663.0f, 1599.0f, 1535.0f, + 1471.0f, 1407.0f, 1343.0f, 1279.0f, 1215.0f, 1151.0f, 1087.0f, 1023.0f, + 975.0f, 943.0f, 911.0f, 879.0f, 847.0f, 815.0f, 783.0f, 751.0f, + 719.0f, 687.0f, 655.0f, 623.0f, 591.0f, 559.0f, 527.0f, 495.0f, + 471.0f, 455.0f, 439.0f, 423.0f, 407.0f, 391.0f, 375.0f, 359.0f, + 343.0f, 327.0f, 311.0f, 295.0f, 279.0f, 263.0f, 247.0f, 231.0f, + 219.0f, 211.0f, 203.0f, 195.0f, 187.0f, 179.0f, 171.0f, 163.0f, + 155.0f, 147.0f, 139.0f, 131.0f, 123.0f, 115.0f, 107.0f, 99.0f, + 93.0f, 89.0f, 85.0f, 81.0f, 77.0f, 73.0f, 69.0f, 65.0f, + 61.0f, 57.0f, 53.0f, 49.0f, 45.0f, 41.0f, 37.0f, 33.0f, + 30.0f, 28.0f, 26.0f, 24.0f, 22.0f, 20.0f, 18.0f, 16.0f, + 14.0f, 12.0f, 10.0f, 8.0f, 6.0f, 4.0f, 2.0f, 0.0f + }; + + static const UINT8 encode[1 << 14] = + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, + 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, + 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, + 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, + 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, + 0x3a, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, + 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, + 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, + 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, + 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x42, 0x42, + 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, + 0x42, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, + 0x43, 0x43, 0x43, 0x43, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, + 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, + 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x46, 0x46, 0x46, + 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, + 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, + 0x47, 0x47, 0x47, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, + 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, + 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x4a, 0x4a, 0x4a, 0x4a, + 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4b, + 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, + 0x4b, 0x4b, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, + 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, + 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, + 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4f, 0x4f, + 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, + 0x4f, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x51, 0x51, 0x51, 0x51, + 0x51, 0x51, 0x51, 0x51, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x53, + 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x54, 0x54, 0x54, 0x54, 0x54, 0x54, + 0x54, 0x54, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x56, 0x56, 0x56, + 0x56, 0x56, 0x56, 0x56, 0x56, 0x57, 0x57, 0x57, 0x57, 0x57, 0x57, 0x57, 0x57, + 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x59, 0x59, 0x59, 0x59, 0x59, + 0x59, 0x59, 0x59, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5b, 0x5b, + 0x5b, 0x5b, 0x5b, 0x5b, 0x5b, 0x5b, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, + 0x5c, 0x5d, 0x5d, 0x5d, 0x5d, 0x5d, 0x5d, 0x5d, 0x5d, 0x5e, 0x5e, 0x5e, 0x5e, + 0x5e, 0x5e, 0x5e, 0x5e, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x60, + 0x60, 0x60, 0x60, 0x61, 0x61, 0x61, 0x61, 0x62, 0x62, 0x62, 0x62, 0x63, 0x63, + 0x63, 0x63, 0x64, 0x64, 0x64, 0x64, 0x65, 0x65, 0x65, 0x65, 0x66, 0x66, 0x66, + 0x66, 0x67, 0x67, 0x67, 0x67, 0x68, 0x68, 0x68, 0x68, 0x69, 0x69, 0x69, 0x69, + 0x6a, 0x6a, 0x6a, 0x6a, 0x6b, 0x6b, 0x6b, 0x6b, 0x6c, 0x6c, 0x6c, 0x6c, 0x6d, + 0x6d, 0x6d, 0x6d, 0x6e, 0x6e, 0x6e, 0x6e, 0x6f, 0x6f, 0x6f, 0x6f, 0x70, 0x70, + 0x71, 0x71, 0x72, 0x72, 0x73, 0x73, 0x74, 0x74, 0x75, 0x75, 0x76, 0x76, 0x77, + 0x77, 0x78, 0x78, 0x79, 0x79, 0x7a, 0x7a, 0x7b, 0x7b, 0x7c, 0x7c, 0x7d, 0x7d, + 0x7e, 0x7e, 0xff, 0xfe, 0xfe, 0xfd, 0xfd, 0xfc, 0xfc, 0xfb, 0xfb, 0xfa, 0xfa, + 0xf9, 0xf9, 0xf8, 0xf8, 0xf7, 0xf7, 0xf6, 0xf6, 0xf5, 0xf5, 0xf4, 0xf4, 0xf3, + 0xf3, 0xf2, 0xf2, 0xf1, 0xf1, 0xf0, 0xf0, 0xef, 0xef, 0xef, 0xef, 0xee, 0xee, + 0xee, 0xee, 0xed, 0xed, 0xed, 0xed, 0xec, 0xec, 0xec, 0xec, 0xeb, 0xeb, 0xeb, + 0xeb, 0xea, 0xea, 0xea, 0xea, 0xe9, 0xe9, 0xe9, 0xe9, 0xe8, 0xe8, 0xe8, 0xe8, + 0xe7, 0xe7, 0xe7, 0xe7, 0xe6, 0xe6, 0xe6, 0xe6, 0xe5, 0xe5, 0xe5, 0xe5, 0xe4, + 0xe4, 0xe4, 0xe4, 0xe3, 0xe3, 0xe3, 0xe3, 0xe2, 0xe2, 0xe2, 0xe2, 0xe1, 0xe1, + 0xe1, 0xe1, 0xe0, 0xe0, 0xe0, 0xe0, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, + 0xdf, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdc, 0xdc, 0xdc, 0xdc, 0xdc, 0xdc, 0xdc, 0xdc, 0xdb, + 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xda, 0xda, 0xda, 0xda, 0xda, 0xda, + 0xda, 0xda, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd8, 0xd8, 0xd8, + 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, + 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, + 0xd5, 0xd5, 0xd5, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd3, 0xd3, + 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, + 0xd2, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd0, 0xd0, 0xd0, 0xd0, + 0xd0, 0xd0, 0xd0, 0xd0, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, + 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, + 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, + 0xcc, 0xcc, 0xcc, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, + 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, + 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xc9, 0xc9, 0xc9, 0xc9, + 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc8, + 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, + 0xc8, 0xc8, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, + 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, + 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc4, 0xc4, + 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, + 0xc4, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, + 0xc3, 0xc3, 0xc3, 0xc3, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, + 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, + 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbb, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, + 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, + 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, + 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, + 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb7, 0xb7, 0xb7, 0xb7, + 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, + 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, + 0xb7, 0xb7, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb5, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, + 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, + 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80 + }; + + return encode[(int)(vol * decode_to_14bits_float[sample]) + ARRAY_SIZE(encode) / 2]; +} diff --git a/pkgs/osu-wine/audio-revert/winepulse.drv/winepulse.drv.spec b/pkgs/osu-wine/audio-revert/winepulse.drv/winepulse.drv.spec new file mode 100644 index 0000000..7aeb175 --- /dev/null +++ b/pkgs/osu-wine/audio-revert/winepulse.drv/winepulse.drv.spec @@ -0,0 +1,11 @@ +# MMDevAPI driver functions +@ stdcall -private GetPriority() AUDDRV_GetPriority +@ stdcall -private GetEndpointIDs(long ptr ptr ptr ptr) AUDDRV_GetEndpointIDs +@ stdcall -private GetAudioEndpoint(ptr ptr ptr) AUDDRV_GetAudioEndpoint +@ stdcall -private GetAudioSessionManager(ptr ptr) AUDDRV_GetAudioSessionManager +@ stdcall -private GetPropValue(ptr ptr ptr) AUDDRV_GetPropValue + +# WinMM driver functions +@ stdcall -private DriverProc(long long long long long) winealsa.drv.DriverProc +@ stdcall -private midMessage(long long long long long) winealsa.drv.midMessage +@ stdcall -private modMessage(long long long long long) winealsa.drv.modMessage diff --git a/pkgs/osu-wine/default.nix b/pkgs/osu-wine/default.nix new file mode 100644 index 0000000..321dd8a --- /dev/null +++ b/pkgs/osu-wine/default.nix @@ -0,0 +1,64 @@ +## Configuration: +# Control you default wine config in nixpkgs-config: +# wine = { +# release = "stable"; # "stable", "unstable", "staging", "wayland" +# build = "wineWow"; # "wine32", "wine64", "wineWow" +# }; +# Make additional configurations on demand: +# wine.override { wineBuild = "wine32"; wineRelease = "staging"; }; +{ lib, stdenv, callPackage, darwin, + wineBuild ? if stdenv.hostPlatform.system == "x86_64-linux" then "wineWow" else "wine32", + gettextSupport ? true, + fontconfigSupport ? stdenv.isLinux, + alsaSupport ? stdenv.isLinux, + gtkSupport ? false, + openglSupport ? true, + tlsSupport ? true, + gstreamerSupport ? false, + cupsSupport ? true, + dbusSupport ? stdenv.isLinux, + openclSupport ? false, + cairoSupport ? stdenv.isLinux, + odbcSupport ? false, + netapiSupport ? false, + cursesSupport ? true, + vaSupport ? false, + pcapSupport ? false, + v4lSupport ? false, + saneSupport ? stdenv.isLinux, + gphoto2Support ? false, + krb5Support ? false, + pulseaudioSupport ? stdenv.isLinux, + udevSupport ? stdenv.isLinux, + xineramaSupport ? stdenv.isLinux, + vulkanSupport ? true, + sdlSupport ? true, + usbSupport ? true, + mingwSupport ? true, + waylandSupport ? stdenv.isLinux, + x11Support ? stdenv.isLinux, + embedInstallers ? false, # The Mono and Gecko MSI installers + moltenvk ? darwin.moltenvk # Allow users to override MoltenVK easily +}: + +let wine-build = build: release: + lib.getAttr build (callPackage ./packages.nix { + wineRelease = release; + supportFlags = { + inherit + alsaSupport cairoSupport cupsSupport cursesSupport dbusSupport + embedInstallers fontconfigSupport gettextSupport gphoto2Support + gstreamerSupport gtkSupport krb5Support mingwSupport netapiSupport + odbcSupport openclSupport openglSupport pcapSupport + pulseaudioSupport saneSupport sdlSupport tlsSupport udevSupport + usbSupport v4lSupport vaSupport vulkanSupport waylandSupport + x11Support xineramaSupport + ; + }; + inherit moltenvk; + }); + +in + callPackage ./osu-wine.nix { + wineUnstable = wine-build wineBuild "unstable"; + } diff --git a/pkgs/osu-wine/osu-wine.nix b/pkgs/osu-wine/osu-wine.nix new file mode 100644 index 0000000..8765158 --- /dev/null +++ b/pkgs/osu-wine/osu-wine.nix @@ -0,0 +1,35 @@ +{ lib, callPackage, autoconf, hexdump, perl, python3, wineUnstable, path }: + +with callPackage "${path}/pkgs/applications/emulators/wine/util.nix" {}; + +let patch = (callPackage ./sources.nix {}).staging; + build-inputs = pkgNames: extra: + (mkBuildInputs wineUnstable.pkgArches pkgNames) ++ extra; + patchList = lib.mapAttrsToList (k: v: ./patches/${k}) (builtins.readDir ./patches); +in assert lib.versions.majorMinor wineUnstable.version == lib.versions.majorMinor patch.version; + +(lib.overrideDerivation (wineUnstable.override { wineRelease = "staging"; }) (self: { + buildInputs = build-inputs [ "perl" "util-linux" "autoconf" "gitMinimal" ] self.buildInputs; + nativeBuildInputs = [ autoconf hexdump perl python3 ] ++ self.nativeBuildInputs; + + prePatch = self.prePatch or "" + '' + patchShebangs tools + cp -r ${patch}/patches ${patch}/staging . + chmod +w patches + patchShebangs ./patches/gitapply.sh + python3 ./staging/patchinstall.py DESTDIR="$PWD" --all ${lib.concatMapStringsSep " " (ps: "-W ${ps}") patch.disabledPatchsets} + for dir in $(ls ${./audio-revert}); do + rm -rf dlls/$dir + cp -r ${./audio-revert}/$dir dlls + chmod -R +w dlls/$dir + done + for patch in ${builtins.concatStringsSep " " patchList}; do + echo "Applying $patch" + patch -p1 < "$patch" + done + ''; +})) // { + meta = wineUnstable.meta // { + description = wineUnstable.meta.description + " (with osu-wine patches)"; + }; +} diff --git a/pkgs/osu-wine/packages.nix b/pkgs/osu-wine/packages.nix new file mode 100644 index 0000000..fabc76c --- /dev/null +++ b/pkgs/osu-wine/packages.nix @@ -0,0 +1,57 @@ +{ stdenv_32bit, lib, pkgs, pkgsi686Linux, pkgsCross, callPackage, substituteAll, moltenvk, path, + wineRelease ? "stable", + supportFlags +}: + +let + src = lib.getAttr wineRelease (callPackage ./sources.nix {}); +in with src; { + wine32 = pkgsi686Linux.callPackage "${path}/pkgs/applications/emulators/wine/base.nix" { + pname = "wine"; + inherit src version supportFlags patches moltenvk wineRelease; + pkgArches = [ pkgsi686Linux ]; + geckos = [ gecko32 ]; + mingwGccs = with pkgsCross; [ mingw32.buildPackages.gcc ]; + monos = [ mono ]; + platforms = [ "i686-linux" "x86_64-linux" ]; + }; + wine64 = callPackage "${path}/pkgs/applications/emulators/wine/base.nix" { + pname = "wine64"; + inherit src version supportFlags patches moltenvk wineRelease; + pkgArches = [ pkgs ]; + mingwGccs = with pkgsCross; [ mingwW64.buildPackages.gcc ]; + geckos = [ gecko64 ]; + monos = [ mono ]; + configureFlags = [ "--enable-win64" ]; + platforms = [ "x86_64-linux" "x86_64-darwin" ]; + mainProgram = "wine64"; + }; + wineWow = callPackage "${path}/pkgs/applications/emulators/wine/base.nix" { + pname = "wine-wow"; + inherit src version supportFlags patches moltenvk wineRelease; + stdenv = stdenv_32bit; + pkgArches = [ pkgs pkgsi686Linux ]; + geckos = [ gecko32 gecko64 ]; + mingwGccs = with pkgsCross; [ mingw32.buildPackages.gcc mingwW64.buildPackages.gcc ]; + monos = [ mono ]; + buildScript = substituteAll { + src = "${path}/pkgs/applications/emulators/wine/builder-wow.sh"; + # pkgconfig has trouble picking the right architecture + pkgconfig64remove = lib.makeSearchPathOutput "dev" "lib/pkgconfig" [ pkgs.glib pkgs.gst_all_1.gstreamer ]; + }; + platforms = [ "x86_64-linux" ]; + mainProgram = "wine64"; + }; + wineWow64 = callPackage "${path}/pkgs/applications/emulators/wine/base.nix" { + pname = "wine-wow64"; + inherit src version patches moltenvk wineRelease; + supportFlags = supportFlags // { mingwSupport = true; }; # Required because we request "--enable-archs=x86_64" + pkgArches = [ pkgs ]; + mingwGccs = with pkgsCross; [ mingw32.buildPackages.gcc mingwW64.buildPackages.gcc ]; + geckos = [ gecko64 ]; + monos = [ mono ]; + configureFlags = [ "--enable-archs=x86_64,i386" ]; + platforms = [ "x86_64-linux" "x86_64-darwin" ]; + mainProgram = "wine"; + }; +} diff --git a/pkgs/osu-wine/patches/0001-add-wine-unicode-again.patch b/pkgs/osu-wine/patches/0001-add-wine-unicode-again.patch new file mode 100644 index 0000000..9dcd675 --- /dev/null +++ b/pkgs/osu-wine/patches/0001-add-wine-unicode-again.patch @@ -0,0 +1,207 @@ +--- a/include/wine/test.h ++++ b/include/wine/test.h +@@ -28,6 +28,13 @@ + #include + #include + ++#ifdef __WINE_CONFIG_H ++#error config.h should not be used in Wine tests ++#endif ++#ifdef __WINE_WINE_UNICODE_H ++#error wine/unicode.h should not be used in Wine tests ++#endif ++ + #ifndef INVALID_FILE_ATTRIBUTES + #define INVALID_FILE_ATTRIBUTES (~0u) + #endif +--- /dev/null ++++ b/include/wine/unicode.h +@@ -0,0 +1,178 @@ ++/* ++ * Wine internal Unicode definitions ++ * ++ * Copyright 2000 Alexandre Julliard ++ * ++ * 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 ++ */ ++ ++#if 0 ++#pragma makedep install ++#endif ++ ++#ifndef __WINE_WINE_UNICODE_H ++#define __WINE_WINE_UNICODE_H ++ ++#include ++ ++#include ++#include ++#include ++#include ++ ++#ifdef __WINE_USE_MSVCRT ++#error This file should not be used with msvcrt headers ++#endif ++ ++#ifndef WINE_UNICODE_INLINE ++#define WINE_UNICODE_INLINE static FORCEINLINE ++#endif ++ ++WINE_UNICODE_INLINE WCHAR tolowerW( WCHAR ch ) ++{ ++ return RtlDowncaseUnicodeChar( ch ); ++} ++ ++WINE_UNICODE_INLINE WCHAR toupperW( WCHAR ch ) ++{ ++ return RtlUpcaseUnicodeChar( ch ); ++} ++ ++WINE_UNICODE_INLINE int isspaceW( WCHAR wc ) ++{ ++ unsigned short type; ++ GetStringTypeW( CT_CTYPE1, &wc, 1, &type ); ++ return type & C1_SPACE; ++} ++ ++WINE_UNICODE_INLINE unsigned int strlenW( const WCHAR *str ) ++{ ++ const WCHAR *s = str; ++ while (*s) s++; ++ return s - str; ++} ++ ++WINE_UNICODE_INLINE WCHAR *strcpyW( WCHAR *dst, const WCHAR *src ) ++{ ++ WCHAR *p = dst; ++ while ((*p++ = *src++)); ++ return dst; ++} ++ ++WINE_UNICODE_INLINE WCHAR *strcatW( WCHAR *dst, const WCHAR *src ) ++{ ++ strcpyW( dst + strlenW(dst), src ); ++ return dst; ++} ++ ++WINE_UNICODE_INLINE WCHAR *strrchrW( const WCHAR *str, WCHAR ch ) ++{ ++ WCHAR *ret = NULL; ++ do { if (*str == ch) ret = (WCHAR *)(ULONG_PTR)str; } while (*str++); ++ return ret; ++} ++ ++WINE_UNICODE_INLINE int strcmpiW( const WCHAR *str1, const WCHAR *str2 ) ++{ ++ for (;;) ++ { ++ int ret = tolowerW(*str1) - tolowerW(*str2); ++ if (ret || !*str1) return ret; ++ str1++; ++ str2++; ++ } ++} ++ ++WINE_UNICODE_INLINE int strncmpiW( const WCHAR *str1, const WCHAR *str2, int n ) ++{ ++ int ret = 0; ++ for ( ; n > 0; n--, str1++, str2++) ++ if ((ret = tolowerW(*str1) - tolowerW(*str2)) || !*str1) break; ++ return ret; ++} ++ ++WINE_UNICODE_INLINE LONG strtolW( LPCWSTR s, LPWSTR *end, INT base ) ++{ ++ BOOL negative = FALSE, empty = TRUE; ++ LONG ret = 0; ++ ++ if (base < 0 || base == 1 || base > 36) return 0; ++ if (end) *end = (WCHAR *)s; ++ while (isspaceW(*s)) s++; ++ ++ if (*s == '-') ++ { ++ negative = TRUE; ++ s++; ++ } ++ else if (*s == '+') s++; ++ ++ if ((base == 0 || base == 16) && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) ++ { ++ base = 16; ++ s += 2; ++ } ++ if (base == 0) base = s[0] != '0' ? 10 : 8; ++ ++ while (*s) ++ { ++ int v; ++ ++ if ('0' <= *s && *s <= '9') v = *s - '0'; ++ else if ('A' <= *s && *s <= 'Z') v = *s - 'A' + 10; ++ else if ('a' <= *s && *s <= 'z') v = *s - 'a' + 10; ++ else break; ++ if (v >= base) break; ++ if (negative) v = -v; ++ s++; ++ empty = FALSE; ++ ++ if (!negative && (ret > MAXLONG / base || ret * base > MAXLONG - v)) ++ ret = MAXLONG; ++ else if (negative && (ret < (LONG)MINLONG / base || ret * base < (LONG)(MINLONG - v))) ++ ret = MINLONG; ++ else ++ ret = ret * base + v; ++ } ++ ++ if (end && !empty) *end = (WCHAR *)s; ++ return ret; ++} ++ ++NTSYSAPI int __cdecl _vsnwprintf(WCHAR*,size_t,const WCHAR*,__ms_va_list); ++ ++static inline int WINAPIV snprintfW( WCHAR *str, size_t len, const WCHAR *format, ...) ++{ ++ int retval; ++ __ms_va_list valist; ++ __ms_va_start(valist, format); ++ retval = _vsnwprintf(str, len, format, valist); ++ __ms_va_end(valist); ++ return retval; ++} ++ ++static inline int WINAPIV sprintfW( WCHAR *str, const WCHAR *format, ...) ++{ ++ int retval; ++ __ms_va_list valist; ++ __ms_va_start(valist, format); ++ retval = _vsnwprintf(str, MAXLONG, format, valist); ++ __ms_va_end(valist); ++ return retval; ++} ++ ++#undef WINE_UNICODE_INLINE ++ ++#endif /* __WINE_WINE_UNICODE_H */ +--- a/include/Makefile.in ++++ b/include/Makefile.in +@@ -873,6 +873,7 @@ + wine/strmbase.h \ + wine/svcctl.idl \ + wine/test.h \ ++ wine/unicode.h \ + wine/unixlib.h \ + wine/vulkan.h \ + wine/vulkan_driver.h \ diff --git a/pkgs/osu-wine/patches/0001-libs-libjpeg-Set-default-DCT-algorithm-to-fastest.patch b/pkgs/osu-wine/patches/0001-libs-libjpeg-Set-default-DCT-algorithm-to-fastest.patch new file mode 100644 index 0000000..0bec693 --- /dev/null +++ b/pkgs/osu-wine/patches/0001-libs-libjpeg-Set-default-DCT-algorithm-to-fastest.patch @@ -0,0 +1,24 @@ +From 1b5de04e1ae401f2f3d7179da0379191886cdfad Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Tue, 2 May 2023 01:36:12 +0200 +Subject: [PATCH] libs/libjpeg: Set default DCT algorithm to fastest. + +--- + libs/jpeg/jconfig.h | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/libs/jpeg/jconfig.h b/libs/jpeg/jconfig.h +index 2d05a3b09026..9f18c71751bd 100644 +--- a/libs/jpeg/jconfig.h ++++ b/libs/jpeg/jconfig.h +@@ -17,6 +17,7 @@ + /* #undef NEED_SHORT_EXTERNAL_NAMES */ + /* Define this if you get warnings about undefined structures. */ + /* #undef INCOMPLETE_TYPES_BROKEN */ ++#define JDCT_DEFAULT JDCT_FASTEST + + /* Define "boolean" as unsigned char, not enum, on Windows systems. */ + #ifdef _WIN32 +-- +2.40.1 + diff --git a/pkgs/osu-wine/patches/0001-revert-staging-alt-tab.patch b/pkgs/osu-wine/patches/0001-revert-staging-alt-tab.patch new file mode 100644 index 0000000..9b430bc --- /dev/null +++ b/pkgs/osu-wine/patches/0001-revert-staging-alt-tab.patch @@ -0,0 +1,253 @@ +--- b/dlls/winex11.drv/event.c ++++ a/dlls/winex11.drv/event.c +@@ -604,27 +604,16 @@ + */ + static void set_focus( Display *display, HWND hwnd, Time time ) + { ++ HWND focus; +- HWND focus, old_active; + Window win; + GUITHREADINFO threadinfo; + +- old_active = NtUserGetForegroundWindow(); +- + /* prevent recursion */ + x11drv_thread_data()->active_window = hwnd; + + TRACE( "setting foreground window to %p\n", hwnd ); + NtUserSetForegroundWindow( hwnd ); + +- /* Some applications expect that a being deactivated topmost window +- * receives the WM_WINDOWPOSCHANGING/WM_WINDOWPOSCHANGED messages, +- * and perform some specific actions. Chessmaster is one of such apps. +- * Window Manager keeps a topmost window on top in z-oder, so there is +- * no need to actually do anything, just send the messages. +- */ +- if (old_active && (NtUserGetWindowLongW( old_active, GWL_EXSTYLE ) & WS_EX_TOPMOST)) +- NtUserSetWindowPos( old_active, hwnd, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOOWNERZORDER ); +- + threadinfo.cbSize = sizeof(threadinfo); + NtUserGetGUIThreadInfo( 0, &threadinfo ); + focus = threadinfo.hwndFocus; +--- b/dlls/win32u/input.c ++++ a/dlls/win32u/input.c +@@ -1375,9 +1375,6 @@ + send_message( hwnd, WM_ACTIVATE, + MAKEWPARAM( mouse ? WA_CLICKACTIVE : WA_ACTIVE, is_iconic(hwnd) ), + (LPARAM)previous ); +- +- send_message( hwnd, WM_NCPOINTERUP, 0, 0); +- + if (NtUserGetAncestor( hwnd, GA_PARENT ) == get_desktop_window()) + NtUserPostMessage( get_desktop_window(), WM_PARENTNOTIFY, WM_NCACTIVATE, (LPARAM)hwnd ); + +--- b/dlls/win32u/input.c ++++ a/dlls/win32u/input.c +@@ -1633,10 +1633,6 @@ + (LPARAM)previous ); + if (NtUserGetAncestor( hwnd, GA_PARENT ) == get_desktop_window()) + NtUserPostMessage( get_desktop_window(), WM_PARENTNOTIFY, WM_NCACTIVATE, (LPARAM)hwnd ); +- +- if (hwnd == NtUserGetForegroundWindow() && !is_iconic( hwnd )) +- NtUserSetActiveWindow( hwnd ); +- + } + + user_driver->pSetActiveWindow( hwnd ); +--- b/dlls/win32u/driver.c ++++ a/dlls/win32u/driver.c +@@ -838,10 +838,6 @@ + hdc, rect.left - dx, rect.top - dy, SRCCOPY, 0, 0 ); + } + +-static void nulldrv_SetActiveWindow( HWND hwnd ) +-{ +-} +- + static void nulldrv_SetCapture( HWND hwnd, UINT flags ) + { + } +@@ -1245,7 +1241,6 @@ + nulldrv_ProcessEvents, + nulldrv_ReleaseDC, + nulldrv_ScrollDC, +- nulldrv_SetActiveWindow, + nulldrv_SetCapture, + loaderdrv_SetDesktopWindow, + nulldrv_SetFocus, +@@ -1325,7 +1320,6 @@ + SET_USER_FUNC(ProcessEvents); + SET_USER_FUNC(ReleaseDC); + SET_USER_FUNC(ScrollDC); +- SET_USER_FUNC(SetActiveWindow); + SET_USER_FUNC(SetCapture); + SET_USER_FUNC(SetDesktopWindow); + SET_USER_FUNC(SetFocus); +--- b/dlls/win32u/input.c ++++ a/dlls/win32u/input.c +@@ -1887,8 +1887,6 @@ + NtUserPostMessage( get_desktop_window(), WM_PARENTNOTIFY, WM_NCACTIVATE, (LPARAM)hwnd ); + } + +- user_driver->pSetActiveWindow( hwnd ); +- + /* now change focus if necessary */ + if (focus) + { +--- b/dlls/winex11.drv/event.c ++++ a/dlls/winex11.drv/event.c +@@ -576,9 +576,6 @@ + Window win; + GUITHREADINFO threadinfo; + +- /* prevent recursion */ +- x11drv_thread_data()->active_window = hwnd; +- + TRACE( "setting foreground window to %p\n", hwnd ); + NtUserSetForegroundWindow( hwnd ); + +@@ -836,8 +833,6 @@ + + if (!focus_win) + { +- x11drv_thread_data()->active_window = 0; +- + /* Abey : 6-Oct-99. Check again if the focus out window is the + Foreground window, because in most cases the messages sent + above must have already changed the foreground window, in which +--- b/dlls/winex11.drv/init.c ++++ a/dlls/winex11.drv/init.c +@@ -421,7 +421,6 @@ + .pProcessEvents = X11DRV_ProcessEvents, + .pReleaseDC = X11DRV_ReleaseDC, + .pScrollDC = X11DRV_ScrollDC, +- .pSetActiveWindow = X11DRV_SetActiveWindow, + .pSetCapture = X11DRV_SetCapture, + .pSetDesktopWindow = X11DRV_SetDesktopWindow, + .pSetFocus = X11DRV_SetFocus, +--- b/dlls/winex11.drv/window.c ++++ a/dlls/winex11.drv/window.c +@@ -2431,54 +2431,6 @@ + } + + +-/*********************************************************************** +- * SetActiveWindow (X11DRV.@) +- */ +-void X11DRV_SetActiveWindow( HWND hwnd ) +-{ +- struct x11drv_thread_data *thread_data = x11drv_init_thread_data(); +- struct x11drv_win_data *data; +- +- TRACE("%p\n", hwnd); +- +- if (thread_data->active_window == hwnd) +- { +- TRACE("ignoring activation for already active window %p\n", hwnd); +- return; +- } +- +- if (!(data = get_win_data( hwnd ))) return; +- +- if (data->mapped && data->managed && !data->iconic) +- { +- XEvent xev; +- struct x11drv_win_data *active = get_win_data( thread_data->active_window ); +- DWORD timestamp = NtUserGetThreadInfo()->message_time - EVENT_x11_time_to_win32_time( 0 ); +- +- TRACE("setting _NET_ACTIVE_WINDOW to %p/%lx, current active %p/%lx\n", +- data->hwnd, data->whole_window, active ? active->hwnd : NULL, active ? active->whole_window : 0 ); +- +- xev.xclient.type = ClientMessage; +- xev.xclient.window = data->whole_window; +- xev.xclient.message_type = x11drv_atom(_NET_ACTIVE_WINDOW); +- xev.xclient.serial = 0; +- xev.xclient.display = data->display; +- xev.xclient.send_event = True; +- xev.xclient.format = 32; +- +- xev.xclient.data.l[0] = 1; /* source: application */ +- xev.xclient.data.l[1] = timestamp; +- xev.xclient.data.l[2] = active ? active->whole_window : 0; +- xev.xclient.data.l[3] = 0; +- xev.xclient.data.l[4] = 0; +- XSendEvent( data->display, root_window, False, SubstructureRedirectMask | SubstructureNotifyMask, &xev ); +- +- if (active) release_win_data( active ); +- } +- +- release_win_data( data ); +-} +- + /*********************************************************************** + * SetCapture (X11DRV.@) + */ +--- b/dlls/winex11.drv/x11drv.h ++++ a/dlls/winex11.drv/x11drv.h +@@ -231,7 +231,6 @@ + const RECT *top_rect, DWORD flags ); + extern void X11DRV_ReleaseDC( HWND hwnd, HDC hdc ); + extern BOOL X11DRV_ScrollDC( HDC hdc, INT dx, INT dy, HRGN update ); +-extern void X11DRV_SetActiveWindow( HWND hwnd ); + extern void X11DRV_SetCapture( HWND hwnd, UINT flags ); + extern void X11DRV_SetDesktopWindow( HWND hwnd ); + extern void X11DRV_SetLayeredWindowAttributes( HWND hwnd, COLORREF key, BYTE alpha, +@@ -383,7 +382,6 @@ + Display *display; + XEvent *current_event; /* event currently being processed */ + HWND grab_hwnd; /* window that currently grabs the mouse */ +- HWND active_window; /* active window */ + HWND last_focus; /* last window that had focus */ + XIM xim; /* input method */ + HWND last_xic_hwnd; /* last xic window */ +@@ -490,7 +488,6 @@ + XATOM__ICC_PROFILE, + XATOM__KDE_NET_WM_STATE_SKIP_SWITCHER, + XATOM__MOTIF_WM_HINTS, +- XATOM__NET_ACTIVE_WINDOW, + XATOM__NET_STARTUP_INFO_BEGIN, + XATOM__NET_STARTUP_INFO, + XATOM__NET_SUPPORTED, +--- b/dlls/winex11.drv/x11drv_main.c ++++ a/dlls/winex11.drv/x11drv_main.c +@@ -154,7 +154,6 @@ + "_ICC_PROFILE", + "_KDE_NET_WM_STATE_SKIP_SWITCHER", + "_MOTIF_WM_HINTS", +- "_NET_ACTIVE_WINDOW", + "_NET_STARTUP_INFO_BEGIN", + "_NET_STARTUP_INFO", + "_NET_SUPPORTED", +--- b/include/wine/gdi_driver.h ++++ a/include/wine/gdi_driver.h +@@ -316,7 +316,6 @@ + BOOL (*pProcessEvents)(DWORD); + void (*pReleaseDC)(HWND,HDC); + BOOL (*pScrollDC)(HDC,INT,INT,HRGN); +- void (*pSetActiveWindow)(HWND); + void (*pSetCapture)(HWND,UINT); + void (*pSetDesktopWindow)(HWND); + void (*pSetFocus)(HWND); +--- b/dlls/winex11.drv/window.c ++++ a/dlls/winex11.drv/window.c +@@ -278,6 +278,9 @@ + if (style & WS_MINIMIZEBOX) ret |= MWM_DECOR_MINIMIZE; + if (style & WS_MAXIMIZEBOX) ret |= MWM_DECOR_MAXIMIZE; + } ++ if (ex_style & WS_EX_DLGMODALFRAME) ret |= MWM_DECOR_BORDER; ++ else if (style & WS_THICKFRAME) ret |= MWM_DECOR_BORDER; ++ else if ((style & (WS_DLGFRAME|WS_BORDER)) == WS_DLGFRAME) ret |= MWM_DECOR_BORDER; + return ret; + } + +--- b/dlls/winex11.drv/window.c ++++ a/dlls/winex11.drv/window.c +@@ -279,7 +279,7 @@ + if (style & WS_MAXIMIZEBOX) ret |= MWM_DECOR_MAXIMIZE; + } + if (ex_style & WS_EX_DLGMODALFRAME) ret |= MWM_DECOR_BORDER; ++ else if (style & WS_THICKFRAME) ret |= MWM_DECOR_BORDER | MWM_DECOR_RESIZEH; +- else if (style & WS_THICKFRAME) ret |= MWM_DECOR_BORDER; + else if ((style & (WS_DLGFRAME|WS_BORDER)) == WS_DLGFRAME) ret |= MWM_DECOR_BORDER; + return ret; + } diff --git a/pkgs/osu-wine/patches/0001-server-Implement-thread-priorities-on-Linux.patch b/pkgs/osu-wine/patches/0001-server-Implement-thread-priorities-on-Linux.patch new file mode 100644 index 0000000..c829ac2 --- /dev/null +++ b/pkgs/osu-wine/patches/0001-server-Implement-thread-priorities-on-Linux.patch @@ -0,0 +1,292 @@ +commit 6a033150c36bea6d704b7537c219e9b13b4387ec +Author: Rémi Bernon +Date: Tue Dec 1 22:49:26 2020 +0100 +Subject: [PATCH 1/3] server: Implement thread priorities on Linux. + +This does not report permission errors in order to avoid any breaking +change, only the parameter checks that were already there are returning +errors. + +Only call setpriority on Linux, as unix_tid is a Mach port on Mac OS. + +--- a/configure.ac ++++ b/configure.ac +@@ -2086,6 +2086,25 @@ + AC_DEFINE(HAVE_SCHED_SETAFFINITY, 1, [Define to 1 if you have the `sched_setaffinity' function.]) + fi + ++AC_CACHE_CHECK([for sched_setscheduler],wine_cv_have_sched_setscheduler, ++ AC_LINK_IFELSE([AC_LANG_PROGRAM( ++[[#define _GNU_SOURCE ++#include ]], [[sched_setscheduler(0, 0, 0);]])],[wine_cv_have_sched_setscheduler=yes],[wine_cv_have_sched_setscheduler=no])) ++if test "$wine_cv_have_sched_setscheduler" = "yes" ++then ++ AC_DEFINE(HAVE_SCHED_SETSCHEDULER, 1, [Define to 1 if you have the `sched_setscheduler' function.]) ++fi ++ ++AC_CACHE_CHECK([for setpriority],wine_cv_have_setpriority, ++ AC_LINK_IFELSE([AC_LANG_PROGRAM( ++[[#define _GNU_SOURCE ++#include ++#include ]], [[setpriority(0, 0, 0);]])],[wine_cv_have_setpriority=yes],[wine_cv_have_setpriority=no])) ++if test "$wine_cv_have_setpriority" = "yes" ++then ++ AC_DEFINE(HAVE_SETPRIORITY, 1, [Define to 1 if you have the `setpriority' function.]) ++fi ++ + dnl **** Check for types **** + + AC_C_INLINE +--- a/server/process.c ++++ b/server/process.c +@@ -1638,6 +1638,24 @@ + release_object( process ); + } + ++static void set_process_priority( struct process *process, int priority ) ++{ ++ struct thread *thread; ++ ++ if (!process->running_threads) ++ { ++ set_error( STATUS_PROCESS_IS_TERMINATING ); ++ return; ++ } ++ ++ LIST_FOR_EACH_ENTRY( thread, &process->thread_list, struct thread, proc_entry ) ++ { ++ set_thread_priority( thread, priority, thread->priority ); ++ } ++ ++ process->priority = priority; ++} ++ + static void set_process_affinity( struct process *process, affinity_t affinity ) + { + struct thread *thread; +@@ -1663,7 +1681,7 @@ + + if ((process = get_process_from_handle( req->handle, PROCESS_SET_INFORMATION ))) + { +- if (req->mask & SET_PROCESS_INFO_PRIORITY) process->priority = req->priority; ++ if (req->mask & SET_PROCESS_INFO_PRIORITY) set_process_priority( process, req->priority ); + if (req->mask & SET_PROCESS_INFO_AFFINITY) set_process_affinity( process, req->affinity ); + release_object( process ); + } +--- a/server/thread.c ++++ b/server/thread.c +@@ -37,6 +37,12 @@ + #define _WITH_CPU_SET_T + #include + #endif ++#ifdef HAVE_SYS_TIME_H ++#include ++#endif ++#ifdef HAVE_SYS_RESOURCE_H ++#include ++#endif + + #include "ntstatus.h" + #define WIN32_NO_STATUS +@@ -253,6 +259,7 @@ + thread->state = RUNNING; + thread->exit_code = 0; + thread->priority = 0; ++ thread->priority_applied = 0; + thread->suspend = 0; + thread->dbg_hidden = 0; + thread->desktop_users = 0; +@@ -648,31 +655,151 @@ + return mask; + } + ++#if defined(HAVE_SCHED_SETSCHEDULER) || defined(HAVE_SETPRIORITY) ++static int get_unix_priority( int priority_class, int priority ) ++{ ++ switch (priority_class) { ++ case PROCESS_PRIOCLASS_IDLE: ++ switch (priority) { ++ case THREAD_PRIORITY_IDLE: return 15; ++ case THREAD_PRIORITY_LOWEST: return 10; ++ case THREAD_PRIORITY_BELOW_NORMAL: return 8; ++ case THREAD_PRIORITY_NORMAL: return 6; ++ case THREAD_PRIORITY_ABOVE_NORMAL: return 4; ++ case THREAD_PRIORITY_HIGHEST: return 2; ++ case THREAD_PRIORITY_TIME_CRITICAL: return -15; ++ } ++ case PROCESS_PRIOCLASS_BELOW_NORMAL: ++ switch (priority) { ++ case THREAD_PRIORITY_IDLE: return 15; ++ case THREAD_PRIORITY_LOWEST: return 8; ++ case THREAD_PRIORITY_BELOW_NORMAL: return 6; ++ case THREAD_PRIORITY_NORMAL: return 4; ++ case THREAD_PRIORITY_ABOVE_NORMAL: return 2; ++ case THREAD_PRIORITY_HIGHEST: return 0; ++ case THREAD_PRIORITY_TIME_CRITICAL: return -15; ++ } ++ case PROCESS_PRIOCLASS_NORMAL: ++ switch (priority) { ++ case THREAD_PRIORITY_IDLE: return 15; ++ case THREAD_PRIORITY_LOWEST: return 4; ++ case THREAD_PRIORITY_BELOW_NORMAL: return 2; ++ case THREAD_PRIORITY_NORMAL: return 0; ++ case THREAD_PRIORITY_ABOVE_NORMAL: return -2; ++ case THREAD_PRIORITY_HIGHEST: return -4; ++ case THREAD_PRIORITY_TIME_CRITICAL: return -15; ++ } ++ case PROCESS_PRIOCLASS_ABOVE_NORMAL: ++ switch (priority) { ++ case THREAD_PRIORITY_IDLE: return 15; ++ case THREAD_PRIORITY_LOWEST: return 0; ++ case THREAD_PRIORITY_BELOW_NORMAL: return -2; ++ case THREAD_PRIORITY_NORMAL: return -4; ++ case THREAD_PRIORITY_ABOVE_NORMAL: return -6; ++ case THREAD_PRIORITY_HIGHEST: return -8; ++ case THREAD_PRIORITY_TIME_CRITICAL: return -15; ++ } ++ case PROCESS_PRIOCLASS_HIGH: ++ switch (priority) { ++ case THREAD_PRIORITY_IDLE: return 15; ++ case THREAD_PRIORITY_LOWEST: return -2; ++ case THREAD_PRIORITY_BELOW_NORMAL: return -4; ++ case THREAD_PRIORITY_NORMAL: return -6; ++ case THREAD_PRIORITY_ABOVE_NORMAL: return -8; ++ case THREAD_PRIORITY_HIGHEST: return -10; ++ case THREAD_PRIORITY_TIME_CRITICAL: return -15; ++ } ++ case PROCESS_PRIOCLASS_REALTIME: ++ switch (priority) { ++ case THREAD_PRIORITY_IDLE: return 1; ++ case -7: ++ case -6: ++ case -5: ++ case -4: ++ case -3: ++ case THREAD_PRIORITY_LOWEST: ++ case THREAD_PRIORITY_BELOW_NORMAL: ++ case THREAD_PRIORITY_NORMAL: ++ case THREAD_PRIORITY_ABOVE_NORMAL: ++ case THREAD_PRIORITY_HIGHEST: ++ case 3: ++ case 4: ++ case 5: ++ case 6: ++ return priority + 9; ++ case THREAD_PRIORITY_TIME_CRITICAL: ++ return 16; ++ } ++ } ++ return 0; ++} ++#endif ++ + #define THREAD_PRIORITY_REALTIME_HIGHEST 6 + #define THREAD_PRIORITY_REALTIME_LOWEST -7 + ++int set_thread_priority( struct thread* thread, int priority_class, int priority ) ++{ ++ int max = THREAD_PRIORITY_HIGHEST; ++ int min = THREAD_PRIORITY_LOWEST; ++ if (priority_class == PROCESS_PRIOCLASS_REALTIME) ++ { ++ max = THREAD_PRIORITY_REALTIME_HIGHEST; ++ min = THREAD_PRIORITY_REALTIME_LOWEST; ++ } ++ ++ if ((priority < min || priority > max) && ++ priority != THREAD_PRIORITY_IDLE && ++ priority != THREAD_PRIORITY_TIME_CRITICAL) ++ { ++ errno = EINVAL; ++ return -1; ++ } ++ ++ if (thread->process->priority == priority_class && ++ thread->priority == priority && ++ thread->priority_applied) ++ return 0; ++ ++ thread->priority = priority; ++ thread->priority_applied = 0; ++ if (thread->unix_tid == -1) ++ return 0; ++ ++#ifdef __linux__ ++ if (priority_class == PROCESS_PRIOCLASS_REALTIME) ++ { ++#ifdef HAVE_SCHED_SETSCHEDULER ++ struct sched_param param; ++ if (sched_getparam( thread->unix_tid, ¶m ) != 0) ++ return 0; /* ignore errors for now */ ++ ++ param.sched_priority = get_unix_priority( priority_class, priority ); ++ if (sched_setscheduler( thread->unix_tid, SCHED_RR|SCHED_RESET_ON_FORK, ¶m ) == 0) ++ return 0; ++#endif ++ } ++ else ++ { ++#ifdef HAVE_SETPRIORITY ++ if (setpriority( PRIO_PROCESS, thread->unix_tid, ++ get_unix_priority( priority_class, priority ) ) == 0) ++ return 0; ++#endif ++ } ++#endif ++ ++ return 0; /* ignore errors for now */ ++} ++ + /* set all information about a thread */ + static void set_thread_info( struct thread *thread, + const struct set_thread_info_request *req ) + { + if (req->mask & SET_THREAD_INFO_PRIORITY) + { +- int max = THREAD_PRIORITY_HIGHEST; +- int min = THREAD_PRIORITY_LOWEST; +- if (thread->process->priority == PROCESS_PRIOCLASS_REALTIME) +- { +- max = THREAD_PRIORITY_REALTIME_HIGHEST; +- min = THREAD_PRIORITY_REALTIME_LOWEST; +- } +- if ((req->priority >= min && req->priority <= max) || +- req->priority == THREAD_PRIORITY_IDLE || +- req->priority == THREAD_PRIORITY_TIME_CRITICAL) +- { +- thread->priority = req->priority; +- set_scheduler_priority( thread ); +- } +- else +- set_error( STATUS_INVALID_PARAMETER ); ++ if (set_thread_priority( thread, thread->process->priority, req->priority )) ++ file_set_error(); + } + if (req->mask & SET_THREAD_INFO_AFFINITY) + { +@@ -1541,6 +1668,7 @@ + + init_thread_context( current ); + generate_debug_event( current, DbgCreateThreadStateChange, &req->entry ); ++ set_thread_priority( current, current->process->priority, current->priority ); + set_thread_affinity( current, current->affinity ); + + reply->suspend = (current->suspend || current->process->suspend || current->context != NULL); +--- a/server/thread.h ++++ b/server/thread.h +@@ -84,6 +84,7 @@ + client_ptr_t entry_point; /* entry point (in client address space) */ + affinity_t affinity; /* affinity mask */ + int priority; /* priority level */ ++ int priority_applied; /* priority level successfully applied status */ + int suspend; /* suspend count */ + int dbg_hidden; /* hidden from debugger */ + obj_handle_t desktop; /* desktop handle */ +@@ -124,6 +125,7 @@ + extern int thread_add_inflight_fd( struct thread *thread, int client, int server ); + extern int thread_get_inflight_fd( struct thread *thread, int client ); + extern struct token *thread_get_impersonation_token( struct thread *thread ); ++extern int set_thread_priority( struct thread *thread, int priority_class, int priority ); + extern int set_thread_affinity( struct thread *thread, affinity_t affinity ); + extern int suspend_thread( struct thread *thread ); + extern int resume_thread( struct thread *thread ); diff --git a/pkgs/osu-wine/patches/0002-midi-fixed-revert.patch b/pkgs/osu-wine/patches/0002-midi-fixed-revert.patch new file mode 100644 index 0000000..b4d51ff --- /dev/null +++ b/pkgs/osu-wine/patches/0002-midi-fixed-revert.patch @@ -0,0 +1,17 @@ +--- b/include/mmddk.h ++++ a/include/mmddk.h +@@ -30,6 +30,14 @@ + extern "C" { + #endif + ++#define MAX_MIDIINDRV (16) ++/* For now I'm making 16 the maximum number of midi devices one can ++ * have. This should be more than enough for everybody. But as a purist, ++ * I intend to make it unbounded in the future, as soon as I figure ++ * a good way to do so. ++ */ ++#define MAX_MIDIOUTDRV (16) ++ + /* ================================== + * Multimedia DDK compatible part + * ================================== */ diff --git a/pkgs/osu-wine/patches/0002-revert-mscvrt-ify-modules.patch b/pkgs/osu-wine/patches/0002-revert-mscvrt-ify-modules.patch new file mode 100644 index 0000000..8294c7c --- /dev/null +++ b/pkgs/osu-wine/patches/0002-revert-mscvrt-ify-modules.patch @@ -0,0 +1,198 @@ +--- a/tools/makedep.c ++++ b/tools/makedep.c +@@ -199,6 +199,7 @@ + const char *staticlib; + const char *importlib; + const char *unixlib; ++ int use_msvcrt; + int data_only; + int is_win16; + int is_exe; +@@ -602,17 +603,6 @@ + + + /******************************************************************* +- * is_using_msvcrt +- * +- * Check if the files of a makefile use msvcrt by default. +- */ +-static int is_using_msvcrt( struct makefile *make ) +-{ +- return make->module || make->testdll; +-} +- +- +-/******************************************************************* + * arch_module_name + */ + static char *arch_module_name( const char *module, unsigned int arch ) +@@ -870,7 +860,7 @@ + file->basename = xstrdup( filename ? filename : name ); + file->filename = obj_dir_path( make, file->basename ); + file->file->flags = FLAG_GENERATED; +- file->use_msvcrt = is_using_msvcrt( make ); ++ file->use_msvcrt = make->use_msvcrt; + list_add_tail( &make->sources, &file->entry ); + if (make == include_makefile) + { +@@ -1620,7 +1610,7 @@ + + memset( file, 0, sizeof(*file) ); + file->name = xstrdup(name); +- file->use_msvcrt = is_using_msvcrt( make ); ++ file->use_msvcrt = make->use_msvcrt; + file->is_external = !!make->extlib; + list_add_tail( &make->sources, &file->entry ); + if (make == include_makefile) +@@ -1818,12 +1808,13 @@ + unsigned int i, arch; + struct incl_file *source, *next, *file, *dlldata = NULL; + struct strarray objs = get_expanded_make_var_array( make, "EXTRA_OBJS" ); ++ int multiarch = archs.count > 1 && make->use_msvcrt; + + LIST_FOR_EACH_ENTRY_SAFE( source, next, &make->sources, struct incl_file, entry ) + { + for (arch = 0; arch < archs.count; arch++) + { +- if (!is_multiarch( arch )) continue; ++ if (!arch != !multiarch) continue; + if (source->file->flags & FLAG_IDL_CLIENT) + { + file = add_generated_source( make, replace_extension( source->name, ".idl", "_c.c" ), NULL, arch ); +@@ -1942,7 +1933,7 @@ + { + for (arch = 0; arch < archs.count; arch++) + { +- if (!is_multiarch( arch )) continue; ++ if (!arch != !multiarch) continue; + file = add_generated_source( make, "testlist.o", "testlist.c", arch ); + add_dependency( file->file, "wine/test.h", INCL_NORMAL ); + add_all_includes( make, file, file->file ); +@@ -2196,6 +2187,7 @@ + */ + static const char *get_default_crt( const struct makefile *make ) + { ++ if (!make->use_msvcrt) return NULL; + if (make->module && is_crt_module( make->module )) return NULL; /* don't add crt import to crt dlls */ + return !make->testdll && (!make->staticlib || make->extlib) ? "ucrtbase" : "msvcrt"; + } +@@ -2352,7 +2344,6 @@ + strarray_add( &ret, strmake( "-I%s", root_src_dir_path( "include/msvcrt" ))); + for (i = 0; i < make->include_paths.count; i++) + strarray_add( &ret, strmake( "-I%s", make->include_paths.str[i] )); +- strarray_add( &ret, get_crt_define( make )); + } + strarray_addall( &ret, make->define_args ); + strarray_addall( &ret, get_expanded_file_local_var( make, obj, "EXTRADEFS" )); +@@ -2412,9 +2403,7 @@ + output_filename( tools_path( make, "winebuild" )); + } + output_filenames( target_flags[arch] ); +- if (arch) return; +- output_filename( "-mno-cygwin" ); +- output_filenames( lddll_flags ); ++ if (!arch) output_filenames( lddll_flags ); + } + + +@@ -2816,6 +2805,7 @@ + struct strarray multiarch_targets[MAX_ARCHS] = { empty_strarray }; + const char *dest; + unsigned int i, arch; ++ int multiarch; + + if (find_include_file( make, strmake( "%s.h", obj ))) source->file->flags |= FLAG_IDL_HEADER; + if (!source->file->flags) return; +@@ -2839,9 +2829,10 @@ + for (i = 0; i < ARRAY_SIZE(idl_outputs); i++) + { + if (!(source->file->flags & idl_outputs[i].flag)) continue; ++ multiarch = (make->use_msvcrt && archs.count > 1); + for (arch = 0; arch < archs.count; arch++) + { +- if (!is_multiarch( arch )) continue; ++ if (!arch != !multiarch) continue; + if (make->disabled[arch]) continue; + dest = strmake( "%s%s%s", arch_dirs[arch], obj, idl_outputs[i].ext ); + if (!find_src_file( make, dest )) strarray_add( &make->clean_files, dest ); +@@ -3152,13 +3143,13 @@ + if (arch) + { + if (source->file->flags & FLAG_C_UNIX) return; +- if (!is_using_msvcrt( make ) && !make->staticlib && !(source->file->flags & FLAG_C_IMPLIB)) return; ++ if (!make->use_msvcrt && !make->staticlib && !(source->file->flags & FLAG_C_IMPLIB)) return; + } + else if (source->file->flags & FLAG_C_UNIX) + { + if (!unix_lib_supported) return; + } +- else if (archs.count > 1 && is_using_msvcrt( make )) ++ else if (archs.count > 1 && make->use_msvcrt) + { + if (!so_dll_supported) return; + if (!(source->file->flags & FLAG_C_IMPLIB) && (!make->staticlib || make->extlib)) return; +@@ -3349,6 +3340,12 @@ + strarray_addall( &all_libs, add_import_libs( make, &dep_libs, default_imports, IMPORT_TYPE_DEFAULT, arch ) ); + if (!arch) strarray_addall( &all_libs, libs ); + ++ if (!make->use_msvcrt) ++ { ++ strarray_addall( &all_libs, get_expanded_make_var_array( make, "UNIX_LIBS" )); ++ strarray_addall( &all_libs, libs ); ++ } ++ + if (delay_load_flags[arch]) + { + for (i = 0; i < make->delayimports.count; i++) +@@ -3541,7 +3538,7 @@ + output( ": %s", obj_dir_path( make, testmodule )); + if (parent) + { +- char *parent_module = arch_module_name( make->testdll, arch ); ++ char *parent_module = arch_module_name( make->testdll, parent->use_msvcrt ? arch : 0 ); + output_filename( obj_dir_path( parent, parent_module )); + if (parent->unixlib) output_filename( obj_dir_path( parent, parent->unixlib )); + } +@@ -3792,11 +3789,15 @@ + } + else if (make->module) + { +- for (arch = 0; arch < archs.count; arch++) ++ if (!make->use_msvcrt) output_module( make, 0 ); ++ else + { +- if (is_multiarch( arch )) output_module( make, arch ); +- if (make->importlib && (is_multiarch( arch ) || !is_native_arch_disabled( make ))) +- output_import_lib( make, arch ); ++ for (arch = 0; arch < archs.count; arch++) ++ { ++ if (is_multiarch( arch )) output_module( make, arch ); ++ if (make->importlib && (is_multiarch( arch ) || !is_native_arch_disabled( make ))) ++ output_import_lib( make, arch ); ++ } + } + if (make->unixlib) output_unix_lib( make ); + if (make->is_exe && !make->is_win16 && unix_lib_supported && strendswith( make->module, ".exe" )) +@@ -4236,9 +4237,13 @@ + } + make->is_win16 = strarray_exists( &make->extradllflags, "-m16" ); + make->data_only = strarray_exists( &make->extradllflags, "-Wb,--data-only" ); ++ make->use_msvcrt = (make->module || make->testdll || make->is_win16) && ++ !strarray_exists( &make->extradllflags, "-mcygwin" ); + make->is_exe = strarray_exists( &make->extradllflags, "-mconsole" ) || + strarray_exists( &make->extradllflags, "-mwindows" ); + ++ if (make->use_msvcrt) strarray_add_uniq( &make->extradllflags, "-mno-cygwin" ); ++ + if (make->module) + { + /* add default install rules if nothing was specified */ +@@ -4296,6 +4301,8 @@ + + add_generated_sources( make ); + ++ if (make->use_msvcrt) strarray_add( &make->define_args, get_crt_define( make )); ++ + LIST_FOR_EACH_ENTRY( file, &make->includes, struct incl_file, entry ) parse_file( make, file, 0 ); + LIST_FOR_EACH_ENTRY( file, &make->sources, struct incl_file, entry ) get_dependencies( file, file ); + diff --git a/pkgs/osu-wine/patches/0002-server-Fallback-to-RTKIT-for-thread-priorities.patch b/pkgs/osu-wine/patches/0002-server-Fallback-to-RTKIT-for-thread-priorities.patch new file mode 100644 index 0000000..d62a4ff --- /dev/null +++ b/pkgs/osu-wine/patches/0002-server-Fallback-to-RTKIT-for-thread-priorities.patch @@ -0,0 +1,145 @@ +commit ebf411c1e5f20c6db7962cea587d6169246078e0 +Author: Rémi Bernon +Date: Wed Jul 3 10:54:06 2019 +0200 +Subject: [PATCH 2/3] server: Fallback to RTKIT for thread priorities. + +sched_setscheduler and setpriority usually require elevated privileges +to succeed and most Linux distributions ship rtkit daemon with a dbus +interface to enable unprivileged control of some scheduling parameters. + +--- a/configure.ac ++++ b/configure.ac +@@ -1416,7 +1416,7 @@ + if test "x$with_dbus" != "xno" + then + WINE_PACKAGE_FLAGS(DBUS,[dbus-1],,,, +- [AC_CHECK_HEADER([dbus/dbus.h], ++ [AC_CHECK_HEADERS([dbus/dbus.h], + [WINE_CHECK_SONAME(dbus-1, dbus_connection_close,,[DBUS_CFLAGS=""],[$DBUS_LIBS])], + [DBUS_CFLAGS=""])]) + fi +--- a/server/Makefile.in ++++ b/server/Makefile.in +@@ -50,6 +50,7 @@ + wineserver.man.in \ + winstation.c + +-UNIX_LIBS = $(LDEXECFLAGS) $(RT_LIBS) $(INOTIFY_LIBS) $(PROCSTAT_LIBS) ++UNIX_CFLAGS = $(DBUS_CFLAGS) ++UNIX_LIBS = $(LDEXECFLAGS) $(RT_LIBS) $(INOTIFY_LIBS) $(PROCSTAT_LIBS) $(DBUS_LIBS) + + unicode_EXTRADEFS = -DNLSDIR="\"${nlsdir}\"" -DBIN_TO_NLSDIR=\"`${MAKEDEP} -R ${bindir} ${nlsdir}`\" +--- a/server/thread.c ++++ b/server/thread.c +@@ -59,6 +59,77 @@ + #include "esync.h" + #include "fsync.h" + ++#ifdef HAVE_DBUS_DBUS_H ++#include ++ ++static int rtkit_set_realtime( dbus_uint64_t process, dbus_uint64_t thread, dbus_uint32_t priority ) ++{ ++ DBusConnection* dbus; ++ DBusMessage *msg; ++ int ret = -1; ++ ++ if ((dbus = dbus_bus_get(DBUS_BUS_SYSTEM, NULL))) ++ { ++ dbus_connection_set_exit_on_disconnect(dbus, 0); ++ ++ if ((msg = dbus_message_new_method_call("org.freedesktop.RealtimeKit1", ++ "/org/freedesktop/RealtimeKit1", ++ "org.freedesktop.RealtimeKit1", ++ "MakeThreadRealtimeWithPID"))) ++ { ++ dbus_message_set_no_reply(msg, 1); ++ ++ if (dbus_message_append_args(msg, ++ DBUS_TYPE_UINT64, &process, ++ DBUS_TYPE_UINT64, &thread, ++ DBUS_TYPE_UINT32, &priority, ++ DBUS_TYPE_INVALID) && ++ dbus_connection_send(dbus, msg, NULL)) ++ ret = 0; ++ ++ dbus_message_unref(msg); ++ } ++ ++ dbus_connection_unref(dbus); ++ } ++ ++ return ret; ++} ++ ++static int rtkit_set_niceness( dbus_uint64_t process, dbus_uint64_t thread, dbus_int32_t niceness ) ++{ ++ DBusConnection* dbus; ++ DBusMessage *msg; ++ int ret = -1; ++ ++ if ((dbus = dbus_bus_get(DBUS_BUS_SYSTEM, NULL))) ++ { ++ dbus_connection_set_exit_on_disconnect(dbus, 0); ++ ++ if ((msg = dbus_message_new_method_call("org.freedesktop.RealtimeKit1", ++ "/org/freedesktop/RealtimeKit1", ++ "org.freedesktop.RealtimeKit1", ++ "MakeThreadHighPriorityWithPID"))) ++ { ++ dbus_message_set_no_reply(msg, 1); ++ ++ if (dbus_message_append_args(msg, ++ DBUS_TYPE_UINT64, &process, ++ DBUS_TYPE_UINT64, &thread, ++ DBUS_TYPE_INT32, &niceness, ++ DBUS_TYPE_INVALID) && ++ dbus_connection_send(dbus, msg, NULL)) ++ ret = 0; ++ ++ dbus_message_unref(msg); ++ } ++ ++ dbus_connection_unref(dbus); ++ } ++ ++ return ret; ++} ++#endif + + /* thread queues */ + +@@ -655,7 +726,8 @@ + return mask; + } + +-#if defined(HAVE_SCHED_SETSCHEDULER) || defined(HAVE_SETPRIORITY) ++#if defined(HAVE_SCHED_SETSCHEDULER) || defined(HAVE_SETPRIORITY) || \ ++ defined(HAVE_DBUS_DBUS_H) + static int get_unix_priority( int priority_class, int priority ) + { + switch (priority_class) { +@@ -778,6 +850,11 @@ + if (sched_setscheduler( thread->unix_tid, SCHED_RR|SCHED_RESET_ON_FORK, ¶m ) == 0) + return 0; + #endif ++#ifdef HAVE_DBUS_DBUS_H ++ if (rtkit_set_realtime( thread->unix_pid, thread->unix_tid, ++ get_unix_priority( priority_class, priority ) ) == 0) ++ return 0; ++#endif + } + else + { +@@ -786,6 +863,11 @@ + get_unix_priority( priority_class, priority ) ) == 0) + return 0; + #endif ++#ifdef HAVE_DBUS_DBUS_H ++ if (rtkit_set_niceness( thread->unix_pid, thread->unix_tid, ++ get_unix_priority( priority_class, priority ) ) == 0) ++ return 0; ++#endif + } + #endif + diff --git a/pkgs/osu-wine/patches/0003-server-Map-THREAD_PRIORITY_IDLE-to-SCHED_IDLE.patch b/pkgs/osu-wine/patches/0003-server-Map-THREAD_PRIORITY_IDLE-to-SCHED_IDLE.patch new file mode 100644 index 0000000..28fd788 --- /dev/null +++ b/pkgs/osu-wine/patches/0003-server-Map-THREAD_PRIORITY_IDLE-to-SCHED_IDLE.patch @@ -0,0 +1,30 @@ +From df72c4d301123c0ea0c33af4bc9d00c47255a664 Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Thu, 26 Jan 2023 22:13:55 +0100 +Subject: [PATCH 3/3] server: Map THREAD_PRIORITY_IDLE to SCHED_IDLE. + +--- + server/thread.c | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +--- a/server/thread.c ++++ b/server/thread.c +@@ -858,6 +858,18 @@ + } + else + { ++#ifdef HAVE_SCHED_SETSCHEDULER ++ if (priority == THREAD_PRIORITY_IDLE) ++ { ++ struct sched_param param; ++ if (sched_getparam( thread->unix_tid, ¶m ) == 0) ++ { ++ param.sched_priority = 0; ++ if (sched_setscheduler( thread->unix_tid, SCHED_IDLE|SCHED_RESET_ON_FORK, ¶m ) == 0) ++ return 0; ++ } ++ } ++#endif + #ifdef HAVE_SETPRIORITY + if (setpriority( PRIO_PROCESS, thread->unix_tid, + get_unix_priority( priority_class, priority ) ) == 0) diff --git a/pkgs/osu-wine/patches/0003-thread-characteristic-audio-priority-from-oglf-patchset.patch b/pkgs/osu-wine/patches/0003-thread-characteristic-audio-priority-from-oglf-patchset.patch new file mode 100644 index 0000000..cf57848 --- /dev/null +++ b/pkgs/osu-wine/patches/0003-thread-characteristic-audio-priority-from-oglf-patchset.patch @@ -0,0 +1,12 @@ +--- a/dlls/avrt/main.c ++++ b/dlls/avrt/main.c +@@ -70,6 +70,9 @@ + return NULL; + } + ++ if (!wcscmp(name, L"Audio") || !wcscmp(name, L"Pro Audio")) ++ SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); ++ + return (HANDLE)0x12345678; + } + diff --git a/pkgs/osu-wine/patches/9989-misc-ps0126-devenum-Register-IEEE-float-for-Direct-Sound-defau.patch b/pkgs/osu-wine/patches/9989-misc-ps0126-devenum-Register-IEEE-float-for-Direct-Sound-defau.patch new file mode 100644 index 0000000..792ae47 --- /dev/null +++ b/pkgs/osu-wine/patches/9989-misc-ps0126-devenum-Register-IEEE-float-for-Direct-Sound-defau.patch @@ -0,0 +1,36 @@ +commit d444330ed7685686f46db7fb8ed1ad0cbec72c7b +Author: Rémi Bernon +Date: Wed Jun 16 17:36:15 2021 +0200 +Subject: [PATCH] devenum: Register IEEE float for Direct Sound default device. + +-- +diff --git a/dlls/devenum/createdevenum.c b/dlls/devenum/createdevenum.c +index 8e9cf56eb09..97855b12b81 100644 +--- a/dlls/devenum/createdevenum.c ++++ b/dlls/devenum/createdevenum.c +@@ -481,7 +481,7 @@ static BOOL CALLBACK register_dsound_devices(GUID *guid, const WCHAR *desc, cons + static const WCHAR defaultW[] = L"Default DirectSound Device"; + IPropertyBag *prop_bag = NULL; + REGFILTERPINS2 rgpins = {0}; +- REGPINTYPES rgtypes = {0}; ++ REGPINTYPES rgtypes[2] = {}; + REGFILTER2 rgf = {0}; + WCHAR clsid[CHARS_IN_GUID]; + VARIANT var; +@@ -512,10 +512,12 @@ static BOOL CALLBACK register_dsound_devices(GUID *guid, const WCHAR *desc, cons + rgf.rgPins2 = &rgpins; + rgpins.dwFlags = REG_PINFLAG_B_RENDERER; + /* FIXME: native registers many more formats */ +- rgpins.nMediaTypes = 1; +- rgpins.lpMediaType = &rgtypes; +- rgtypes.clsMajorType = &MEDIATYPE_Audio; +- rgtypes.clsMinorType = &MEDIASUBTYPE_PCM; ++ rgpins.nMediaTypes = 2; ++ rgpins.lpMediaType = rgtypes; ++ rgtypes[0].clsMajorType = &MEDIATYPE_Audio; ++ rgtypes[0].clsMinorType = &MEDIASUBTYPE_PCM; ++ rgtypes[1].clsMajorType = &MEDIATYPE_Audio; ++ rgtypes[1].clsMinorType = &MEDIASUBTYPE_IEEE_FLOAT; + + write_filter_data(prop_bag, &rgf); + diff --git a/pkgs/osu-wine/patches/9989-misc-ps0171-server-Don-t-wait-for-low-level-hook-result-when-q.patch b/pkgs/osu-wine/patches/9989-misc-ps0171-server-Don-t-wait-for-low-level-hook-result-when-q.patch new file mode 100644 index 0000000..bb8b614 --- /dev/null +++ b/pkgs/osu-wine/patches/9989-misc-ps0171-server-Don-t-wait-for-low-level-hook-result-when-q.patch @@ -0,0 +1,60 @@ +From: Piotr Caban +Subject: [PATCH v2] server: Don't wait for low level hook result when queuing hardware message. +Message-Id: +Date: Tue, 21 Sep 2021 15:51:35 +0200 + + +Without the change graphic drivers are blocking until low level hooks +are processed when injecting keyboard and mouse events. Causes 2-seconds +(timeout) freeze in GtaV. + +Signed-off-by: Piotr Caban +--- +v2: + - don't specify sender in send_hook_ll_message to avoid queuing result + + server/queue.c | 16 +++++++++++++--- + 1 file changed, 13 insertions(+), 3 deletions(-) + +diff --git a/server/queue.c b/server/queue.c +index e4903bcb79f..5c19348eeba 100644 +--- a/server/queue.c ++++ b/server/queue.c +@@ -1839,7 +1839,12 @@ static int queue_mouse_message( struct desktop *desktop, user_handle_t win, cons + /* specify a sender only when sending the last message */ + if (!(flags & ((1 << ARRAY_SIZE( messages )) - 1))) + { +- if (!(wait = send_hook_ll_message( desktop, msg, input, sender ))) ++ if (origin == IMO_HARDWARE) ++ { ++ if (!send_hook_ll_message( desktop, msg, input, NULL )) ++ queue_hardware_message( desktop, msg, 0 ); ++ } ++ else if (!(wait = send_hook_ll_message( desktop, msg, input, sender ))) + queue_hardware_message( desktop, msg, 0 ); + } + else if (!send_hook_ll_message( desktop, msg, input, NULL )) +@@ -1860,7 +1865,7 @@ static int queue_keyboard_message( struct desktop *desktop, user_handle_t win, c + struct thread *foreground; + unsigned char vkey = input->kbd.vkey; + unsigned int message_code, time; +- int wait; ++ int wait = 0; + + if (!(time = input->kbd.time)) time = get_tick_count(); + +@@ -1981,7 +1986,12 @@ static int queue_keyboard_message( struct desktop *desktop, user_handle_t win, c + msg_data->flags |= (flags & (KF_EXTENDED | KF_ALTDOWN | KF_UP)) >> 8; + } + +- if (!(wait = send_hook_ll_message( desktop, msg, input, sender ))) ++ if (origin == IMO_HARDWARE) ++ { ++ if (!send_hook_ll_message( desktop, msg, input, NULL )) ++ queue_hardware_message( desktop, msg, 1 ); ++ } ++ else if (!(wait = send_hook_ll_message( desktop, msg, input, sender ))) + queue_hardware_message( desktop, msg, 1 ); + + return wait; + diff --git a/pkgs/osu-wine/patches/9999-diffs-8bpp-copy-support.patch b/pkgs/osu-wine/patches/9999-diffs-8bpp-copy-support.patch new file mode 100644 index 0000000..0c67346 --- /dev/null +++ b/pkgs/osu-wine/patches/9999-diffs-8bpp-copy-support.patch @@ -0,0 +1,55 @@ +diff --git a/dlls/windowscodecs/converter.c b/dlls/windowscodecs/converter.c +index 11111111111..11111111111 100644 +--- a/dlls/windowscodecs/converter.c ++++ b/dlls/windowscodecs/converter.c +@@ -1034,6 +1034,50 @@ static HRESULT copypixels_to_24bppBGR(struct FormatConverter *This, const WICRec + + switch (source_format) + { ++ case format_8bppGray: ++ if (prc) ++ { ++ HRESULT res; ++ INT x, y; ++ BYTE *srcdata; ++ UINT srcstride, srcdatasize; ++ const BYTE *srcrow; ++ const BYTE *srcbyte; ++ BYTE *dstrow; ++ BYTE *dstpixel; ++ ++ srcstride = prc->Width; ++ srcdatasize = srcstride * prc->Height; ++ ++ srcdata = HeapAlloc(GetProcessHeap(), 0, srcdatasize); ++ if (!srcdata) return E_OUTOFMEMORY; ++ ++ res = IWICBitmapSource_CopyPixels(This->source, prc, srcstride, srcdatasize, srcdata); ++ ++ if (SUCCEEDED(res)) ++ { ++ srcrow = srcdata; ++ dstrow = pbBuffer; ++ for (y=0; yHeight; y++) { ++ srcbyte = srcrow; ++ dstpixel = dstrow; ++ for (x=0; xWidth; x++) ++ { ++ *dstpixel++ = *srcbyte; ++ *dstpixel++ = *srcbyte; ++ *dstpixel++ = *srcbyte; ++ srcbyte++; ++ } ++ srcrow += srcstride; ++ dstrow += cbStride; ++ } ++ } ++ ++ HeapFree(GetProcessHeap(), 0, srcdata); ++ ++ return res; ++ } ++ return S_OK; + case format_24bppBGR: + case format_24bppRGB: + if (prc) diff --git a/pkgs/osu-wine/patches/9999-diffs-hide-wine-version.patch b/pkgs/osu-wine/patches/9999-diffs-hide-wine-version.patch new file mode 100644 index 0000000..bd2a004 --- /dev/null +++ b/pkgs/osu-wine/patches/9999-diffs-hide-wine-version.patch @@ -0,0 +1,54 @@ +diff --git a/dlls/kernel32/module.c b/dlls/kernel32/module.c +index 11111111111..11111111111 100644 +--- a/dlls/kernel32/module.c ++++ b/dlls/kernel32/module.c +@@ -262,6 +262,34 @@ BOOL WINAPI GetBinaryTypeA( LPCSTR lpApplicationName, LPDWORD lpBinaryType ) + return GetBinaryTypeW(NtCurrentTeb()->StaticUnicodeString.Buffer, lpBinaryType); + } + ++static BOOL block_wine_get_version = FALSE; ++ ++BOOL CALLBACK init_block_wine_get_version( INIT_ONCE* init_once, PVOID param, PVOID *ctx ) ++{ ++ WCHAR *buffer; ++ DWORD size; ++ ++ if ((size = GetEnvironmentVariableW( L"WINE_BLOCK_GET_VERSION", NULL, 0 ))) ++ { ++ if (!(buffer = HeapAlloc( GetProcessHeap(), 0, sizeof(*buffer) * size ))) ++ { ++ ERR("No memory.\n"); ++ return FALSE; ++ } ++ ++ if (GetEnvironmentVariableW( L"WINE_BLOCK_GET_VERSION", buffer, size ) != size - 1) ++ { ++ ERR("Error getting WINE_BLOCK_GET_VERSION env variable.\n"); ++ return FALSE; ++ } ++ ++ block_wine_get_version = *buffer && !!wcsncmp( buffer, L"0", 1 ); ++ ++ HeapFree( GetProcessHeap(), 0, buffer ); ++ } ++ return TRUE; ++} ++ + /*********************************************************************** + * GetProcAddress (KERNEL32.@) + * +@@ -279,6 +307,14 @@ FARPROC get_proc_address( HMODULE hModule, LPCSTR function ) + { + FARPROC fp; + ++ if ((ULONG_PTR)function >> 16) ++ { ++ static INIT_ONCE init_once = INIT_ONCE_STATIC_INIT; ++ InitOnceExecuteOnce( &init_once, init_block_wine_get_version, NULL, NULL ); ++ if (block_wine_get_version && !strncmp( function, "wine_get_version", 16 )) ++ return NULL; ++ } ++ + if (!hModule) hModule = NtCurrentTeb()->Peb->ImageBaseAddress; + + if ((ULONG_PTR)function >> 16) diff --git a/pkgs/osu-wine/patches/9999-diffs-use-clock_nanosleep-for-delay.patch b/pkgs/osu-wine/patches/9999-diffs-use-clock_nanosleep-for-delay.patch new file mode 100644 index 0000000..d138fae --- /dev/null +++ b/pkgs/osu-wine/patches/9999-diffs-use-clock_nanosleep-for-delay.patch @@ -0,0 +1,46 @@ +diff --git a/dlls/ntdll/unix/sync.c b/dlls/ntdll/unix/sync.c +index e303367c29f..3723d21f885 100644 +--- a/dlls/ntdll/unix/sync.c ++++ b/dlls/ntdll/unix/sync.c +@@ -2788,10 +2788,39 @@ NTSTATUS WINAPI NtDelayExecution( BOOLEAN alertable, const LARGE_INTEGER *timeou + } + else + { ++ LONGLONG ticks = timeout->QuadPart; + LARGE_INTEGER now; +- timeout_t when, diff; ++ timeout_t when = ticks, diff; + +- if ((when = timeout->QuadPart) < 0) ++#if defined(HAVE_CLOCK_GETTIME) && defined(HAVE_CLOCK_NANOSLEEP) ++ static BOOL disable_clock_nanosleep = FALSE; ++ if (!disable_clock_nanosleep && ticks != 0) ++ { ++ struct timespec when; ++ int err; ++ ++ if (ticks < 0) ++ { ++ clock_gettime( CLOCK_REALTIME, &when ); ++ when.tv_sec += (time_t)(-ticks / TICKSPERSEC); ++ when.tv_nsec += (long)((-ticks % TICKSPERSEC) * 100); ++ } ++ else ++ { ++ when.tv_sec = (time_t)((ticks / TICKSPERSEC) - SECS_1601_TO_1970); ++ when.tv_nsec = (long)((ticks % TICKSPERSEC) * 100); ++ } ++ ++ usleep(0); ++ while ((err = clock_nanosleep( CLOCK_REALTIME, TIMER_ABSTIME, &when, NULL )) == EINTR); ++ if (!err) ++ return STATUS_SUCCESS; ++ else ++ disable_clock_nanosleep = TRUE; ++ } ++#endif ++ ++ if (when < 0) + { + NtQuerySystemTime( &now ); + when = now.QuadPart - when; diff --git a/pkgs/osu-wine/patches/9999-map-import-crash-fix.patch b/pkgs/osu-wine/patches/9999-map-import-crash-fix.patch new file mode 100644 index 0000000..0c16605 --- /dev/null +++ b/pkgs/osu-wine/patches/9999-map-import-crash-fix.patch @@ -0,0 +1,13 @@ +## osu! fix: disables assertion causing game to crash when importing maps +diff --git a/dlls/ntdll/unix/thread.c b/dlls/ntdll/unix/thread.c +index 9e84ec3cc96..dfa2a2781bc 100644 +--- a/dlls/ntdll/unix/thread.c ++++ b/dlls/ntdll/unix/thread.c +@@ -1783,7 +1783,6 @@ NTSTATUS get_thread_context( HANDLE handle, void *context, BOOL *self, USHORT ma + */ + void ntdll_set_exception_jmp_buf( __wine_jmp_buf *jmp ) + { +- assert( !jmp || !ntdll_get_thread_data()->jmp_buf ); + ntdll_get_thread_data()->jmp_buf = jmp; + } + diff --git a/pkgs/osu-wine/patches/display-cache-less-frequent-update.patch b/pkgs/osu-wine/patches/display-cache-less-frequent-update.patch new file mode 100644 index 0000000..741914d --- /dev/null +++ b/pkgs/osu-wine/patches/display-cache-less-frequent-update.patch @@ -0,0 +1,82 @@ +diff --git a/dlls/win32u/sysparams.c b/dlls/win32u/sysparams.c +index 11111111111..11111111111 100644 +--- a/dlls/win32u/sysparams.c ++++ b/dlls/win32u/sysparams.c +@@ -26,9 +26,14 @@ + + #include + #include ++#include + + #include "ntstatus.h" + #define WIN32_NO_STATUS ++#include "windef.h" ++#include "winbase.h" ++#include "winternl.h" ++#include "ddk/wdm.h" + #include "ntgdi_private.h" + #include "ntuser_private.h" + #include "devpropdef.h" +@@ -1679,12 +1684,31 @@ static void clear_display_devices(void) + } + } + ++static ULONGLONG last_update = 0; ++ ++#define user_shared_data ((volatile const struct _KUSER_SHARED_DATA *)0x7ffe0000) ++ ++static ULONGLONG get_tick_count(void) ++{ ++ ULONG high, low; ++ ++ do ++ { ++ high = user_shared_data->TickCount.High1Time; ++ low = user_shared_data->TickCount.LowPart; ++ } ++ while (high != user_shared_data->TickCount.High2Time); ++ /* note: we ignore TickCountMultiplier */ ++ return (ULONGLONG)high << 32 | low; ++} ++ + static BOOL update_display_cache_from_registry(void) + { + DWORD adapter_id, monitor_id, monitor_count = 0, size; + KEY_BASIC_INFORMATION key; + struct adapter *adapter; + struct monitor *monitor, *monitor2; ++ ULONGLONG tick_count; + HANDLE mutex = NULL; + NTSTATUS status; + BOOL ret; +@@ -1694,12 +1718,18 @@ static BOOL update_display_cache_from_registry(void) + sizeof(devicemap_video_keyW) ))) + return FALSE; + ++ if ((tick_count = get_tick_count()) - last_update < 1000) return TRUE; ++ + status = NtQueryKey( video_key, KeyBasicInformation, &key, + offsetof(KEY_BASIC_INFORMATION, Name), &size ); + if (status && status != STATUS_BUFFER_OVERFLOW) + return FALSE; + +- if (key.LastWriteTime.QuadPart <= last_query_display_time) return TRUE; ++ if (key.LastWriteTime.QuadPart <= last_query_display_time) ++ { ++ last_update = tick_count; ++ return TRUE; ++ } + + mutex = get_display_device_init_mutex(); + pthread_mutex_lock( &display_lock ); +@@ -1746,7 +1776,10 @@ static BOOL update_display_cache_from_registry(void) + } + + if ((ret = !list_empty( &adapters ) && !list_empty( &monitors ))) ++ { + last_query_display_time = key.LastWriteTime.QuadPart; ++ last_update = tick_count; ++ } + pthread_mutex_unlock( &display_lock ); + release_display_device_init_mutex( mutex ); + return ret; diff --git a/pkgs/osu-wine/patches/ps0071-p0002-winex11.drv-Bypass-compositor-in-fullscreen-.patch b/pkgs/osu-wine/patches/ps0071-p0002-winex11.drv-Bypass-compositor-in-fullscreen-.patch new file mode 100644 index 0000000..8ef831b --- /dev/null +++ b/pkgs/osu-wine/patches/ps0071-p0002-winex11.drv-Bypass-compositor-in-fullscreen-.patch @@ -0,0 +1,70 @@ +From 138916577787bbdf19cf9523b15603f73b4f4472 Mon Sep 17 00:00:00 2001 +From: Kai Krakow +Date: Thu, 4 Oct 2018 05:51:20 +0200 +Subject: [PATCH 2/2] winex11.drv: Bypass compositor in fullscreen mode. + +Bypass the compositor in fullscreen mode. This reduces stutter +introduced by window updates in the background and also allows for maybe +a few more FPS. To not change the visual appearance of the desktop for +windowed games, this hack only enables itself when the game was switched +to fullscreen mode, and returns to default WM setting when the game +leaves fullscreen mode. + +Compositors tend to cause severe stutter if the game is GPU-bound. +--- + dlls/winex11.drv/window.c | 7 +++++++ + dlls/winex11.drv/x11drv.h | 1 + + dlls/winex11.drv/x11drv_main.c | 1 + + 3 files changed, 9 insertions(+) + +--- a/dlls/winex11.drv/window.c ++++ b/dlls/winex11.drv/window.c +@@ -1036,6 +1036,7 @@ + void update_net_wm_states( struct x11drv_win_data *data ) + { + UINT i, style, ex_style, new_state = 0; ++ unsigned long net_wm_bypass_compositor = 0; + + if (!data->managed) return; + if (data->whole_window == root_window) return; +@@ -1048,7 +1049,10 @@ + if ((style & WS_MAXIMIZE) && (style & WS_CAPTION) == WS_CAPTION) + new_state |= (1 << NET_WM_STATE_MAXIMIZED); + else if (!(style & WS_MINIMIZE)) ++ { ++ net_wm_bypass_compositor = 1; + new_state |= (1 << NET_WM_STATE_FULLSCREEN); ++ } + } + else if (style & WS_MAXIMIZE) + new_state |= (1 << NET_WM_STATE_MAXIMIZED); +@@ -1112,6 +1116,9 @@ + } + data->net_wm_state = new_state; + update_net_wm_fullscreen_monitors( data ); ++ ++ XChangeProperty( data->display, data->whole_window, x11drv_atom(_NET_WM_BYPASS_COMPOSITOR), XA_CARDINAL, ++ 32, PropModeReplace, (unsigned char *)&net_wm_bypass_compositor, 1 ); + } + + /*********************************************************************** +--- a/dlls/winex11.drv/x11drv.h ++++ b/dlls/winex11.drv/x11drv.h +@@ -501,6 +501,7 @@ + XATOM__NET_SYSTEM_TRAY_OPCODE, + XATOM__NET_SYSTEM_TRAY_S0, + XATOM__NET_SYSTEM_TRAY_VISUAL, ++ XATOM__NET_WM_BYPASS_COMPOSITOR, + XATOM__NET_WM_FULLSCREEN_MONITORS, + XATOM__NET_WM_ICON, + XATOM__NET_WM_MOVERESIZE, +--- a/dlls/winex11.drv/x11drv_main.c ++++ b/dlls/winex11.drv/x11drv_main.c +@@ -159,6 +159,7 @@ + "_NET_SYSTEM_TRAY_OPCODE", + "_NET_SYSTEM_TRAY_S0", + "_NET_SYSTEM_TRAY_VISUAL", ++ "_NET_WM_BYPASS_COMPOSITOR", + "_NET_WM_FULLSCREEN_MONITORS", + "_NET_WM_ICON", + "_NET_WM_MOVERESIZE", diff --git a/pkgs/osu-wine/patches/ps0128-HACK-Fix-osu-alt-tab-on-certain-window-managers.patch b/pkgs/osu-wine/patches/ps0128-HACK-Fix-osu-alt-tab-on-certain-window-managers.patch new file mode 100644 index 0000000..8f36250 --- /dev/null +++ b/pkgs/osu-wine/patches/ps0128-HACK-Fix-osu-alt-tab-on-certain-window-managers.patch @@ -0,0 +1,28 @@ +From 5c8c060fc9d1d20eebe12da2e6dacddd88c07d82 Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Sun, 26 Nov 2023 18:29:53 +0100 +Subject: [PATCH] HACK: Fix osu! alt-tab. + +--- + dlls/win32u/window.c | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/dlls/win32u/window.c b/dlls/win32u/window.c +index 11111111111..11111111111 100644 +--- a/dlls/win32u/window.c ++++ b/dlls/win32u/window.c +@@ -3514,6 +3514,11 @@ BOOL set_window_pos( WINDOWPOS *winpos, int parent_x, int parent_y ) + + orig_flags = winpos->flags; + ++ /* HACK: fix osu! taking back focus immediately when it is unfocused. */ ++ if (winpos->hwndInsertAfter == HWND_NOTOPMOST && ++ (get_window_long( winpos->hwnd, GWL_EXSTYLE ) & WS_EX_TOPMOST)) ++ winpos->flags |= SWP_NOACTIVATE | SWP_NOZORDER; ++ + /* First, check z-order arguments. */ + if (!(winpos->flags & SWP_NOZORDER)) + { +-- +0.0.0 + diff --git a/pkgs/osu-wine/patches/ps0417-p0002-winex11.drv-Add-OpenGL-latency-reduction-cod.patch b/pkgs/osu-wine/patches/ps0417-p0002-winex11.drv-Add-OpenGL-latency-reduction-cod.patch new file mode 100644 index 0000000..e20dcce --- /dev/null +++ b/pkgs/osu-wine/patches/ps0417-p0002-winex11.drv-Add-OpenGL-latency-reduction-cod.patch @@ -0,0 +1,324 @@ +From ec87cc3742130c138e4caa37084c92c46b9cb9ad Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Sun, 3 Jul 2022 15:54:01 +0200 +Subject: [PATCH 2/2] winex11.drv: Add OpenGL latency reduction code. + +--- + dlls/winex11.drv/opengl.c | 255 +++++++++++++++++++++++++++++++++++++- + 1 file changed, 252 insertions(+), 3 deletions(-) + +diff --git a/dlls/winex11.drv/opengl.c b/dlls/winex11.drv/opengl.c +index 11111111111..11111111111 100644 +--- a/dlls/winex11.drv/opengl.c ++++ b/dlls/winex11.drv/opengl.c +@@ -42,6 +42,8 @@ + #include "xcomposite.h" + #include "winternl.h" + #include "wine/debug.h" ++#include "wine/server.h" ++#include "../win32u/ntuser_private.h" + + #ifdef SONAME_LIBGL + + +@@ -225,6 +229,8 @@ enum dc_gl_layered_type + DC_GL_LAYERED_ATTRIBUTES, + }; + ++typedef LONGLONG rtime_t; ++ + struct gl_drawable + { + LONG ref; /* reference count */ + +@@ -3443,6 +3454,130 @@ static void X11DRV_WineGL_LoadExtensions(void) + } + } + ++static inline BOOL allow_latency_reduction( void ) ++{ ++ static int status = -1; ++ if (status == -1) ++ { ++ const char *env = getenv( "WINE_OPENGL_LATENCY_REDUCTION" ); ++ status = !!(env && atoi(env)); ++ } ++ return status == 1; ++} ++ ++#define TICKSPERSEC 10000000 ++ ++typedef struct ftime_t { ++ LONGLONG time; ++ ULONGLONG freq; ++} ftime_t; ++ ++static inline ftime_t current_ftime( void ) ++{ ++ LARGE_INTEGER counter, freq; ++ ftime_t ret; ++ NtQueryPerformanceCounter( &counter, &freq ); ++ ret.time = counter.QuadPart; ++ ret.freq = (ULONGLONG)freq.QuadPart; ++ return ret; ++} ++ ++static inline rtime_t ftime_to_rtime( ftime_t ftime, BOOL round_up ) ++{ ++ ftime.time *= TICKSPERSEC; ++ if (round_up) ++ ftime.time += ftime.freq - 1; ++ return ftime.time / ftime.freq; ++} ++ ++static inline rtime_t current_rtime( BOOL round_up ) ++{ ++ return ftime_to_rtime( current_ftime(), round_up ); ++} ++ ++static rtime_t get_vblank_interval( HWND hwnd ) ++{ ++ HMONITOR monitor; ++ UNICODE_STRING device_name; ++ MONITORINFOEXW moninfo = { sizeof(MONITORINFOEXW) }; ++ DEVMODEW devmode = { {0}, 0, 0, sizeof(DEVMODEW) }; ++ ++ monitor = NtUserMonitorFromWindow( hwnd, MONITOR_DEFAULTTONEAREST ); ++ if (!monitor || !NtUserGetMonitorInfo( monitor, (MONITORINFO*)&moninfo )) ++ return 0; ++ ++ RtlInitUnicodeString( &device_name, moninfo.szDevice ); ++ if (!NtUserEnumDisplaySettings( &device_name, ENUM_CURRENT_SETTINGS, &devmode, 0 ) ++ || devmode.dmDisplayFrequency <= 1) ++ return 0; ++ MESSAGE("detected display frequency: %u\n", devmode.dmDisplayFrequency); ++ return TICKSPERSEC / devmode.dmDisplayFrequency; ++} ++ ++#define FRAMETIME_MARGIN_SHIFT 2 ++ ++static inline rtime_t frame_time_with_margin( rtime_t frame_time ) ++{ ++ return frame_time + (frame_time >> FRAMETIME_MARGIN_SHIFT) + 3500; ++} ++ ++static void get_swap_interval(GLXDrawable drawable, int *interval) ++{ ++ /* HACK: does not work correctly with __GL_SYNC_TO_VBLANK */ ++ /*pglXQueryDrawable(gdi_display, gl->drawable, GLX_SWAP_INTERVAL_EXT, (unsigned int*)interval);*/ ++ *interval = 0; ++} ++ ++#define WAIT_MASK (QS_MOUSEBUTTON | QS_KEY | QS_SENDMESSAGE | QS_TIMER | QS_HOTKEY) ++ ++static void msg_wait( const LARGE_INTEGER *timeout ) ++{ ++ LARGE_INTEGER to = *timeout, to2 = to; ++ rtime_t start, end; ++ DWORD ret; ++ ++ /* HACK: __wine_msg_wait_objects likes to wait for about 1 ms too long */ ++ ++ if (to2.QuadPart < 0) ++ { ++ to2.QuadPart += 10000; ++ if (to2.QuadPart >= 0) ++ { ++ end = current_rtime( TRUE ); ++ goto busy_loop; ++ } ++ } ++ else if (to2.QuadPart >= 10000) ++ to2.QuadPart -= 10000; ++ ++ if (to2.QuadPart >= 0) ++ { ++ __wine_msg_wait_objects( 0, NULL, &to2, WAIT_MASK, MWMO_INPUTAVAILABLE ); ++ return; ++ } ++ ++again: ++ start = current_rtime( FALSE ); ++ ret = __wine_msg_wait_objects( 0, NULL, &to2, WAIT_MASK, MWMO_INPUTAVAILABLE ); ++ if (ret == WAIT_OBJECT_0) ++ return; ++ end = current_rtime( TRUE ); ++ ++ to.QuadPart += end - start; ++ if (to.QuadPart < -11000) ++ { ++ to2.QuadPart = to.QuadPart + 10000; ++ goto again; ++ } ++ ++busy_loop: ++ if (to.QuadPart < -1000) ++ { ++ end = end - to.QuadPart - 1000; ++ while (current_rtime( TRUE ) < end) ++ YieldProcessor(); ++ } ++} + + /** + * glxdrv_SwapBuffers +@@ -3457,6 +3592,11 @@ static BOOL glxdrv_wglSwapBuffers( HDC hdc ) + INT64 ust, msc, sbc, target_sbc = 0; + HWND hwnd; + ++ BOOL enable_latency_reduction = FALSE; ++ BOOL synchronize_to_vblank = FALSE; ++ rtime_t frame_end_time; ++ rtime_t next_vblank_time = 0; ++ + TRACE("(%p)\n", hdc); + + escape.code = X11DRV_PRESENT_DRAWABLE; +@@ -3469,18 +3609,78 @@ static BOOL glxdrv_wglSwapBuffers( HDC hdc ) + return FALSE; + } + ++ if (allow_latency_reduction()) ++ enable_latency_reduction = gl->type == DC_GL_WINDOW ++ || gl->type == DC_GL_CHILD_WIN || gl->type == DC_GL_PIXMAP_WIN; ++ ++ if (enable_latency_reduction) ++ { ++ if (ctx && (gl->type == DC_GL_WINDOW || gl->type == DC_GL_CHILD_WIN ++ || gl->type == DC_GL_PIXMAP_WIN)) ++ sync_context( ctx ); ++ pglFinish(); ++ frame_end_time = current_rtime( TRUE ); ++ } ++ + pthread_mutex_lock( &context_mutex ); +- if (gl->refresh_swap_interval) ++ ++ if (enable_latency_reduction) ++ { ++ if (!gl->vblank_interval) ++ { ++ HWND hwnd = 0; ++ assert(!XFindContext( gdi_display, gl->window, winContext, (char **)&hwnd )); ++ assert(hwnd); ++ gl->vblank_interval = get_vblank_interval( hwnd ); ++ assert(gl->vblank_interval); ++ } ++ ++ if (gl->last_vblank_time) ++ { ++ next_vblank_time = gl->last_vblank_time + gl->vblank_interval; ++ while (next_vblank_time < frame_end_time) ++ next_vblank_time += gl->vblank_interval; ++ } ++ ++ if (gl->last_swap_time) ++ { ++ rtime_t new_frame_time = frame_end_time - gl->last_swap_time; ++ if (new_frame_time >= gl->frame_time) ++ gl->frame_time = new_frame_time; ++ else if (gl->frame_time > new_frame_time * 3) ++ gl->frame_time = frame_time_with_margin( new_frame_time ); ++ else ++ gl->frame_time = (gl->frame_time * 20 + new_frame_time) / 21; ++ } ++ ++ if (frame_end_time - gl->last_vblank_time >= TICKSPERSEC ++ || (!gl->refresh_swap_interval && next_vblank_time - frame_end_time <= frame_time_with_margin( gl->frame_time ))) ++ synchronize_to_vblank = TRUE; ++ } ++ ++ if (synchronize_to_vblank) ++ { ++ if (!gl->previous_frame_synchronized) ++ { ++ get_swap_interval(gl->drawable, &gl->swap_interval); ++ if (!set_swap_interval(gl->drawable, 1)) ++ synchronize_to_vblank = FALSE; ++ gl->previous_frame_synchronized = TRUE; ++ } ++ } ++ else if (gl->refresh_swap_interval || gl->previous_frame_synchronized) + { + set_swap_interval(gl->drawable, gl->swap_interval); + gl->refresh_swap_interval = FALSE; ++ gl->previous_frame_synchronized = FALSE; + } ++ + pthread_mutex_unlock( &context_mutex ); + + switch (gl->type) + { + case DC_GL_PIXMAP_WIN: +- if (ctx) sync_context( ctx ); ++ if (!enable_latency_reduction && ctx) sync_context( ctx ); + escape.drawable = gl->pixmap; + if (pglXCopySubBufferMESA) { + /* (glX)SwapBuffers has an implicit glFlush effect, however +@@ -3501,7 +3701,7 @@ static BOOL glxdrv_wglSwapBuffers( HDC hdc ) + break; + case DC_GL_WINDOW: + case DC_GL_CHILD_WIN: +- if (ctx) sync_context( ctx ); ++ if (!enable_latency_reduction && ctx) sync_context( ctx ); + if (gl->type == DC_GL_CHILD_WIN) escape.drawable = gl->window; + /* fall through */ + default: +@@ -3519,5 +3719,54 @@ static BOOL glxdrv_wglSwapBuffers( HDC hdc ) + pglXWaitForSbcOML( gdi_display, gl->drawable, target_sbc, &ust, &msc, &sbc ); + ++ ++ if (enable_latency_reduction) ++ { ++ rtime_t current_time = current_rtime( FALSE ); ++ ++ if (!synchronize_to_vblank && gl->last_vblank_time && gl->frame_time) ++ { ++ LARGE_INTEGER timeout; ++ ++ next_vblank_time = gl->last_vblank_time + gl->vblank_interval; ++ while (next_vblank_time < current_time + frame_time_with_margin( gl->frame_time )) ++ next_vblank_time += gl->vblank_interval; ++ ++ timeout.QuadPart = -(next_vblank_time - frame_time_with_margin( gl->frame_time ) - current_time); ++ if (timeout.QuadPart < 0 && -timeout.QuadPart < TICKSPERSEC) ++ msg_wait( &timeout ); ++ ++ current_time = current_rtime( FALSE ); ++ } ++ ++ pthread_mutex_lock( &context_mutex ); ++ ++ gl->last_swap_time = current_time; ++ if (synchronize_to_vblank) ++ gl->last_vblank_time = current_time; ++ ++ pthread_mutex_unlock( &context_mutex ); ++ ++ if (synchronize_to_vblank && gl->frame_time) ++ { ++ LARGE_INTEGER timeout; ++ ++ next_vblank_time = gl->last_vblank_time + gl->vblank_interval; ++ while (next_vblank_time < current_time + frame_time_with_margin( gl->frame_time )) ++ next_vblank_time += gl->vblank_interval; ++ ++ timeout.QuadPart = -(next_vblank_time - frame_time_with_margin( gl->frame_time ) - current_time); ++ if (timeout.QuadPart < 0 && -timeout.QuadPart < TICKSPERSEC) ++ { ++ msg_wait( &timeout ); ++ ++ current_time = current_rtime( FALSE ); ++ pthread_mutex_lock( &context_mutex ); ++ gl->last_swap_time = current_time; ++ pthread_mutex_unlock( &context_mutex ); ++ } ++ } ++ } ++ + release_gl_drawable( gl ); + + if (ctx && escape.drawable) +-- +2.40.0 + diff --git a/pkgs/osu-wine/patches/ps0741-ntdll-Implement-NtFlushProcessWriteBuffers.patch b/pkgs/osu-wine/patches/ps0741-ntdll-Implement-NtFlushProcessWriteBuffers.patch new file mode 100644 index 0000000..6fa6123 --- /dev/null +++ b/pkgs/osu-wine/patches/ps0741-ntdll-Implement-NtFlushProcessWriteBuffers.patch @@ -0,0 +1,319 @@ +From a21d85ace24116af87b83738909001c1e7cf87c2 Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Wed, 23 Nov 2022 15:47:49 +0100 +Subject: [PATCH 1/3] ntdll: Add MADV_DONTNEED-based implementation of + NtFlushProcessWriteBuffers. + +Credits to Avi Kivity (scylladb) and Aliaksei Kandratsenka (gperftools) for this trick, see [1]. + +[1] https://github.com/scylladb/seastar/commit/77a58e4dc020233f66fccb8d9e8f7a8b7f9210c4 +--- + dlls/ntdll/unix/virtual.c | 52 +++++++++++++++++++++++++++++++++++++- + tools/winapi/nativeapi.dat | 1 + + 2 files changed, 52 insertions(+), 1 deletion(-) + +diff --git a/dlls/ntdll/unix/virtual.c b/dlls/ntdll/unix/virtual.c +index 8087a12785c..de8f8b6ebc1 100644 +--- a/dlls/ntdll/unix/virtual.c ++++ b/dlls/ntdll/unix/virtual.c +@@ -215,6 +215,11 @@ struct range_entry + static struct range_entry *free_ranges; + static struct range_entry *free_ranges_end; + ++#if defined(__linux__) && (defined(__i386__) || defined(__x86_64__)) ++static void *dontneed_page; ++static pthread_mutex_t dontneed_page_mutex = PTHREAD_MUTEX_INITIALIZER; ++#endif ++ + + static inline BOOL is_beyond_limit( const void *addr, size_t size, const void *limit ) + { +@@ -5174,14 +5179,58 @@ NTSTATUS WINAPI NtFlushInstructionCache( HANDLE handle, const void *addr, SIZE_T + } + + ++static BOOL try_madvise( void ) ++{ ++ BOOL success = FALSE; ++ char *mem; ++ ++ pthread_mutex_lock(&dontneed_page_mutex); ++ /* Credits to Avi Kivity (scylladb) and Aliaksei Kandratsenka (gperftools) for this trick, ++ see https://github.com/scylladb/seastar/commit/77a58e4dc020233f66fccb8d9e8f7a8b7f9210c4 */ ++ mem = dontneed_page; ++ if (!mem) ++ { ++ int ret; ++ /* Allocate one page of memory that we can call madvise() on */ ++ mem = anon_mmap_alloc( page_size, PROT_READ | PROT_WRITE ); ++ if (mem == MAP_FAILED) ++ goto failed; ++ /* If the memory is locked, e.g. by a call to mlockall(MCL_FUTURE), the madvise() call below ++ will fail with error EINVAL, so unlock it here */ ++ ret = munlock( mem, page_size ); ++ /* munlock() may fail on old kernels if we don't have sufficient permissions, but that is not ++ a problem since in that case we didn't have permission to lock the memory either */ ++ if (ret && errno != EPERM) ++ goto failed; ++ dontneed_page = mem; ++ } ++ /* Force the page into memory to make madvise() have real work to do */ ++ *mem = 3; ++ /* Evict the page from memory to force the kernel to send an IPI to all threads of this process, ++ which has the side effect of executing a memory barrier in those threads */ ++ success = !madvise( mem, page_size, MADV_DONTNEED ); ++failed: ++ pthread_mutex_unlock(&dontneed_page_mutex); ++ return success; ++} ++ ++ + /********************************************************************** + * NtFlushProcessWriteBuffers (NTDLL.@) + */ + NTSTATUS WINAPI NtFlushProcessWriteBuffers(void) + { + static int once = 0; +- if (!once++) FIXME( "stub\n" ); +- return STATUS_SUCCESS; ++ if (try_madvise()) ++ { ++#ifdef __aarch64__ ++ /* Some ARMv8 processors can broadcast TLB invalidations using the TLBI instruction, ++ the madvise trick does not work on those */ ++ if (!once++) FIXME( "memory barrier may not work on this platform\n" ); ++#endif ++ return; ++ } ++ if (!once++) FIXME( "no implementation available on this platform\n" ); + } + + +diff --git a/tools/winapi/nativeapi.dat b/tools/winapi/nativeapi.dat +index ade20b5ee68..5512c4f1833 100644 +--- a/tools/winapi/nativeapi.dat ++++ b/tools/winapi/nativeapi.dat +@@ -134,6 +134,7 @@ log10 + logb + longjmp + lseek ++madvise + malloc + mblen + memccpy +-- +GitLab + + +From d3afd6ff2ffe7942d6e0846dea52a3884111a06a Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Wed, 23 Nov 2022 15:47:50 +0100 +Subject: [PATCH 2/3] ntdll: Add sys_membarrier-based implementation of + NtFlushProcessWriteBuffers. + +Uses the MEMBARRIER_CMD_PRIVATE_EXPEDITED membarrier command introduced in Linux 4.14. +--- + dlls/ntdll/unix/virtual.c | 49 ++++++++++++++++++++++++++++++++++++++- + 1 file changed, 48 insertions(+), 1 deletion(-) + +diff --git a/dlls/ntdll/unix/virtual.c b/dlls/ntdll/unix/virtual.c +index de8f8b6ebc1..e90bdb3abfb 100644 +--- a/dlls/ntdll/unix/virtual.c ++++ b/dlls/ntdll/unix/virtual.c +@@ -39,6 +39,9 @@ + #ifdef HAVE_SYS_SYSINFO_H + # include + #endif ++#ifdef HAVE_SYS_SYSCALL_H ++# include ++#endif + #ifdef HAVE_SYS_SYSCTL_H + # include + #endif +@@ -215,10 +218,16 @@ struct range_entry + static struct range_entry *free_ranges; + static struct range_entry *free_ranges_end; + +-#if defined(__linux__) && (defined(__i386__) || defined(__x86_64__)) ++#ifdef __linux__ ++#ifdef __NR_membarrier ++static BOOL membarrier_exp_available; ++static pthread_once_t membarrier_init_once = PTHREAD_ONCE_INIT; ++#endif ++#if defined(__i386__) || defined(__x86_64__) + static void *dontneed_page; + static pthread_mutex_t dontneed_page_mutex = PTHREAD_MUTEX_INITIALIZER; + #endif ++#endif + + + static inline BOOL is_beyond_limit( const void *addr, size_t size, const void *limit ) +@@ -5179,6 +5188,42 @@ NTSTATUS WINAPI NtFlushInstructionCache( HANDLE handle, const void *addr, SIZE_T + } + + ++#if defined(__linux__) && defined(__NR_membarrier) ++#define MEMBARRIER_CMD_QUERY 0x00 ++#define MEMBARRIER_CMD_PRIVATE_EXPEDITED 0x08 ++#define MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED 0x10 ++ ++ ++static int membarrier( int cmd, unsigned int flags, int cpu_id ) ++{ ++ return syscall( __NR_membarrier, cmd, flags, cpu_id ); ++} ++ ++ ++static void membarrier_init( void ) ++{ ++ static const int exp_required_cmds = ++ MEMBARRIER_CMD_PRIVATE_EXPEDITED | MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED; ++ int available_cmds = membarrier( MEMBARRIER_CMD_QUERY, 0, 0 ); ++ if (available_cmds == -1) ++ return; ++ if ((available_cmds & exp_required_cmds) == exp_required_cmds) ++ membarrier_exp_available = !membarrier( MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED, 0, 0 ); ++} ++ ++ ++static BOOL try_exp_membarrier( void ) ++{ ++ pthread_once(&membarrier_init_once, membarrier_init); ++ if (!membarrier_exp_available) ++ return FALSE; ++ return !membarrier( MEMBARRIER_CMD_PRIVATE_EXPEDITED, 0, 0 ); ++} ++#else ++static BOOL try_exp_membarrier( void ) { return 0; } ++#endif ++ ++ + static BOOL try_madvise( void ) + { + BOOL success = FALSE; +@@ -5221,6 +5266,8 @@ failed: + void WINAPI NtFlushProcessWriteBuffers(void) + { + static int once = 0; ++ if (try_exp_membarrier()) ++ return; + if (try_madvise()) + { + #ifdef __aarch64__ +-- +GitLab + + +From 48f1d7cad78235c5c9e64c419235289608294440 Mon Sep 17 00:00:00 2001 +From: Torge Matthies +Date: Wed, 23 Nov 2022 15:47:50 +0100 +Subject: [PATCH 3/3] ntdll: Add thread_get_register_pointer_values-based + implementation of NtFlushProcessWriteBuffers. + +--- + dlls/ntdll/unix/virtual.c | 68 +++++++++++++++++++++++++++++++++++++++ + 1 file changed, 68 insertions(+) + +diff --git a/dlls/ntdll/unix/virtual.c b/dlls/ntdll/unix/virtual.c +index e90bdb3abfb..c5a2f878e3b 100644 +--- a/dlls/ntdll/unix/virtual.c ++++ b/dlls/ntdll/unix/virtual.c +@@ -65,6 +65,9 @@ + #if defined(__APPLE__) + # include + # include ++# include ++# include ++# include + #endif + + #include "ntstatus.h" +@@ -218,6 +221,11 @@ struct range_entry + static struct range_entry *free_ranges; + static struct range_entry *free_ranges_end; + ++#ifdef __APPLE__ ++static kern_return_t (*p_thread_get_register_pointer_values)( thread_t, uintptr_t*, size_t*, uintptr_t* ); ++static pthread_once_t tgrpvs_init_once = PTHREAD_ONCE_INIT; ++#endif ++ + #ifdef __linux__ + #ifdef __NR_membarrier + static BOOL membarrier_exp_available; +@@ -5188,6 +5196,64 @@ NTSTATUS WINAPI NtFlushInstructionCache( HANDLE handle, const void *addr, SIZE_T + } + + ++#ifdef __APPLE__ ++ ++static void tgrpvs_init( void ) ++{ ++ p_thread_get_register_pointer_values = dlsym( RTLD_DEFAULT, "thread_get_register_pointer_values" ); ++} ++ ++static BOOL try_mach_tgrpvs( void ) ++{ ++ /* Taken from https://github.com/dotnet/runtime/blob/7be37908e5a1cbb83b1062768c1649827eeaceaa/src/coreclr/pal/src/thread/process.cpp#L2799 */ ++ mach_msg_type_number_t count, i = 0; ++ thread_act_array_t threads; ++ kern_return_t kret; ++ BOOL success = FALSE; ++ ++ pthread_once(&tgrpvs_init_once, tgrpvs_init); ++ if (!p_thread_get_register_pointer_values) ++ return FALSE; ++ ++ /* Get references to all threads of this process */ ++ kret = task_threads( mach_task_self(), &threads, &count ); ++ if (kret) ++ return FALSE; ++ ++ /* Iterate through the threads in the list */ ++ while (i < count) ++ { ++ uintptr_t reg_values[128]; ++ size_t reg_count = ARRAY_SIZE( reg_values ); ++ uintptr_t sp; ++ ++ /* Request the thread's register pointer values to force the thread to go through a memory barrier */ ++ kret = p_thread_get_register_pointer_values( threads[i], &sp, ®_count, reg_values ); ++ /* This function always fails when querying Rosetta's exception handling thread, so we only treat ++ KERN_INSUFFICIENT_BUFFER_SIZE as an error, like .NET core does. */ ++ if (kret == KERN_INSUFFICIENT_BUFFER_SIZE) ++ goto fail; ++ ++ /* Deallocate thread reference once we're done with it */ ++ kret = mach_port_deallocate( mach_task_self(), threads[i++] ); ++ if (kret) ++ goto fail; ++ } ++ success = TRUE; ++fail: ++ /* Deallocate remaining thread references */ ++ while (i < count) ++ mach_port_deallocate( mach_task_self(), threads[i++] ); ++ /* Deallocate thread list */ ++ vm_deallocate( mach_task_self(), (vm_address_t)threads, count * sizeof(threads[0]) ); ++ return success; ++} ++ ++#else ++static BOOL try_mach_tgrpvs( void ) { return 0; } ++#endif ++ ++ + #if defined(__linux__) && defined(__NR_membarrier) + #define MEMBARRIER_CMD_QUERY 0x00 + #define MEMBARRIER_CMD_PRIVATE_EXPEDITED 0x08 +@@ -5266,6 +5332,8 @@ failed: + void WINAPI NtFlushProcessWriteBuffers(void) + { + static int once = 0; ++ if (try_mach_tgrpvs()) ++ return; + if (try_exp_membarrier()) + return; + if (try_madvise()) +-- +GitLab + diff --git a/pkgs/osu-wine/patches/revert-x11-gnome-fix.patch b/pkgs/osu-wine/patches/revert-x11-gnome-fix.patch new file mode 100644 index 0000000..21d2806 --- /dev/null +++ b/pkgs/osu-wine/patches/revert-x11-gnome-fix.patch @@ -0,0 +1,27 @@ +## Patch to revert commit 35193586 which supposedly broke osu! +## on GNOME on X11 due to compositor not turning off even with fullscreen enabled + +--- b/dlls/winex11.drv/window.c ++++ a/dlls/winex11.drv/window.c +@@ -1542,21 +1542,9 @@ + attrib.border_pixel = 0; + attrib.colormap = default_colormap; + +-#ifdef HAVE_LIBXSHAPE +- { +- static XRectangle empty_rect; +- dummy_parent = XCreateWindow( gdi_display, root_window, 0, 0, 1, 1, 0, +- default_visual.depth, InputOutput, default_visual.visual, +- CWColormap | CWBorderPixel | CWOverrideRedirect, &attrib ); +- XShapeCombineRectangles( gdi_display, dummy_parent, ShapeBounding, 0, 0, &empty_rect, 1, +- ShapeSet, YXBanded ); +- } +-#else + dummy_parent = XCreateWindow( gdi_display, root_window, -1, -1, 1, 1, 0, default_visual.depth, + InputOutput, default_visual.visual, + CWColormap | CWBorderPixel | CWOverrideRedirect, &attrib ); +- WARN("Xshape support is not compiled in. Applications under XWayland may have poor performance.\n"); +-#endif + XMapWindow( gdi_display, dummy_parent ); + } + return dummy_parent; diff --git a/pkgs/osu-wine/sources.nix b/pkgs/osu-wine/sources.nix new file mode 100644 index 0000000..84948d5 --- /dev/null +++ b/pkgs/osu-wine/sources.nix @@ -0,0 +1,177 @@ +{ pkgs ? import {}, path ? pkgs.path }: +## we default to importing here, so that you can use +## a simple shell command to insert new hashes into this file +## e.g. with emacs C-u M-x shell-command +## +## nix-prefetch-url sources.nix -A {stable{,.mono,.gecko64,.gecko32}, unstable, staging, winetricks} + +# here we wrap fetchurl and fetchFromGitHub, in order to be able to pass additional args around it +let fetchurl = args@{url, hash, ...}: + pkgs.fetchurl { inherit url hash; } // args; + fetchFromGitHub = args@{owner, repo, rev, hash, ...}: + pkgs.fetchFromGitHub { inherit owner repo rev hash; } // args; + fetchFromGitLab = args@{domain, owner, repo, rev, hash, ...}: + pkgs.fetchFromGitLab { inherit domain owner repo rev hash; } // args; + + updateScriptPreamble = '' + set -eou pipefail + PATH=${with pkgs; lib.makeBinPath [ common-updater-scripts coreutils curl gnugrep gnused jq nix ]} + sources_file=${__curPos.file} + source ${./update-lib.sh} + ''; + + inherit (pkgs) writeShellScript; +in rec { + + stable = fetchurl rec { + version = "9.0"; + url = "https://dl.winehq.org/wine/source/9.0/wine-${version}.tar.xz"; + hash = "sha256-fP0JClOV9bdtlbtd76yKMSyN5MBwwRY7i1jaODMMpu4="; + + ## see http://wiki.winehq.org/Gecko + gecko32 = fetchurl rec { + version = "2.47.4"; + url = "https://dl.winehq.org/wine/wine-gecko/${version}/wine-gecko-${version}-x86.msi"; + hash = "sha256-Js7MR3BrCRkI9/gUvdsHTGG+uAYzGOnvxaf3iYV3k9Y="; + }; + gecko64 = fetchurl rec { + version = "2.47.4"; + url = "https://dl.winehq.org/wine/wine-gecko/${version}/wine-gecko-${version}-x86_64.msi"; + hash = "sha256-5ZC32YijLWqkzx2Ko6o9M3Zv3Uz0yJwtzCCV7LKNBm8="; + }; + + ## see http://wiki.winehq.org/Mono + mono = fetchurl rec { + version = "8.1.0"; + url = "https://dl.winehq.org/wine/wine-mono/${version}/wine-mono-${version}-x86.msi"; + hash = "sha256-DtPsUzrvebLzEhVZMc97EIAAmsDFtMK8/rZ4rJSOCBA="; + }; + + patches = [ + # Also look for root certificates at $NIX_SSL_CERT_FILE + "${path}/pkgs/applications/emulators/wine/cert-path.patch" + ]; + + updateScript = writeShellScript "update-wine-stable" ('' + ${updateScriptPreamble} + major=''${UPDATE_NIX_OLD_VERSION%%.*} + latest_stable=$(get_latest_wine_version "$major.0") + + # Can't use autobump on stable because we don't want the path + # to become . + if [[ "$UPDATE_NIX_OLD_VERSION" != "$latest_stable" ]]; then + set_version_and_hash stable "$latest_stable" "$(nix-prefetch-url "$wine_url_base/source/$major.0/wine-$latest_stable.tar.xz")" + fi + + do_update + ''); + }; + + unstable = fetchurl rec { + # NOTE: Don't forget to change the hash for staging as well. + version = "9.0"; + url = "https://dl.winehq.org/wine/source/9.0/wine-${version}.tar.xz"; + hash = "sha256-fP0JClOV9bdtlbtd76yKMSyN5MBwwRY7i1jaODMMpu4="; + inherit (stable) patches; + + ## see http://wiki.winehq.org/Gecko + gecko32 = fetchurl rec { + version = "2.47.4"; + url = "https://dl.winehq.org/wine/wine-gecko/${version}/wine-gecko-${version}-x86.msi"; + hash = "sha256-Js7MR3BrCRkI9/gUvdsHTGG+uAYzGOnvxaf3iYV3k9Y="; + }; + gecko64 = fetchurl rec { + version = "2.47.4"; + url = "https://dl.winehq.org/wine/wine-gecko/${version}/wine-gecko-${version}-x86_64.msi"; + hash = "sha256-5ZC32YijLWqkzx2Ko6o9M3Zv3Uz0yJwtzCCV7LKNBm8="; + }; + + ## see http://wiki.winehq.org/Mono + mono = fetchurl rec { + version = "8.1.0"; + url = "https://dl.winehq.org/wine/wine-mono/${version}/wine-mono-${version}-x86.msi"; + hash = "sha256-DtPsUzrvebLzEhVZMc97EIAAmsDFtMK8/rZ4rJSOCBA="; + }; + + updateScript = writeShellScript "update-wine-unstable" '' + ${updateScriptPreamble} + major=''${UPDATE_NIX_OLD_VERSION%%.*} + latest_unstable=$(get_latest_wine_version "$major.x") + latest_gecko=$(get_latest_lib_version wine-gecko) + latest_mono=$(get_latest_lib_version wine-mono) + + update_staging() { + staging_url=$(get_source_attr staging.url) + set_source_attr staging hash "\"$(to_sri "$(nix-prefetch-url --unpack "''${staging_url//$1/$2}")")\"" + } + + autobump unstable "$latest_unstable" "" update_staging + autobump unstable.gecko32 "$latest_gecko" + autobump unstable.gecko64 "$latest_gecko" + autobump unstable.mono "$latest_mono" + + do_update + ''; + }; + + staging = fetchFromGitHub rec { + # https://github.com/wine-staging/wine-staging/releases + inherit (unstable) version; + hash = "sha256-lE/95OZigifreaRRCPkvA+Z0FqsBmm018jD6leSysXU="; + owner = "wine-staging"; + repo = "wine-staging"; + rev = "v${version}"; + + disabledPatchsets = [ ]; + }; + + wayland = fetchFromGitLab { + # https://gitlab.collabora.com/alf/wine/-/tree/wayland + version = "8.2"; + hash = "sha256-Eb2SFBIeQQ3cVZkUQcwNT5mcYe0ShFxBdMc3BlqkwTo="; + domain = "gitlab.collabora.com"; + owner = "alf"; + repo = "wine"; + rev = "b2547ddf9e08cafce98cf7734d5c4ec926ef3536"; + + inherit (unstable) gecko32 gecko64; + + inherit (unstable) mono; + + updateScript = writeShellScript "update-wine-wayland" '' + ${updateScriptPreamble} + wayland_rev=$(get_source_attr wayland.rev) + + latest_wayland_rev=$(curl -s 'https://gitlab.collabora.com/api/v4/projects/2847/repository/branches/wayland' | jq -r .commit.id) + + if [[ "$wayland_rev" != "$latest_wayland_rev" ]]; then + latest_wayland=$(curl -s 'https://gitlab.collabora.com/alf/wine/-/raw/wayland/VERSION' | cut -f3 -d' ') + wayland_url=$(get_source_attr wayland.url) + set_version_and_hash wayland "$latest_wayland" "$(nix-prefetch-url --unpack "''${wayland_url/$wayland_rev/$latest_wayland_rev}")" + set_source_attr wayland rev "\"$latest_wayland_rev\"" + fi + + do_update + ''; + }; + + winetricks = fetchFromGitHub rec { + # https://github.com/Winetricks/winetricks/releases + version = "20240105"; + hash = "sha256-YTEgb19aoM54KK8/IjrspoChzVnWAEItDlTxpfpS52w="; + owner = "Winetricks"; + repo = "winetricks"; + rev = version; + + updateScript = writeShellScript "update-winetricks" '' + ${updateScriptPreamble} + winetricks_repourl=$(get_source_attr winetricks.gitRepoUrl) + + latest_winetricks=$(list-git-tags --url="$winetricks_repourl" | grep -E '^[0-9]{8}$' | sort --reverse --numeric-sort | head -n 1) + + autobump winetricks "$latest_winetricks" 'nix-prefetch-url --unpack' + + do_update + ''; + }; +}