aboutsummaryrefslogtreecommitdiff
path: root/utils/winmm_drv/src
diff options
context:
space:
mode:
Diffstat (limited to 'utils/winmm_drv/src')
-rw-r--r--utils/winmm_drv/src/MidiSynth.cpp720
-rw-r--r--utils/winmm_drv/src/MidiSynth.h90
-rw-r--r--utils/winmm_drv/src/stdafx.h83
-rw-r--r--utils/winmm_drv/src/targetver.h24
-rw-r--r--utils/winmm_drv/src/winmm_drv.cpp387
-rw-r--r--utils/winmm_drv/src/winmm_drv.def6
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