// ==============================================================================
//
// miniport.cpp - miniport driver implementation for FM synth.
// Copyright (c) Microsoft.  All rights reserved.
//
// ==============================================================================

#include "private.h"    // contains class definitions.

#define STR_MODULENAME "FMSynth: "

#pragma warning (disable : 4100 4127 4211)

#pragma code_seg("PAGE")
// ==============================================================================
// CreateMiniportMidiFM()
// Creates a MIDI FM miniport driver.  This uses a
// macro from STDUNK.H to do all the work.
// ==============================================================================
_When_((PoolType&NonPagedPoolMustSucceed)!=0,
    __drv_reportError("Must succeed pool allocations are forbidden. "
                      "Allocation failures cause a system crash"))
NTSTATUS CreateMiniportMidiFM
(
OUT     PUNKNOWN *  Unknown,
IN      REFCLSID    ClassID,
IN      PUNKNOWN    UnknownOuter    OPTIONAL,
IN      POOL_TYPE   PoolType
)
{
    PAGED_CODE();

    ASSERT(Unknown);

    _DbgPrintF(DEBUGLVL_VERBOSE, ("CreateMiniportMidiFM"));

//  expand STD_CREATE_BODY_ to take constructor(boolean) for whether to include volume
    NTSTATUS ntStatus;
    CMiniportMidiFM *p =
        new(PoolType,'MFcP') CMiniportMidiFM(
                                 UnknownOuter,
                                 (IsEqualGUIDAligned(ClassID,CLSID_MiniportDriverFmSynthWithVol))
                             );









    if (p)
    {
        *Unknown = PUNKNOWN((PMINIPORTMIDI)(p));
        (*Unknown)->AddRef();
        ntStatus = STATUS_SUCCESS;
    }
    else
    {
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
    }
    return ntStatus;
}

#pragma code_seg("PAGE")
// ==============================================================================
// CMiniportMidiFM::ProcessResources()
// Processes the resource list.
// ==============================================================================
NTSTATUS
CMiniportMidiFM::
ProcessResources
(
IN  PRESOURCELIST   ResourceList
)
{
    PAGED_CODE();

    ASSERT(ResourceList);
    if (!ResourceList)
    {
        return STATUS_DEVICE_CONFIGURATION_ERROR;
    }

    _DbgPrintF(DEBUGLVL_VERBOSE,("CMiniportMidiFM::ProcessResources"));

    //
    // Get counts for the types of resources.
    //
    ULONG       countIO     = ResourceList->NumberOfPorts();
    ULONG       countIRQ    = ResourceList->NumberOfInterrupts();
    ULONG       countDMA    = ResourceList->NumberOfDmas();

    NTSTATUS ntStatus = STATUS_SUCCESS;

    //
    // Make sure we have the expected number of resources.
    //
    if  (   (countIO != 1)
        ||  (countIRQ != 0)
        ||  (countDMA != 0)
        )
    {
        ntStatus = STATUS_DEVICE_CONFIGURATION_ERROR;
    }

    if (NT_SUCCESS(ntStatus))
    {
        //
        // Get the port address.
        //
        m_PortBase = PUCHAR(ResourceList->FindTranslatedPort(0)->u.Port.Start.QuadPart);
        _DbgPrintF(DEBUGLVL_VERBOSE, ("Port Address = 0x%p", m_PortBase));
    }

    return ntStatus;
}

#pragma code_seg("PAGE")
// ==============================================================================
// CMiniportMidiFM::NonDelegatingQueryInterface()
// Obtains an interface.  This function works just like a COM QueryInterface
// call and is used if the object is not being aggregated.
// ==============================================================================
STDMETHODIMP_(NTSTATUS) CMiniportMidiFM::NonDelegatingQueryInterface
(
    _In_ REFIID  Interface,
    _COM_Outptr_   PVOID * Object
)
{
    PAGED_CODE();

    ASSERT(Object);

    _DbgPrintF(DEBUGLVL_VERBOSE,("CMiniportMidiFM::NonDelegatingQueryInterface"));

    if (IsEqualGUIDAligned(Interface,IID_IUnknown))
    {
        *Object = PVOID(PUNKNOWN(PMINIPORT(this)));
    }
    else if (IsEqualGUIDAligned(Interface,IID_IMiniport))
    {
        *Object = PVOID(PMINIPORT(this));
    }
    else if (IsEqualGUIDAligned(Interface,IID_IMiniportMidi))
    {
        *Object = PVOID(PMINIPORTMIDI(this));
    }
    else if (IsEqualGUIDAligned(Interface, IID_IPowerNotify))
    {
        *Object = PVOID(PPOWERNOTIFY(this));
    }
    else
    {
        *Object = NULL;
    }

    if (*Object)
    {
        //
        // We reference the interface for the caller.
        //
        PUNKNOWN((PMINIPORT)*Object)->AddRef();
        return STATUS_SUCCESS;
    }

    return STATUS_INVALID_PARAMETER;
}

#pragma code_seg()
// ==============================================================================
// CMiniportMidiFM::~CMiniportMidiFM()
// Destructor.
// ==============================================================================
CMiniportMidiFM::~CMiniportMidiFM
(
void
)
{
    KIRQL   oldIrql;
    _DbgPrintF(DEBUGLVL_VERBOSE,("CMiniportMidiFM::~CMiniportMidiFM"));

    KeAcquireSpinLock(&m_SpinLock,&oldIrql);
    // Set silence on the device
    Opl3_BoardReset();

    KeReleaseSpinLock(&m_SpinLock,oldIrql);

    if (m_Port)
    {
        m_Port->Release();
    }
}

#pragma code_seg()
// ==============================================================================
// CMiniportMidiFM::Init()
// Initializes a the miniport.
// ==============================================================================
STDMETHODIMP_(NTSTATUS)
CMiniportMidiFM::
Init
(
    _In_      PUNKNOWN        UnknownAdapter  OPTIONAL,
    _In_      PRESOURCELIST   ResourceList,
    _In_      PPORTMIDI       Port_,
    _Out_     PSERVICEGROUP * ServiceGroup
)
{
    ASSERT(ResourceList);
    if (!ResourceList)
    {
        return STATUS_DEVICE_CONFIGURATION_ERROR;
    }
    ASSERT(Port_);
    ASSERT(ServiceGroup);

    int i;

    _DbgPrintF(DEBUGLVL_VERBOSE,("CMiniportMidiFM::init"));

    //
    // AddRef() is required because we are keeping this pointer.
    //
    m_Port = Port_;
    m_Port->AddRef();

    //
    // m_fStreamExists is not explicitly set to FALSE because C++ zeros
    // them out on a 'new'
    //

    KeInitializeSpinLock(&m_SpinLock);
    //
    // We want the IAdapterCommon interface on the adapter common object,
    // which is given to us as a IUnknown.  The QueryInterface call gives us
    // an AddRefed pointer to the interface we want.
    //
    NTSTATUS ntStatus = ProcessResources(ResourceList);

    if (NT_SUCCESS(ntStatus))
    {
        KIRQL oldIrql;
        KeAcquireSpinLock(&m_SpinLock,&oldIrql);

        for (i = 0; i < 0x200; i++)    // initialize the shadow registers, used
           m_SavedRegValues[i] = 0x00; // in case of power-down during playback

        // Initialize the hardware.
        // 1. First check to see if an opl device is present.
        // 2. Then determine if it is an opl2 or opl3. Bail if opl2.
        // 3. Call Opl3_BoardReset to silence and reset the device.
        if (SoundSynthPresent(m_PortBase, m_PortBase))
        {
            // Now check if the device is an opl2 or opl3 type.
            // The patches are already declared for opl3. So Init() is not defined.
            // For opl2 we have to go through an init and load the patches structure.
            if (SoundMidiIsOpl3())
            {
                _DbgPrintF(DEBUGLVL_VERBOSE, ("CMiniportMidiFM::Init Type = OPL3"));
                // now silence the device and reset the board.
                Opl3_BoardReset();

                *ServiceGroup = NULL;
            }
            else
            {
                _DbgPrintF(DEBUGLVL_TERSE, ("CMiniportMidiFM::Init Type = OPL2"));
                ntStatus = STATUS_NOT_IMPLEMENTED;
            }

        }
        else
        {
            _DbgPrintF(DEBUGLVL_TERSE, ("CMiniportMidiFM::Init SoundSynthPresent failed"));
            ntStatus = STATUS_DEVICE_CONFIGURATION_ERROR;
        }
        KeReleaseSpinLock(&m_SpinLock,oldIrql);
    }
    else
    {
        _DbgPrintF(DEBUGLVL_TERSE, ("CMiniportMidiFM::Init ProcessResources failed"));
    }

    _DbgPrintF(DEBUGLVL_VERBOSE, ("CMiniportMidiFM::Init returning 0x%X", ntStatus));

    if (!NT_SUCCESS(ntStatus))
    {
        //
        // clean up our mess
        //

        // release the port
        m_Port->Release();
        m_Port = NULL;
    }

    return ntStatus;
}

#pragma code_seg("PAGE")
// ==============================================================================
// NewStream()
// Creates a new stream.
// ==============================================================================
STDMETHODIMP_(NTSTATUS)
CMiniportMidiFM::
NewStream
(
    _Out_     PMINIPORTMIDISTREAM *   Stream,
    _In_opt_  PUNKNOWN                OuterUnknown    OPTIONAL,
    _When_((PoolType&NonPagedPoolMustSucceed)!=0,
        __drv_reportError("Must succeed pool allocations are forbidden. "
                          "Allocation failures cause a system crash"))
    _In_      POOL_TYPE               PoolType,
    _In_      ULONG                   Pin,
    _In_      BOOLEAN                 Capture,
    _In_      PKSDATAFORMAT           DataFormat,
    _Out_     PSERVICEGROUP *         ServiceGroup
)
{
    PAGED_CODE();

    NTSTATUS ntStatus = STATUS_SUCCESS;

    if (m_fStreamExists)
    {
        _DbgPrintF(DEBUGLVL_TERSE,("CMiniportMidiFM::NewStream stream already exists"));
        ntStatus = STATUS_INVALID_DEVICE_REQUEST;
    }
    else
    {
        _DbgPrintF(DEBUGLVL_VERBOSE,("CMiniportMidiFM::NewStream"));
        CMiniportMidiStreamFM *pStream =
            new(PoolType) CMiniportMidiStreamFM(OuterUnknown);

        if (pStream)
        {
            pStream->AddRef();

            ntStatus = pStream->Init(this,m_PortBase);

            if (NT_SUCCESS(ntStatus))
            {
                *Stream = PMINIPORTMIDISTREAM(pStream);
                (*Stream)->AddRef();

                *ServiceGroup = NULL;
                m_fStreamExists = TRUE;
            }

            pStream->Release();
        }
        else
        {
            _DbgPrintF(DEBUGLVL_TERSE,("CMiniportMidiFM::NewStream failed, no memory"));
            ntStatus = STATUS_INSUFFICIENT_RESOURCES;
        }
    }

    return ntStatus;
}

