You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
476 lines
14 KiB
476 lines
14 KiB
/****************************************************************************
|
|
*
|
|
* 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;
|
|
}
|
|
}
|
|
}
|