/*
 * COPYRIGHT:       See COPYING in the top level directory
 * PROJECT:         ReactOS Kernel Streaming
 * FILE:            drivers/wdm/audio/legacy/wdmaud/mmixer.c
 * PURPOSE:         WDM Legacy Mixer
 * PROGRAMMER:      Johannes Anderwald
 */

#include "wdmaud.h"

#include <mmixer.h>

#define NDEBUG
#include <debug.h>

PVOID Alloc(ULONG NumBytes);
MIXER_STATUS Close(HANDLE hDevice);
VOID Free(PVOID Block);
VOID Copy(PVOID Src, PVOID Dst, ULONG NumBytes);
MIXER_STATUS Open(IN LPWSTR DevicePath, OUT PHANDLE hDevice);
MIXER_STATUS Control(IN HANDLE hMixer, IN ULONG dwIoControlCode, IN PVOID lpInBuffer, IN ULONG nInBufferSize, OUT PVOID lpOutBuffer, ULONG nOutBufferSize, PULONG lpBytesReturned);
MIXER_STATUS Enum(IN  PVOID EnumContext, IN  ULONG DeviceIndex, OUT LPWSTR * DeviceName, OUT PHANDLE OutHandle, OUT PHANDLE OutKey);
MIXER_STATUS OpenKey(IN HANDLE hKey, IN LPWSTR SubKey, IN ULONG DesiredAccess, OUT PHANDLE OutKey);
MIXER_STATUS CloseKey(IN HANDLE hKey);
MIXER_STATUS QueryKeyValue(IN HANDLE hKey, IN LPWSTR KeyName, OUT PVOID * ResultBuffer, OUT PULONG ResultLength, OUT PULONG KeyType);
PVOID AllocEventData(IN ULONG ExtraSize);
VOID FreeEventData(IN PVOID EventData);

MIXER_CONTEXT MixerContext =
{
    sizeof(MIXER_CONTEXT),
    NULL,
    Alloc,
    Control,
    Free,
    Open,
    Close,
    Copy,
    OpenKey,
    QueryKeyValue,
    CloseKey,
    AllocEventData,
    FreeEventData
};

GUID CategoryGuid = {STATIC_KSCATEGORY_AUDIO};

MIXER_STATUS
QueryKeyValue(
    IN HANDLE hKey,
    IN LPWSTR lpKeyName,
    OUT PVOID * ResultBuffer,
    OUT PULONG ResultLength,
    OUT PULONG KeyType)
{
    NTSTATUS Status;
    UNICODE_STRING KeyName;
    ULONG Length;
    PKEY_VALUE_PARTIAL_INFORMATION PartialInformation;

    /* initialize key name */
    RtlInitUnicodeString(&KeyName, lpKeyName);

    /* now query MatchingDeviceId key */
    Status = ZwQueryValueKey(hKey, &KeyName, KeyValuePartialInformation, NULL, 0, &Length);

    /* check for success */
    if (Status != STATUS_BUFFER_TOO_SMALL)
        return MM_STATUS_UNSUCCESSFUL;

    /* allocate a buffer for key data */
    PartialInformation = AllocateItem(NonPagedPool, Length);

    if (!PartialInformation)
        return MM_STATUS_NO_MEMORY;


    /* now query MatchingDeviceId key */
    Status = ZwQueryValueKey(hKey, &KeyName, KeyValuePartialInformation, PartialInformation, Length, &Length);

    /* check for success */
    if (!NT_SUCCESS(Status))
    {
        FreeItem(PartialInformation);
        return MM_STATUS_UNSUCCESSFUL;
    }

    if (KeyType)
    {
        /* return key type */
        *KeyType = PartialInformation->Type;
    }

    if (ResultLength)
    {
        /* return data length */
        *ResultLength = PartialInformation->DataLength;
    }

    *ResultBuffer = AllocateItem(NonPagedPool, PartialInformation->DataLength);
    if (!*ResultBuffer)
    {
        /* not enough memory */
        FreeItem(PartialInformation);
        return MM_STATUS_NO_MEMORY;
    }

    /* copy key value */
    RtlMoveMemory(*ResultBuffer, PartialInformation->Data, PartialInformation->DataLength);

    /* free key info */
    FreeItem(PartialInformation);

    return MM_STATUS_SUCCESS;
}