#pragma code_seg("PAGE")
/*----------------------------------------------------------------------------
 FUNCTION NAME- CMiniportMidiFM::PowerChangeNotify()
 ENTRY      --- IN  POWER_STATE     NewState
                        power management status
 RETURN     --- void
 *------------------------------------------------------------------------- */
STDMETHODIMP_(void) CMiniportMidiFM::PowerChangeNotify(
    _In_  POWER_STATE     PowerState
)
{
    PAGED_CODE();
    _DbgPrintF(DEBUGLVL_VERBOSE,("CMiniportMidiFM::PowerChangeNotify(%d)",PowerState.DeviceState));

    switch (PowerState.DeviceState)
    {
        case PowerDeviceD0:
            if (m_PowerState.DeviceState != PowerDeviceD0) // check for power state delta
            {
                MiniportMidiFMResume();
            }
            break;

        case PowerDeviceD1:
        case PowerDeviceD2:
        case PowerDeviceD3:
        default:
            //  Don't need to do anything special, we always remember where we are.
            break;
    }
    m_PowerState.DeviceState = PowerState.DeviceState;
}

#pragma code_seg()
// ==========================================================================
// ==========================================================================
void
CMiniportMidiFM::
MiniportMidiFMResume()
{
    KIRQL   oldIrql;
    BYTE    i;

    _DbgPrintF(DEBUGLVL_VERBOSE,("CMiniportMidiFM::MiniportMidiFMResume"));
    KeAcquireSpinLock(&m_SpinLock,&oldIrql);
    //  We never touch these--set them to the default value anyway.
    //  AD_LSI
    SoundMidiSendFM(m_PortBase, AD_LSI, m_SavedRegValues[AD_LSI]);
    //  AD_LSI2
    SoundMidiSendFM(m_PortBase, AD_LSI2, m_SavedRegValues[AD_LSI2]);
    //  AD_TIMER1
    SoundMidiSendFM(m_PortBase, AD_TIMER1, m_SavedRegValues[AD_TIMER1]);
    //  AD_TIMER2
    SoundMidiSendFM(m_PortBase, AD_TIMER2, m_SavedRegValues[AD_TIMER2]);

    //  AD_MASK
    SoundMidiSendFM(m_PortBase, AD_MASK, m_SavedRegValues[AD_MASK]);

    //  AD_CONNECTION
    SoundMidiSendFM(m_PortBase, AD_CONNECTION, m_SavedRegValues[AD_CONNECTION]);

    //  AD_NEW
    SoundMidiSendFM(m_PortBase, AD_NEW, m_SavedRegValues[AD_NEW]);

    //  AD_NTS
    SoundMidiSendFM(m_PortBase, AD_NTS, m_SavedRegValues[AD_NTS]);

    //  AD_DRUM
    SoundMidiSendFM(m_PortBase, AD_DRUM, m_SavedRegValues[AD_DRUM]);

    for (i = 0; i <= 0x15; i++)
    {
        if ((i & 0x07) <= 0x05)
        {
            //  AD_MULT
            //  AD_MULT2
            SoundMidiSendFM(m_PortBase, AD_MULT + i, m_SavedRegValues[AD_MULT + i]);
            SoundMidiSendFM(m_PortBase, AD_MULT2 + i, m_SavedRegValues[AD_MULT2 + i]);

            //  AD_LEVEL
            //  AD_LEVEL2
            //  turn off all the oscillators
            SoundMidiSendFM(m_PortBase, AD_LEVEL + i, m_SavedRegValues[AD_LEVEL + i]);
            SoundMidiSendFM(m_PortBase, AD_LEVEL2 + i, m_SavedRegValues[AD_LEVEL2 + i]);

            //  AD_AD
            //  AD_AD2
            SoundMidiSendFM(m_PortBase, AD_AD + i, m_SavedRegValues[AD_AD + i]);
            SoundMidiSendFM(m_PortBase, AD_AD2 + i, m_SavedRegValues[AD_AD2 + i]);

            //  AD_SR
            //  AD_SR2
            SoundMidiSendFM(m_PortBase, AD_SR + i, m_SavedRegValues[AD_SR + i]);
            SoundMidiSendFM(m_PortBase, AD_SR2 + i, m_SavedRegValues[AD_SR2 + i]);

            //  AD_WAVE
            //  AD_WAVE2
            SoundMidiSendFM(m_PortBase, AD_WAVE + i, m_SavedRegValues[AD_WAVE + i]);
            SoundMidiSendFM(m_PortBase, AD_WAVE2 + i, m_SavedRegValues[AD_WAVE2 + i]);
        }
    }

    for (i = 0; i <= 0x08; i++)
    {
        //  AD_FNUMBER
        //  AD_FNUMBER2
        SoundMidiSendFM(m_PortBase, AD_FNUMBER + i, m_SavedRegValues[AD_FNUMBER + i]);
        SoundMidiSendFM(m_PortBase, AD_FNUMBER2 + i, m_SavedRegValues[AD_FNUMBER2 + i]);

        //  AD_FEEDBACK
        //  AD_FEEDBACK2
        SoundMidiSendFM(m_PortBase, AD_FEEDBACK + i, m_SavedRegValues[AD_FEEDBACK + i]);
        SoundMidiSendFM(m_PortBase, AD_FEEDBACK2 + i, m_SavedRegValues[AD_FEEDBACK2 + i]);

        //  AD_BLOCK
        //  AD_BLOCK2
        SoundMidiSendFM(m_PortBase, AD_BLOCK + i, m_SavedRegValues[AD_BLOCK + i]);
        SoundMidiSendFM(m_PortBase, AD_BLOCK2 + i, m_SavedRegValues[AD_BLOCK2 + i]);
    }
    KeReleaseSpinLock(&m_SpinLock,oldIrql);

    _DbgPrintF(DEBUGLVL_VERBOSE,("Done with CMiniportMidiFM::MiniportMidiFMResume"));
}

#pragma code_seg()
void
CMiniportMidiFM::
Opl3_BoardReset()
{
    ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);

    BYTE i;

    _DbgPrintF(DEBUGLVL_VERBOSE,("CMiniportMidiFM::Opl3_BoardReset"));
    /* ---- silence the chip -------- */

    /* tell the FM chip to use 4-operator mode, and
    fill in any other random variables */
    SoundMidiSendFM(m_PortBase, AD_NEW, 0x01);
    SoundMidiSendFM(m_PortBase, AD_MASK, 0x60);
    SoundMidiSendFM(m_PortBase, AD_CONNECTION, 0x00);
    SoundMidiSendFM(m_PortBase, AD_NTS, 0x00);

    /* turn off the drums, and use high vibrato/modulation */
    SoundMidiSendFM(m_PortBase, AD_DRUM, 0xc0);

    /* turn off all the oscillators */
    for (i = 0; i <= 0x15; i++)
    {
        if ((i & 0x07) <= 0x05)
        {
            SoundMidiSendFM(m_PortBase, AD_LEVEL + i, 0x3f);
            SoundMidiSendFM(m_PortBase, AD_LEVEL2 + i, 0x3f);
        }
    };

    /* turn off all the voices */
    for (i = 0; i <= 0x08; i++)
    {
        SoundMidiSendFM(m_PortBase, AD_BLOCK + i, 0x00);
        SoundMidiSendFM(m_PortBase, AD_BLOCK2 + i, 0x00);
    };
}


// ==============================================================================
// PinDataRangesStream
// Structures indicating range of valid format values for streaming pins.
// ==============================================================================
static
KSDATARANGE_MUSIC PinDataRangesStream[] =
{
    {
        {
            sizeof(KSDATARANGE_MUSIC),
            0,
            0,
            0,
            STATICGUIDOF(KSDATAFORMAT_TYPE_MUSIC),
            STATICGUIDOF(KSDATAFORMAT_SUBTYPE_MIDI),
            STATICGUIDOF(KSDATAFORMAT_SPECIFIER_NONE)
        },
        STATICGUIDOF(KSMUSIC_TECHNOLOGY_FMSYNTH),
        NUM2VOICES,
        NUM2VOICES,
        0xffffffff
    }
};

// ==============================================================================
// PinDataRangePointersStream
// List of pointers to structures indicating range of valid format values
// for streaming pins.
// ==============================================================================
static
PKSDATARANGE PinDataRangePointersStream[] =
{
    PKSDATARANGE(&PinDataRangesStream[0])
};

// ==============================================================================
// PinDataRangesBridge
// Structures indicating range of valid format values for bridge pins.
// ==============================================================================
static
KSDATARANGE PinDataRangesBridge[] =
{
   {
      sizeof(KSDATARANGE),
      0,
      0,
      0,
      STATICGUIDOF(KSDATAFORMAT_TYPE_MUSIC),
      STATICGUIDOF(KSDATAFORMAT_SUBTYPE_MIDI_BUS),
      STATICGUIDOF(KSDATAFORMAT_SPECIFIER_NONE)
   }
};

// ==============================================================================
// PinDataRangePointersBridge
// List of pointers to structures indicating range of valid format values
// for bridge pins.
// ==============================================================================
static
PKSDATARANGE PinDataRangePointersBridge[] =
{
    &PinDataRangesBridge[0]
};

// ==============================================================================
// MiniportPins
// List of pins.
// ==============================================================================
static
PCPIN_DESCRIPTOR MiniportPins[] =
{
    {
        1,1,1,  // InstanceCount
        NULL,   // AutomationTable
        {       // KsPinDescriptor
            0,                                          // InterfacesCount
            NULL,                                       // Interfaces
            0,                                          // MediumsCount
            NULL,                                       // Mediums
            SIZEOF_ARRAY(PinDataRangePointersStream),   // DataRangesCount
            PinDataRangePointersStream,                 // DataRanges
            KSPIN_DATAFLOW_IN,                          // DataFlow
            KSPIN_COMMUNICATION_SINK,                   // Communication
            (GUID *) &KSCATEGORY_SYNTHESIZER,           // Category
            NULL,                                       // Name
            0                                           // Reserved
        }
    },
    {
        0,0,0,  // InstanceCount
        NULL,   // AutomationTable
        {       // KsPinDescriptor
            0,                                          // InterfacesCount
            NULL,                                       // Interfaces
            0,                                          // MediumsCount
            NULL,                                       // Mediums
            SIZEOF_ARRAY(PinDataRangePointersBridge),   // DataRangesCount
            PinDataRangePointersBridge,                 // DataRanges
            KSPIN_DATAFLOW_OUT,                         // DataFlow
            KSPIN_COMMUNICATION_NONE,                   // Communication
            (GUID *) &KSCATEGORY_AUDIO,                 // Category
            NULL,                                       // Name
            0                                           // Reserved
        }
    }
};

