diff options
Diffstat (limited to 'utils/winmm_drv/src')
-rw-r--r-- | utils/winmm_drv/src/MidiSynth.cpp | 720 | ||||
-rw-r--r-- | utils/winmm_drv/src/MidiSynth.h | 90 | ||||
-rw-r--r-- | utils/winmm_drv/src/stdafx.h | 83 | ||||
-rw-r--r-- | utils/winmm_drv/src/targetver.h | 24 | ||||
-rw-r--r-- | utils/winmm_drv/src/winmm_drv.cpp | 387 | ||||
-rw-r--r-- | utils/winmm_drv/src/winmm_drv.def | 6 |
6 files changed, 1310 insertions, 0 deletions
diff --git a/utils/winmm_drv/src/MidiSynth.cpp b/utils/winmm_drv/src/MidiSynth.cpp new file mode 100644 index 0000000..29cbb78 --- /dev/null +++ b/utils/winmm_drv/src/MidiSynth.cpp @@ -0,0 +1,720 @@ +/* Copyright (C) 2011, 2012 Sergey V. Mikayev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "stdafx.h" + +namespace OPL3Emu +{ + +#define DRIVER_MODE + +static MidiSynth &midiSynth = MidiSynth::getInstance(); + +static class MidiStream +{ +private: + static const unsigned int maxPos = 1024; + unsigned int startpos; + unsigned int endpos; + DWORD stream[maxPos][2]; + +public: + MidiStream() + { + startpos = 0; + endpos = 0; + } + + DWORD PutMessage(DWORD msg, DWORD timestamp) + { + unsigned int newEndpos = endpos; + + newEndpos++; + if(newEndpos == maxPos) // check for buffer rolloff + newEndpos = 0; + if(startpos == newEndpos) // check for buffer full + return -1; + stream[endpos][0] = msg; // ok to put data and update endpos + stream[endpos][1] = timestamp; + endpos = newEndpos; + return 0; + } + + DWORD GetMidiMessage() + { + if(startpos == endpos) // check for buffer empty + return -1; + DWORD msg = stream[startpos][0]; + startpos++; + if(startpos == maxPos) // check for buffer rolloff + startpos = 0; + return msg; + } + + DWORD PeekMessageTime() + { + if(startpos == endpos) // check for buffer empty + return (DWORD) -1; + return stream[startpos][1]; + } + + DWORD PeekMessageTimeAt(unsigned int pos) + { + if(startpos == endpos) // check for buffer empty + return -1; + unsigned int peekPos = (startpos + pos) % maxPos; + return stream[peekPos][1]; + } + + void Clean() + { + startpos = 0; + endpos = 0; + memset(stream, 0, sizeof(stream)); + } + +} midiStream; + +static class SynthEventWin32 +{ +private: + HANDLE hEvent; + +public: + int Init() + { + hEvent = CreateEvent(NULL, false, true, NULL); + if(hEvent == NULL) + { + MessageBoxW(NULL, L"Can't create sync object", L"libADLMIDI", MB_OK | MB_ICONEXCLAMATION); + return 1; + } + return 0; + } + + void Close() + { + CloseHandle(hEvent); + } + + void Wait() + { + WaitForSingleObject(hEvent, INFINITE); + } + + void Release() + { + SetEvent(hEvent); + } +} synthEvent; + +static class WaveOutWin32 +{ +private: + HWAVEOUT hWaveOut; + WAVEHDR *WaveHdr; + HANDLE hEvent; + DWORD chunks; + DWORD prevPlayPos; + DWORD getPosWraps; + bool stopProcessing; + +public: + int Init(Bit16s *buffer, unsigned int bufferSize, unsigned int chunkSize, bool useRingBuffer, unsigned int sampleRate) + { + DWORD callbackType = CALLBACK_NULL; + DWORD_PTR callback = (DWORD_PTR)NULL; + hEvent = NULL; + if(!useRingBuffer) + { + hEvent = CreateEvent(NULL, false, true, NULL); + callback = (DWORD_PTR)hEvent; + callbackType = CALLBACK_EVENT; + } + + PCMWAVEFORMAT wFormat = {WAVE_FORMAT_PCM, 2, sampleRate, sampleRate * 4, 4, 16}; + + // Open waveout device + int wResult = waveOutOpen(&hWaveOut, WAVE_MAPPER, (LPWAVEFORMATEX)&wFormat, callback, (DWORD_PTR)&midiSynth, callbackType); + if(wResult != MMSYSERR_NOERROR) + { + MessageBoxW(NULL, L"Failed to open waveform output device", L"libADLMIDI", MB_OK | MB_ICONEXCLAMATION); + return 2; + } + + // Prepare headers + chunks = useRingBuffer ? 1 : bufferSize / chunkSize; + WaveHdr = new WAVEHDR[chunks]; + LPSTR chunkStart = (LPSTR)buffer; + DWORD chunkBytes = 4 * chunkSize; + for(UINT i = 0; i < chunks; i++) + { + if(useRingBuffer) + { + WaveHdr[i].dwBufferLength = 4 * bufferSize; + WaveHdr[i].lpData = chunkStart; + WaveHdr[i].dwFlags = WHDR_BEGINLOOP | WHDR_ENDLOOP; + WaveHdr[i].dwLoops = -1L; + } + else + { + WaveHdr[i].dwBufferLength = chunkBytes; + WaveHdr[i].lpData = chunkStart; + WaveHdr[i].dwFlags = 0L; + WaveHdr[i].dwLoops = 0L; + chunkStart += chunkBytes; + } + wResult = waveOutPrepareHeader(hWaveOut, &WaveHdr[i], sizeof(WAVEHDR)); + if(wResult != MMSYSERR_NOERROR) + { + MessageBoxW(NULL, L"Failed to Prepare Header", L"libADLMIDI", MB_OK | MB_ICONEXCLAMATION); + return 3; + } + } + stopProcessing = false; + return 0; + } + + int Close() + { + stopProcessing = true; + SetEvent(hEvent); + int wResult = waveOutReset(hWaveOut); + if(wResult != MMSYSERR_NOERROR) + { + MessageBoxW(NULL, L"Failed to Reset WaveOut", L"libADLMIDI", MB_OK | MB_ICONEXCLAMATION); + return 8; + } + + for(UINT i = 0; i < chunks; i++) + { + wResult = waveOutUnprepareHeader(hWaveOut, &WaveHdr[i], sizeof(WAVEHDR)); + if(wResult != MMSYSERR_NOERROR) + { + MessageBoxW(NULL, L"Failed to Unprepare Wave Header", L"libADLMIDI", MB_OK | MB_ICONEXCLAMATION); + return 8; + } + } + delete[] WaveHdr; + WaveHdr = NULL; + + wResult = waveOutClose(hWaveOut); + if(wResult != MMSYSERR_NOERROR) + { + MessageBoxW(NULL, L"Failed to Close WaveOut", L"libADLMIDI", MB_OK | MB_ICONEXCLAMATION); + return 8; + } + if(hEvent != NULL) + { + CloseHandle(hEvent); + hEvent = NULL; + } + return 0; + } + + int Start() + { + getPosWraps = 0; + prevPlayPos = 0; + for(UINT i = 0; i < chunks; i++) + { + if(waveOutWrite(hWaveOut, &WaveHdr[i], sizeof(WAVEHDR)) != MMSYSERR_NOERROR) + { + MessageBoxW(NULL, L"Failed to write block to device", L"libADLMIDI", MB_OK | MB_ICONEXCLAMATION); + return 4; + } + } + _beginthread(RenderingThread, 16384, this); + return 0; + } + + int Pause() + { + if(waveOutPause(hWaveOut) != MMSYSERR_NOERROR) + { + MessageBoxW(NULL, L"Failed to Pause wave playback", L"libADLMIDI", MB_OK | MB_ICONEXCLAMATION); + return 9; + } + return 0; + } + + int Resume() + { + if(waveOutRestart(hWaveOut) != MMSYSERR_NOERROR) + { + MessageBoxW(NULL, L"Failed to Resume wave playback", L"libADLMIDI", MB_OK | MB_ICONEXCLAMATION); + return 9; + } + return 0; + } + + UINT64 GetPos() + { + MMTIME mmTime; + mmTime.wType = TIME_SAMPLES; + + if(waveOutGetPosition(hWaveOut, &mmTime, sizeof(MMTIME)) != MMSYSERR_NOERROR) + { + MessageBoxW(NULL, L"Failed to get current playback position", L"libADLMIDI", MB_OK | MB_ICONEXCLAMATION); + return 10; + } + + if(mmTime.wType != TIME_SAMPLES) + { + MessageBoxW(NULL, L"Failed to get # of samples played", L"libADLMIDI", MB_OK | MB_ICONEXCLAMATION); + return 10; + } + + // Deal with waveOutGetPosition() wraparound. For 16-bit stereo output, it equals 2^27, + // presumably caused by the internal 32-bit counter of bits played. + // The output of that nasty waveOutGetPosition() isn't monotonically increasing + // even during 2^27 samples playback, so we have to ensure the difference is big enough... + int delta = mmTime.u.sample - prevPlayPos; + if(delta < -(1 << 26)) + { + std::cout << "OPL3: GetPos() wrap: " << delta << "\n"; + ++getPosWraps; + } + prevPlayPos = mmTime.u.sample; + return mmTime.u.sample + getPosWraps * (1 << 27); + } + + static void RenderingThread(void *); +} s_waveOut; + +void WaveOutWin32::RenderingThread(void *) +{ + if(s_waveOut.chunks == 1) + { + // Rendering using single looped ring buffer + while(!s_waveOut.stopProcessing) + midiSynth.RenderAvailableSpace(); + } + else + { + while(!s_waveOut.stopProcessing) + { + bool allBuffersRendered = true; + for(UINT i = 0; i < s_waveOut.chunks; i++) + { + if(s_waveOut.WaveHdr[i].dwFlags & WHDR_DONE) + { + allBuffersRendered = false; + midiSynth.Render((Bit16s *)s_waveOut.WaveHdr[i].lpData, s_waveOut.WaveHdr[i].dwBufferLength / 4); + if(waveOutWrite(s_waveOut.hWaveOut, &s_waveOut.WaveHdr[i], sizeof(WAVEHDR)) != MMSYSERR_NOERROR) + MessageBoxW(NULL, L"Failed to write block to device", L"libADLMIDI", MB_OK | MB_ICONEXCLAMATION); + midiSynth.CheckForSignals(); + } + } + if(allBuffersRendered) + WaitForSingleObject(s_waveOut.hEvent, INFINITE); + } + } +} + + +MidiSynth::MidiSynth() : + synth(NULL) +{ + m_setupInit = false; + setupDefault(&m_setup); + loadSetup(); + ::openSignalListener(); +} + +MidiSynth::~MidiSynth() +{ + if(synth) + adl_close(synth); + synth = NULL; + ::closeSignalListener(); +} + +MidiSynth &MidiSynth::getInstance() +{ + static MidiSynth *instance = new MidiSynth; + return *instance; +} + +// Renders all the available space in the single looped ring buffer +void MidiSynth::RenderAvailableSpace() +{ + DWORD playPos = s_waveOut.GetPos() % bufferSize; + DWORD framesToRender; + + if(playPos < framesRendered) + { + // Buffer wrap, render 'till the end of the buffer + framesToRender = bufferSize - framesRendered; + } + else + { + framesToRender = playPos - framesRendered; + if(framesToRender < chunkSize) + { + Sleep(1 + (chunkSize - framesToRender) * 1000 / sampleRate); + return; + } + } + midiSynth.Render(buffer + 2 * framesRendered, framesToRender); +} + +// Renders totalFrames frames starting from bufpos +// The number of frames rendered is added to the global counter framesRendered +void MidiSynth::Render(Bit16s *bufpos, DWORD totalFrames) +{ + while(totalFrames > 0) + { + DWORD timeStamp; + // Incoming MIDI messages timestamped with the current audio playback position + midiLatency + while((timeStamp = midiStream.PeekMessageTime()) == framesRendered) + { + DWORD msg = midiStream.GetMidiMessage(); + + synthEvent.Wait(); + + Bit8u event = msg & 0xFF; + Bit8u channel = msg & 0x0F; + Bit8u p1 = (msg >> 8) & 0x7f; + Bit8u p2 = (msg >> 16) & 0x7f; + + event &= 0xF0; + + if(event == 0xF0) + { + switch (channel) + { + case 0xF: + adl_reset(synth); + break; + } + } + + switch(event & 0xF0) + { + case 0x80: + adl_rt_noteOff(synth, channel, p1); + break; + case 0x90: + adl_rt_noteOn(synth, channel, p1, p2); + break; + case 0xA0: + adl_rt_noteAfterTouch(synth, channel, p1, p2); + break; + case 0xB0: + adl_rt_controllerChange(synth, channel, p1, p2); + break; + case 0xC0: + adl_rt_patchChange(synth, channel, p1); + break; + case 0xD0: + adl_rt_channelAfterTouch(synth, channel, p1); + break; + case 0xE0: + adl_rt_pitchBendML(synth, channel, p2, p1); + break; + } + + synthEvent.Release(); + } + + // Find out how many frames to render. The value of timeStamp == -1 indicates the MIDI buffer is empty + DWORD framesToRender = timeStamp - framesRendered; + if(framesToRender > totalFrames) + { + // MIDI message is too far - render the rest of frames + framesToRender = totalFrames; + } + + synthEvent.Wait(); + adl_generate(synth, framesToRender * 2, bufpos); + synthEvent.Release(); + framesRendered += framesToRender; + bufpos += 2 * framesToRender; // each frame consists of two samples for both the Left and Right channels + totalFrames -= framesToRender; + } + + // Wrap framesRendered counter + if(framesRendered >= bufferSize) + framesRendered -= bufferSize; +} + +void MidiSynth::CheckForSignals() +{ + int cmd = ::hasReloadSetupSignal(); + + if(cmd == 0) + return; + + switch(cmd) + { + case 1: // Reload settings on the fly + this->loadSetup(); + LoadSynthSetup(); + break; + + case 2: + adl_reset(synth); + break; + + default: + break; + } + + if(cmd > 0) + ::resetSignal(); +} + +unsigned int MidiSynth::MillisToFrames(unsigned int millis) +{ + return UINT(sampleRate * millis / 1000.f); +} + +void MidiSynth::LoadSettings() +{ + sampleRate = 49716; + bufferSize = MillisToFrames(100); + chunkSize = MillisToFrames(10); + midiLatency = MillisToFrames(0); + useRingBuffer = false; + if(!useRingBuffer) + { + // Number of chunks should be ceil(bufferSize / chunkSize) + DWORD chunks = (bufferSize + chunkSize - 1) / chunkSize; + // Refine bufferSize as chunkSize * number of chunks, no less then the specified value + bufferSize = chunks * chunkSize; + } +} + +int MidiSynth::Init() +{ + LoadSettings(); + buffer = new Bit16s[2 * bufferSize]; // each frame consists of two samples for both the Left and Right channels + + // Init synth + if(synthEvent.Init()) + return 1; + + synth = adl_init(49716); + if(!synth) + { + MessageBoxW(NULL, L"Can't open Synth", L"libADLMIDI", MB_OK | MB_ICONEXCLAMATION); + return 1; + } + + m_setupInit = false; + LoadSynthSetup(); + + UINT wResult = s_waveOut.Init(buffer, bufferSize, chunkSize, useRingBuffer, sampleRate); + if(wResult) return wResult; + + // Start playing stream + adl_generate(synth, bufferSize * 2, buffer); + framesRendered = 0; + + wResult = s_waveOut.Start(); + return wResult; +} + +int MidiSynth::Reset() +{ +#ifdef DRIVER_MODE + return 0; +#endif + + UINT wResult = s_waveOut.Pause(); + if(wResult) return wResult; + + synthEvent.Wait(); + + if(synth) + adl_close(synth); + + synth = adl_init(49716); + if(!synth) + { + MessageBoxW(NULL, L"Can't open Synth", L"libADLMIDI", MB_OK | MB_ICONEXCLAMATION); + return 1; + } + + m_setupInit = false; + LoadSynthSetup(); + + synthEvent.Release(); + + wResult = s_waveOut.Resume(); + return wResult; +} + +void MidiSynth::ResetSynth() +{ + synthEvent.Wait(); + adl_reset(synth); + midiStream.Clean(); + synthEvent.Release(); +} + +void MidiSynth::PanicSynth() +{ + synthEvent.Wait(); + adl_panic(synth); + synthEvent.Release(); +} + +void MidiSynth::PushMIDI(DWORD msg) +{ + midiStream.PutMessage(msg, (s_waveOut.GetPos() + midiLatency) % bufferSize); +} + +void MidiSynth::PlaySysex(Bit8u *bufpos, DWORD len) +{ + synthEvent.Wait(); + adl_rt_systemExclusive(synth, bufpos, len); + synthEvent.Release(); +} + +void MidiSynth::loadSetup() +{ + ::loadSetup(&m_setup); +} + +void MidiSynth::LoadSynthSetup() +{ + if(!m_setupInit || m_setupCurrent.emulatorId != m_setup.emulatorId) + { + adl_switchEmulator(synth, m_setup.emulatorId); + m_setupCurrent.emulatorId = m_setup.emulatorId; + } + + if(!m_setupInit || m_setupCurrent.numChips != m_setup.numChips) + { + adl_setNumChips(synth, m_setup.numChips); + m_setupCurrent.numChips = m_setup.numChips; + } + + if(!m_setupInit || m_setupCurrent.flagDeepTremolo != m_setup.flagDeepTremolo) + { + switch(m_setup.flagDeepTremolo) + { + case BST_INDETERMINATE: + adl_setHTremolo(synth, -1); + break; + case BST_CHECKED: + adl_setHTremolo(synth, 1); + break; + case BST_UNCHECKED: + adl_setHTremolo(synth, 0); + break; + } + m_setupCurrent.flagDeepTremolo = m_setup.flagDeepTremolo; + } + + if(!m_setupInit || m_setupCurrent.flagDeepVibrato != m_setup.flagDeepVibrato) + { + switch(m_setup.flagDeepVibrato) + { + case BST_INDETERMINATE: + adl_setHVibrato(synth, -1); + break; + case BST_CHECKED: + adl_setHVibrato(synth, 1); + break; + case BST_UNCHECKED: + adl_setHVibrato(synth, 0); + break; + } + m_setupCurrent.flagDeepVibrato = m_setup.flagDeepVibrato; + } + + if(!m_setupInit || m_setupCurrent.flagSoftPanning != m_setup.flagSoftPanning) + { + adl_setSoftPanEnabled(synth, m_setup.flagSoftPanning); + m_setupCurrent.flagSoftPanning = m_setup.flagSoftPanning; + } + + + if(!m_setupInit || m_setupCurrent.flagScaleModulators != m_setup.flagScaleModulators) + { + adl_setScaleModulators(synth, m_setup.flagScaleModulators); + m_setupCurrent.flagScaleModulators = m_setup.flagScaleModulators; + } + + if(!m_setupInit || m_setupCurrent.flagFullBrightness != m_setup.flagFullBrightness) + { + adl_setFullRangeBrightness(synth, m_setup.flagFullBrightness); + m_setupCurrent.flagFullBrightness = m_setup.flagFullBrightness; + } + + if(!m_setupInit || m_setupCurrent.volumeModel != m_setup.volumeModel) + { + adl_setVolumeRangeModel(synth, m_setup.volumeModel); + m_setupCurrent.volumeModel = m_setup.volumeModel; + } + + if(!m_setupInit || m_setupCurrent.numChips != m_setup.numChips) + { + adl_setNumChips(synth, m_setup.numChips); + m_setupCurrent.numChips = m_setup.numChips; + } + + if(!m_setupInit || m_setupCurrent.num4ops != m_setup.num4ops) + { + adl_setNumFourOpsChn(synth, m_setup.num4ops); + m_setupCurrent.num4ops = m_setup.num4ops; + } + + if(!m_setupInit || + m_setupCurrent.useExternalBank != m_setup.useExternalBank || + m_setupCurrent.bankId != m_setup.bankId || + wcscmp(m_setupCurrent.bankPath, m_setup.bankPath) != 0 + ) + { + if(m_setup.useExternalBank) + { + char pathUtf8[MAX_PATH * 4]; + ZeroMemory(pathUtf8, MAX_PATH * 4); + int len = WideCharToMultiByte(CP_UTF8, 0, m_setup.bankPath, wcslen(m_setup.bankPath), pathUtf8, MAX_PATH * 4, 0, 0); + pathUtf8[len] = '\0'; + adl_openBankFile(synth, pathUtf8); + } + else + adl_setBank(synth, m_setup.bankId); + + m_setupCurrent.useExternalBank = m_setup.useExternalBank; + m_setupCurrent.bankId = m_setup.bankId; + wcscpy(m_setupCurrent.bankPath, m_setup.bankPath); + } + + m_setupInit = true; +} + +void MidiSynth::Close() +{ + s_waveOut.Pause(); + s_waveOut.Close(); + synthEvent.Wait(); + //synth->close(); + + // Cleanup memory + if(synth) + adl_close(synth); + synth = NULL; + delete buffer; + + synthEvent.Close(); +} + +} diff --git a/utils/winmm_drv/src/MidiSynth.h b/utils/winmm_drv/src/MidiSynth.h new file mode 100644 index 0000000..bf2da02 --- /dev/null +++ b/utils/winmm_drv/src/MidiSynth.h @@ -0,0 +1,90 @@ +/* Copyright (C) 2011, 2012 Sergey V. Mikayev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "stdafx.h" +#include "regconfig.h" + +#ifndef OPL3EMU_MIDISYNTH_H +#define OPL3EMU_MIDISYNTH_H + +typedef unsigned char Bit8u; +typedef signed char Bit8s; +typedef unsigned short Bit16u; +typedef signed short Bit16s; +typedef unsigned long Bit32u; +typedef signed long Bit32s; +typedef unsigned __int64 Bit64u; +typedef signed __int64 Bit64s; +typedef unsigned int Bitu; +typedef signed int Bits; + +namespace OPL3Emu +{ + +class MidiSynth +{ +private: + unsigned int sampleRate; + unsigned int midiLatency; + unsigned int bufferSize; + unsigned int chunkSize; + bool useRingBuffer; + bool resetEnabled; + float outputGain; + float reverbOutputGain; + bool reverbEnabled; + bool reverbOverridden; + Bit8u reverbMode; + Bit8u reverbTime; + Bit8u reverbLevel; + + Bit16s *buffer; + DWORD framesRendered; + + ADL_MIDIPlayer *synth; + + bool m_setupInit; + DriverSettings m_setup; + DriverSettings m_setupCurrent; + + unsigned int MillisToFrames(unsigned int millis); + void LoadSettings(); + + MidiSynth(); + ~MidiSynth(); + +public: + static MidiSynth &getInstance(); + int Init(); + void Close(); + int Reset(); + void ResetSynth(); + void PanicSynth(); + void RenderAvailableSpace(); + void Render(Bit16s *bufpos, DWORD totalFrames); + void CheckForSignals(); + void PushMIDI(DWORD msg); + void PlaySysex(Bit8u *bufpos, DWORD len); + + void loadSetup(); + + void LoadSynthSetup(); +}; + +} +#endif diff --git a/utils/winmm_drv/src/stdafx.h b/utils/winmm_drv/src/stdafx.h new file mode 100644 index 0000000..dd8b15c --- /dev/null +++ b/utils/winmm_drv/src/stdafx.h @@ -0,0 +1,83 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#pragma once + +#include "targetver.h" + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files: +#include <windows.h> +#include <mmsystem.h> +#ifndef __MINGW32__ +#include <mmddk.h> +#endif +#include <mmreg.h> +#include <process.h> +#include <iostream> +#include <adlmidi.h> +#include "MidiSynth.h" + +#ifdef __MINGW32__ +// Define missing in MinGW macros here + +#ifndef EXTERN_C +#ifdef __cplusplus + #define EXTERN_C extern "C" + #define EXTERN_C_START extern "C" { + #define EXTERN_C_END } +#else + #define EXTERN_C extern + #define EXTERN_C_START + #define EXTERN_C_END +#endif +#endif + +#ifndef STDAPICALLTYPE +#define STDAPICALLTYPE __stdcall +#endif + +#ifndef STDAPI_ +#define STDAPI_(type) EXTERN_C type STDAPICALLTYPE +#endif + +#define DRV_PNPINSTALL (DRV_RESERVED + 11) + +#ifndef MMNOMIDI + +typedef struct midiopenstrmid_tag +{ + DWORD dwStreamID; + UINT uDeviceID; +} MIDIOPENSTRMID; + +typedef struct midiopendesc_tag { + HMIDI hMidi; + DWORD_PTR dwCallback; + DWORD_PTR dwInstance; + DWORD_PTR dnDevNode; + DWORD cIds; + MIDIOPENSTRMID rgIds[1]; +} MIDIOPENDESC; + +typedef MIDIOPENDESC FAR *LPMIDIOPENDESC; + +#endif // MMNOMIDI + +#define MODM_GETNUMDEVS 1 +#define MODM_GETDEVCAPS 2 +#define MODM_OPEN 3 +#define MODM_CLOSE 4 +#define MODM_PREPARE 5 +#define MODM_UNPREPARE 6 +#define MODM_DATA 7 +#define MODM_LONGDATA 8 +#define MODM_RESET 9 +#define MODM_GETVOLUME 10 +#define MODM_SETVOLUME 11 +#define MODM_CACHEPATCHES 12 +#define MODM_CACHEDRUMPATCHES 13 + +#endif // __MINGW32__ diff --git a/utils/winmm_drv/src/targetver.h b/utils/winmm_drv/src/targetver.h new file mode 100644 index 0000000..658f76f --- /dev/null +++ b/utils/winmm_drv/src/targetver.h @@ -0,0 +1,24 @@ +#pragma once + +// The following macros define the minimum required platform. The minimum required platform +// is the earliest version of Windows, Internet Explorer etc. that has the necessary features to run +// your application. The macros work by enabling all features available on platform versions up to and +// including the version specified. + +// Modify the following defines if you have to target a platform prior to the ones specified below. +// Refer to MSDN for the latest info on corresponding values for different platforms. +#ifndef WINVER // Specifies that the minimum required platform is Windows NT4. +#define WINVER 0x0510 // Change this to the appropriate value to target other versions of Windows. +#endif + +#ifndef _WIN32_WINNT // Specifies that the minimum required platform is Windows 2000. +#define _WIN32_WINNT 0x0510 // Change this to the appropriate value to target other versions of Windows. +#endif + +#ifndef _WIN32_WINDOWS // Specifies that the minimum required platform is Windows 98. +#define _WIN32_WINDOWS 0x0410 // Change this to the appropriate value to target Windows Me or later. +#endif + +#ifndef _WIN32_IE // Specifies that the minimum required platform is Internet Explorer 5.0. +#define _WIN32_IE 0x0500 // Change this to the appropriate value to target other versions of IE. +#endif diff --git a/utils/winmm_drv/src/winmm_drv.cpp b/utils/winmm_drv/src/winmm_drv.cpp new file mode 100644 index 0000000..7f65dda --- /dev/null +++ b/utils/winmm_drv/src/winmm_drv.cpp @@ -0,0 +1,387 @@ +/* Copyright (C) 2003, 2004, 2005 Dean Beeler, Jerome Fisher + * Copyright (C) 2011, 2012 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "stdafx.h" + +#define MAX_DRIVERS 8 +#define MAX_CLIENTS 8 // Per driver + +#ifdef __MINGW32__ + +#if !defined(MM_MPU401_MIDIOUT) && !defined(MM_MPU401_MIDIOUT) +typedef struct tagMIDIOUTCAPS2A { + WORD wMid; + WORD wPid; + MMVERSION vDriverVersion; + CHAR szPname[MAXPNAMELEN]; + WORD wTechnology; + WORD wVoices; + WORD wNotes; + WORD wChannelMask; + DWORD dwSupport; + GUID ManufacturerGuid; + GUID ProductGuid; + GUID NameGuid; +} MIDIOUTCAPS2A,*PMIDIOUTCAPS2A,*NPMIDIOUTCAPS2A,*LPMIDIOUTCAPS2A; + +typedef struct tagMIDIOUTCAPS2W { + WORD wMid; + WORD wPid; + MMVERSION vDriverVersion; + WCHAR szPname[MAXPNAMELEN]; + WORD wTechnology; + WORD wVoices; + WORD wNotes; + WORD wChannelMask; + DWORD dwSupport; + GUID ManufacturerGuid; + GUID ProductGuid; + GUID NameGuid; +} MIDIOUTCAPS2W,*PMIDIOUTCAPS2W,*NPMIDIOUTCAPS2W,*LPMIDIOUTCAPS2W; +#endif + +#ifndef MM_UNMAPPED +#define MM_UNMAPPED 0xffff +#endif +#ifndef MM_MPU401_MIDIOUT +#define MM_MPU401_MIDIOUT 10 +#endif + +//BOOL APIENTRY DriverCallback(DWORD_PTR dwCallback, +// DWORD dwFlags, +// HDRVR hDevice, +// DWORD dwMsg, +// DWORD_PTR dwUser, +// DWORD_PTR dwParam1, +// DWORD_PTR dwParam2); + +typedef BOOL (APIENTRY *DriverCallbackPtr)(DWORD_PTR dwCallback, + DWORD dwFlags, + HDRVR hDevice, + DWORD dwMsg, + DWORD_PTR dwUser, + DWORD_PTR dwParam1, + DWORD_PTR dwParam2); + +static HMODULE s_winmm_dll = NULL; +static DriverCallbackPtr s_DriverCallback = NULL; + +#define DriverCallback s_DriverCallback + +static void initWorkarounds() +{ + s_winmm_dll = LoadLibraryW(L"winmm.dll"); + if(s_winmm_dll) + { + s_DriverCallback = (DriverCallbackPtr)GetProcAddress(s_winmm_dll, "DriverCallback"); + if(!s_DriverCallback) + MessageBoxW(NULL, L"Failed to get a workaround for DriverCallback", L"libADLMIDI", MB_OK | MB_ICONEXCLAMATION); + } + else + { + MessageBoxW(NULL, L"Failed to open a library for a workaround for DriverCallback", L"libADLMIDI", MB_OK | MB_ICONEXCLAMATION); + } +} + +static void freeWorkarounds() +{ + if(s_winmm_dll) + FreeLibrary(s_winmm_dll); + s_winmm_dll = NULL; + s_DriverCallback = NULL; +} +#endif + +static OPL3Emu::MidiSynth &midiSynth = OPL3Emu::MidiSynth::getInstance(); +static bool synthOpened = false; +//static HWND hwnd = NULL; +static int driverCount; + +struct Driver +{ + bool open; + int clientCount; + HDRVR hdrvr; + struct Client + { + bool allocated; + DWORD_PTR instance; + DWORD flags; + DWORD_PTR callback; + DWORD synth_instance; + } clients[MAX_CLIENTS]; +} drivers[MAX_DRIVERS]; + +EXTERN_C LONG __declspec(dllexport) __stdcall DriverProc(DWORD dwDriverID, HDRVR hdrvr, UINT wMessage, LONG dwParam1, LONG dwParam2) +{ + (void)dwDriverID; (void)dwParam1; (void)dwParam2; + + switch(wMessage) + { + case DRV_LOAD: +#ifdef __MINGW32__ + initWorkarounds(); +#endif + memset(drivers, 0, sizeof(drivers)); + driverCount = 0; + return DRV_OK; + case DRV_ENABLE: + return DRV_OK; + case DRV_OPEN: + int driverNum; + if(driverCount == MAX_DRIVERS) + return 0; + else + { + for(driverNum = 0; driverNum < MAX_DRIVERS; driverNum++) + { + if(!drivers[driverNum].open) + break; + if(driverNum == MAX_DRIVERS) + return 0; + } + } + drivers[driverNum].open = true; + drivers[driverNum].clientCount = 0; + drivers[driverNum].hdrvr = hdrvr; + driverCount++; + return DRV_OK; + case DRV_INSTALL: + case DRV_PNPINSTALL: + return DRV_OK; + case DRV_QUERYCONFIGURE: + return 0; + case DRV_CONFIGURE: + return DRVCNF_OK; + case DRV_CLOSE: + for(int i = 0; i < MAX_DRIVERS; i++) + { + if(drivers[i].open && drivers[i].hdrvr == hdrvr) + { + drivers[i].open = false; + --driverCount; + return DRV_OK; + } + } + return DRV_CANCEL; + case DRV_DISABLE: + return DRV_OK; + case DRV_FREE: +#ifdef __MINGW32__ + freeWorkarounds(); +#endif + return DRV_OK; + case DRV_REMOVE: + return DRV_OK; + } + return DRV_OK; +} + + +EXTERN_C HRESULT modGetCaps(PVOID capsPtr, DWORD capsSize) +{ + MIDIOUTCAPSA *myCapsA; + MIDIOUTCAPSW *myCapsW; + MIDIOUTCAPS2A *myCaps2A; + MIDIOUTCAPS2W *myCaps2W; + + CHAR synthName[] = "libADLMIDI synth\0"; + WCHAR synthNameW[] = L"libADLMIDI synth\0"; + + switch(capsSize) + { + case(sizeof(MIDIOUTCAPSA)): + myCapsA = (MIDIOUTCAPSA *)capsPtr; + myCapsA->wMid = MM_UNMAPPED; + myCapsA->wPid = MM_MPU401_MIDIOUT; + memcpy(myCapsA->szPname, synthName, sizeof(synthName)); + myCapsA->wTechnology = MOD_MIDIPORT; + myCapsA->vDriverVersion = 0x0090; + myCapsA->wVoices = 0; + myCapsA->wNotes = 0; + myCapsA->wChannelMask = 0xffff; + myCapsA->dwSupport = 0; + return MMSYSERR_NOERROR; + + case(sizeof(MIDIOUTCAPSW)): + myCapsW = (MIDIOUTCAPSW *)capsPtr; + myCapsW->wMid = MM_UNMAPPED; + myCapsW->wPid = MM_MPU401_MIDIOUT; + memcpy(myCapsW->szPname, synthNameW, sizeof(synthNameW)); + myCapsW->wTechnology = MOD_MIDIPORT; + myCapsW->vDriverVersion = 0x0090; + myCapsW->wVoices = 0; + myCapsW->wNotes = 0; + myCapsW->wChannelMask = 0xffff; + myCapsW->dwSupport = 0; + return MMSYSERR_NOERROR; + + case(sizeof(MIDIOUTCAPS2A)): + myCaps2A = (MIDIOUTCAPS2A *)capsPtr; + myCaps2A->wMid = MM_UNMAPPED; + myCaps2A->wPid = MM_MPU401_MIDIOUT; + memcpy(myCaps2A->szPname, synthName, sizeof(synthName)); + myCaps2A->wTechnology = MOD_MIDIPORT; + myCaps2A->vDriverVersion = 0x0090; + myCaps2A->wVoices = 0; + myCaps2A->wNotes = 0; + myCaps2A->wChannelMask = 0xffff; + myCaps2A->dwSupport = 0; + return MMSYSERR_NOERROR; + + case(sizeof(MIDIOUTCAPS2W)): + myCaps2W = (MIDIOUTCAPS2W *)capsPtr; + myCaps2W->wMid = MM_UNMAPPED; + myCaps2W->wPid = MM_MPU401_MIDIOUT; + memcpy(myCaps2W->szPname, synthNameW, sizeof(synthNameW)); + myCaps2W->wTechnology = MOD_MIDIPORT; + myCaps2W->vDriverVersion = 0x0090; + myCaps2W->wVoices = 0; + myCaps2W->wNotes = 0; + myCaps2W->wChannelMask = 0xffff; + myCaps2W->dwSupport = 0; + return MMSYSERR_NOERROR; + + default: + return MMSYSERR_ERROR; + } +} + +void DoCallback(int driverNum, DWORD_PTR clientNum, DWORD msg, DWORD_PTR param1, DWORD_PTR param2) +{ + Driver::Client *client = &drivers[driverNum].clients[clientNum]; +#ifdef __MINGW32__ + if(s_DriverCallback) + initWorkarounds(); +#endif + DriverCallback(client->callback, client->flags, drivers[driverNum].hdrvr, msg, client->instance, param1, param2); +} + +LONG OpenDriver(Driver *driver, UINT uDeviceID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dwParam1, DWORD_PTR dwParam2) +{ + (void)uMsg; + + int clientNum; + if(driver->clientCount == 0) + clientNum = 0; + else if(driver->clientCount == MAX_CLIENTS) + return MMSYSERR_ALLOCATED; + else + { + int i; + for(i = 0; i < MAX_CLIENTS; i++) + { + if(!driver->clients[i].allocated) + break; + } + if(i == MAX_CLIENTS) + return MMSYSERR_ALLOCATED; + clientNum = i; + } + + MIDIOPENDESC *desc = (MIDIOPENDESC *)dwParam1; + driver->clients[clientNum].allocated = true; + driver->clients[clientNum].flags = HIWORD(dwParam2); + driver->clients[clientNum].callback = desc->dwCallback; + driver->clients[clientNum].instance = desc->dwInstance; + *(LONG *)dwUser = clientNum; + driver->clientCount++; + DoCallback(uDeviceID, clientNum, MOM_OPEN, (DWORD_PTR)NULL, (DWORD_PTR)NULL); + return MMSYSERR_NOERROR; +} + +LONG CloseDriver(Driver *driver, UINT uDeviceID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dwParam1, DWORD_PTR dwParam2) +{ + (void)uMsg; (void)dwParam1; (void)dwParam2; + + if(!driver->clients[dwUser].allocated) + return MMSYSERR_INVALPARAM; + driver->clients[dwUser].allocated = false; + driver->clientCount--; + DoCallback(uDeviceID, dwUser, MOM_CLOSE, (DWORD_PTR)NULL, (DWORD_PTR)NULL); + return MMSYSERR_NOERROR; +} + +EXTERN_C DWORD __declspec(dllexport) __stdcall modMessage(DWORD uDeviceID, DWORD uMsg, DWORD_PTR dwUser, DWORD_PTR dwParam1, DWORD_PTR dwParam2) +{ + MIDIHDR *midiHdr; + Driver *driver = &drivers[uDeviceID]; + DWORD instance; + switch(uMsg) + { + case MODM_OPEN: + if(!synthOpened) + { + if(midiSynth.Init() != 0) return MMSYSERR_ERROR; + synthOpened = true; + } + instance = (DWORD)NULL; + DWORD res; + res = OpenDriver(driver, uDeviceID, uMsg, dwUser, dwParam1, dwParam2); + driver->clients[*(LONG *)dwUser].synth_instance = instance; + return res; + + case MODM_CLOSE: + if(driver->clients[dwUser].allocated == false) + return MMSYSERR_ERROR; + if(synthOpened) + { + midiSynth.Reset(); + midiSynth.ResetSynth(); + } + return CloseDriver(driver, uDeviceID, uMsg, dwUser, dwParam1, dwParam2); + + case MODM_PREPARE: + return MMSYSERR_NOTSUPPORTED; + + case MODM_UNPREPARE: + return MMSYSERR_NOTSUPPORTED; + + case MODM_RESET: + if(synthOpened) + midiSynth.PanicSynth(); + return MMSYSERR_NOERROR; + + case MODM_GETDEVCAPS: + return modGetCaps((PVOID)dwParam1, (DWORD)dwParam2); + + case MODM_DATA: + if(driver->clients[dwUser].allocated == false) + return MMSYSERR_ERROR; + midiSynth.PushMIDI((DWORD)dwParam1); + return MMSYSERR_NOERROR; + + case MODM_LONGDATA: + if(driver->clients[dwUser].allocated == false) + return MMSYSERR_ERROR; + midiHdr = (MIDIHDR *)dwParam1; + if((midiHdr->dwFlags & MHDR_PREPARED) == 0) + return MIDIERR_UNPREPARED; + midiSynth.PlaySysex((unsigned char *)midiHdr->lpData, midiHdr->dwBufferLength); + midiHdr->dwFlags |= MHDR_DONE; + midiHdr->dwFlags &= ~MHDR_INQUEUE; + DoCallback(uDeviceID, dwUser, MOM_DONE, dwParam1, (DWORD_PTR)NULL); + return MMSYSERR_NOERROR; + + case MODM_GETNUMDEVS: + return 0x1; + + default: + return MMSYSERR_NOERROR; + break; + } +} diff --git a/utils/winmm_drv/src/winmm_drv.def b/utils/winmm_drv/src/winmm_drv.def new file mode 100644 index 0000000..8d105bc --- /dev/null +++ b/utils/winmm_drv/src/winmm_drv.def @@ -0,0 +1,6 @@ +LIBRARY adlmidisynth +EXPORTS + DriverProc + modMessage + adl_getBankNames + adl_getBanksCount |