MIXER_STATUS
OpenKey(
    IN HANDLE hKey,
    IN LPWSTR lpSubKeyName,
    IN ULONG DesiredAccess,
    OUT PHANDLE OutKey)
{
    OBJECT_ATTRIBUTES ObjectAttributes;
    UNICODE_STRING SubKeyName;
    NTSTATUS Status;

    /* initialize sub key name */
    RtlInitUnicodeString(&SubKeyName, lpSubKeyName);

    /* initialize key attributes */
    InitializeObjectAttributes(&ObjectAttributes, &SubKeyName, OBJ_CASE_INSENSITIVE | OBJ_OPENIF, hKey, NULL);

    /* open the key */
    Status = ZwOpenKey(OutKey, DesiredAccess, &ObjectAttributes);

    if (NT_SUCCESS(Status))
        return MM_STATUS_SUCCESS;
    else
        return MM_STATUS_UNSUCCESSFUL;
}

MIXER_STATUS
CloseKey(
    IN HANDLE hKey)
{
    if (ZwClose(hKey) == STATUS_SUCCESS)
        return MM_STATUS_SUCCESS;
    else
        return MM_STATUS_UNSUCCESSFUL;
}


PVOID Alloc(ULONG NumBytes)
{
    return AllocateItem(NonPagedPool, NumBytes);
}

MIXER_STATUS
Close(HANDLE hDevice)
{
    if (ZwClose(hDevice) == STATUS_SUCCESS)
        return MM_STATUS_SUCCESS;
    else
        return MM_STATUS_UNSUCCESSFUL;
}

VOID
Free(PVOID Block)
{
    FreeItem(Block);
}

VOID
Copy(PVOID Src, PVOID Dst, ULONG NumBytes)
{
    RtlMoveMemory(Src, Dst, NumBytes);
}

MIXER_STATUS
Open(
    IN LPWSTR DevicePath,
    OUT PHANDLE hDevice)
{
    if (WdmAudOpenSysAudioDevice(DevicePath, hDevice) == STATUS_SUCCESS)
        return MM_STATUS_SUCCESS;
    else
        return MM_STATUS_UNSUCCESSFUL;
}

MIXER_STATUS
Control(
    IN HANDLE hMixer,
    IN ULONG dwIoControlCode,
    IN PVOID lpInBuffer,
    IN ULONG nInBufferSize,
    OUT PVOID lpOutBuffer,
    ULONG nOutBufferSize,
    PULONG lpBytesReturned)
{
    NTSTATUS Status;
    PFILE_OBJECT FileObject;

    /* get file object */
    Status = ObReferenceObjectByHandle(hMixer, GENERIC_READ | GENERIC_WRITE, *IoFileObjectType, KernelMode, (PVOID*)&FileObject, NULL);
    if (!NT_SUCCESS(Status))
    {
        DPRINT("failed to reference %p with %lx\n", hMixer, Status);
        return MM_STATUS_UNSUCCESSFUL;
    }

    /* perform request */
    Status = KsSynchronousIoControlDevice(FileObject, KernelMode, dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer, nOutBufferSize, lpBytesReturned);

    /* release object reference */
    ObDereferenceObject(FileObject);

    if (Status == STATUS_MORE_ENTRIES || Status == STATUS_BUFFER_OVERFLOW || Status == STATUS_BUFFER_TOO_SMALL)
    {
        /* more data is available */
        return MM_STATUS_MORE_ENTRIES;
    }
    else if (Status == STATUS_SUCCESS)
    {
        /* operation succeeded */
        return MM_STATUS_SUCCESS;
    }
    else
    {
        DPRINT("Failed with %lx\n", Status);
        return MM_STATUS_UNSUCCESSFUL;
    }
}

