aboutsummaryrefslogtreecommitdiff
path: root/utils/winmm_drv/src/MidiSynth.cpp
diff options
context:
space:
mode:
authorWohlstand <admin@wohlnet.ru>2020-09-28 19:35:24 +0300
committerWohlstand <admin@wohlnet.ru>2020-09-28 19:35:24 +0300
commit455ac435481558c09ee1824b1e6dcc43f277527d (patch)
tree641268a2117543cd1a66ec9c4b3e3d790170f22e /utils/winmm_drv/src/MidiSynth.cpp
parent595a9dc35ad1c41dac96d7fd7e3b3c7ccaac9947 (diff)
parentbaefee8dbe094a05ae89b0f9b909d19982711dc7 (diff)
downloadlibADLMIDI-455ac435481558c09ee1824b1e6dcc43f277527d.tar.gz
libADLMIDI-455ac435481558c09ee1824b1e6dcc43f277527d.tar.bz2
libADLMIDI-455ac435481558c09ee1824b1e6dcc43f277527d.zip
Merge branch 'master' of github.com:Wohlstand/libADLMIDI
Diffstat (limited to 'utils/winmm_drv/src/MidiSynth.cpp')
-rw-r--r--utils/winmm_drv/src/MidiSynth.cpp720
1 files changed, 720 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();
+}
+
+}