/*****************************************************************************
 * PropertiesVolume
 *****************************************************************************
 * Properties for volume controls.
 */
static
PCPROPERTY_ITEM PropertiesVolume[] =
{
    {
        &KSPROPSETID_Audio,
        KSPROPERTY_AUDIO_VOLUMELEVEL,
        KSPROPERTY_TYPE_GET | KSPROPERTY_TYPE_SET | KSPROPERTY_TYPE_BASICSUPPORT,
        PropertyHandler_Level
    },
    {
        &KSPROPSETID_Audio,
        KSPROPERTY_AUDIO_CPU_RESOURCES,
        KSPROPERTY_TYPE_GET,
        PropertyHandler_CpuResources
    }
};

/*****************************************************************************
 * AutomationVolume
 *****************************************************************************
 * Automation table for volume controls.
 */
DEFINE_PCAUTOMATION_TABLE_PROP(AutomationVolume,PropertiesVolume);

// ==============================================================================
// MiniportNodes
// List of nodes.
// ==============================================================================
static
PCNODE_DESCRIPTOR MiniportNodes[] =
{
    {
            // synth node, #0
        0,                      // Flags
        NULL,                   // AutomationTable
        &KSNODETYPE_SYNTHESIZER,// Type
        NULL                    // Name TODO: fill in with correct GUID
    },
    {
            // volume node, #1
        0,                      // Flags
        &AutomationVolume,      // AutomationTable
        &KSNODETYPE_VOLUME,     // Type
        NULL                    // Name TODO: fill in with correct GUID
    }
};

// ==============================================================================
// MiniportConnections
// List of connections.
// ==============================================================================

/*****************************************************************************
 *      Table of topology unit connections.
 *
 * Pin numbering is technically arbitrary, but the convention established here
 * is to number a solitary output pin 0 (looks like an 'o') and a solitary
 * input pin 1 (looks like an 'i').  Even destinations, which have no output,
 * have an input pin numbered 1 and no pin 0.
 *
 * Nodes are more likely to have multiple ins than multiple outs, so the more
 * general rule would be that inputs are numbered >=1.  If a node has multiple
 * outs, none of these conventions apply.
 *
 * Nodes have at most one control value.  Mixers are therefore simple summing
 * nodes with no per-pin levels.  Rather than assigning a unique pin to each
 * input to a mixer, all inputs are connected to pin 1.  This is acceptable
 * because there is no functional distinction between the inputs.
 *
 * There are no multiplexers in this topology, so there is no opportunity to
 * give an example of a multiplexer.  A multiplexer should have a single
 * output pin (0) and multiple input pins (1..n).  Its control value is an
 * integer in the range 1..n indicating which input is connected to the
 * output.
 *
 * In the case of connections to pins, as opposed to connections to nodes, the
 * node is identified as PCFILTER_NODE and the pin number identifies the
 * particular filter pin.
 *****************************************************************************
 */
enum {
    eFMSynthNode  = 0,
    eFMVolumeNode
};

enum {
    eFMNodeOutput = 0,
    eFMNodeInput  = 1
};

enum {
    eFilterInput = eFMNodeOutput,
    eBridgeOutput = eFMNodeInput
};

static
PCCONNECTION_DESCRIPTOR MiniportConnections[] =
{
    //  FromNode,       FromPin,        ToNode,         ToPin
    {   PCFILTER_NODE,  eFilterInput,   eFMSynthNode,   eFMNodeInput }, // Stream in to synth.
    {   eFMSynthNode,   eFMNodeOutput,  PCFILTER_NODE,  eBridgeOutput } // Synth to bridge out.
};

// different connection struct for volume version
static
PCCONNECTION_DESCRIPTOR MiniportWithVolConnections[] =
{
    //  FromNode,       FromPin,        ToNode,         ToPin
    {   PCFILTER_NODE,  eFilterInput,   eFMSynthNode,   eFMNodeInput }, // Stream in to synth.
    {   eFMSynthNode,   eFMNodeOutput,  eFMVolumeNode,  eFMNodeInput }, // Synth to volume.
    {   eFMVolumeNode,  eFMNodeOutput,  PCFILTER_NODE,  eBridgeOutput } // volume to bridge out.
};

////////////////////////////////////////////////////////////////////////////////
// MiniportCategories
//
// List of categories.
static
GUID MiniportCategories[] =
{
    STATICGUIDOF(KSCATEGORY_AUDIO),
    STATICGUIDOF(KSCATEGORY_RENDER),
    STATICGUIDOF(KSCATEGORY_SYNTHESIZER)
};

// ==============================================================================
// MiniportDescription
// Complete description of the miniport.
// ==============================================================================
static
PCFILTER_DESCRIPTOR MiniportFilterDescriptor =
{
    0,                                  // Version
    NULL,                               // AutomationTable
    sizeof(PCPIN_DESCRIPTOR),           // PinSize
    SIZEOF_ARRAY(MiniportPins),         // PinCount
    MiniportPins,                       // Pins
    sizeof(PCNODE_DESCRIPTOR),          // NodeSize
    1,                                  // NodeCount - no volume node
    MiniportNodes,                      // Nodes
    SIZEOF_ARRAY(MiniportConnections),  // ConnectionCount
    MiniportConnections,                // Connections
    SIZEOF_ARRAY(MiniportCategories),   // CategoryCount
    MiniportCategories                  // Categories
};

static
PCFILTER_DESCRIPTOR MiniportFilterWithVolDescriptor =
{
    0,                                          // Version
    NULL,                                       // AutomationTable
    sizeof(PCPIN_DESCRIPTOR),                   // PinSize
    SIZEOF_ARRAY(MiniportPins),                 // PinCount
    MiniportPins,                               // Pins
    sizeof(PCNODE_DESCRIPTOR),                  // NodeSize
    2,                                          // NodeCount - extra volume node
    MiniportNodes,                              // Nodes
    SIZEOF_ARRAY(MiniportWithVolConnections),   // ConnectionCount
    MiniportWithVolConnections,                 // Connections
    0,                                          // CategoryCount
    NULL                                        // Categories
};

#pragma code_seg("PAGE")
// ==============================================================================
// CMiniportMidiFM::GetDescription()
// Gets the topology.
// Pass back appropriate descriptor, depending on whether volume node is needed.
// ==============================================================================
STDMETHODIMP_(NTSTATUS)
CMiniportMidiFM::
GetDescription
(
    _Out_     PPCFILTER_DESCRIPTOR *  OutFilterDescriptor
)
{
    PAGED_CODE();

    ASSERT(OutFilterDescriptor);

    _DbgPrintF(DEBUGLVL_VERBOSE,("CMiniportMidiFM::GetDescription"));

    if (m_volNodeNeeded)
    {
        *OutFilterDescriptor = &MiniportFilterWithVolDescriptor;
        _DbgPrintF(DEBUGLVL_VERBOSE, ("Getting descriptor of new FM miniport with volume node"));
    }
    else
    {
        *OutFilterDescriptor = &MiniportFilterDescriptor;
    }

    return STATUS_SUCCESS;
}

#pragma code_seg("PAGE")
// ==============================================================================
// CMiniportMidiStreamFM::NonDelegatingQueryInterface()
// Obtains an interface.  This function works just like a COM QueryInterface
// call and is used if the object is not being aggregated.
// ==============================================================================
STDMETHODIMP_(NTSTATUS) CMiniportMidiStreamFM::NonDelegatingQueryInterface
(
    _In_ REFIID  Interface,
    _COM_Outptr_   PVOID * Object
)
{
    PAGED_CODE();

    ASSERT(Object);

    _DbgPrintF(DEBUGLVL_VERBOSE,("CMiniportMidiStreamFM::NonDelegatingQueryInterface"));

    if (IsEqualGUIDAligned(Interface,IID_IUnknown))
    {
        *Object = PVOID(PUNKNOWN(PMINIPORT(this)));
    }
    else
    if (IsEqualGUIDAligned(Interface,IID_IMiniportMidiStream))
    {
        *Object = PVOID(PMINIPORTMIDISTREAM(this));
    }
    else
    {
        *Object = NULL;
    }

    if (*Object)
    {
        //
        // We reference the interface for the caller.
        //
        PUNKNOWN(PMINIPORT(*Object))->AddRef();
        return STATUS_SUCCESS;
    }

    return STATUS_INVALID_PARAMETER;
}

#pragma code_seg("PAGE")
// ==============================================================================
// CMiniportMidiStreamFM::~CMiniportMidiStreamFM()
// Destructor.
// ==============================================================================
CMiniportMidiStreamFM::~CMiniportMidiStreamFM
(
void
)
{
    PAGED_CODE();

    _DbgPrintF(DEBUGLVL_VERBOSE,("CMiniportMidiStreamFM::~CMiniportMidiStreamFM"));

    Opl3_AllNotesOff();

    if (m_Miniport)
    {
        m_Miniport->m_fStreamExists = FALSE;
        m_Miniport->Release();
    }
}

#pragma code_seg("PAGE")
// ==============================================================================
// CMiniportMidiStreamFM::Init()
// Initializes a the miniport.
// ==============================================================================
NTSTATUS
CMiniportMidiStreamFM::
Init
(
    IN      CMiniportMidiFM *   Miniport,
    IN      PUCHAR              PortBase
)
{
    PAGED_CODE();

    ASSERT(Miniport);
    ASSERT(PortBase);

    int i;

    _DbgPrintF(DEBUGLVL_VERBOSE,("CMiniportMidiStreamFM::Init"));

    //
    // AddRef() is required because we are keeping this pointer.
    //
    m_Miniport = Miniport;
    m_Miniport->AddRef();

    m_PortBase = PortBase;

    // init some members
    m_dwCurTime = 1;    /* for note on/off */
    /* volume */
    m_wSynthAttenL = 0;        /* in 1.5dB steps */
    m_wSynthAttenR = 0;        /* in 1.5dB steps */

    m_MinVolValue  = 0xFFD0C000;    //  minimum -47.25(dB) * 0x10000
    m_MaxVolValue  = 0x00000000;    //  maximum  0    (dB) * 0x10000
    m_VolStepDelta = 0x0000C000;    //  steps of 0.75 (dB) * 0x10000
    m_SavedVolValue[CHAN_LEFT] = m_SavedVolValue[CHAN_RIGHT] = 0;

    /* start attenuations at -3 dB, which is 90 MIDI level */
    for (i = 0; i < NUMCHANNELS; i++)
    {
        m_bChanAtten[i] = 4;
        m_bStereoMask[i] = 0xff;
    };

    return STATUS_SUCCESS;
}

