windows-nt/Source/XPSP1/NT/multimedia/media/drivers/synthlib/midint.c
2020-09-26 16:20:57 +08:00

394 lines
9.2 KiB
C

/******************************************************************
midint.c - midi routines for NT
Copyright (c) 1991-1994 Microsoft Corporation
*******************************************************************/
#include <windows.h>
#include <mmsystem.h>
#include <mmddk.h>
#include <devioctl.h>
#include <ntddwave.h>
#include <ntddmidi.h>
#include <ntddaux.h>
#include "driver.h"
//
// global variable saying whether the kernel driver thinks
// we have an opl3-type or an adlib-type device
//
UINT gMidiType;
//
// For NT we pipe the port writes to the kernel driver in batches.
// Each batch is a pair of port,data values in DeviceData.
//
// MidiPosition contains the next position to use in the array.
SYNTH_DATA DeviceData[SYNTH_DATA_SIZE];
int MidiPosition;
HANDLE MidiDeviceHandle;
static MIDI_DD_VOLUME MidiVolume;
static MIDI_DD_VOLUME CurrentVolume;
static OVERLAPPED WriteOverlapped; // We need to use this, otherwise
// write file complains.
static OVERLAPPED VolumeOverlapped;// For asynch IO for volume notify
/*
* Translate a win error code (ERROR_...) to a multi-media error code
* (MMSYSERR_...).
*
*/
/*************************************************************************
VolLinearToLog - converts a linear scale to logarithm
0xffff -> 0
0x0001 -> 191
inputs
WORD volume - 0 to 0xffff
returns
BYTE - value in decibels attenuation, each unit is 1.5 dB
*/
BYTE VolLinearToLog (WORD volume)
{
WORD gain, shift;
WORD temp;
WORD lut[16] = {0,0,0,1,1,1,2,2,2,2,3,3,3,3,3,3};
BYTE out;
/* get an estimate to within 6 dB of gain */
for (temp = volume, gain = 0, shift = 0;
temp != 0;
gain += 4, temp >>= 1, shift++);
/* look at highest 3 bits in number into look-up-table to
find how many more dB */
if (shift > 5)
temp = volume >> (shift - 5);
else if (shift < 5)
temp = volume << (5 - shift);
else
temp = volume;
temp &= 0x000f;
gain += lut[temp];
out = (BYTE) ((16 * 4) + 3 - gain);
return (out < 128) ? out : (BYTE)127;
}
/*
* Set the MIDI device volume
*/
VOID MidiSetTheVolume(DWORD Left, DWORD Right)
{
CurrentVolume.Left = Left;
CurrentVolume.Right = Right;
//
// Call the routine to store and set the settings
//
MidiNewVolume(VolLinearToLog(HIWORD(Left)), VolLinearToLog(HIWORD(Right)));
}
/*
* See if the device volume has changed, if it has then copy it
* to our local variables.
*
* This is achieved by passing an IOCTL_SOUND_GET_CHANGED volume
* packet to the kernel driver then testing if it's completed.
*/
VOID MidiCheckVolume(VOID)
{
DWORD BytesReturned;
if (WaitForSingleObject(VolumeOverlapped.hEvent, 0) == 0) {
//
// We got a volume change - Set the volume we've now got
//
MidiSetTheVolume(MidiVolume.Left, MidiVolume.Right);
//
// Wait until the volume does not change (so the IO does
// not complete
//
while (DeviceIoControl(MidiDeviceHandle,
IOCTL_SOUND_GET_CHANGED_VOLUME,
&MidiVolume,
sizeof(MidiVolume),
&MidiVolume,
sizeof(MidiVolume),
&BytesReturned,
&VolumeOverlapped)) {
MidiSetTheVolume(MidiVolume.Left, MidiVolume.Right);
}
if (GetLastError() == ERROR_IO_PENDING) {
//
// This is what we want
//
return;
} else {
//
// We failed so make sure the next caller doesn't hang!
//
SetEvent(VolumeOverlapped.hEvent);
}
}
}
/*
* Send any data in our output strem to the kernel driver
*/
VOID MidiFlush(VOID)
{
DWORD BytesWritten;
if (MidiPosition != 0) {
WriteFile(MidiDeviceHandle,
DeviceData,
MidiPosition * sizeof(SYNTH_DATA),
&BytesWritten,
&WriteOverlapped);
}
//
// We know our kernel driver doesn't operate asynchronously so
// we don't need to wait for the write to complete.
//
MidiPosition = 0;
}
/*
* Close the kernel device (if write type)
*/
VOID MidiCloseDevice(HANDLE DeviceHandle)
{
/*
* Close the device first to stop any more events
*/
CloseHandle(DeviceHandle);
CloseHandle(VolumeOverlapped.hEvent);
CloseHandle(WriteOverlapped.hEvent);
DeviceHandle = NULL;
VolumeOverlapped.hEvent = NULL;
WriteOverlapped.hEvent = NULL;
}
/*
* Open the kernel device corresponding to our midi device
*/
MMRESULT MidiOpenDevice(LPHANDLE lpHandle, BOOL Write)
{
MMRESULT mmr;
HANDLE DeviceHandle;
mmr = sndOpenDev(SYNTH_DEVICE, 0, &DeviceHandle,
Write ? GENERIC_READ | GENERIC_WRITE :
GENERIC_READ);
if (mmr != MMSYSERR_NOERROR) {
return mmr;
}
//
// Load patches etc if we're actually going to write to the device
//
if (Write) {
DWORD dwBytesReturned;
/*
* Try to set it into OPL3 mode
*/
gMidiType = DeviceIoControl(DeviceHandle,
IOCTL_MIDI_SET_OPL3_MODE,
NULL,
0,
NULL,
0,
&dwBytesReturned,
NULL) ?
TYPE_OPL3 : TYPE_ADLIB;
/*
* always call MidiInit, in case we have not loaded the patches
* for this device type. MidiInit can have a static bInit if needed
*/
MidiInit();
//
// Create an event for waiting for volume changes and an
// event for writes.
//
VolumeOverlapped.hEvent = CreateEvent(NULL, FALSE, TRUE, NULL);
if (VolumeOverlapped.hEvent == NULL) {
CloseHandle(DeviceHandle);
return sndTranslateStatus();
}
WriteOverlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (WriteOverlapped.hEvent == NULL) {
CloseHandle(VolumeOverlapped.hEvent);
CloseHandle(DeviceHandle);
return sndTranslateStatus();
}
}
//
// Return our handle to the caller
//
*lpHandle = DeviceHandle;
//
// Set ourselves up to find out about volume changes
//
if (Write) {
MidiCheckVolume();
}
return MMSYSERR_NOERROR;
}
/*
* Read the current volume setting direct from the kernel driver
*/
MMRESULT MidiGetVolume(LPDWORD lpVolume)
{
HANDLE hDevice;
MIDI_DD_VOLUME Vol;
MMRESULT mRc;
DWORD BytesReturned;
//
// Open a new device and get the volume
//
mRc = MidiOpenDevice(&hDevice, FALSE); // Open for read only
if (mRc == MMSYSERR_NOERROR) {
if (!DeviceIoControl(hDevice,
IOCTL_MIDI_GET_VOLUME,
NULL,
0,
&Vol,
sizeof(MIDI_DD_VOLUME),
&BytesReturned,
NULL)) {
mRc = sndTranslateStatus();
} else {
*lpVolume = (DWORD)MAKELONG(HIWORD(Vol.Left), HIWORD(Vol.Right));
}
CloseHandle(hDevice);
}
return mRc;
}
/*
* Set the volume by calling the kernel driver - this will cause our
* IOCTL_SOUND_GET_CHANGED_VOLUME packet to complete
*/
MMRESULT MidiSetVolume(DWORD Left, DWORD Right)
{
HANDLE hDevice;
MIDI_DD_VOLUME Vol;
MMRESULT mRc;
DWORD BytesReturned;
//
// Open a new device and set the volume
//
Vol.Left = Left;
Vol.Right = Right;
mRc = MidiOpenDevice(&hDevice, FALSE); // Open for read only
if (mRc == MMSYSERR_NOERROR) {
if (!DeviceIoControl(hDevice,
IOCTL_MIDI_SET_VOLUME,
&Vol,
sizeof(MIDI_DD_VOLUME),
NULL,
0,
&BytesReturned,
NULL)) {
mRc = sndTranslateStatus();
}
CloseHandle(hDevice);
}
return mRc;
}
/**************************************************************
* MidiSendFM - Sends a byte to the FM chip.
*
* inputs
* WORD wAddress - 0x00 to 0x1ff
* BYTE bValue - value wirtten
* returns
* none
*/
VOID FAR PASCAL MidiSendFM (DWORD wAddress, BYTE bValue)
{
// NT :
//
// Pipe our port writes to the kernel driver
// Note that MidiFlush is called again after each midi message
// is processed by modMessage.
//
if (MidiPosition == SYNTH_DATA_SIZE) {
MidiFlush();
}
DeviceData[MidiPosition].IoPort = wAddress < 0x100 ? 0x388 : 0x38a;
DeviceData[MidiPosition].PortData = (WORD)(BYTE)wAddress;
DeviceData[MidiPosition + 1].IoPort = wAddress < 0x100 ? 0x389 : 0x38b;
DeviceData[MidiPosition + 1].PortData = (WORD)bValue;
MidiPosition += 2;
}