MIXER_STATUS
Enum(
    IN  PVOID EnumContext,
    IN  ULONG DeviceIndex,
    OUT LPWSTR * DeviceName,
    OUT PHANDLE OutHandle,
    OUT PHANDLE OutKey)
{
    PDEVICE_OBJECT DeviceObject;
    ULONG DeviceCount;
    NTSTATUS Status;
    UNICODE_STRING KeyName;

    /* get enumeration context */
    DeviceObject = (PDEVICE_OBJECT)EnumContext;

    /* get device count */
    DeviceCount = GetSysAudioDeviceCount(DeviceObject);

    if (DeviceIndex >= DeviceCount)
    {
        /* no more devices */
        return MM_STATUS_NO_MORE_DEVICES;
    }

    /* get device name */
    Status = GetSysAudioDevicePnpName(DeviceObject, DeviceIndex, DeviceName);

    if (!NT_SUCCESS(Status))
    {
        /* failed to retrieve device name */
        return MM_STATUS_UNSUCCESSFUL;
    }

    /* intialize key name */
    RtlInitUnicodeString(&KeyName, *DeviceName);

    /* open device interface key */
    Status = IoOpenDeviceInterfaceRegistryKey(&KeyName, GENERIC_READ | GENERIC_WRITE, OutKey);
#if 0
    if (!NT_SUCCESS(Status))
    {
        /* failed to open key */
        DPRINT("IoOpenDeviceInterfaceRegistryKey failed with %lx\n", Status);
        FreeItem(*DeviceName);
        return MM_STATUS_UNSUCCESSFUL;
    }
#endif

    /* open device handle */
    Status = OpenDevice(*DeviceName, OutHandle, NULL);
    if (!NT_SUCCESS(Status))
    {
        /* failed to open device */
        return MM_STATUS_UNSUCCESSFUL;
    }

    return MM_STATUS_SUCCESS;
}

PVOID
AllocEventData(
    IN ULONG ExtraSize)
{
    PKSEVENTDATA Data = (PKSEVENTDATA)AllocateItem(NonPagedPool, sizeof(KSEVENTDATA) + ExtraSize);
    if (!Data)
        return NULL;

    Data->EventObject.Event = AllocateItem(NonPagedPool, sizeof(KEVENT));
    if (!Data->EventHandle.Event)
    {
        FreeItem(Data);
        return NULL;
    }

    KeInitializeEvent(Data->EventObject.Event, NotificationEvent, FALSE);

    Data->NotificationType = KSEVENTF_EVENT_HANDLE;
    return Data;
}

VOID
FreeEventData(IN PVOID EventData)
{
    PKSEVENTDATA Data = (PKSEVENTDATA)EventData;

    FreeItem(Data->EventHandle.Event);
    FreeItem(Data);
}

VOID
CALLBACK
EventCallback(
    IN PVOID MixerEventContext,
    IN HANDLE hMixer,
    IN ULONG NotificationType,
    IN ULONG Value)
{
    PWDMAUD_CLIENT ClientInfo;
    PEVENT_ENTRY Entry;
    ULONG Index;

    /* get client context */
    ClientInfo = (PWDMAUD_CLIENT)MixerEventContext;

    /* now search for the mixer which originated the request */
    for(Index = 0; Index < ClientInfo->NumPins; Index++)
    {
        if (ClientInfo->hPins[Index].Handle == hMixer && ClientInfo->hPins[Index].Type == MIXER_DEVICE_TYPE)
        {
            if (ClientInfo->hPins[Index].NotifyEvent)
            {
                /* allocate event entry */
                Entry = AllocateItem(NonPagedPool, sizeof(EVENT_ENTRY));
                if (!Entry)
                {
                    /* no memory */
                    break;
                }

                /* setup event entry */
                Entry->NotificationType = NotificationType;
                Entry->Value = Value;
                Entry->hMixer = hMixer;

                /* insert entry */
                InsertTailList(&ClientInfo->MixerEventList, &Entry->Entry);

                /* now notify the client */
                KeSetEvent(ClientInfo->hPins[Index].NotifyEvent, 0, FALSE);
            }
            /* done */
            break;
        }
    }
}


NTSTATUS
WdmAudMixerInitialize(
    IN PDEVICE_OBJECT DeviceObject)
{
    MIXER_STATUS Status;

    /* initialize the mixer library */
    Status = MMixerInitialize(&MixerContext, Enum, (PVOID)DeviceObject);

    if (Status != MM_STATUS_SUCCESS)
    {
        /* failed to initialize mmixer library */
        DPRINT("MMixerInitialize failed with %lx\n", Status);
    }

    return Status;
}

NTSTATUS
WdmAudMixerCapabilities(
    IN PDEVICE_OBJECT DeviceObject,
    IN  PWDMAUD_DEVICE_INFO DeviceInfo,
    IN  PWDMAUD_CLIENT ClientInfo,
    IN PWDMAUD_DEVICE_EXTENSION DeviceExtension)
{
    if (MMixerGetCapabilities(&MixerContext, DeviceInfo->DeviceIndex, &DeviceInfo->u.MixCaps) == MM_STATUS_SUCCESS)
        return STATUS_SUCCESS;

    return STATUS_INVALID_PARAMETER;
}