#pragma code_seg("PAGE")
// ==============================================================================
// CMiniportMidiStreamFM::SetState()
// Sets the transport state.
// ==============================================================================
STDMETHODIMP_(NTSTATUS)
CMiniportMidiStreamFM::
SetState
(
    IN      KSSTATE     NewState
)
{
    PAGED_CODE();

    _DbgPrintF(DEBUGLVL_VERBOSE,("CMiniportMidiStreamFM::SetState"));

    NTSTATUS ntStatus = STATUS_SUCCESS;

    switch (NewState)
    {
    case KSSTATE_STOP:
    case KSSTATE_ACQUIRE:
    case KSSTATE_PAUSE:
        Opl3_AllNotesOff();
        break;

    case KSSTATE_RUN:
        break;
    }

    return ntStatus;
}

#pragma code_seg("PAGE")
// ==============================================================================
// CMiniportMidiStreamFM::SetFormat()
// Sets the format.
// ==============================================================================
STDMETHODIMP_(NTSTATUS)
CMiniportMidiStreamFM::
SetFormat
(
    _In_      PKSDATAFORMAT   Format
)
{
    PAGED_CODE();

    ASSERT(Format);

    _DbgPrintF(DEBUGLVL_VERBOSE,("CMiniportMidiStreamFM::SetFormat"));

    return STATUS_SUCCESS;
}

#pragma code_seg("PAGE")
/*****************************************************************************
 * BasicSupportHandler()
 *****************************************************************************
 * Assists in BASICSUPPORT accesses on level properties -
 * this is declared as a friend method in the header file.
 */
static
NTSTATUS BasicSupportHandler
(
    IN  PPCPROPERTY_REQUEST PropertyRequest
)
{
    PAGED_CODE();
    ASSERT(PropertyRequest);

    _DbgPrintF(DEBUGLVL_VERBOSE, ("BasicSupportHandler"));

    NTSTATUS ntStatus = STATUS_BUFFER_TOO_SMALL;

    if (PropertyRequest->ValueSize >= (sizeof(KSPROPERTY_DESCRIPTION)))
    {
        // if return buffer can hold a KSPROPERTY_DESCRIPTION, return it
        PKSPROPERTY_DESCRIPTION PropDesc = PKSPROPERTY_DESCRIPTION(PropertyRequest->Value);

        PropDesc->AccessFlags = KSPROPERTY_TYPE_BASICSUPPORT |
                                KSPROPERTY_TYPE_GET |
                                KSPROPERTY_TYPE_SET;
        PropDesc->DescriptionSize   = sizeof(KSPROPERTY_DESCRIPTION) +
                                      sizeof(KSPROPERTY_MEMBERSHEADER) +
                                      sizeof(KSPROPERTY_STEPPING_LONG);
        PropDesc->PropTypeSet.Set   = KSPROPTYPESETID_General;
        PropDesc->PropTypeSet.Id    = VT_I4;
        PropDesc->PropTypeSet.Flags = 0;
        PropDesc->MembersListCount  = 1;
        PropDesc->Reserved          = 0;

        // if return buffer can also hold a range description, return it too
        if (PropertyRequest->ValueSize >= (sizeof(KSPROPERTY_DESCRIPTION) +
                                           sizeof(KSPROPERTY_MEMBERSHEADER) +
                                           sizeof(KSPROPERTY_STEPPING_LONG)))
        {
            // fill in the members header
            PKSPROPERTY_MEMBERSHEADER Members = PKSPROPERTY_MEMBERSHEADER(PropDesc + 1);

            Members->MembersFlags   = KSPROPERTY_MEMBER_STEPPEDRANGES;
            Members->MembersSize    = sizeof(KSPROPERTY_STEPPING_LONG);
            Members->MembersCount   = 1;
            Members->Flags          = 0;

            // fill in the stepped range
            PKSPROPERTY_STEPPING_LONG Range = PKSPROPERTY_STEPPING_LONG(Members + 1);

            switch (PropertyRequest->Node)
            {
                case eFMVolumeNode:
                    CMiniportMidiStreamFM *that = (CMiniportMidiStreamFM *)PropertyRequest->MinorTarget;

                    if (that)
                    {
                        Range->Bounds.SignedMinimum = that->m_MinVolValue;
                        Range->Bounds.SignedMaximum = that->m_MaxVolValue;
                        Range->SteppingDelta        = that->m_VolStepDelta;
                        break;
                    }
                    else
                    {
                        return STATUS_INVALID_PARAMETER;
                    }
            }

            Range->Reserved = 0;

            _DbgPrintF(DEBUGLVL_VERBOSE, ("---Node: %d  Max: 0x%X  Min: 0x%X  Step: 0x%X",PropertyRequest->Node,
                                       Range->Bounds.SignedMaximum,
                                       Range->Bounds.SignedMinimum,
                                       Range->SteppingDelta));

            // set the return value size
            PropertyRequest->ValueSize = sizeof(KSPROPERTY_DESCRIPTION) +
                                         sizeof(KSPROPERTY_MEMBERSHEADER) +
                                         sizeof(KSPROPERTY_STEPPING_LONG);
        }
        else
        {
            // set the return value size
            PropertyRequest->ValueSize = sizeof(KSPROPERTY_DESCRIPTION);
        }
        ntStatus = STATUS_SUCCESS;

    }
    else if (PropertyRequest->ValueSize >= sizeof(ULONG))
    {
        // if return buffer can hold a ULONG, return the access flags
        PULONG AccessFlags = PULONG(PropertyRequest->Value);

        *AccessFlags = KSPROPERTY_TYPE_BASICSUPPORT |
                       KSPROPERTY_TYPE_GET |
                       KSPROPERTY_TYPE_SET;

        // set the return value size
        PropertyRequest->ValueSize = sizeof(ULONG);
        ntStatus = STATUS_SUCCESS;

    }
    return ntStatus;
}

#pragma code_seg("PAGE")
/*****************************************************************************
 * PropertyHandler_Level()
 *****************************************************************************
 * Accesses a KSAUDIO_LEVEL property.
 */
static
NTSTATUS PropertyHandler_Level
(
    IN  PPCPROPERTY_REQUEST PropertyRequest
)
{
    PAGED_CODE();

    ASSERT(PropertyRequest);

    _DbgPrintF(DEBUGLVL_VERBOSE,("PropertyHandler_Level"));


    NTSTATUS        ntStatus = STATUS_INVALID_PARAMETER;
    LONG            channel;

    // validate node
    if (PropertyRequest->Node == eFMVolumeNode)
    {
        if (PropertyRequest->Verb & KSPROPERTY_TYPE_GET)
        {
            // get the instance channel parameter
            if (PropertyRequest->InstanceSize >= sizeof(LONG))
            {
                channel = *(PLONG(PropertyRequest->Instance));

                // only support get requests on either mono/left(0) or right(1) channels
                if ((channel == CHAN_LEFT) || (channel == CHAN_RIGHT))
                {
                    // validate and get the output parameter
                    if (PropertyRequest->ValueSize >= sizeof(LONG))
                    {
                        PLONG Level = (PLONG)PropertyRequest->Value;

                        // check if volume property request
                        if (PropertyRequest->PropertyItem->Id == KSPROPERTY_AUDIO_VOLUMELEVEL)
                        {
                            CMiniportMidiStreamFM *that = (CMiniportMidiStreamFM *)PropertyRequest->MinorTarget;
                            if (that)
                            {
                                *Level = that->GetFMAtten(channel);
                                PropertyRequest->ValueSize = sizeof(LONG);
                                ntStatus = STATUS_SUCCESS;
                            }
                            //  if (!that) return STATUS_INVALID_PARAMETER

                        }   // (PropertyItem->Id == KSPROPERTY_AUDIO_VOLUMELEVEL)
                    }     // (ValueSize >= sizeof(LONG))
                }       // ((channel == CHAN_LEFT) || (channel == CHAN_RIGHT))
            }         // (InstanceSize >= sizeof(LONG))
        }           // (Verb & KSPROPERTY_TYPE_GET)

        else if (PropertyRequest->Verb & KSPROPERTY_TYPE_SET)
        {
            // get the instance channel parameter
            if (PropertyRequest->InstanceSize >= sizeof(LONG))
            {
                channel = *(PLONG(PropertyRequest->Instance));

                // only support get requests on either mono/left (0), right (1), or master (-1) channels
                if ((channel == CHAN_LEFT) || (channel == CHAN_RIGHT) || (channel == CHAN_MASTER))
                {
                    // validate and get the input parameter
                    if (PropertyRequest->ValueSize == sizeof(LONG))
                    {
                        PLONG level = (PLONG)PropertyRequest->Value;

                        if (PropertyRequest->PropertyItem->Id == KSPROPERTY_AUDIO_VOLUMELEVEL)
                        {
                            CMiniportMidiStreamFM *that = (CMiniportMidiStreamFM *)PropertyRequest->MinorTarget;
                            if (that)
                            {
                                that->SetFMAtten(channel,*level);
                                ntStatus = STATUS_SUCCESS;
                            }
                            //  if (!that) return STATUS_INVALID_PARAMETER

                        }   // (PropertyItem->Id == KSPROPERTY_AUDIO_VOLUMELEVEL)
                    }     // (ValueSize == sizeof(LONG))
                }       // ((channel == CHAN_LEFT) || (channel == CHAN_RIGHT) || (channel == CHAN_MASTER))
            }         // (InstanceSize >= sizeof(LONG))
        }           // (Verb & KSPROPERTY_TYPE_SET)

        else if (PropertyRequest->Verb & KSPROPERTY_TYPE_BASICSUPPORT)
        {
            if (PropertyRequest->PropertyItem->Id == KSPROPERTY_AUDIO_VOLUMELEVEL)
            {
                ntStatus = BasicSupportHandler(PropertyRequest);
            }
        }   // (Verb & KSPROPERTY_TYPE_BASICSUPPORT)
    }     // (Node == eFMVolumeNode)

    return ntStatus;
}

