aboutsummaryrefslogtreecommitdiffstats
path: root/Alc/backends/winmm.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Alc/backends/winmm.cpp')
-rw-r--r--Alc/backends/winmm.cpp793
1 files changed, 793 insertions, 0 deletions
diff --git a/Alc/backends/winmm.cpp b/Alc/backends/winmm.cpp
new file mode 100644
index 00000000..c32298a9
--- /dev/null
+++ b/Alc/backends/winmm.cpp
@@ -0,0 +1,793 @@
+/**
+ * OpenAL cross platform audio library
+ * Copyright (C) 1999-2007 by authors.
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * Or go to http://www.gnu.org/copyleft/lgpl.html
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <memory.h>
+
+#include <windows.h>
+#include <mmsystem.h>
+
+#include "alMain.h"
+#include "alu.h"
+#include "ringbuffer.h"
+#include "threads.h"
+
+#include "backends/base.h"
+
+namespace {
+
+#ifndef WAVE_FORMAT_IEEE_FLOAT
+#define WAVE_FORMAT_IEEE_FLOAT 0x0003
+#endif
+
+#define DEVNAME_HEAD "OpenAL Soft on "
+
+
+vector_al_string PlaybackDevices;
+vector_al_string CaptureDevices;
+
+void clear_devlist(vector_al_string *list)
+{
+ VECTOR_FOR_EACH(al_string, *list, alstr_reset);
+ VECTOR_RESIZE(*list, 0, 0);
+}
+
+
+void ProbePlaybackDevices(void)
+{
+ ALuint numdevs;
+ ALuint i;
+
+ clear_devlist(&PlaybackDevices);
+
+ numdevs = waveOutGetNumDevs();
+ VECTOR_RESIZE(PlaybackDevices, 0, numdevs);
+ for(i = 0;i < numdevs;i++)
+ {
+ WAVEOUTCAPSW WaveCaps;
+ const al_string *iter;
+ al_string dname;
+
+ AL_STRING_INIT(dname);
+ if(waveOutGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
+ {
+ ALuint count = 0;
+ while(1)
+ {
+ alstr_copy_cstr(&dname, DEVNAME_HEAD);
+ alstr_append_wcstr(&dname, WaveCaps.szPname);
+ if(count != 0)
+ {
+ char str[64];
+ snprintf(str, sizeof(str), " #%d", count+1);
+ alstr_append_cstr(&dname, str);
+ }
+ count++;
+
+#define MATCH_ENTRY(i) (alstr_cmp(dname, *(i)) == 0)
+ VECTOR_FIND_IF(iter, const al_string, PlaybackDevices, MATCH_ENTRY);
+ if(iter == VECTOR_END(PlaybackDevices)) break;
+#undef MATCH_ENTRY
+ }
+
+ TRACE("Got device \"%s\", ID %u\n", alstr_get_cstr(dname), i);
+ }
+ VECTOR_PUSH_BACK(PlaybackDevices, dname);
+ }
+}
+
+void ProbeCaptureDevices(void)
+{
+ ALuint numdevs;
+ ALuint i;
+
+ clear_devlist(&CaptureDevices);
+
+ numdevs = waveInGetNumDevs();
+ VECTOR_RESIZE(CaptureDevices, 0, numdevs);
+ for(i = 0;i < numdevs;i++)
+ {
+ WAVEINCAPSW WaveCaps;
+ const al_string *iter;
+ al_string dname;
+
+ AL_STRING_INIT(dname);
+ if(waveInGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
+ {
+ ALuint count = 0;
+ while(1)
+ {
+ alstr_copy_cstr(&dname, DEVNAME_HEAD);
+ alstr_append_wcstr(&dname, WaveCaps.szPname);
+ if(count != 0)
+ {
+ char str[64];
+ snprintf(str, sizeof(str), " #%d", count+1);
+ alstr_append_cstr(&dname, str);
+ }
+ count++;
+
+#define MATCH_ENTRY(i) (alstr_cmp(dname, *(i)) == 0)
+ VECTOR_FIND_IF(iter, const al_string, CaptureDevices, MATCH_ENTRY);
+ if(iter == VECTOR_END(CaptureDevices)) break;
+#undef MATCH_ENTRY
+ }
+
+ TRACE("Got device \"%s\", ID %u\n", alstr_get_cstr(dname), i);
+ }
+ VECTOR_PUSH_BACK(CaptureDevices, dname);
+ }
+}
+
+} // namespace
+
+struct ALCwinmmPlayback final : public ALCbackend {
+ RefCount WaveBuffersCommitted;
+ WAVEHDR WaveBuffer[4];
+
+ HWAVEOUT OutHdl;
+
+ WAVEFORMATEX Format;
+
+ ATOMIC(ALenum) killNow;
+ althrd_t thread;
+};
+
+static void ALCwinmmPlayback_Construct(ALCwinmmPlayback *self, ALCdevice *device);
+static void ALCwinmmPlayback_Destruct(ALCwinmmPlayback *self);
+
+static void CALLBACK ALCwinmmPlayback_waveOutProc(HWAVEOUT device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2);
+static int ALCwinmmPlayback_mixerProc(void *arg);
+
+static ALCenum ALCwinmmPlayback_open(ALCwinmmPlayback *self, const ALCchar *name);
+static ALCboolean ALCwinmmPlayback_reset(ALCwinmmPlayback *self);
+static ALCboolean ALCwinmmPlayback_start(ALCwinmmPlayback *self);
+static void ALCwinmmPlayback_stop(ALCwinmmPlayback *self);
+static DECLARE_FORWARD2(ALCwinmmPlayback, ALCbackend, ALCenum, captureSamples, ALCvoid*, ALCuint)
+static DECLARE_FORWARD(ALCwinmmPlayback, ALCbackend, ALCuint, availableSamples)
+static DECLARE_FORWARD(ALCwinmmPlayback, ALCbackend, ClockLatency, getClockLatency)
+static DECLARE_FORWARD(ALCwinmmPlayback, ALCbackend, void, lock)
+static DECLARE_FORWARD(ALCwinmmPlayback, ALCbackend, void, unlock)
+DECLARE_DEFAULT_ALLOCATORS(ALCwinmmPlayback)
+
+DEFINE_ALCBACKEND_VTABLE(ALCwinmmPlayback);
+
+
+static void ALCwinmmPlayback_Construct(ALCwinmmPlayback *self, ALCdevice *device)
+{
+ new (self) ALCwinmmPlayback{};
+ ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
+ SET_VTABLE2(ALCwinmmPlayback, ALCbackend, self);
+
+ InitRef(&self->WaveBuffersCommitted, 0);
+ self->OutHdl = NULL;
+
+ ATOMIC_INIT(&self->killNow, AL_TRUE);
+}
+
+static void ALCwinmmPlayback_Destruct(ALCwinmmPlayback *self)
+{
+ if(self->OutHdl)
+ waveOutClose(self->OutHdl);
+ self->OutHdl = 0;
+
+ ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
+ self->~ALCwinmmPlayback();
+}
+
+
+/* ALCwinmmPlayback_waveOutProc
+ *
+ * Posts a message to 'ALCwinmmPlayback_mixerProc' everytime a WaveOut Buffer
+ * is completed and returns to the application (for more data)
+ */
+static void CALLBACK ALCwinmmPlayback_waveOutProc(HWAVEOUT UNUSED(device), UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR UNUSED(param2))
+{
+ ALCwinmmPlayback *self = (ALCwinmmPlayback*)instance;
+
+ if(msg != WOM_DONE)
+ return;
+
+ DecrementRef(&self->WaveBuffersCommitted);
+ PostThreadMessage(self->thread, msg, 0, param1);
+}
+
+FORCE_ALIGN static int ALCwinmmPlayback_mixerProc(void *arg)
+{
+ auto self = static_cast<ALCwinmmPlayback*>(arg);
+ ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
+ WAVEHDR *WaveHdr;
+ MSG msg;
+
+ SetRTPriority();
+ althrd_setname(althrd_current(), MIXER_THREAD_NAME);
+
+ while(GetMessage(&msg, NULL, 0, 0))
+ {
+ if(msg.message != WOM_DONE)
+ continue;
+
+ if(ATOMIC_LOAD(&self->killNow, almemory_order_acquire))
+ {
+ if(ReadRef(&self->WaveBuffersCommitted) == 0)
+ break;
+ continue;
+ }
+
+ WaveHdr = ((WAVEHDR*)msg.lParam);
+ ALCwinmmPlayback_lock(self);
+ aluMixData(device, WaveHdr->lpData, WaveHdr->dwBufferLength /
+ self->Format.nBlockAlign);
+ ALCwinmmPlayback_unlock(self);
+
+ // Send buffer back to play more data
+ waveOutWrite(self->OutHdl, WaveHdr, sizeof(WAVEHDR));
+ IncrementRef(&self->WaveBuffersCommitted);
+ }
+
+ return 0;
+}
+
+
+static ALCenum ALCwinmmPlayback_open(ALCwinmmPlayback *self, const ALCchar *deviceName)
+{
+ ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
+ const al_string *iter;
+ UINT DeviceID;
+ MMRESULT res;
+
+ if(VECTOR_SIZE(PlaybackDevices) == 0)
+ ProbePlaybackDevices();
+
+ // Find the Device ID matching the deviceName if valid
+#define MATCH_DEVNAME(iter) (!alstr_empty(*(iter)) && \
+ (!deviceName || alstr_cmp_cstr(*(iter), deviceName) == 0))
+ VECTOR_FIND_IF(iter, const al_string, PlaybackDevices, MATCH_DEVNAME);
+ if(iter == VECTOR_END(PlaybackDevices))
+ return ALC_INVALID_VALUE;
+#undef MATCH_DEVNAME
+
+ DeviceID = (UINT)(iter - VECTOR_BEGIN(PlaybackDevices));
+
+retry_open:
+ memset(&self->Format, 0, sizeof(WAVEFORMATEX));
+ if(device->FmtType == DevFmtFloat)
+ {
+ self->Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
+ self->Format.wBitsPerSample = 32;
+ }
+ else
+ {
+ self->Format.wFormatTag = WAVE_FORMAT_PCM;
+ if(device->FmtType == DevFmtUByte || device->FmtType == DevFmtByte)
+ self->Format.wBitsPerSample = 8;
+ else
+ self->Format.wBitsPerSample = 16;
+ }
+ self->Format.nChannels = ((device->FmtChans == DevFmtMono) ? 1 : 2);
+ self->Format.nBlockAlign = self->Format.wBitsPerSample *
+ self->Format.nChannels / 8;
+ self->Format.nSamplesPerSec = device->Frequency;
+ self->Format.nAvgBytesPerSec = self->Format.nSamplesPerSec *
+ self->Format.nBlockAlign;
+ self->Format.cbSize = 0;
+
+ if((res=waveOutOpen(&self->OutHdl, DeviceID, &self->Format, (DWORD_PTR)&ALCwinmmPlayback_waveOutProc, (DWORD_PTR)self, CALLBACK_FUNCTION)) != MMSYSERR_NOERROR)
+ {
+ if(device->FmtType == DevFmtFloat)
+ {
+ device->FmtType = DevFmtShort;
+ goto retry_open;
+ }
+ ERR("waveOutOpen failed: %u\n", res);
+ goto failure;
+ }
+
+ alstr_copy(&device->DeviceName, VECTOR_ELEM(PlaybackDevices, DeviceID));
+ return ALC_NO_ERROR;
+
+failure:
+ if(self->OutHdl)
+ waveOutClose(self->OutHdl);
+ self->OutHdl = NULL;
+
+ return ALC_INVALID_VALUE;
+}
+
+static ALCboolean ALCwinmmPlayback_reset(ALCwinmmPlayback *self)
+{
+ ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
+
+ device->UpdateSize = (ALuint)((ALuint64)device->UpdateSize *
+ self->Format.nSamplesPerSec /
+ device->Frequency);
+ device->UpdateSize = (device->UpdateSize*device->NumUpdates + 3) / 4;
+ device->NumUpdates = 4;
+ device->Frequency = self->Format.nSamplesPerSec;
+
+ if(self->Format.wFormatTag == WAVE_FORMAT_IEEE_FLOAT)
+ {
+ if(self->Format.wBitsPerSample == 32)
+ device->FmtType = DevFmtFloat;
+ else
+ {
+ ERR("Unhandled IEEE float sample depth: %d\n", self->Format.wBitsPerSample);
+ return ALC_FALSE;
+ }
+ }
+ else if(self->Format.wFormatTag == WAVE_FORMAT_PCM)
+ {
+ if(self->Format.wBitsPerSample == 16)
+ device->FmtType = DevFmtShort;
+ else if(self->Format.wBitsPerSample == 8)
+ device->FmtType = DevFmtUByte;
+ else
+ {
+ ERR("Unhandled PCM sample depth: %d\n", self->Format.wBitsPerSample);
+ return ALC_FALSE;
+ }
+ }
+ else
+ {
+ ERR("Unhandled format tag: 0x%04x\n", self->Format.wFormatTag);
+ return ALC_FALSE;
+ }
+
+ if(self->Format.nChannels == 2)
+ device->FmtChans = DevFmtStereo;
+ else if(self->Format.nChannels == 1)
+ device->FmtChans = DevFmtMono;
+ else
+ {
+ ERR("Unhandled channel count: %d\n", self->Format.nChannels);
+ return ALC_FALSE;
+ }
+ SetDefaultWFXChannelOrder(device);
+
+ return ALC_TRUE;
+}
+
+static ALCboolean ALCwinmmPlayback_start(ALCwinmmPlayback *self)
+{
+ ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
+ ALbyte *BufferData;
+ ALint BufferSize;
+ ALuint i;
+
+ ATOMIC_STORE(&self->killNow, AL_FALSE, almemory_order_release);
+ if(althrd_create(&self->thread, ALCwinmmPlayback_mixerProc, self) != althrd_success)
+ return ALC_FALSE;
+
+ InitRef(&self->WaveBuffersCommitted, 0);
+
+ // Create 4 Buffers
+ BufferSize = device->UpdateSize*device->NumUpdates / 4;
+ BufferSize *= FrameSizeFromDevFmt(device->FmtChans, device->FmtType, device->AmbiOrder);
+
+ BufferData = static_cast<ALbyte*>(calloc(4, BufferSize));
+ for(i = 0;i < 4;i++)
+ {
+ memset(&self->WaveBuffer[i], 0, sizeof(WAVEHDR));
+ self->WaveBuffer[i].dwBufferLength = BufferSize;
+ self->WaveBuffer[i].lpData = ((i==0) ? (CHAR*)BufferData :
+ (self->WaveBuffer[i-1].lpData +
+ self->WaveBuffer[i-1].dwBufferLength));
+ waveOutPrepareHeader(self->OutHdl, &self->WaveBuffer[i], sizeof(WAVEHDR));
+ waveOutWrite(self->OutHdl, &self->WaveBuffer[i], sizeof(WAVEHDR));
+ IncrementRef(&self->WaveBuffersCommitted);
+ }
+
+ return ALC_TRUE;
+}
+
+static void ALCwinmmPlayback_stop(ALCwinmmPlayback *self)
+{
+ void *buffer = NULL;
+ int i;
+
+ if(ATOMIC_EXCHANGE(&self->killNow, AL_TRUE, almemory_order_acq_rel))
+ return;
+ althrd_join(self->thread, &i);
+
+ // Release the wave buffers
+ for(i = 0;i < 4;i++)
+ {
+ waveOutUnprepareHeader(self->OutHdl, &self->WaveBuffer[i], sizeof(WAVEHDR));
+ if(i == 0) buffer = self->WaveBuffer[i].lpData;
+ self->WaveBuffer[i].lpData = NULL;
+ }
+ free(buffer);
+}
+
+
+
+struct ALCwinmmCapture final : public ALCbackend {
+ RefCount WaveBuffersCommitted;
+ WAVEHDR WaveBuffer[4];
+
+ HWAVEIN InHdl;
+
+ ll_ringbuffer_t *Ring;
+
+ WAVEFORMATEX Format;
+
+ ATOMIC(ALenum) killNow;
+ althrd_t thread;
+};
+
+static void ALCwinmmCapture_Construct(ALCwinmmCapture *self, ALCdevice *device);
+static void ALCwinmmCapture_Destruct(ALCwinmmCapture *self);
+
+static void CALLBACK ALCwinmmCapture_waveInProc(HWAVEIN device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2);
+static int ALCwinmmCapture_captureProc(void *arg);
+
+static ALCenum ALCwinmmCapture_open(ALCwinmmCapture *self, const ALCchar *name);
+static DECLARE_FORWARD(ALCwinmmCapture, ALCbackend, ALCboolean, reset)
+static ALCboolean ALCwinmmCapture_start(ALCwinmmCapture *self);
+static void ALCwinmmCapture_stop(ALCwinmmCapture *self);
+static ALCenum ALCwinmmCapture_captureSamples(ALCwinmmCapture *self, ALCvoid *buffer, ALCuint samples);
+static ALCuint ALCwinmmCapture_availableSamples(ALCwinmmCapture *self);
+static DECLARE_FORWARD(ALCwinmmCapture, ALCbackend, ClockLatency, getClockLatency)
+static DECLARE_FORWARD(ALCwinmmCapture, ALCbackend, void, lock)
+static DECLARE_FORWARD(ALCwinmmCapture, ALCbackend, void, unlock)
+DECLARE_DEFAULT_ALLOCATORS(ALCwinmmCapture)
+
+DEFINE_ALCBACKEND_VTABLE(ALCwinmmCapture);
+
+
+static void ALCwinmmCapture_Construct(ALCwinmmCapture *self, ALCdevice *device)
+{
+ new (self) ALCwinmmCapture{};
+ ALCbackend_Construct(STATIC_CAST(ALCbackend, self), device);
+ SET_VTABLE2(ALCwinmmCapture, ALCbackend, self);
+
+ InitRef(&self->WaveBuffersCommitted, 0);
+ self->InHdl = NULL;
+
+ ATOMIC_INIT(&self->killNow, AL_TRUE);
+}
+
+static void ALCwinmmCapture_Destruct(ALCwinmmCapture *self)
+{
+ void *buffer = NULL;
+ int i;
+
+ /* Tell the processing thread to quit and wait for it to do so. */
+ if(!ATOMIC_EXCHANGE(&self->killNow, AL_TRUE, almemory_order_acq_rel))
+ {
+ PostThreadMessage(self->thread, WM_QUIT, 0, 0);
+
+ althrd_join(self->thread, &i);
+
+ /* Make sure capture is stopped and all pending buffers are flushed. */
+ waveInReset(self->InHdl);
+
+ // Release the wave buffers
+ for(i = 0;i < 4;i++)
+ {
+ waveInUnprepareHeader(self->InHdl, &self->WaveBuffer[i], sizeof(WAVEHDR));
+ if(i == 0) buffer = self->WaveBuffer[i].lpData;
+ self->WaveBuffer[i].lpData = NULL;
+ }
+ free(buffer);
+ }
+
+ ll_ringbuffer_free(self->Ring);
+ self->Ring = NULL;
+
+ // Close the Wave device
+ if(self->InHdl)
+ waveInClose(self->InHdl);
+ self->InHdl = 0;
+
+ ALCbackend_Destruct(STATIC_CAST(ALCbackend, self));
+ self->~ALCwinmmCapture();
+}
+
+
+/* ALCwinmmCapture_waveInProc
+ *
+ * Posts a message to 'ALCwinmmCapture_captureProc' everytime a WaveIn Buffer
+ * is completed and returns to the application (with more data).
+ */
+static void CALLBACK ALCwinmmCapture_waveInProc(HWAVEIN UNUSED(device), UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR UNUSED(param2))
+{
+ ALCwinmmCapture *self = (ALCwinmmCapture*)instance;
+
+ if(msg != WIM_DATA)
+ return;
+
+ DecrementRef(&self->WaveBuffersCommitted);
+ PostThreadMessage(self->thread, msg, 0, param1);
+}
+
+static int ALCwinmmCapture_captureProc(void *arg)
+{
+ auto self = static_cast<ALCwinmmCapture*>(arg);
+ WAVEHDR *WaveHdr;
+ MSG msg;
+
+ althrd_setname(althrd_current(), RECORD_THREAD_NAME);
+
+ while(GetMessage(&msg, NULL, 0, 0))
+ {
+ if(msg.message != WIM_DATA)
+ continue;
+ /* Don't wait for other buffers to finish before quitting. We're
+ * closing so we don't need them. */
+ if(ATOMIC_LOAD(&self->killNow, almemory_order_acquire))
+ break;
+
+ WaveHdr = ((WAVEHDR*)msg.lParam);
+ ll_ringbuffer_write(self->Ring, WaveHdr->lpData,
+ WaveHdr->dwBytesRecorded / self->Format.nBlockAlign
+ );
+
+ // Send buffer back to capture more data
+ waveInAddBuffer(self->InHdl, WaveHdr, sizeof(WAVEHDR));
+ IncrementRef(&self->WaveBuffersCommitted);
+ }
+
+ return 0;
+}
+
+
+static ALCenum ALCwinmmCapture_open(ALCwinmmCapture *self, const ALCchar *name)
+{
+ ALCdevice *device = STATIC_CAST(ALCbackend, self)->mDevice;
+ const al_string *iter;
+ ALbyte *BufferData = NULL;
+ DWORD CapturedDataSize;
+ ALint BufferSize;
+ UINT DeviceID;
+ MMRESULT res;
+ ALuint i;
+
+ if(VECTOR_SIZE(CaptureDevices) == 0)
+ ProbeCaptureDevices();
+
+ // Find the Device ID matching the deviceName if valid
+#define MATCH_DEVNAME(iter) (!alstr_empty(*(iter)) && (!name || alstr_cmp_cstr(*iter, name) == 0))
+ VECTOR_FIND_IF(iter, const al_string, CaptureDevices, MATCH_DEVNAME);
+ if(iter == VECTOR_END(CaptureDevices))
+ return ALC_INVALID_VALUE;
+#undef MATCH_DEVNAME
+
+ DeviceID = (UINT)(iter - VECTOR_BEGIN(CaptureDevices));
+
+ switch(device->FmtChans)
+ {
+ case DevFmtMono:
+ case DevFmtStereo:
+ break;
+
+ case DevFmtQuad:
+ case DevFmtX51:
+ case DevFmtX51Rear:
+ case DevFmtX61:
+ case DevFmtX71:
+ case DevFmtAmbi3D:
+ return ALC_INVALID_ENUM;
+ }
+
+ switch(device->FmtType)
+ {
+ case DevFmtUByte:
+ case DevFmtShort:
+ case DevFmtInt:
+ case DevFmtFloat:
+ break;
+
+ case DevFmtByte:
+ case DevFmtUShort:
+ case DevFmtUInt:
+ return ALC_INVALID_ENUM;
+ }
+
+ memset(&self->Format, 0, sizeof(WAVEFORMATEX));
+ self->Format.wFormatTag = ((device->FmtType == DevFmtFloat) ?
+ WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM);
+ self->Format.nChannels = ChannelsFromDevFmt(device->FmtChans, device->AmbiOrder);
+ self->Format.wBitsPerSample = BytesFromDevFmt(device->FmtType) * 8;
+ self->Format.nBlockAlign = self->Format.wBitsPerSample *
+ self->Format.nChannels / 8;
+ self->Format.nSamplesPerSec = device->Frequency;
+ self->Format.nAvgBytesPerSec = self->Format.nSamplesPerSec *
+ self->Format.nBlockAlign;
+ self->Format.cbSize = 0;
+
+ if((res=waveInOpen(&self->InHdl, DeviceID, &self->Format, (DWORD_PTR)&ALCwinmmCapture_waveInProc, (DWORD_PTR)self, CALLBACK_FUNCTION)) != MMSYSERR_NOERROR)
+ {
+ ERR("waveInOpen failed: %u\n", res);
+ goto failure;
+ }
+
+ // Allocate circular memory buffer for the captured audio
+ CapturedDataSize = device->UpdateSize*device->NumUpdates;
+
+ // Make sure circular buffer is at least 100ms in size
+ if(CapturedDataSize < (self->Format.nSamplesPerSec / 10))
+ CapturedDataSize = self->Format.nSamplesPerSec / 10;
+
+ self->Ring = ll_ringbuffer_create(CapturedDataSize, self->Format.nBlockAlign, false);
+ if(!self->Ring) goto failure;
+
+ InitRef(&self->WaveBuffersCommitted, 0);
+
+ // Create 4 Buffers of 50ms each
+ BufferSize = self->Format.nAvgBytesPerSec / 20;
+ BufferSize -= (BufferSize % self->Format.nBlockAlign);
+
+ BufferData = static_cast<ALbyte*>(calloc(4, BufferSize));
+ if(!BufferData) goto failure;
+
+ for(i = 0;i < 4;i++)
+ {
+ memset(&self->WaveBuffer[i], 0, sizeof(WAVEHDR));
+ self->WaveBuffer[i].dwBufferLength = BufferSize;
+ self->WaveBuffer[i].lpData = ((i==0) ? (CHAR*)BufferData :
+ (self->WaveBuffer[i-1].lpData +
+ self->WaveBuffer[i-1].dwBufferLength));
+ self->WaveBuffer[i].dwFlags = 0;
+ self->WaveBuffer[i].dwLoops = 0;
+ waveInPrepareHeader(self->InHdl, &self->WaveBuffer[i], sizeof(WAVEHDR));
+ waveInAddBuffer(self->InHdl, &self->WaveBuffer[i], sizeof(WAVEHDR));
+ IncrementRef(&self->WaveBuffersCommitted);
+ }
+
+ ATOMIC_STORE(&self->killNow, AL_FALSE, almemory_order_release);
+ if(althrd_create(&self->thread, ALCwinmmCapture_captureProc, self) != althrd_success)
+ goto failure;
+
+ alstr_copy(&device->DeviceName, VECTOR_ELEM(CaptureDevices, DeviceID));
+ return ALC_NO_ERROR;
+
+failure:
+ if(BufferData)
+ {
+ for(i = 0;i < 4;i++)
+ waveInUnprepareHeader(self->InHdl, &self->WaveBuffer[i], sizeof(WAVEHDR));
+ free(BufferData);
+ }
+
+ ll_ringbuffer_free(self->Ring);
+ self->Ring = NULL;
+
+ if(self->InHdl)
+ waveInClose(self->InHdl);
+ self->InHdl = NULL;
+
+ return ALC_INVALID_VALUE;
+}
+
+static ALCboolean ALCwinmmCapture_start(ALCwinmmCapture *self)
+{
+ waveInStart(self->InHdl);
+ return ALC_TRUE;
+}
+
+static void ALCwinmmCapture_stop(ALCwinmmCapture *self)
+{
+ waveInStop(self->InHdl);
+}
+
+static ALCenum ALCwinmmCapture_captureSamples(ALCwinmmCapture *self, ALCvoid *buffer, ALCuint samples)
+{
+ ll_ringbuffer_read(self->Ring, static_cast<char*>(buffer), samples);
+ return ALC_NO_ERROR;
+}
+
+static ALCuint ALCwinmmCapture_availableSamples(ALCwinmmCapture *self)
+{
+ return (ALCuint)ll_ringbuffer_read_space(self->Ring);
+}
+
+
+struct ALCwinmmBackendFactory final : public ALCbackendFactory {
+ ALCwinmmBackendFactory() noexcept;
+};
+#define ALCWINMMBACKENDFACTORY_INITIALIZER GET_VTABLE2(ALCwinmmBackendFactory, ALCbackendFactory)
+
+static ALCboolean ALCwinmmBackendFactory_init(ALCwinmmBackendFactory *self);
+static void ALCwinmmBackendFactory_deinit(ALCwinmmBackendFactory *self);
+static ALCboolean ALCwinmmBackendFactory_querySupport(ALCwinmmBackendFactory *self, ALCbackend_Type type);
+static void ALCwinmmBackendFactory_probe(ALCwinmmBackendFactory *self, enum DevProbe type, al_string *outnames);
+static ALCbackend* ALCwinmmBackendFactory_createBackend(ALCwinmmBackendFactory *self, ALCdevice *device, ALCbackend_Type type);
+
+DEFINE_ALCBACKENDFACTORY_VTABLE(ALCwinmmBackendFactory);
+
+ALCwinmmBackendFactory::ALCwinmmBackendFactory() noexcept
+ : ALCbackendFactory{ALCWINMMBACKENDFACTORY_INITIALIZER}
+{ }
+
+
+static ALCboolean ALCwinmmBackendFactory_init(ALCwinmmBackendFactory* UNUSED(self))
+{
+ VECTOR_INIT(PlaybackDevices);
+ VECTOR_INIT(CaptureDevices);
+
+ return ALC_TRUE;
+}
+
+static void ALCwinmmBackendFactory_deinit(ALCwinmmBackendFactory* UNUSED(self))
+{
+ clear_devlist(&PlaybackDevices);
+ VECTOR_DEINIT(PlaybackDevices);
+
+ clear_devlist(&CaptureDevices);
+ VECTOR_DEINIT(CaptureDevices);
+}
+
+static ALCboolean ALCwinmmBackendFactory_querySupport(ALCwinmmBackendFactory* UNUSED(self), ALCbackend_Type type)
+{
+ if(type == ALCbackend_Playback || type == ALCbackend_Capture)
+ return ALC_TRUE;
+ return ALC_FALSE;
+}
+
+static void ALCwinmmBackendFactory_probe(ALCwinmmBackendFactory* UNUSED(self), enum DevProbe type, al_string *outnames)
+{
+ switch(type)
+ {
+#define APPEND_OUTNAME(n) do { \
+ if(!alstr_empty(*(n))) \
+ alstr_append_range(outnames, VECTOR_BEGIN(*(n)), VECTOR_END(*(n))+1); \
+} while(0)
+ case ALL_DEVICE_PROBE:
+ ProbePlaybackDevices();
+ VECTOR_FOR_EACH(const al_string, PlaybackDevices, APPEND_OUTNAME);
+ break;
+
+ case CAPTURE_DEVICE_PROBE:
+ ProbeCaptureDevices();
+ VECTOR_FOR_EACH(const al_string, CaptureDevices, APPEND_OUTNAME);
+ break;
+#undef APPEND_OUTNAME
+ }
+}
+
+static ALCbackend* ALCwinmmBackendFactory_createBackend(ALCwinmmBackendFactory* UNUSED(self), ALCdevice *device, ALCbackend_Type type)
+{
+ if(type == ALCbackend_Playback)
+ {
+ ALCwinmmPlayback *backend;
+ NEW_OBJ(backend, ALCwinmmPlayback)(device);
+ if(!backend) return NULL;
+ return STATIC_CAST(ALCbackend, backend);
+ }
+ if(type == ALCbackend_Capture)
+ {
+ ALCwinmmCapture *backend;
+ NEW_OBJ(backend, ALCwinmmCapture)(device);
+ if(!backend) return NULL;
+ return STATIC_CAST(ALCbackend, backend);
+ }
+
+ return NULL;
+}
+
+ALCbackendFactory *ALCwinmmBackendFactory_getFactory(void)
+{
+ static ALCwinmmBackendFactory factory{};
+ return STATIC_CAST(ALCbackendFactory, &factory);
+}