NTSTATUS
WdmAudControlOpenMixer(
    IN  PDEVICE_OBJECT DeviceObject,
    IN  PIRP Irp,
    IN  PWDMAUD_DEVICE_INFO DeviceInfo,
    IN  PWDMAUD_CLIENT ClientInfo)
{
    HANDLE hMixer;
    PWDMAUD_HANDLE Handles;
    //PWDMAUD_DEVICE_EXTENSION DeviceExtension;
    NTSTATUS Status;
    PKEVENT EventObject = NULL;

    DPRINT("WdmAudControlOpenMixer\n");

    //DeviceExtension = (PWDMAUD_DEVICE_EXTENSION)DeviceObject->DeviceExtension;

    if (DeviceInfo->u.hNotifyEvent)
    {
        Status = ObReferenceObjectByHandle(DeviceInfo->u.hNotifyEvent, EVENT_MODIFY_STATE, *ExEventObjectType, UserMode, (LPVOID*)&EventObject, NULL);

        if (!NT_SUCCESS(Status))
        {
            DPRINT1("Invalid notify event passed %p from client %p\n", DeviceInfo->u.hNotifyEvent, ClientInfo);
            DbgBreakPoint();
            return SetIrpIoStatus(Irp, STATUS_UNSUCCESSFUL, 0);
        }
    }

    if (MMixerOpen(&MixerContext, DeviceInfo->DeviceIndex, ClientInfo, EventCallback, &hMixer) != MM_STATUS_SUCCESS)
    {
        ObDereferenceObject(EventObject);
        DPRINT1("Failed to open mixer\n");
        return SetIrpIoStatus(Irp, STATUS_UNSUCCESSFUL, 0);
    }


    Handles = AllocateItem(NonPagedPool, sizeof(WDMAUD_HANDLE) * (ClientInfo->NumPins+1));

    if (Handles)
    {
        if (ClientInfo->NumPins)
        {
            RtlMoveMemory(Handles, ClientInfo->hPins, sizeof(WDMAUD_HANDLE) * ClientInfo->NumPins);
            FreeItem(ClientInfo->hPins);
        }

        ClientInfo->hPins = Handles;
        ClientInfo->hPins[ClientInfo->NumPins].Handle = hMixer;
        ClientInfo->hPins[ClientInfo->NumPins].Type = MIXER_DEVICE_TYPE;
        ClientInfo->hPins[ClientInfo->NumPins].NotifyEvent = EventObject;
        ClientInfo->NumPins++;
    }
    else
    {
        ObDereferenceObject(EventObject);
        return SetIrpIoStatus(Irp, STATUS_UNSUCCESSFUL, sizeof(WDMAUD_DEVICE_INFO));
    }

    DeviceInfo->hDevice = hMixer;

    return SetIrpIoStatus(Irp, STATUS_SUCCESS, sizeof(WDMAUD_DEVICE_INFO));
}

NTSTATUS
NTAPI
WdmAudGetControlDetails(
    IN  PDEVICE_OBJECT DeviceObject,
    IN  PIRP Irp,
    IN  PWDMAUD_DEVICE_INFO DeviceInfo,
    IN  PWDMAUD_CLIENT ClientInfo)
{
    MIXER_STATUS Status;

    /* clear hmixer type flag */
    DeviceInfo->Flags &= ~MIXER_OBJECTF_HMIXER;

    /* query mmixer library */
    Status = MMixerGetControlDetails(&MixerContext, DeviceInfo->hDevice, DeviceInfo->DeviceIndex, DeviceInfo->Flags, &DeviceInfo->u.MixDetails);

    if (Status == MM_STATUS_SUCCESS)
        return SetIrpIoStatus(Irp, STATUS_SUCCESS, sizeof(WDMAUD_DEVICE_INFO));
    else
        return SetIrpIoStatus(Irp, STATUS_UNSUCCESSFUL, sizeof(WDMAUD_DEVICE_INFO));
}