#pragma code_seg()
// convert from 16.16 dB to [0,63], set m_wSynthAttenR
void
CMiniportMidiStreamFM::
SetFMAtten
(
    IN LONG channel,
    IN LONG level
)
{
    KIRQL   oldIrql;
    if ((channel == CHAN_LEFT) || (channel == CHAN_MASTER))
    {
        m_SavedVolValue[CHAN_LEFT] = level;

        if (level > m_MaxVolValue)
            m_wSynthAttenL = 0;
        else if (level < m_MinVolValue)
            m_wSynthAttenL = 63;
        else
            m_wSynthAttenL = WORD(-level / (LONG)m_VolStepDelta);
    }
    if ((channel == CHAN_RIGHT) || (channel == CHAN_MASTER))
    {
        m_SavedVolValue[CHAN_RIGHT] = level;

        if (level > m_MaxVolValue)
            m_wSynthAttenR = 0;
        else if (level < m_MinVolValue)
            m_wSynthAttenR = 63;
        else
            m_wSynthAttenR = WORD(-level / (LONG)m_VolStepDelta);
    }
#ifdef USE_KDPRINT
    KdPrint(("'StreamFM::SetFMAtten: channel: 0x%X, level: 0x%X, m_wSynthAttenL: 0x%X, m_wSynthAttenR: 0x%X \n",
                                     channel,       level,       m_wSynthAttenL,       m_wSynthAttenR));
#else   //  USE_KDPRINT
    _DbgPrintF(DEBUGLVL_VERBOSE,("StreamFM::SetFMAtten: channel: 0x%X, level: 0x%X, m_wSynthAttenL: 0x%X, m_wSynthAttenR: 0x%X \n",
                                                        channel,       level,       m_wSynthAttenL,       m_wSynthAttenR));
#endif  //  USE_KDPRINT

    KeAcquireSpinLock(&m_Miniport->m_SpinLock,&oldIrql);
    Opl3_SetVolume(0xFF); //  0xFF means all channels
    KeReleaseSpinLock(&m_Miniport->m_SpinLock,oldIrql);
}

#pragma code_seg("PAGE")
/*****************************************************************************
 * PropertyHandler_CpuResources()
 *****************************************************************************
 * Processes a KSPROPERTY_AUDIO_CPU_RESOURCES request
 */
static
NTSTATUS PropertyHandler_CpuResources
(
    IN  PPCPROPERTY_REQUEST   PropertyRequest
)
{
    PAGED_CODE();

    ASSERT(PropertyRequest);

    _DbgPrintF(DEBUGLVL_VERBOSE,("PropertyHandler_CpuResources"));

    NTSTATUS ntStatus = STATUS_INVALID_DEVICE_REQUEST;

    // validate node
    if(PropertyRequest->Node == eFMVolumeNode)
    {
        if(PropertyRequest->Verb & KSPROPERTY_TYPE_GET)
        {
            if(PropertyRequest->ValueSize >= sizeof(LONG))
            {
                *(PLONG(PropertyRequest->Value)) = KSAUDIO_CPU_RESOURCES_NOT_HOST_CPU;
                PropertyRequest->ValueSize = sizeof(LONG);
                ntStatus = STATUS_SUCCESS;
            }
            else
            {
                _DbgPrintF(DEBUGLVL_VERBOSE,("PropertyHandler_CpuResources failed, buffer too small"));
                ntStatus = STATUS_BUFFER_TOO_SMALL;
            }
        }
    }
    return ntStatus;
}

#pragma code_seg()
// ==============================================================================
// SoundMidiSendFM
//  Writes out to the device.
//  Called from DPC code (Write->WriteMidiData->Opl3_NoteOn->Opl3_FMNote->here)
// ==============================================================================
void
CMiniportMidiFM::
SoundMidiSendFM
(
    IN                          PUCHAR PortBase,
    _In_range_(0, 0x200 - 1)    ULONG Address,
    IN                          UCHAR Data
)
{
    ASSERT(Address < 0x200);
    ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);

    // these delays need to be 23us at least for old opl2 chips, even
    // though new chips can handle 1 us delays.

#ifdef USE_KDPRINT
    KdPrint(("'SoundMidiSendFM(%02x %02x) \n",Address,Data));
#else   //  USE_KDPRINT
    _DbgPrintF(DEBUGLVL_VERBOSE, ("%X\t%X", Address,Data));
#endif  //  USE_KDPRINT
    WRITE_PORT_UCHAR(PortBase + (Address < 0x100 ? 0 : 2), (UCHAR)Address);
    KeStallExecutionProcessor(23);

    WRITE_PORT_UCHAR(PortBase + (Address < 0x100 ? 1 : 3), Data);
    KeStallExecutionProcessor(23);

    m_SavedRegValues[Address] = Data;
}


#pragma code_seg()
// ==============================================================================
// Service()
// DPC-mode service call from the port.
// ==============================================================================
STDMETHODIMP_(void)
CMiniportMidiFM::
Service
(   void
)
{
}

#pragma code_seg()
// ==============================================================================
// CMiniportMidiStreamFM::Read()
// Reads incoming MIDI data.
// ==============================================================================
STDMETHODIMP_(NTSTATUS)
CMiniportMidiStreamFM::
Read
(
    _In_      PVOID   BufferAddress,
    _In_      ULONG   Length,
    _Out_     PULONG  BytesRead
)
{
    return STATUS_NOT_IMPLEMENTED;
}

#pragma code_seg()
// ==============================================================================
// CMiniportMidiStreamFM::Write()
// Writes outgoing MIDI data.
//
// N.B.!!!
// THIS DATA SINK ASSUMES THAT DATA COMES IN ONE MESSAGE AT A TIME!!!
// IF LENGTH IS MORE THAN THREE BYTES, SUCH AS SYSEX OR MULTIPLE MIDI
// MESSAGES, ALL THE DATA IS DROPPED UNCEREMONIOUSLY ON THE FLOOR!!!
// ALSO DOES NOT PLAY WELL WITH RUNNING STATUS!!!
//
// CLEARLY, THIS MINIPORT HAS SOME "ISSUES".
//
// ==============================================================================
STDMETHODIMP_(NTSTATUS)
CMiniportMidiStreamFM::
Write
(
    _In_      PVOID   BufferAddress,  // pointer to Midi Data.
    _In_      ULONG   Length,
    _Out_     PULONG  BytesWritten
)
{
    ASSERT(BufferAddress);
    ASSERT(BytesWritten);

    _DbgPrintF(DEBUGLVL_VERBOSE, ("CMiniportMidiStreamFM::Write"));

    BYTE    statusByte = *(PBYTE)BufferAddress & 0xF0;
    *BytesWritten = Length;

    if (statusByte < 0x80)
    {
        _DbgPrintF(DEBUGLVL_TERSE, ("CMiniportMidiStreamFM::Write requires first byte to be status -- ignored"));
    }
    else if (statusByte == 0xF0)
    {
        _DbgPrintF(DEBUGLVL_VERBOSE, ("StreamFM::Write doesn't handle System messages -- ignored"));
    }
    else if (statusByte == 0xA0)
    {
        _DbgPrintF(DEBUGLVL_VERBOSE, ("StreamFM::Write doesn't handle Polyphonic key pressure/Aftertouch messages -- ignored"));
    }
    else if (statusByte == 0xD0)
    {
        _DbgPrintF(DEBUGLVL_VERBOSE, ("StreamFM::Write doesn't handle Channel pressure/Aftertouch messages -- ignored"));
    }
    else if (Length < 4)
    {
        WriteMidiData(*(DWORD *)BufferAddress);
    }
    else
    {
        _DbgPrintF(DEBUGLVL_TERSE, ("StreamFM::Write doesn't handle Length > 3."));
    }
    return STATUS_SUCCESS;
}

// ==============================================================================
// ==============================================================================
// Private Methods of CMiniportMidiFM
// ==============================================================================
// ==============================================================================


#pragma code_seg()
// =================================================================
// SoundMidiIsOpl3
// Checks if the midi synthesizer is Opl3 compatible or just adlib-compatible.
// returns:  TRUE if OPL3-compatible chip. FALSE otherwise.
//
// NOTE: This has been taken as is from the nt driver code.
// =================================================================
BOOL CMiniportMidiFM::
SoundMidiIsOpl3(void)
{
    ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);

    BOOL bIsOpl3 = FALSE;

    /*
     * theory: an opl3-compatible synthesizer chip looks
     * exactly like two separate 3812 synthesizers (for left and right
     * channels) until switched into opl3 mode. Then, the timer-control
     * register for the right half is replaced by a channel connection register
     * (among other changes).
     *
     * We can detect 3812 synthesizers by starting a timer and looking for
     * timer overflow. So if we find 3812s at both left and right addresses,
     * we then switch to opl3 mode and look again for the right-half. If we
     * still find it, then the switch failed and we have an old synthesizer
     * if the right half disappeared, we have a new opl3 synthesizer.
     *
     * NB we use either monaural base-level synthesis, or stereo opl3
     * synthesis. If we discover two 3812s (as on early SB Pro and
     * PAS), we ignore one of them.
     */

    /*
     * nice theory - but wrong. The timer on the right half of the
     * opl3 chip reports its status in the left-half status register.
     * There is no right-half status register on the opl3 chip.
     */


    /* ensure base mode */
    SoundMidiSendFM(m_PortBase, AD_NEW, 0x00);
    KeStallExecutionProcessor(20);

    /* look for right half of chip */
    if (SoundSynthPresent(m_PortBase + 2, m_PortBase))
    {
        /* yes - is this two separate chips or a new opl3 chip ? */
        /* switch to opl3 mode */
        SoundMidiSendFM(m_PortBase, AD_NEW, 0x01);
        KeStallExecutionProcessor(20);

        if (!SoundSynthPresent(m_PortBase + 2, m_PortBase))
        {
            _DbgPrintF(DEBUGLVL_VERBOSE, ("CMiniportMidiFM: In SoundMidiIsOpl3 right half disappeared"));
            /* right-half disappeared - so opl3 */
            bIsOpl3 = TRUE;
        }
    }

    if (!bIsOpl3)
    {
        /* reset to 3812 mode */
        SoundMidiSendFM(m_PortBase, AD_NEW, 0x00);
        KeStallExecutionProcessor(20);
    }

    _DbgPrintF(DEBUGLVL_VERBOSE, ("CMiniportMidiFM: In SoundMidiIsOpl3 returning bIsOpl3 = 0x%X", bIsOpl3));
    return(bIsOpl3);
}

