windows-nt/Source/XPSP1/NT/drivers/wdm/audio/legacy/wdmaud.drv/midiout.c
2020-09-26 16:20:57 +08:00

477 lines
14 KiB
C

/****************************************************************************
*
* midiout.c
*
* WDM Audio support for Midi Output devices
*
* Copyright (C) Microsoft Corporation, 1997 - 1999 All Rights Reserved.
*
* History
* 5-12-97 - Noel Cross (NoelC)
*
***************************************************************************/
#include "wdmdrv.h"
#ifndef UNDER_NT
#pragma alloc_text(FIXCODE, modMessage)
#pragma alloc_text(FIXCODE, midiOutWrite)
#endif
/****************************************************************************
This function conforms to the standard Midi output driver message proc
(modMessage), which is documented in mmddk.h
****************************************************************************/
DWORD FAR PASCAL _loadds modMessage
(
UINT id,
UINT msg,
DWORD_PTR dwUser,
DWORD_PTR dwParam1,
DWORD_PTR dwParam2
)
{
LPDEVICEINFO pOutClient;
LPDWORD pVolume;
LPDEVICEINFO DeviceInfo;
MMRESULT mmr;
switch (msg)
{
case MODM_INIT:
DPF(DL_TRACE|FA_MIDI, ("MODM_INIT") );
return wdmaudAddRemoveDevNode(MidiOutDevice, (LPCWSTR)dwParam2, TRUE);
case DRVM_EXIT:
DPF(DL_TRACE|FA_MIDI, ("DRVM_EXIT: MidiOut") );
return wdmaudAddRemoveDevNode(MidiOutDevice, (LPCWSTR)dwParam2, FALSE);
case MODM_GETNUMDEVS:
DPF(DL_TRACE|FA_MIDI, ("MODM_GETNUMDEVS") );
return wdmaudGetNumDevs(MidiOutDevice, (LPCWSTR)dwParam1);
case MODM_GETDEVCAPS:
DPF(DL_TRACE|FA_MIDI, ("MODM_GETDEVCAPS") );
if (DeviceInfo = GlobalAllocDeviceInfo((LPWSTR)dwParam2))
{
DeviceInfo->DeviceType = MidiOutDevice;
DeviceInfo->DeviceNumber = id;
mmr = wdmaudGetDevCaps(DeviceInfo, (MDEVICECAPSEX FAR*)dwParam1);
GlobalFreeDeviceInfo(DeviceInfo);
return mmr;
} else {
MMRRETURN( MMSYSERR_NOMEM );
}
case MODM_PREFERRED:
DPF(DL_TRACE|FA_MIDI, ("MODM_PREFERRED") );
return wdmaudSetPreferredDevice(
MidiOutDevice,
id,
dwParam1,
dwParam2);
case MODM_OPEN:
{
LPMIDIOPENDESC pmod = (LPMIDIOPENDESC)dwParam1;
DPF(DL_TRACE|FA_MIDI, ("MODM_OPEN") );
if (DeviceInfo = GlobalAllocDeviceInfo((LPWSTR)pmod->dnDevNode))
{
DeviceInfo->DeviceType = MidiOutDevice;
DeviceInfo->DeviceNumber = id;
#ifdef UNDER_NT
DeviceInfo->DeviceHandle = (HANDLE32)pmod->hMidi;
#else
DeviceInfo->DeviceHandle = (HANDLE32)MAKELONG(pmod->hMidi,0);
#endif
mmr = midiOpen(DeviceInfo, dwUser, pmod, (DWORD)dwParam2);
GlobalFreeDeviceInfo(DeviceInfo);
return mmr;
} else {
MMRRETURN( MMSYSERR_NOMEM );
}
}
case MODM_CLOSE:
DPF(DL_TRACE|FA_MIDI, ("MODM_CLOSE") );
pOutClient = (LPDEVICEINFO)dwUser;
if( ( (mmr=IsValidDeviceInfo(pOutClient)) != MMSYSERR_NOERROR) ||
( (mmr=IsValidDeviceState(pOutClient->DeviceState,FALSE)) != MMSYSERR_NOERROR ) )
{
MMRRETURN( mmr );
}
midiOutAllNotesOff( pOutClient );
mmr = wdmaudCloseDev( pOutClient );
if (MMSYSERR_NOERROR == mmr)
{
//
// Tell the caller we're done
//
midiCallback(pOutClient, MOM_CLOSE, 0L, 0L);
ISVALIDDEVICEINFO(pOutClient);
ISVALIDDEVICESTATE(pOutClient->DeviceState,FALSE);
midiCleanUp(pOutClient);
}
return mmr;
case MODM_DATA:
DPF(DL_TRACE|FA_MIDI, ("MODM_DATA") );
if( ( (mmr=IsValidDeviceInfo((LPDEVICEINFO)dwUser)) != MMSYSERR_NOERROR ) ||
( (mmr=IsValidDeviceState(((LPDEVICEINFO)dwUser)->DeviceState,FALSE)) != MMSYSERR_NOERROR ) )
{
MMRRETURN( mmr );
}
//
// dwParam1 = MIDI event dword (1, 2 or 3 bytes)
//
return midiOutWrite((LPDEVICEINFO)dwUser, (DWORD)dwParam1);
case MODM_LONGDATA:
DPF(DL_TRACE|FA_MIDI, ("MODM_LONGDATA") );
pOutClient = (LPDEVICEINFO)dwUser;
{
LPMIDIHDR lpHdr;
if( ( (mmr=IsValidDeviceInfo(pOutClient)) != MMSYSERR_NOERROR ) ||
( (mmr=IsValidDeviceState(pOutClient->DeviceState,FALSE)) != MMSYSERR_NOERROR ) ||
( (mmr=IsValidMidiHeader((LPMIDIHDR)dwParam1)) != MMSYSERR_NOERROR) )
{
MMRRETURN( mmr );
}
//
// check if it's been prepared
//
lpHdr = (LPMIDIHDR)dwParam1;
if (!(lpHdr->dwFlags & MHDR_PREPARED))
{
MMRRETURN( MIDIERR_UNPREPARED );
}
// Send the data long....
mmr = wdmaudSubmitMidiOutHeader(pOutClient, lpHdr);
//
// The docs say that this call can return an error. Why we didn't
// I don't know. Thus, these lines are getting commented out.
//
// DPFASSERT( mmr == MMSYSERR_NOERROR );
// mmr = MMSYSERR_NOERROR;
// note that clearing the done bit or setting the inqueue bit
// isn't necessary here since this function is synchronous -
// the client will not get control back until it's done.
lpHdr->dwFlags |= MHDR_DONE;
// notify client
//BUGBUG: this is a no-op from the set above?
if (mmr == MMSYSERR_NOERROR)
{
midiCallback(pOutClient, MOM_DONE, (DWORD_PTR)lpHdr, 0L);
}
return mmr;
}
case MODM_RESET:
DPF(DL_TRACE|FA_MIDI, ("MODM_RESET") );
pOutClient = (LPDEVICEINFO)dwUser;
if( ( (mmr=IsValidDeviceInfo(pOutClient)) != MMSYSERR_NOERROR ) ||
( (mmr=IsValidDeviceState(pOutClient->DeviceState,FALSE)) != MMSYSERR_NOERROR ) )
{
MMRRETURN( mmr );
}
midiOutAllNotesOff(pOutClient);
return MMSYSERR_NOERROR;
case MODM_SETVOLUME:
DPF(DL_TRACE|FA_MIDI, ("MODM_SETVOLUME") );
pOutClient = GlobalAllocDeviceInfo((LPWSTR)dwParam2);
if (NULL == pOutClient)
{
MMRRETURN( MMSYSERR_NOMEM );
}
pOutClient->DeviceType = MidiOutDevice;
pOutClient->DeviceNumber = id;
pOutClient->OpenDone = 0;
PRESETERROR(pOutClient);
mmr = wdmaudIoControl(pOutClient,
sizeof(DWORD),
(LPBYTE)&dwParam1,
IOCTL_WDMAUD_MIDI_OUT_SET_VOLUME);
POSTEXTRACTERROR(mmr,pOutClient);
GlobalFreeDeviceInfo(pOutClient);
return mmr;
case MODM_GETVOLUME:
DPF(DL_TRACE|FA_MIDI, ("MODM_GETVOLUME") );
pOutClient = GlobalAllocDeviceInfo((LPWSTR)dwParam2);
if (pOutClient)
{
pVolume = (LPDWORD) GlobalAllocPtr( GPTR, sizeof(DWORD));
if (pVolume)
{
pOutClient->DeviceType = MidiOutDevice;
pOutClient->DeviceNumber = id;
pOutClient->OpenDone = 0;
PRESETERROR(pOutClient);
mmr = wdmaudIoControl(pOutClient,
sizeof(DWORD),
(LPBYTE)pVolume,
IOCTL_WDMAUD_MIDI_OUT_GET_VOLUME);
POSTEXTRACTERROR(mmr,pOutClient);
//
// Only copy back info on success.
//
if( MMSYSERR_NOERROR == mmr )
*((DWORD FAR *) dwParam1) = *pVolume;
GlobalFreePtr(pVolume);
} else {
mmr = MMSYSERR_NOMEM;
}
GlobalFreeDeviceInfo(pOutClient);
} else {
mmr = MMSYSERR_NOMEM;
}
return mmr;
#ifdef MIDI_STREAM
// TODO: Are we going to support the Midi Streaming
// messages in this rev?
case MODM_PROPERTIES:
return modProperty (&gMidiOutClient, (LPBYTE)dwParam1, dwParam2);
case MODM_STRMDATA:
return modStreamData (&gMidiOutClient, (LPMIDIHDR)dwParam1, (UINT)dwParam2);
case MODM_GETPOS:
return modGetStreamPosition (&gMidiOutClient, (LPMMTIME)dwParam1);
case MODM_STOP:
return modStreamReset (&gMidiOutClient);
case MODM_RESTART:
return modStreamRestart (&gMidiOutClient, dwParam1, dwParam2);
case MODM_PAUSE:
return modStreamPause (&gMidiOutClient);
#endif // MIDI_STREAM support
#ifdef MIDI_THRU
case DRVM_ADD_THRU:
case DRVM_REMOVE_THRU:
// TODO: How do a support thruing in the kernel if I
// only get a device handle from this message.
#endif // MIDI_THRU support
default:
MMRRETURN( MMSYSERR_NOTSUPPORTED );
}
//
// Should not get here
//
DPFASSERT(0);
MMRRETURN( MMSYSERR_NOTSUPPORTED );
}
/****************************************************************************
* @doc INTERNAL
*
* @api DWORD | midiOutWrite | Synchronously process a midi output
* buffer.
*
* @rdesc A MMSYS... type return code for the application.
***************************************************************************/
MMRESULT FAR midiOutWrite
(
LPDEVICEINFO pClient,
DWORD ulEvent
)
{
MMRESULT mmr = MMSYSERR_ERROR;
BYTE bStatus;
BYTE bNote;
BYTE bVelocity;
UINT uChannel;
DWORD idx;
LPBYTE lpEntry;
bStatus = (BYTE)(ulEvent & 0xFF);
if (!IS_STATUS( bStatus ))
{
bNote = bStatus;
bVelocity = (BYTE)(( ulEvent >> 8 ) & 0x0FF );
bStatus = pClient->DeviceState->bMidiStatus;
}
else
{
bNote = (BYTE)(( ulEvent >> 8 ) & 0xFF );
bVelocity = (BYTE)(( ulEvent >> 16 ) & 0xFF );
pClient->DeviceState->bMidiStatus = bStatus;
}
uChannel = MIDI_CHANNEL( bStatus );
bStatus = MIDI_STATUS( bStatus );
if (MIDI_NOTEON == bStatus ||
MIDI_NOTEOFF == bStatus)
{
idx = ( uChannel << 7 ) | bNote;
lpEntry = &pClient->DeviceState->lpNoteOnMap[idx];
if (( 0 == bVelocity ) ||
( MIDI_NOTEOFF == bStatus ))
{
if (*lpEntry)
{
--*lpEntry;
}
}
else
{
if (*lpEntry < 255)
{
++*lpEntry;
}
}
}
//
// Send the MIDI short message
//
mmr = wdmaudIoControl(pClient,
0, // DataBuffer contains a value and not a pointer
// so we don't need a size.
#ifdef UNDER_NT
UlongToPtr(ulEvent),
#else
&ulEvent,
#endif
IOCTL_WDMAUD_MIDI_OUT_WRITE_DATA);
return mmr;
}
/****************************************************************************
* @doc INTERNAL
*
* @api VOID | midiOutAllNotesOff | Turn off all the notes on this client,
* using the note on map that has been built from outgoing short messages.
*
***************************************************************************/
VOID FAR midiOutAllNotesOff
(
LPDEVICEINFO pClient
)
{
UINT uNote;
UINT uChannel;
LPBYTE lpNoteOnMap;
LPBYTE lpNoteOnMapEnd;
DWORD dwMessage;
UINT uNoteOffs = 0;
// First turn off the sustain controller on all channels to terminate
// post-note off sound
//
for (uChannel = 0;
uChannel < MIDI_CHANNELS;
uChannel++)
{
dwMessage = MIDI_SUSTAIN( 0, uChannel );
// WorkItem: shouldn't we check the return value here?
wdmaudIoControl(pClient,
0, // DataBuffer contains a value and not a pointer
// so we don't need a size.
#ifdef UNDER_NT
UlongToPtr(dwMessage),
#else
&dwMessage,
#endif
IOCTL_WDMAUD_MIDI_OUT_WRITE_DATA);
}
// Iterate through the map and track what note and channel each entry corresponds
// to
//
lpNoteOnMap = pClient->DeviceState->lpNoteOnMap;
lpNoteOnMapEnd = lpNoteOnMap + MIDI_NOTE_MAP_SIZE;
uNote = 0;
uChannel = 0;
for ( ;
lpNoteOnMap < lpNoteOnMapEnd;
lpNoteOnMap++ )
{
BYTE bCount = *lpNoteOnMap;
if (bCount)
{
// This note on this channel has some instances playing. Build a note off
// and shut them down
//
*lpNoteOnMap = 0;
dwMessage = MIDI_NOTE_OFF( uNote, uChannel );
while (bCount--)
{
wdmaudIoControl(pClient,
0, // DataBuffer contains a value and not a pointer
// so we don't need a size.
#ifdef UNDER_NT
UlongToPtr(dwMessage),
#else
&dwMessage,
#endif
IOCTL_WDMAUD_MIDI_OUT_WRITE_DATA);
uNoteOffs++;
}
}
if (++uNote >= MIDI_NOTES)
{
uNote = 0;
++uChannel;
}
}
}