NTSTATUS
NTAPI
WdmAudGetLineInfo(
    IN  PDEVICE_OBJECT DeviceObject,
    IN  PIRP Irp,
    IN  PWDMAUD_DEVICE_INFO DeviceInfo,
    IN  PWDMAUD_CLIENT ClientInfo)
{
    MIXER_STATUS Status;

    /* clear hmixer type flag */
    DeviceInfo->Flags &= ~MIXER_OBJECTF_HMIXER;

    /* query mixer library */
    Status = MMixerGetLineInfo(&MixerContext, DeviceInfo->hDevice, DeviceInfo->DeviceIndex, DeviceInfo->Flags, &DeviceInfo->u.MixLine);

    if (Status == MM_STATUS_SUCCESS)
        return SetIrpIoStatus(Irp, STATUS_SUCCESS, sizeof(WDMAUD_DEVICE_INFO));
    else
        return SetIrpIoStatus(Irp, STATUS_UNSUCCESSFUL, sizeof(WDMAUD_DEVICE_INFO));
}

NTSTATUS
NTAPI
WdmAudGetLineControls(
    IN  PDEVICE_OBJECT DeviceObject,
    IN  PIRP Irp,
    IN  PWDMAUD_DEVICE_INFO DeviceInfo,
    IN  PWDMAUD_CLIENT ClientInfo)
{
    MIXER_STATUS Status;

    /* clear hmixer type flag */
    DeviceInfo->Flags &= ~MIXER_OBJECTF_HMIXER;

    /* query mixer library */
    Status = MMixerGetLineControls(&MixerContext, DeviceInfo->hDevice, DeviceInfo->DeviceIndex, DeviceInfo->Flags, &DeviceInfo->u.MixControls);

    if (Status == MM_STATUS_SUCCESS)
        return SetIrpIoStatus(Irp, STATUS_SUCCESS, sizeof(WDMAUD_DEVICE_INFO));
    else
        return SetIrpIoStatus(Irp, STATUS_UNSUCCESSFUL, sizeof(WDMAUD_DEVICE_INFO));


}

NTSTATUS
NTAPI
WdmAudSetControlDetails(
    IN  PDEVICE_OBJECT DeviceObject,
    IN  PIRP Irp,
    IN  PWDMAUD_DEVICE_INFO DeviceInfo,
    IN  PWDMAUD_CLIENT ClientInfo)
{
    MIXER_STATUS Status;

    /* clear hmixer type flag */
    DeviceInfo->Flags &= ~MIXER_OBJECTF_HMIXER;

    /* query mixer library */
    Status = MMixerSetControlDetails(&MixerContext, DeviceInfo->hDevice, DeviceInfo->DeviceIndex, DeviceInfo->Flags, &DeviceInfo->u.MixDetails);

    if (Status == MM_STATUS_SUCCESS)
        return SetIrpIoStatus(Irp, STATUS_SUCCESS, sizeof(WDMAUD_DEVICE_INFO));
    else
        return SetIrpIoStatus(Irp, STATUS_UNSUCCESSFUL, sizeof(WDMAUD_DEVICE_INFO));
}

NTSTATUS
NTAPI
WdmAudGetMixerEvent(
    IN  PDEVICE_OBJECT DeviceObject,
    IN  PIRP Irp,
    IN  PWDMAUD_DEVICE_INFO DeviceInfo,
    IN  PWDMAUD_CLIENT ClientInfo)
{
    PLIST_ENTRY Entry;
    PEVENT_ENTRY EventEntry;

    /* enumerate event list and check if there is a new event */
    Entry = ClientInfo->MixerEventList.Flink;

    while(Entry != &ClientInfo->MixerEventList)
    {
        /* grab event entry */
        EventEntry = (PEVENT_ENTRY)CONTAINING_RECORD(Entry, EVENT_ENTRY, Entry);

        if (EventEntry->hMixer == DeviceInfo->hDevice)
        {
            /* found an entry */
            DeviceInfo->u.MixerEvent.hMixer = EventEntry->hMixer;
            DeviceInfo->u.MixerEvent.NotificationType = EventEntry->NotificationType;
            DeviceInfo->u.MixerEvent.Value = EventEntry->Value;

            /* remove entry from list */
            RemoveEntryList(&EventEntry->Entry);

            /* free event entry */
            FreeItem(EventEntry);

            /* done */
            return SetIrpIoStatus(Irp, STATUS_SUCCESS, sizeof(WDMAUD_DEVICE_INFO));
        }

        /* move to next */
        Entry = Entry->Flink;
    }

    /* no event entry available */
    return SetIrpIoStatus(Irp, STATUS_UNSUCCESSFUL, sizeof(WDMAUD_DEVICE_INFO));
}

ULONG
WdmAudGetMixerDeviceCount()
{
    return MMixerGetCount(&MixerContext);
}