#pragma code_seg()
// ==============================================================================
// SoundSynthPresent
//
// Detect the presence or absence of a 3812 (opl2/adlib-compatible) synthesizer
// at the given i/o address by starting the timer and looking for an
// overflow. Can be used to detect left and right synthesizers separately.
//
// Returns: True if a synthesiser is present at that address and false if not.
//
// NOTE: This and has been taken as is from the nt driver code.
// ==============================================================================
BOOL
CMiniportMidiFM::
SoundSynthPresent
(
IN PUCHAR   base,
IN PUCHAR inbase
)
{
    ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);

    UCHAR t1, t2;
    // check if the chip is present
    SoundMidiSendFM(base, 4, 0x60);             // mask T1 & T2
    SoundMidiSendFM(base, 4, 0x80);             // reset IRQ

    t1 = READ_PORT_UCHAR((PUCHAR)inbase);       // read status register

    SoundMidiSendFM(base, 2, 0xff);             // set timer - 1 latch
    SoundMidiSendFM(base, 4, 0x21);             // unmask & start T1

    // this timer should go off in 80 us. It sometimes
    // takes more than 100us, but will always have expired within
    // 200 us if it is ever going to.
    KeStallExecutionProcessor(200);

    t2 = READ_PORT_UCHAR((PUCHAR)inbase);       // read status register

    SoundMidiSendFM(base, 4, 0x60);
    SoundMidiSendFM(base, 4, 0x80);

    if (!((t1 & 0xE0) == 0) || !((t2 & 0xE0) == 0xC0))
    {
        _DbgPrintF(DEBUGLVL_VERBOSE, ("SoundSynthPresent: returning false"));
        return(FALSE);
    }
    _DbgPrintF(DEBUGLVL_VERBOSE, ("SoundSynthPresent: returning true"));
    return TRUE;
}


// ==============================================================================
// this array gives the offsets of the slots within an opl2
// chip. This is needed to set the attenuation for all slots to max,
// to ensure that the chip is silenced completely - switching off the
// voices alone will not do this.
// ==============================================================================
BYTE offsetSlot[] =
{
    0, 1, 2, 3, 4, 5,
    8, 9, 10, 11, 12, 13,
    16, 17, 18, 19, 20, 21
};

#pragma code_seg()
// =========================================================================
// WriteMidiData
//      Converts a MIDI atom into the corresponding FM transaction.
// =========================================================================
void
CMiniportMidiStreamFM::
WriteMidiData(DWORD dwData)
{
    BYTE    bMsgType,bChannel, bVelocity, bNote;
    WORD    wTemp;
    KIRQL   oldIrql;

    bMsgType = (BYTE) dwData & (BYTE)0xf0;
    bChannel = (BYTE) dwData & (BYTE)0x0f;
    bNote = (BYTE) ((WORD) dwData >> 8) & (BYTE)0x7f;
    bVelocity = (BYTE) (dwData >> 16) & (BYTE)0x7f;

#ifdef USE_KDPRINT
    KdPrint(("'StreamFM::WriteMidiData: (%x %x %x) \n",bMsgType+bChannel,bNote,bVelocity));
#else   //  USE_KDPRINT
    _DbgPrintF(DEBUGLVL_VERBOSE,("StreamFM::WriteMidiData: (%x %x %x) \n",bMsgType+bChannel,bNote,bVelocity));
#endif  //  USE_KDPRINT
    KeAcquireSpinLock(&m_Miniport->m_SpinLock,&oldIrql);
    switch (bMsgType)
    {
        case 0x90:      /* turn key on, or key off if volume == 0 */
            if (bVelocity)
            {
                if (bChannel == DRUMCHANNEL)
                {
                    Opl3_NoteOn((BYTE)(bNote + 128),bNote,bChannel,bVelocity,(short)m_iBend[bChannel]);
                }
                else
                {
                    Opl3_NoteOn((BYTE)m_bPatch[bChannel],bNote,bChannel,bVelocity,(short) m_iBend[bChannel]);
                }
                break;
            } // if bVelocity.
            //NOTE: no break specified here. On an else case we want to continue through and turn key off

        case 0x80:
            /* turn key off */
            //  we don't care what the velocity is on note off
            if (bChannel == DRUMCHANNEL)
            {
                Opl3_NoteOff((BYTE) (bNote + 128),bNote, bChannel, 0);
            }
            else
            {
                Opl3_NoteOff ((BYTE) m_bPatch[bChannel],bNote, bChannel, m_bSustain[ bChannel ]);
            }
            break;

        case 0xb0:
            /* change control */
            switch (bNote)
            {
                case 7:
                    /* change channel volume */
                    Opl3_ChannelVolume(bChannel,gbVelocityAtten[bVelocity >> 1]);
                    break;

                case 8:
                case 10:
                    /* change the pan level */
                    Opl3_SetPan(bChannel, bVelocity);
                    break;

                case 64:
                    /* Change the sustain level */
                    Opl3_SetSustain(bChannel, bVelocity);
                    break;

                default:
                    if (bNote >= 120)        /* Channel mode messages */
                    {
                        Opl3_ChannelNotesOff(bChannel);
                    }
                    //  else unknown controller
            };
            break;

        case 0xc0:
            if (bChannel != DRUMCHANNEL)
            {
               m_bPatch[ bChannel ] = bNote ;

            }
            break;

        case 0xe0:  // pitch bend
            wTemp = ((WORD) bVelocity << 9) | ((WORD) bNote << 2);
            m_iBend[bChannel] = (short) (WORD) (wTemp + 0x8000);
            Opl3_PitchBend(bChannel, m_iBend[bChannel]);

            break;
    };
    KeReleaseSpinLock(&m_Miniport->m_SpinLock,oldIrql);

    return;
}

// ========================= opl3 specific methods ============================
#pragma code_seg()
// ==========================================================================
// Opl3_AllNotesOff - turn off all notes
// ==========================================================================
void
CMiniportMidiStreamFM::
Opl3_AllNotesOff()
{
    BYTE i;
    KIRQL   oldIrql;

    KeAcquireSpinLock(&m_Miniport->m_SpinLock,&oldIrql);
    for (i = 0; i < NUM2VOICES; i++)
    {
        Opl3_NoteOff(m_Voice[i].bPatch, m_Voice[i].bNote, m_Voice[i].bChannel, 0);
    }
    KeReleaseSpinLock(&m_Miniport->m_SpinLock,oldIrql);
}

#pragma code_seg()
// ==========================================================================
//  void Opl3_NoteOff
//
//  Description:
//     This turns off a note, including drums with a patch
//     # of the drum note + 128, but the first drum instrument is at MIDI note _35_.
//
//  Parameters:
//     BYTE bPatch
//        MIDI patch
//
//     BYTE bNote
//        MIDI note
//
//     BYTE bChannel
//        MIDI channel
//
//  Return Value:
//     Nothing.
//
//
// ==========================================================================
void
CMiniportMidiStreamFM::
Opl3_NoteOff
(
    BYTE            bPatch,
    BYTE            bNote,
    BYTE            bChannel,
    BYTE            bSustain
)
{
   ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);

   patchStruct FAR  *lpPS ;
   WORD             wOffset, wTemp ;

   // Find the note slot
   wTemp = Opl3_FindFullSlot( bNote, bChannel ) ;

   if (wTemp != 0xffff)
   {
      if (bSustain)
      {
          // This channel is sustained, don't really turn the note off,
          // just flag it.
          //
          m_Voice[ wTemp ].bSusHeld = 1;

          return;
      }

      // get a pointer to the patch
      lpPS = glpPatch + (BYTE) m_Voice[ wTemp ].bPatch ;

      // shut off the note portion
      // we have the note slot, turn it off.
      wOffset = wTemp;
      if (wTemp >= (NUM2VOICES / 2))
         wOffset += (0x100 - (NUM2VOICES / 2));

      m_Miniport->SoundMidiSendFM(m_PortBase, AD_BLOCK + wOffset,
                  (BYTE)(m_Voice[ wTemp ].bBlock[ 0 ] & 0x1f) ) ;

      // Note this...
      m_Voice[ wTemp ].bOn = FALSE ;
      m_Voice[ wTemp ].bBlock[ 0 ] &= 0x1f ;
      m_Voice[ wTemp ].bBlock[ 1 ] &= 0x1f ;
      m_Voice[ wTemp ].dwTime = m_dwCurTime ;
   }
}

#pragma code_seg()
// ==========================================================================
//  WORD Opl3_FindFullSlot
//
//  Description:
//     This finds a slot with a specific note, and channel.
//     If it is not found then 0xFFFF is returned.
//
//  Parameters:
//     BYTE bNote
//        MIDI note number
//
//     BYTE bChannel
//        MIDI channel #
//
//  Return Value:
//     WORD
//        note slot #, or 0xFFFF if can't find it
//
//
// ==========================================================================
WORD
CMiniportMidiStreamFM::
Opl3_FindFullSlot
(
    BYTE            bNote,
    BYTE            bChannel
)
{
   ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);

   WORD  i ;

   for (i = 0; i < NUM2VOICES; i++)
   {
      if ((bChannel == m_Voice[ i ].bChannel)
            && (bNote == m_Voice[ i ].bNote)
            && (m_Voice[ i ].bOn))
      {
            return ( i ) ;
      }
   // couldn't find it
   }
   return ( 0xFFFF ) ;
}


#pragma code_seg()
//------------------------------------------------------------------------
//  void Opl3_FMNote
//
//  Description:
//     Turns on an FM-synthesizer note.
//
//  Parameters:
//     WORD wNote
//        the note number from 0 to NUMVOICES
//
//     noteStruct FAR *lpSN
//        structure containing information about what
//        is to be played.
//
//  Return Value:
//     Nothing.
//------------------------------------------------------------------------
void
CMiniportMidiStreamFM::
Opl3_FMNote
(
    WORD                wNote,
    noteStruct FAR *    lpSN
)
{
   ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);

   WORD            i ;
   WORD            wOffset ;
   operStruct FAR  *lpOS ;

   // write out a note off, just to make sure...

   wOffset = wNote;
   if (wNote >= (NUM2VOICES / 2))
      wOffset += (0x100 - (NUM2VOICES / 2));

   m_Miniport->SoundMidiSendFM(m_PortBase, AD_BLOCK + wOffset, 0 ) ;

   // writing the operator information

