/**************************************************************************** * * 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; } } }