ULONG
WdmAudGetWaveInDeviceCount()
{
    return MMixerGetWaveInCount(&MixerContext);
}

ULONG
WdmAudGetWaveOutDeviceCount()
{
    return MMixerGetWaveOutCount(&MixerContext);
}

ULONG
WdmAudGetMidiInDeviceCount()
{
    return MMixerGetMidiInCount(&MixerContext);
}

ULONG
WdmAudGetMidiOutDeviceCount()
{
    return MMixerGetWaveOutCount(&MixerContext);
}

NTSTATUS
WdmAudGetPnpNameByIndexAndType(
    IN ULONG DeviceIndex,
    IN SOUND_DEVICE_TYPE DeviceType,
    OUT LPWSTR *DevicePath)
{
    if (DeviceType == WAVE_IN_DEVICE_TYPE || DeviceType == WAVE_OUT_DEVICE_TYPE)
    {
        if (MMixerGetWaveDevicePath(&MixerContext, DeviceType == WAVE_IN_DEVICE_TYPE, DeviceIndex, DevicePath) == MM_STATUS_SUCCESS)
            return STATUS_SUCCESS;
        else
            return STATUS_UNSUCCESSFUL;
    }
    else if (DeviceType == MIDI_IN_DEVICE_TYPE || DeviceType == MIDI_OUT_DEVICE_TYPE)
    {
        if (MMixerGetMidiDevicePath(&MixerContext, DeviceType == MIDI_IN_DEVICE_TYPE, DeviceIndex, DevicePath) == MM_STATUS_SUCCESS)
            return STATUS_SUCCESS;
        else
            return STATUS_UNSUCCESSFUL;
    }
    else if (DeviceType == MIXER_DEVICE_TYPE)
    {
        UNIMPLEMENTED;
    }

    return STATUS_UNSUCCESSFUL;
}

NTSTATUS
WdmAudWaveCapabilities(
    IN PDEVICE_OBJECT DeviceObject,
    IN PWDMAUD_DEVICE_INFO DeviceInfo,
    IN PWDMAUD_CLIENT ClientInfo,
    IN PWDMAUD_DEVICE_EXTENSION DeviceExtension)
{
    MIXER_STATUS Status = MM_STATUS_UNSUCCESSFUL;

    if (DeviceInfo->DeviceType == WAVE_IN_DEVICE_TYPE)
    {
        /* get capabilities */
        Status = MMixerWaveInCapabilities(&MixerContext, DeviceInfo->DeviceIndex, &DeviceInfo->u.WaveInCaps);
    }
    else if (DeviceInfo->DeviceType == WAVE_OUT_DEVICE_TYPE)
    {
        /* get capabilities */
        Status = MMixerWaveOutCapabilities(&MixerContext, DeviceInfo->DeviceIndex, &DeviceInfo->u.WaveOutCaps);
    }

    if (Status == MM_STATUS_SUCCESS)
        return STATUS_SUCCESS;
    else
        return Status;
}

NTSTATUS
WdmAudMidiCapabilities(
    IN PDEVICE_OBJECT DeviceObject,
    IN PWDMAUD_DEVICE_INFO DeviceInfo,
    IN PWDMAUD_CLIENT ClientInfo,
    IN PWDMAUD_DEVICE_EXTENSION DeviceExtension)
{
    MIXER_STATUS Status = MM_STATUS_UNSUCCESSFUL;

    if (DeviceInfo->DeviceType == MIDI_IN_DEVICE_TYPE)
    {
        /* get capabilities */
        Status = MMixerMidiInCapabilities(&MixerContext, DeviceInfo->DeviceIndex, &DeviceInfo->u.MidiInCaps);
    }
    else if (DeviceInfo->DeviceType == WAVE_OUT_DEVICE_TYPE)
    {
        /* get capabilities */
        Status = MMixerMidiOutCapabilities(&MixerContext, DeviceInfo->DeviceIndex, &DeviceInfo->u.MidiOutCaps);
    }

    if (Status == MM_STATUS_SUCCESS)
        return STATUS_SUCCESS;
    else
        return STATUS_UNSUCCESSFUL;
}