//   for (i = 0; i < (WORD)((wNote < NUM4VOICES) ? NUMOPS : 2); i++)
   for (i = 0; i < 2; i++)
   {
      lpOS = &lpSN -> op[ i ] ;
      wOffset = gw2OpOffset[ wNote ][ i ] ;
      m_Miniport->SoundMidiSendFM( m_PortBase, 0x20 + wOffset, lpOS -> bAt20) ;
      m_Miniport->SoundMidiSendFM( m_PortBase, 0x40 + wOffset, lpOS -> bAt40) ;
      m_Miniport->SoundMidiSendFM( m_PortBase, 0x60 + wOffset, lpOS -> bAt60) ;
      m_Miniport->SoundMidiSendFM( m_PortBase, 0x80 + wOffset, lpOS -> bAt80) ;
      m_Miniport->SoundMidiSendFM( m_PortBase, 0xE0 + wOffset, lpOS -> bAtE0) ;

   }

   // write out the voice information
   wOffset = (wNote < 9) ? wNote : (wNote + 0x100 - 9) ;
   m_Miniport->SoundMidiSendFM(m_PortBase, 0xa0 + wOffset, lpSN -> bAtA0[ 0 ] ) ;
   m_Miniport->SoundMidiSendFM(m_PortBase, 0xc0 + wOffset, lpSN -> bAtC0[ 0 ] ) ;

   // Note on...
   m_Miniport->SoundMidiSendFM(m_PortBase, 0xb0 + wOffset,
               (BYTE)(lpSN -> bAtB0[ 0 ] | 0x20) ) ;

} // end of Opl3_FMNote()

#pragma code_seg()
//=======================================================================
//  WORD Opl3_NoteOn
//
//  Description:
//     This turns on a note, including drums with a patch # of the
//     drum note + 0x80.  The first GM drum instrument is mapped to note 35 instead of zero, though, so
//     we expect 0 as the first drum patch (acoustic kick) if note 35 comes in.
//
//  Parameters:
//     BYTE bPatch
//        MIDI patch
//
//     BYTE bNote
//        MIDI note
//
//     BYTE bChannel
//        MIDI channel
//
//     BYTE bVelocity
//        velocity value
//
//     short iBend
//        current pitch bend from -32768 to 32767
//
//  Return Value:
//     WORD
//        note slot #, or 0xFFFF if it is inaudible
//=======================================================================
void
CMiniportMidiStreamFM::
Opl3_NoteOn
(
    BYTE            bPatch,
    BYTE            bNote,
    BYTE            bChannel,
    BYTE            bVelocity,
    short           iBend
)
{
   ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);

   WORD             wTemp, i, j ;
   BYTE             b4Op, bTemp, bMode, bStereo ;
   patchStruct FAR  *lpPS ;
   DWORD            dwBasicPitch, dwPitch[ 2 ] ;
   noteStruct       NS ;

   // Get a pointer to the patch
   lpPS = glpPatch + bPatch ;

   // Find out the basic pitch according to our
   // note value.  This may be adjusted because of
   // pitch bends or special qualities for the note.

   dwBasicPitch = gdwPitch[ bNote % 12 ] ;
   bTemp = bNote / (BYTE) 12 ;
   if (bTemp > (BYTE) (60 / 12))
      dwBasicPitch = AsLSHL( dwBasicPitch, (BYTE)(bTemp - (BYTE)(60/12)) ) ;
   else if (bTemp < (BYTE) (60/12))
      dwBasicPitch = AsULSHR( dwBasicPitch, (BYTE)((BYTE) (60/12) - bTemp) ) ;

   // Copy the note information over and modify
   // the total level and pitch according to
   // the velocity, midi volume, and tuning.

   RtlCopyMemory( (LPSTR) &NS, (LPSTR) &lpPS -> note, sizeof( noteStruct ) ) ;
   b4Op = (BYTE)(NS.bOp != PATCH_1_2OP) ;

   for (j = 0; j < 2; j++)
   {
      // modify pitch
      dwPitch[ j ] = dwBasicPitch ;
      bTemp = (BYTE)((NS.bAtB0[ j ] >> 2) & 0x07) ;
      if (bTemp > 4)
         dwPitch[ j ] = AsLSHL( dwPitch[ j ], (BYTE)(bTemp - (BYTE)4) ) ;
      else if (bTemp < 4)
         dwPitch[ j ] = AsULSHR( dwPitch[ j ], (BYTE)((BYTE)4 - bTemp) ) ;

      wTemp = Opl3_CalcFAndB( Opl3_CalcBend( dwPitch[ j ], iBend ) ) ;
      NS.bAtA0[ j ] = (BYTE) wTemp ;
      NS.bAtB0[ j ] = (BYTE) 0x20 | (BYTE) (wTemp >> 8) ;
   }

   // Modify level for each operator, but only
   // if they are carrier waves

   bMode = (BYTE) ((NS.bAtC0[ 0 ] & 0x01) * 2 + 4) ;

   for (i = 0; i < 2; i++)
   {
      wTemp = (BYTE)
          Opl3_CalcVolume(  (BYTE)(NS.op[ i ].bAt40 & (BYTE) 0x3f),
                            bChannel,
                            bVelocity,
                            (BYTE) i,
                            bMode ) ;
      NS.op[ i ].bAt40 = (NS.op[ i ].bAt40 & (BYTE)0xc0) | (BYTE) wTemp ;
   }

   // Do stereo panning, but cutting off a left or
   // right channel if necessary...

   bStereo = Opl3_CalcStereoMask( bChannel ) ;
   NS.bAtC0[ 0 ] &= bStereo ;

   // Find an empty slot, and use it...
   wTemp = Opl3_FindEmptySlot( bPatch ) ;

   Opl3_FMNote(wTemp, &NS ) ;
   m_Voice[ wTemp ].bNote = bNote ;
   m_Voice[ wTemp ].bChannel = bChannel ;
   m_Voice[ wTemp ].bPatch = bPatch ;
   m_Voice[ wTemp ].bVelocity = bVelocity ;
   m_Voice[ wTemp ].bOn = TRUE ;
   m_Voice[ wTemp ].dwTime = m_dwCurTime++ ;
   m_Voice[ wTemp ].dwOrigPitch[0] = dwPitch[ 0 ] ;  // not including bend
   m_Voice[ wTemp ].dwOrigPitch[1] = dwPitch[ 1 ] ;  // not including bend
   m_Voice[ wTemp ].bBlock[0] = NS.bAtB0[ 0 ] ;
   m_Voice[ wTemp ].bBlock[1] = NS.bAtB0[ 1 ] ;
   m_Voice[ wTemp ].bSusHeld = 0;


} // end of Opl3_NoteOn()

#pragma code_seg()
//=======================================================================
//Opl3_CalcFAndB - Calculates the FNumber and Block given a frequency.
//
//inputs
//       DWORD   dwPitch - pitch
//returns
//        WORD - High byte contains the 0xb0 section of the
//                        block and fNum, and the low byte contains the
//                        0xa0 section of the fNumber
//=======================================================================
WORD
CMiniportMidiStreamFM::
Opl3_CalcFAndB(DWORD dwPitch)
{
    ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);

    BYTE    bBlock;

    /* bBlock is like an exponential to dwPitch (or FNumber) */
    for (bBlock = 1; dwPitch >= 0x400; dwPitch >>= 1, bBlock++)
        ;

    if (bBlock > 0x07)
        bBlock = 0x07;  /* we cant do anything about this */

    /* put in high two bits of F-num into bBlock */
    return ((WORD) bBlock << 10) | (WORD) dwPitch;
}

#pragma code_seg()
//=======================================================================
//Opl3_CalcBend - This calculates the effects of pitch bend
//        on an original value.
//
//inputs
//        DWORD   dwOrig - original frequency
//        short   iBend - from -32768 to 32768, -2 half steps to +2
//returns
//        DWORD - new frequency
//=======================================================================
DWORD
CMiniportMidiStreamFM::
Opl3_CalcBend (DWORD dwOrig, short iBend)
{
    ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
    NTSTATUS status = STATUS_SUCCESS;
    KFLOATING_SAVE  FloatSave;
    DWORD   dw;

    status = KeSaveFloatingPointState(&FloatSave);
    if( status == STATUS_SUCCESS )
    {
    /* do different things depending upon positive or
        negative bend */
    if (iBend > 0)
    {
        dw = (DWORD)((iBend * (LONG)(256.0 * (EQUAL * EQUAL - 1.0))) >> 8);
        dwOrig += (DWORD)(AsULMUL(dw, dwOrig) >> 15);
    }
    else if (iBend < 0)
    {
        dw = (DWORD)(((-iBend) * (LONG)(256.0 * (1.0 - 1.0 / EQUAL / EQUAL))) >> 8);
        dwOrig -= (DWORD)(AsULMUL(dw, dwOrig) >> 15);
    }

        KeRestoreFloatingPointState(&FloatSave);
    }

    return dwOrig;
}

#pragma code_seg()
//=======================================================================
// Opl3_CalcVolume - This calculates the attenuation for an operator.
//
//inputs
//        BYTE    bOrigAtten - original attenuation in 0.75 dB units
//        BYTE    bChannel - MIDI channel
//        BYTE    bVelocity - velocity of the note
//        BYTE    bOper - operator number (from 0 to 3)
//        BYTE    bMode - voice mode (from 0 through 7 for
//                                modulator/carrier selection)
//returns
//        BYTE - new attenuation in 0.75 dB units, maxing out at 0x3f.
//=======================================================================
BYTE
CMiniportMidiStreamFM::
Opl3_CalcVolume(BYTE bOrigAtten,BYTE bChannel,BYTE bVelocity,BYTE bOper,BYTE bMode)
{
    ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);

    BYTE        bVolume;
    WORD        wTemp;
    WORD        wMin;

    switch (bMode) {
        case 0:
                bVolume = (BYTE)(bOper == 3);
                break;
        case 1:
                bVolume = (BYTE)((bOper == 1) || (bOper == 3));
                break;
        case 2:
                bVolume = (BYTE)((bOper == 0) || (bOper == 3));
                break;
        case 3:
                bVolume = (BYTE)(bOper != 1);
                break;
        case 4:
                bVolume = (BYTE)((bOper == 1) || (bOper == 3));
                break;
        case 5:
                bVolume = (BYTE)(bOper >= 1);
                break;
        case 6:
                bVolume = (BYTE)(bOper <= 2);
                break;
        case 7:
                bVolume = TRUE;
                break;
        default:
                bVolume = FALSE;
                break;
        };
    if (!bVolume)
        return bOrigAtten; /* this is a modulator wave */

    wMin =(m_wSynthAttenL < m_wSynthAttenR) ? m_wSynthAttenL : m_wSynthAttenR;
    wTemp = bOrigAtten +
            ((wMin << 1) +
            m_bChanAtten[bChannel] +
            gbVelocityAtten[bVelocity >> 1]);
    return (wTemp > 0x3f) ? (BYTE) 0x3f : (BYTE) wTemp;
}

