windows-nt/Source/XPSP1/NT/drivers/ddk/wdmaudio/fmsynth/miniport.cpp
2020-09-26 16:20:57 +08:00

2480 lines
76 KiB
C++

// ==============================================================================
//
// miniport.cpp - miniport driver implementation for FM synth.
// Copyright (c) 1996-2000 Microsoft Corporation. All rights reserved.
//
// ==============================================================================
#include "private.h" // contains class definitions.
#define STR_MODULENAME "FMSynth: "
#pragma code_seg("PAGE")
// ==============================================================================
// CreateMiniportMidiFM()
// Creates a MIDI FM miniport driver. This uses a
// macro from STDUNK.H to do all the work.
// ==============================================================================
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))
);
#ifdef DEBUG
if (IsEqualGUIDAligned(ClassID,CLSID_MiniportDriverFmSynthWithVol))
{
_DbgPrintF(DEBUGLVL_VERBOSE, ("Creating new FM miniport with volume node"));
}
#endif
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%X", 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
(
REFIID Interface,
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
)
{
PAGED_CODE();
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 PUNKNOWN OuterUnknown OPTIONAL,
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));
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
(
REFIID Interface,
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_INVALID_DEVICE_REQUEST;
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 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);
DWORD dw;
/* 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);
}
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 ;
// Make sure that we are actually open...
if (!glpPatch)
return ;
// 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;
}