MIXER_STATUS
CreatePinCallback(
    IN PVOID Ctx,
    IN ULONG VirtualDeviceId,
    IN ULONG PinId,
    IN HANDLE hFilter,
    IN PKSPIN_CONNECT PinConnect,
    IN ACCESS_MASK DesiredAccess,
    OUT PHANDLE PinHandle)
{
    ULONG BytesReturned;
    SYSAUDIO_INSTANCE_INFO InstanceInfo;
    NTSTATUS Status;
    ULONG FreeIndex;
    PPIN_CREATE_CONTEXT Context = (PPIN_CREATE_CONTEXT)Ctx;

    /* setup property request */
    InstanceInfo.Property.Set = KSPROPSETID_Sysaudio;
    InstanceInfo.Property.Id = KSPROPERTY_SYSAUDIO_INSTANCE_INFO;
    InstanceInfo.Property.Flags = KSPROPERTY_TYPE_SET;
    InstanceInfo.Flags = 0;
    InstanceInfo.DeviceNumber = VirtualDeviceId;

    /* attach to virtual device */
    Status = KsSynchronousIoControlDevice(Context->DeviceExtension->FileObject, KernelMode, IOCTL_KS_PROPERTY, (PVOID)&InstanceInfo, sizeof(SYSAUDIO_INSTANCE_INFO), NULL, 0, &BytesReturned);

    if (!NT_SUCCESS(Status))
        return MM_STATUS_UNSUCCESSFUL;

    /* close existing pin */
    FreeIndex = ClosePin(Context->ClientInfo, VirtualDeviceId, PinId, Context->DeviceType);

    /* now create the pin */
    Status = KsCreatePin(Context->DeviceExtension->hSysAudio, PinConnect, DesiredAccess, PinHandle);

    /* check for success */
    if (!NT_SUCCESS(Status))
        return MM_STATUS_UNSUCCESSFUL;

    /* store the handle */
    Status = InsertPinHandle(Context->ClientInfo, VirtualDeviceId, PinId, Context->DeviceType, *PinHandle, FreeIndex);
    if (!NT_SUCCESS(Status))
    {
        /* failed to insert handle */
        ZwClose(*PinHandle);
        return MM_STATUS_UNSUCCESSFUL;
    }

    return MM_STATUS_SUCCESS;
}

NTSTATUS
WdmAudControlOpenWave(
    IN  PDEVICE_OBJECT DeviceObject,
    IN  PIRP Irp,
    IN  PWDMAUD_DEVICE_INFO DeviceInfo,
    IN  PWDMAUD_CLIENT ClientInfo)
{
    MIXER_STATUS Status;
    PIN_CREATE_CONTEXT Context;

    Context.ClientInfo = ClientInfo;
    Context.DeviceExtension = (PWDMAUD_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
    Context.DeviceType = DeviceInfo->DeviceType;

    Status = MMixerOpenWave(&MixerContext, DeviceInfo->DeviceIndex, DeviceInfo->DeviceType == WAVE_IN_DEVICE_TYPE, &DeviceInfo->u.WaveFormatEx, CreatePinCallback, &Context, &DeviceInfo->hDevice);

    if (Status == MM_STATUS_SUCCESS)
        return SetIrpIoStatus(Irp, STATUS_SUCCESS, sizeof(WDMAUD_DEVICE_INFO));
    else
        return SetIrpIoStatus(Irp, STATUS_NOT_SUPPORTED, sizeof(WDMAUD_DEVICE_INFO));
}

NTSTATUS
WdmAudControlOpenMidi(
    IN  PDEVICE_OBJECT DeviceObject,
    IN  PIRP Irp,
    IN  PWDMAUD_DEVICE_INFO DeviceInfo,
    IN  PWDMAUD_CLIENT ClientInfo)
{
    MIXER_STATUS Status;
    PIN_CREATE_CONTEXT Context;

    Context.ClientInfo = ClientInfo;
    Context.DeviceExtension = (PWDMAUD_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
    Context.DeviceType = DeviceInfo->DeviceType;

    Status = MMixerOpenMidi(&MixerContext, DeviceInfo->DeviceIndex, DeviceInfo->DeviceType == MIDI_IN_DEVICE_TYPE, CreatePinCallback, &Context, &DeviceInfo->hDevice);

    if (Status == MM_STATUS_SUCCESS)
        return SetIrpIoStatus(Irp, STATUS_SUCCESS, sizeof(WDMAUD_DEVICE_INFO));
    else
        return SetIrpIoStatus(Irp, STATUS_NOT_SUPPORTED, sizeof(WDMAUD_DEVICE_INFO));
}