#pragma code_seg()
// ===========================================================================
// Opl3_ChannelNotesOff - turn off all notes on a channel
// ===========================================================================
void
CMiniportMidiStreamFM::
Opl3_ChannelNotesOff(BYTE bChannel)
{
    ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);

    int i;

    for (i = 0; i < NUM2VOICES; i++)
    {
       if ((m_Voice[ i ].bOn) && (m_Voice[ i ].bChannel == bChannel))
       {
          Opl3_NoteOff(m_Voice[i].bPatch, m_Voice[i].bNote,m_Voice[i].bChannel, 0) ;
       }
    }
}

#pragma code_seg()
// ===========================================================================
/* Opl3_ChannelVolume - set the volume level for an individual channel.
 *
 * inputs
 *      BYTE    bChannel - channel number to change
 *      WORD    wAtten  - attenuation in 1.5 db units
 *
 * returns
 *      none
 */
// ===========================================================================
void
CMiniportMidiStreamFM::
Opl3_ChannelVolume(BYTE bChannel, WORD wAtten)
{
    ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);

    m_bChanAtten[bChannel] = (BYTE)wAtten;

    Opl3_SetVolume(bChannel);
}

#pragma code_seg()
// ===========================================================================
//  void Opl3_SetVolume
//
//  Description:
//     This should be called if a volume level has changed.
//     This will adjust the levels of all the playing voices.
//
//  Parameters:
//     BYTE bChannel
//        channel # of 0xFF for all channels
//
//  Return Value:
//     Nothing.
//
// ===========================================================================
void
CMiniportMidiStreamFM::
Opl3_SetVolume
(
    BYTE   bChannel
)
{
   ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);

   WORD            i, j, wTemp, wOffset ;
   noteStruct FAR  *lpPS ;
   BYTE            bMode, bStereo ;

   // Loop through all the notes looking for the right
   // channel.  Anything with the right channel gets
   // its pitch bent.
   for (i = 0; i < NUM2VOICES; i++)
   {
      if ((m_Voice[ i ].bChannel == bChannel) || (bChannel == 0xff))
      {
         // Get a pointer to the patch
         lpPS = &(glpPatch + m_Voice[ i ].bPatch) -> note ;

         // Modify level for each operator, IF they are carrier waves...
         bMode = (BYTE) ( (lpPS->bAtC0[0] & 0x01) * 2 + 4);

         for (j = 0; j < 2; j++)
         {
            wTemp = (BYTE) Opl3_CalcVolume(
               (BYTE) (lpPS -> op[j].bAt40 & (BYTE) 0x3f),
               m_Voice[i].bChannel, m_Voice[i].bVelocity,
               (BYTE) j,            bMode ) ;

            // Write new value.
            wOffset = gw2OpOffset[ i ][ j ] ;
            m_Miniport->SoundMidiSendFM(
               m_PortBase, 0x40 + wOffset,
               (BYTE) ((lpPS -> op[j].bAt40 & (BYTE)0xc0) | (BYTE) wTemp) ) ;
         }

         // Do stereo pan, but cut left or right channel if needed.
         bStereo = Opl3_CalcStereoMask( m_Voice[ i ].bChannel ) ;
         wOffset = i;
         if (i >= (NUM2VOICES / 2))
             wOffset += (0x100 - (NUM2VOICES / 2));
         m_Miniport->SoundMidiSendFM(m_PortBase, 0xc0 + wOffset, (BYTE)(lpPS -> bAtC0[ 0 ] & bStereo) ) ;
      }
   }
} // end of Opl3_SetVolume

#pragma code_seg()
// ===========================================================================
// Opl3_SetPan - set the left-right pan position.
//
// inputs
//      BYTE    bChannel - channel number to alter
//      BYTE    bPan     - 0-47 for left, 81-127 for right, or somewhere in the middle.
//
// returns - none
//
//  As a side note, I think it's odd that (since 64 = CENTER, 127 = RIGHT and 0 = LEFT)
//  there are 63 intermediate gradations for the left side, but 62 for the right.
// ===========================================================================
void
CMiniportMidiStreamFM::
Opl3_SetPan(BYTE bChannel, BYTE bPan)
{
    ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);

    /* change the pan level */
    if (bPan > (64 + 16))
            m_bStereoMask[bChannel] = 0xef;      /* let only right channel through */
    else if (bPan < (64 - 16))
            m_bStereoMask[bChannel] = 0xdf;      /* let only left channel through */
    else
            m_bStereoMask[bChannel] = 0xff;      /* let both channels */

    /* change any curently playing patches */
    Opl3_SetVolume(bChannel);
}


#pragma code_seg()
// ===========================================================================
//  void Opl3_PitchBend
//
//  Description:
//     This pitch bends a channel.
//
//  Parameters:
//     BYTE bChannel
//        channel
//
//     short iBend
//        values from -32768 to 32767, being -2 to +2 half steps
//
//  Return Value:
//     Nothing.
// ===========================================================================
void
CMiniportMidiStreamFM::
Opl3_PitchBend
(
    BYTE        bChannel,
    short        iBend
)
{
   ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);

   WORD   i, wTemp[ 2 ], wOffset, j ;
   DWORD  dwNew ;

   // Remember the current bend..
   m_iBend[ bChannel ] = iBend ;

   // Loop through all the notes looking for
   // the correct channel.  Anything with the
   // correct channel gets its pitch bent...
   for (i = 0; i < NUM2VOICES; i++)
      if (m_Voice[ i ].bChannel == bChannel)
      {
         j = 0 ;
         dwNew = Opl3_CalcBend( m_Voice[ i ].dwOrigPitch[ j ], iBend ) ;
         wTemp[ j ] = Opl3_CalcFAndB( dwNew ) ;
         m_Voice[ i ].bBlock[ j ] =
            (m_Voice[ i ].bBlock[ j ] & (BYTE) 0xe0) |
               (BYTE) (wTemp[ j ] >> 8) ;

         wOffset = i;
         if (i >= (NUM2VOICES / 2))
             wOffset += (0x100 - (NUM2VOICES / 2));

         m_Miniport->SoundMidiSendFM(m_PortBase, AD_BLOCK + wOffset, m_Voice[ i ].bBlock[ 0 ] ) ;
         m_Miniport->SoundMidiSendFM(m_PortBase, AD_FNUMBER + wOffset, (BYTE) wTemp[ 0 ] ) ;
      }
} // end of Opl3_PitchBend


#pragma code_seg()
// ===========================================================================
//  Opl3_CalcStereoMask - This calculates the stereo mask.
//
//  inputs
//            BYTE  bChannel - MIDI channel
//  returns
//            BYTE  mask (for register 0xc0-c8) for eliminating the
//                  left/right/both channels
// ===========================================================================
BYTE
CMiniportMidiStreamFM::
Opl3_CalcStereoMask(BYTE bChannel)
{
    ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);

    WORD        wLeft, wRight;

    /* figure out the basic levels of the 2 channels */
    wLeft = (m_wSynthAttenL << 1) + m_bChanAtten[bChannel];
    wRight = (m_wSynthAttenR << 1) + m_bChanAtten[bChannel];

    /* if both are too quiet then mask to nothing */
    if ((wLeft > 0x3f) && (wRight > 0x3f))
        return 0xcf;

    /* if one channel is significantly quieter than the other than
        eliminate it */
    if ((wLeft + 8) < wRight)
        return (BYTE)(0xef & m_bStereoMask[bChannel]);   /* right is too quiet so eliminate */
    else if ((wRight + 8) < wLeft)
        return (BYTE)(0xdf & m_bStereoMask[bChannel]);   /* left too quiet so eliminate */
    else
        return (BYTE)(m_bStereoMask[bChannel]);  /* use both channels */
}

#pragma code_seg()
//------------------------------------------------------------------------
//  WORD Opl3_FindEmptySlot
//
//  Description:
//     This finds an empty note-slot for a MIDI voice.
//     If there are no empty slots then this looks for the oldest
//     off note.  If this doesn't work then it looks for the oldest
//     on-note of the same patch.  If all notes are still on then
//     this finds the oldests turned-on-note.
//
//  Parameters:
//     BYTE bPatch
//        MIDI patch that will replace it.
//
//  Return Value:
//     WORD
//        note slot #
//
//
//------------------------------------------------------------------------
WORD
CMiniportMidiStreamFM::
Opl3_FindEmptySlot(BYTE bPatch)
{
   ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);

   WORD   i, found ;
   DWORD  dwOldest ;

   // First, look for a slot with a time == 0
   for (i = 0;  i < NUM2VOICES; i++)
      if (!m_Voice[ i ].dwTime)
         return ( i ) ;

   // Now, look for a slot of the oldest off-note
   dwOldest = 0xffffffff ;
   found = 0xffff ;

   for (i = 0; i < NUM2VOICES; i++)
      if (!m_Voice[ i ].bOn && (m_Voice[ i ].dwTime < dwOldest))
      {
         dwOldest = m_Voice[ i ].dwTime ;
         found = i ;
      }
   if (found != 0xffff)
      return ( found ) ;

   // Now, look for a slot of the oldest note with
   // the same patch
   dwOldest = 0xffffffff ;
   found = 0xffff ;
   for (i = 0; i < NUM2VOICES; i++)
      if ((m_Voice[ i ].bPatch == bPatch) && (m_Voice[ i ].dwTime < dwOldest))
      {
         dwOldest = m_Voice[ i ].dwTime ;
         found = i ;
      }
   if (found != 0xffff)
      return ( found ) ;

   // Now, just look for the oldest voice
   found = 0 ;
   dwOldest = m_Voice[ found ].dwTime ;
   for (i = (found + 1); i < NUM2VOICES; i++)
      if (m_Voice[ i ].dwTime < dwOldest)
      {
         dwOldest = m_Voice[ i ].dwTime ;
         found = i ;
      }

   return ( found ) ;

} // end of Opl3_FindEmptySlot()

#pragma code_seg()
//------------------------------------------------------------------------
//  WORD Opl3_SetSustain
//
//  Description:
//     Set the sustain controller on the current channel.
//
//  Parameters:
//     BYTE bSusLevel
//        The new sustain level
//
//
//------------------------------------------------------------------------
VOID
CMiniportMidiStreamFM::
Opl3_SetSustain(BYTE bChannel,BYTE bSusLevel)
{
    ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);

    WORD            i;

    if (m_bSustain[ bChannel ] && !bSusLevel)
    {
        // Sustain has just been turned off for this channel
        // Go through and turn off all notes that are being held for sustain
        //
        for (i = 0; i < NUM2VOICES; i++)
        {
            if ((bChannel == m_Voice[ i ].bChannel) &&
                m_Voice[ i ].bSusHeld)
            {
                Opl3_NoteOff(m_Voice[i].bPatch, m_Voice[i].bNote, m_Voice[i].bChannel, 0);
            }
        }
    }
    m_bSustain[ bChannel ] = bSusLevel;
}
