Commit d2920b18 authored by David Reid's avatar David Reid

Initial commit.

parents
mini_al - Mini Audio Library
============================
mini_al is a simple library for playing and recording audio. It's focused on simplicity and has
a very small number of APIs. This is not a full-featured audio library, nor will it ever be. It
is intended to be used as a quick and easy way to connect to an audio device and deliver and
capture audio data from speakers and microphones.
C/C++, single file, public domain.
Features
========
- A very simple API.
- Both synchronous and asynchronous APIs.
Examples
========
Asynchonous API
---------------
In asynchronous mode, mini_al will request and deliver audio data through callbacks.
```
mal_uint32 on_send_audio_data(mal_device* pDevice, mal_uint32 sampleCount, void* pSamples)
{
return drwav_read_f32((drwav*)pDevice->pUserData, sampleCount, pSamples);
}
int main()
{
mal_uint32 channels = 2;
mal_uint32 sampleRate = 44100;
mal_uint32 fragmentSizeInFrames = 512;
mal_uint32 fragmentCount = 2;
mal_device playbackDevice;
if (mal_device_init(&playbackDevice, mal_device_type_playback, NULL, mal_format_f32, channels, sampleRate, fragmentSizeInFrames, fragmentCount) != MAL_SUCCESS) {
return -1;
}
mal_device_set_send_callback(&playbackDevice, on_send_audio_data);
mal_device_start(&playbackDevice);
printf("Press Enter to quit...\n");
getchar();
mal_device_uninit(&playbackDevice);
return 0;
}
```
Synchronous API
---------------
In synchronous mode, the application submits audio data to the device using APIs which block until
the device is ready to receive it.
```
int main()
{
mal_uint32 channels = 2;
mal_uint32 sampleRate = 44100;
mal_uint32 fragmentSizeInFrames = 512;
mal_uint32 fragmentCount = 2;
mal_device playbackDevice;
if (mal_device_init_synchronous(&playbackDevice, mal_device_type_playback, NULL, mal_format_f32, channels, sampleRate, fragmentSizeInFrames, fragmentCount) != MAL_SUCCESS) {
return -1;
}
mal_device_start(&playbackDevice);
for (;;) {
float pSamples[512];
mal_uint32 samplesToWrite = drwav_read_f32(&wav, fragmentSizeInFrames * fragmentCount, pSamples);
if (samplesToWrite == 0) {
break;
}
mal_uint32 samplesWritten = mal_device_write(&playbackDevice, samplesToWrite, pSamples);
if (samplesWritte != samplesToWrite) {
break;
}
}
mal_device_uninit(&playbackDevice);
return 0;
}
```
\ No newline at end of file
// Mini audio library. Public domain. See "unlicense" statement at the end of this file.
// mini_al - v0.0 - UNRELEASED
//
// David Reid - mackron@gmail.com
// USAGE
// =====
//
// (WRITE ME)
//
//
// NOTES
// =====
// - This library uses an asynchronous API delivering and requesting audio data. Each device will have
// it's own worker thread which is managed by the library.
// - This is not currently thread-safe, but can still be used from multiple threads if you do your own
// synchronization.
#ifndef dr_mal_h
#define dr_mal_h
#ifdef __cplusplus
extern "C" {
#endif
// Platform/backend detection.
#ifdef _WIN32
#define MAL_WIN32
#ifndef MAL_NO_DSOUND
#define MAL_ENABLE_DSOUND
#endif
#else
#define MAL_POSIX
#if !defined(MAL_NO_ALSA) && defined(__linux__)
#define MAL_ENABLE_ALSA
#endif
// Unfortunate #includes, but needed for pthread_t and sem_t types.
#include <pthread.h>
#include <semaphore.h>
#endif
#ifndef MAL_NO_NULL
#define MAL_ENABLE_NULL
#endif
#if defined(_MSC_VER) && _MSC_VER < 1600
typedef signed char mal_int8;
typedef unsigned char mal_uint8;
typedef signed short mal_int16;
typedef unsigned short mal_uint16;
typedef signed int mal_int32;
typedef unsigned int mal_uint32;
typedef signed __int64 mal_int64;
typedef unsigned __int64 mal_uint64;
#else
#include <stdint.h>
typedef int8_t mal_int8;
typedef uint8_t mal_uint8;
typedef int16_t mal_int16;
typedef uint16_t mal_uint16;
typedef int32_t mal_int32;
typedef uint32_t mal_uint32;
typedef int64_t mal_int64;
typedef uint64_t mal_uint64;
#endif
typedef mal_int8 mal_bool8;
typedef mal_int32 mal_bool32;
#define MAL_TRUE 1
#define MAL_FALSE 0
typedef void* mal_handle;
typedef void* mal_ptr;
#ifdef MAL_WIN32
typedef mal_handle mal_thread;
typedef mal_handle mal_semaphore;
#else
typedef pthread_t mal_thread;
typedef sem_t mal_semaphore;
#endif
#ifdef MAL_ENABLE_DSOUND
#define MAL_MAX_FRAGMENTS_DSOUND 16
#endif
typedef int mal_result;
#define MAL_SUCCESS 0
#define MAL_UNKNOWN_ERROR -1
#define MAL_INVALID_ARGS -2
#define MAL_OUT_OF_MEMORY -3
#define MAL_NO_BACKEND -16
#define MAL_DEVICE_ALREADY_STARTED -17
#define MAL_DEVICE_ALREADY_STOPPED -18
#define MAL_FAILED_TO_INIT_BACKEND -19
#define MAL_FORMAT_NOT_SUPPORTED -20
typedef struct mal_device mal_device;
typedef void (* mal_recv_proc)(mal_device* pDevice, mal_uint32 sampleCount, const void* pSamples);
typedef mal_uint32 (* mal_send_proc)(mal_device* pDevice, mal_uint32 sampleCount, void* pSamples);
typedef enum
{
mal_api_null,
mal_api_dsound,
mal_api_alsa
} mal_api;
typedef enum
{
mal_device_type_playback,
mal_device_type_capture
} mal_device_type;
typedef enum
{
// I like to keep these explicitly defined because they're used as a key into a lookup table.
mal_format_u8 = 0,
mal_format_s16 = 1,
mal_format_s24 = 2,
mal_format_s32 = 3,
mal_format_f32 = 4,
mal_format_f64 = 5,
mal_format_alaw = 6,
mal_format_mulaw = 7
} mal_format;
typedef union
{
char name[256]; // ALSA uses a name string for identification.
mal_uint8 guid[16]; // DirectSound uses a GUID to identify a device.
} mal_device_id;
typedef struct
{
mal_device_id id;
char description[256];
} mal_device_info;
struct mal_device
{
mal_api api; // DirectSound, ALSA, etc.
mal_device_type type;
mal_format format;
mal_uint32 channels;
mal_uint32 sampleRate;
mal_uint32 fragmentSizeInFrames;
mal_uint32 fragmentCount;
mal_uint32 flags;
mal_recv_proc onRecv;
mal_send_proc onSend;
void* pUserData; // Application defined data.
union
{
#ifdef MAL_ENABLE_DSOUND
struct
{
/*HMODULE*/ mal_handle hDSoundDLL;
/*LPDIRECTSOUND8*/ mal_ptr pPlayback;
/*LPDIRECTSOUNDBUFFER*/ mal_ptr pPlaybackPrimaryBuffer;
/*LPDIRECTSOUNDBUFFER*/ mal_ptr pPlaybackBuffer;
/*LPDIRECTSOUNDCAPTURE8*/ mal_ptr pCapture;
/*LPDIRECTSOUNDCAPTUREBUFFER8*/ mal_ptr pCaptureBuffer;
/*LPDIRECTSOUNDNOTIFY*/ mal_ptr pNotify;
/*HANDLE*/ mal_handle pNotifyEvents[MAL_MAX_FRAGMENTS_DSOUND]; // One event handle for each fragment.
/*HANDLE*/ mal_handle hStopEvent;
mal_thread thread;
mal_semaphore semaphore; // <-- This is used to wake up the worker thread.
} dsound;
#endif
#ifdef MAL_ENABLE_ALSA
struct
{
/*snd_pcm_t**/mal_ptr pPCM;
mal_thread thread;
mal_semaphore semaphore; // <-- This is used to wake up the thread.
void* pIntermediaryBuffer;
} alsa;
#endif
#ifdef MAL_ENABLE_NULL
struct
{
int unused;
} null_device;
#endif
};
};
// Enumerates over each device of the given type (playback or capture).
//
// Thread Safety: SAFE, SEE NOTES.
// This API uses an application-defined buffer for output. This is thread-safe so long as the
// application ensures mutal exclusion to the output buffer at their level.
//
// Efficiency: SLOW
// This API dynamically links to backend DLLs/SOs (such as dsound.dll).
mal_result mal_enumerate_devices(mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo);
// Initializes a device.
//
// The device ID (pDeviceID) can be null, in which case the default device is used. Otherwise, you
// can retrieve the ID by calling mal_enumerate_devices() and retrieve the ID from the returned
// information.
//
// This will try it's hardest to create a valid device, even if it means adjusting input arguments.
// Use pDevice->channels, pDevice->sampleRate, etc. to determine the actual properties after
// initialization.
//
// Thread Safety: SAFE
// This API is thread safe so long as the application does not try to use the device object before
// this call has returned.
//
// Efficiency: SLOW
// This API will dynamically link to backend DLLs/SOs like dsound.dll, and is otherwise just slow
// due to the fact that it's an initialization API.
mal_result mal_device_init(mal_device* pDevice, mal_device_type type, mal_device_id* pDeviceID, mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_uint32 fragmentSizeInFrames, mal_uint32 fragmentCount);
// Uninitializes a device.
//
// This will explicitly stop the device. You do not need to call mal_device_stop() beforehand, but it's
// harmless if you do.
//
// Thread Safety: UNSAFE
// This API shouldn't crash in a multi-threaded environment, but results are undefined if an application
// attempts to do something with the device at the same time as uninitializing.
//
// Efficiency: SLOW
// This will stop the device with mal_device_stop() which is a slow, synchronized call. It also needs
// to destroy internal objects like the backend-specific objects and the background thread.
void mal_device_uninit(mal_device* pDevice);
// Sets the callback to use when the application has receives data from the device.
//
// Thread Safety: SAFE
// This API is implemented as a simple atomic assignment.
//
// Efficiency: FAST
// This is just an atomic assignment.
void mal_device_set_recv_callback(mal_device* pDevice, mal_recv_proc proc);
// Sets the callback to use when the application needs to send data to the device for playback.
//
// Thread Safety: SAFE
// This API is implemented as a simple atomic assignment.
//
// Efficiency: FAST
// This is just an atomic assignment.
void mal_device_set_send_callback(mal_device* pDevice, mal_send_proc proc);
// Activates the device. For playback devices this begins playback. For recording devices it begins
// recording.
//
// Thread Safety: SAFE
//
// Efficiency: SLOW
// This API needs to wait on the worker thread via a semaphore.
mal_result mal_device_start(mal_device* pDevice);
// Puts the device to sleep, but does not uninitialize it. Use mal_device_start() to start it up again.
//
// Thread Safety: SAFE
//
// Efficiency: SLOW
// This API needs to wait on the worker thread to stop the backend device properly before returning.
mal_result mal_device_stop(mal_device* pDevice);
// Determines whether or not the device is started.
//
// Thread Safety: SAFE
// If another thread calls mal_device_start() or mal_device_stop() at this same time as this function
// is called, there's a very small chance the return value will out of sync.
//
// Efficiency: FAST
// This is implemented with a simple accessor.
mal_bool32 mal_device_is_started(mal_device* pDevice);
// Retrieves the size of a fragment in bytes for the given device.
//
// Thread Safety: SAFE
// This is calculated from constant values which are set at initialization time and never change.
//
// Efficiency: FAST
// This is implemented with just a few 32-bit integer multiplications.
mal_uint32 mal_device_get_fragment_size_in_bytes(mal_device* pDevice);
// Retrieves the size of a sample in bytes for the given format.
//
// Thread Safety: SAFE
// This is API is pure.
//
// Efficiency: FAST
// This is implemented with a lookup table.
mal_uint32 mal_get_sample_size_in_bytes(mal_format format);
#ifdef __cplusplus
}
#endif
#endif //dr_mal_h
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// IMPLEMENTATION
//
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
#ifdef MAL_IMPLEMENTATION
#ifdef MAL_WIN32
#include <windows.h>
#else
#include <string.h> // For memset()
#endif
#ifdef MAL_POSIX
#include <pthread.h>
#include <semaphore.h>
#endif
#include <assert.h>
#include <errno.h>
#include <stdio.h> // For printf() debugging. TODO: Delete this and replace with a proper logging system.
#ifdef MAL_WIN32
#define MAL_THREADCALL WINAPI
typedef mal_uint32 mal_thread_result;
#else
#define MAL_THREADCALL
typedef void* mal_thread_result;
#endif
typedef mal_thread_result (MAL_THREADCALL * mal_thread_entry_proc)(void* pData);
#define MAL_FLAG_INITIALIZED (1 << 0)
#define MAL_FLAG_AWAKE (1 << 1)
#define MAL_FLAG_STARTED (1 << 2) // Whether or not the device is currently awake and running.
#define MAL_FLAG_TERMINATING (1 << 3) // Used for thread management.
///////////////////////////////////////////////////////////////////////////////
//
// Standard Library Stuff
//
///////////////////////////////////////////////////////////////////////////////
#ifndef mal_zero_memory
#ifdef MAL_WIN32
#define mal_zero_memory(p, sz) ZeroMemory((p), (sz))
#else
#define mal_zero_memory(p, sz) memset((p), 0, (sz))
#endif
#endif
#define mal_zero_object(p) mal_zero_memory((p), sizeof(*(p)))
#ifndef mal_copy_memory
#ifdef _WIN32
#define mal_copy_memory(dst, src, sz) CopyMemory((dst), (src), (sz))
#else
#define mal_copy_memory(dst, src, sz) memcpy((dst), (src), (sz))
#endif
#endif
#ifndef mal_malloc
#ifdef MAL_WIN32
#define mal_malloc(sz) HeapAlloc(GetProcessHeap(), 0, (sz))
#else
#define mal_malloc(sz) malloc((sz))
#endif
#endif
#ifndef mal_free
#ifdef MAL_WIN32
#define mal_free(p) HeapFree(GetProcessHeap(), 0, (p))
#else
#define mal_free(p) free((p))
#endif
#endif
#ifndef mal_assert
#ifdef MAL_WIN32
#define mal_assert(condition) assert(condition)
#else
#define mal_assert(condition) assert(condition)
#endif
#endif
static int mal_strncpy_s(char* dst, size_t dstSizeInBytes, const char* src, size_t count)
{
if (dst == 0) {
return EINVAL;
}
if (dstSizeInBytes == 0) {
return EINVAL;
}
if (src == 0) {
dst[0] = '\0';
return EINVAL;
}
size_t maxcount = count;
if (count == ((size_t)-1) || count >= dstSizeInBytes) { // -1 = _TRUNCATE
maxcount = dstSizeInBytes - 1;
}
size_t i;
for (i = 0; i < maxcount && src[i] != '\0'; ++i) {
dst[i] = src[i];
}
if (src[i] == '\0' || i == count || count == ((size_t)-1)) {
dst[i] = '\0';
return 0;
}
dst[0] = '\0';
return ERANGE;
}
// Thanks to good old Bit Twiddling Hacks for this one: http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
static unsigned int mal_next_power_of_2(unsigned int x)
{
x--;
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
x++;
return x;
}
///////////////////////////////////////////////////////////////////////////////
//
// Threading
//
///////////////////////////////////////////////////////////////////////////////
#ifdef MAL_WIN32
mal_bool32 mal_thread_create__win32(mal_thread* pThread, mal_thread_entry_proc entryProc, void* pData)
{
*pThread = CreateThread(NULL, 0, entryProc, pData, 0, NULL);
if (*pThread == NULL) {
return MAL_FALSE;
}
return MAL_TRUE;
}
void mal_thread_wait__win32(mal_thread* pThread)
{
WaitForSingleObject(*pThread, INFINITE);
}
mal_bool32 mal_semaphore_create__win32(mal_semaphore* pSemaphore, int initialValue)
{
*pSemaphore = CreateSemaphoreA(NULL, initialValue, LONG_MAX, NULL);
if (*pSemaphore == NULL) {
return MAL_FALSE;
}
return MAL_TRUE;
}
void mal_semaphore_delete__win32(mal_semaphore* pSemaphore)
{
CloseHandle(*pSemaphore);
}
mal_bool32 mal_semaphore_wait__win32(mal_semaphore* pSemaphore)
{
return WaitForSingleObject(*pSemaphore, INFINITE) == WAIT_OBJECT_0;
}
mal_bool32 mal_semaphore_release__win32(mal_semaphore* pSemaphore)
{
return ReleaseSemaphore(*pSemaphore, 1, NULL) != 0;
}
#endif
#ifdef MAL_POSIX
mal_bool32 mal_thread_create__posix(mal_thread* pThread, mal_thread_entry_proc entryProc, void* pData)
{
return pthread_create(pThread, NULL, entryProc, pData) == 0;
}
void mal_thread_wait__posix(mal_thread* pThread)
{
pthread_join(*pThread, NULL);
}
mal_bool32 mal_semaphore_create__posix(mal_semaphore* pSemaphore, int initialValue)
{
return sem_init(pSemaphore, 0, (unsigned int)initialValue) != -1;
}
void mal_semaphore_delete__posix(mal_semaphore* pSemaphore)
{
sem_close(pSemaphore);
}
mal_bool32 mal_semaphore_wait__posix(mal_semaphore* pSemaphore)
{
return sem_wait(pSemaphore) != -1;
}
mal_bool32 mal_semaphore_release__posix(mal_semaphore* pSemaphore)
{
return sem_post(pSemaphore) != -1;
}
#endif
mal_bool32 mal_thread_create(mal_thread* pThread, mal_thread_entry_proc entryProc, void* pData)
{
if (pThread == NULL || entryProc == NULL) return MAL_FALSE;
#ifdef MAL_WIN32
return mal_thread_create__win32(pThread, entryProc, pData);
#endif
#ifdef MAL_POSIX
return mal_thread_create__posix(pThread, entryProc, pData);
#endif
}
void mal_thread_wait(mal_thread* pThread)
{
if (pThread == NULL) return;
#ifdef MAL_WIN32
mal_thread_wait__win32(pThread);
#endif
#ifdef MAL_POSIX
mal_thread_wait__posix(pThread);
#endif
}
mal_bool32 mal_semaphore_create(mal_semaphore* pSemaphore, int initialValue)
{
if (pSemaphore == NULL) return MAL_FALSE;
#ifdef MAL_WIN32
return mal_semaphore_create__win32(pSemaphore, initialValue);
#endif
#ifdef MAL_POSIX
return mal_semaphore_create__posix(pSemaphore, initialValue);
#endif
}
void mal_semaphore_delete(mal_semaphore* pSemaphore)
{
if (pSemaphore == NULL) return;
#ifdef MAL_WIN32
mal_semaphore_delete__win32(pSemaphore);
#endif
#ifdef MAL_POSIX
mal_semaphore_delete__posix(pSemaphore);
#endif
}
mal_bool32 mal_semaphore_wait(mal_semaphore* pSemaphore)
{
if (pSemaphore == NULL) return MAL_FALSE;
#ifdef MAL_WIN32
return mal_semaphore_wait__win32(pSemaphore);
#endif
#ifdef MAL_POSIX
return mal_semaphore_wait__posix(pSemaphore);
#endif
}
mal_bool32 mal_semaphore_release(mal_semaphore* pSemaphore)
{
if (pSemaphore == NULL) return MAL_FALSE;
#ifdef MAL_WIN32
return mal_semaphore_release__win32(pSemaphore);
#endif
#ifdef MAL_POSIX
return mal_semaphore_release__posix(pSemaphore);
#endif
}
// A helper function for reading sample data from the client. Returns the number of samples read from the client. Remaining samples
// are filled with silence.
static inline mal_uint32 mal_device__read_fragment_from_client(mal_device* pDevice, mal_uint32 sampleCount, void* pSamples)
{
mal_assert(pDevice != NULL);
mal_assert(sampleCount > 0);
mal_assert(pSamples != NULL);
mal_uint32 samplesRead = 0;
if (pDevice->onSend) {
samplesRead = pDevice->onSend(pDevice, sampleCount, pSamples);
}
mal_uint32 sampleSize = mal_get_sample_size_in_bytes(pDevice->format);
mal_uint32 consumedBytes = samplesRead*sampleSize;
mal_uint32 remainingBytes = (sampleCount - samplesRead)*sampleSize;
mal_zero_memory((mal_uint8*)pSamples + consumedBytes, remainingBytes);
return samplesRead;
}
// A helper for sending sample data to the client.
static inline void mal_device__send_fragment_to_client(mal_device* pDevice, mal_uint32 sampleCount, const void* pSamples)
{
mal_assert(pDevice != NULL);
mal_assert(sampleCount > 0);
mal_assert(pSamples != NULL);
if (pDevice->onRecv) {
pDevice->onRecv(pDevice, sampleCount, pSamples);
}
}
///////////////////////////////////////////////////////////////////////////////
//
// Null Backend
//
///////////////////////////////////////////////////////////////////////////////
#ifdef MAL_ENABLE_NULL
mal_result mal_enumerate_devices__null(mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo)
{
mal_uint32 infoSize = *pCount;
*pCount = 1; // There's only one "device" each for playback and recording for the null backend.
if (pInfo != NULL && infoSize > 0) {
mal_zero_memory(&pInfo->id, sizeof(pInfo->id));
if (type == mal_device_type_playback) {
mal_strncpy_s(pInfo->description, sizeof(pInfo->description), "NULL Playback Device", (size_t)-1);
} else {
mal_strncpy_s(pInfo->description, sizeof(pInfo->description), "NULL Capture Device", (size_t)-1);
}
}
return MAL_SUCCESS;
}
void mal_device_uninit__null(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
}
mal_result mal_device_init__null(mal_device* pDevice, mal_device_type type, mal_device_id* pDeviceID, mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_uint32 fragmentSizeInFrames, mal_uint32 fragmentCount)
{
mal_assert(pDevice != NULL);
pDevice->api = mal_api_null;
// TODO: Implement me.
return MAL_NO_BACKEND;
}
mal_result mal_device_start__null(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
return MAL_UNKNOWN_ERROR;
}
mal_result mal_device_stop__null(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
return MAL_UNKNOWN_ERROR;
}
#endif
///////////////////////////////////////////////////////////////////////////////
//
// DirectSound Backend
//
///////////////////////////////////////////////////////////////////////////////
#ifdef MAL_ENABLE_DSOUND
#include <dsound.h>
#include <mmreg.h> // WAVEFORMATEX
static GUID MAL_GUID_NULL = {0x00000000, 0x0000, 0x0000, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
static GUID _g_mal_GUID_IID_DirectSoundNotify = {0xb0210783, 0x89cd, 0x11d0, {0xaf, 0x08, 0x00, 0xa0, 0xc9, 0x25, 0xcd, 0x16}};
static GUID _g_mal_GUID_IID_IDirectSoundCaptureBuffer8 = {0x00990df4, 0x0dbb, 0x4872, {0x83, 0x3e, 0x6d, 0x30, 0x3e, 0x80, 0xae, 0xb6}};
static GUID _g_mal_GUID_KSDATAFORMAT_SUBTYPE_PCM = {0x00000001, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};
static GUID _g_mal_GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = {0x00000003, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};
static GUID _g_mal_GUID_KSDATAFORMAT_SUBTYPE_ALAW = {0x00000006, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};
static GUID _g_mal_GUID_KSDATAFORMAT_SUBTYPE_MULAW = {0x00000007, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};
#ifdef __cplusplus
static GUID g_mal_GUID_IID_DirectSoundNotify = _g_mal_GUID_IID_DirectSoundNotify;
static GUID g_mal_GUID_IID_IDirectSoundCaptureBuffer8 = _g_mal_GUID_IID_IDirectSoundCaptureBuffer8;
#else
static GUID* g_mal_GUID_IID_DirectSoundNotify = &_g_mal_GUID_IID_DirectSoundNotify;
static GUID* g_mal_GUID_IID_IDirectSoundCaptureBuffer8 = &_g_mal_GUID_IID_IDirectSoundCaptureBuffer8;
#endif
typedef HRESULT (WINAPI * mal_DirectSoundCreate8Proc)(LPCGUID pcGuidDevice, LPDIRECTSOUND8 *ppDS8, LPUNKNOWN pUnkOuter);
typedef HRESULT (WINAPI * mal_DirectSoundEnumerateAProc)(LPDSENUMCALLBACKA pDSEnumCallback, LPVOID pContext);
typedef HRESULT (WINAPI * mal_DirectSoundCaptureCreate8Proc)(LPCGUID pcGuidDevice, LPDIRECTSOUNDCAPTURE8 *ppDSC8, LPUNKNOWN pUnkOuter);
typedef HRESULT (WINAPI * mal_DirectSoundCaptureEnumerateAProc)(LPDSENUMCALLBACKA pDSEnumCallback, LPVOID pContext);
#define MAL_FLAG_DSOUND_HAS_SEMAPHORE (1 << 16) // Used for cleanup.
#define MAL_FLAG_DSOUND_HAS_THREAD (1 << 17) // Used for cleanup.
static HMODULE mal_open_dsound_dll()
{
return LoadLibraryW(L"dsound.dll");
}
static void mal_close_dsound_dll(HMODULE hModule)
{
FreeLibrary(hModule);
}
static mal_result mal_device__read_fragment_from_client__dsound(mal_device* pDevice, mal_uint32 fragmentIndex)
{
mal_assert(pDevice != NULL);
DWORD fragmentSizeInBytes = pDevice->fragmentSizeInFrames * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format);
DWORD offset = fragmentIndex * fragmentSizeInBytes;
void* pLockPtr;
DWORD lockSize;
if (FAILED(IDirectSoundBuffer_Lock((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer, offset, fragmentSizeInBytes, &pLockPtr, &lockSize, NULL, NULL, 0))) {
return MAL_UNKNOWN_ERROR;
}
mal_device__read_fragment_from_client(pDevice, pDevice->fragmentSizeInFrames * pDevice->channels, pLockPtr);
IDirectSoundBuffer_Unlock((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer, pLockPtr, lockSize, NULL, 0);
return MAL_SUCCESS;
}
static mal_result mal_device__send_fragment_to_client__dsound(mal_device* pDevice, mal_uint32 fragmentIndex)
{
mal_assert(pDevice != NULL);
DWORD fragmentSizeInBytes = pDevice->fragmentSizeInFrames * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format);
DWORD offset = fragmentIndex * fragmentSizeInBytes;
void* pLockPtr;
DWORD lockSize;
if (FAILED(IDirectSoundCaptureBuffer_Lock((LPDIRECTSOUNDCAPTUREBUFFER8)pDevice->dsound.pCaptureBuffer, offset, fragmentSizeInBytes, &pLockPtr, &lockSize, NULL, NULL, 0))) {
return MAL_UNKNOWN_ERROR;
}
mal_device__send_fragment_to_client(pDevice, pDevice->fragmentSizeInFrames * pDevice->channels, pLockPtr);
IDirectSoundCaptureBuffer_Unlock((LPDIRECTSOUNDCAPTUREBUFFER)pDevice->dsound.pCaptureBuffer, pLockPtr, lockSize, NULL, 0);
return MAL_SUCCESS;
}
mal_thread_result MAL_THREADCALL mal_worker_thread__dsound(void* pData)
{
mal_device* pDevice = (mal_device*)pData;
mal_assert(pDevice != NULL);
for (;;) {
mal_semaphore_wait(&pDevice->dsound.semaphore);
// Just break if we're terminating.
if (pDevice->flags & MAL_FLAG_TERMINATING) {
break;
}
// Continue if the device has been stopped.
if (!mal_device_is_started(pDevice)) {
if (pDevice->type == mal_device_type_playback) {
IDirectSoundBuffer_Stop((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer);
IDirectSoundBuffer_SetCurrentPosition((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer, 0);
} else {
IDirectSoundCaptureBuffer_Stop((LPDIRECTSOUNDCAPTUREBUFFER)pDevice->dsound.pCaptureBuffer);
}
continue;
}
// Getting here means we just started the device and we need to wait for the device to
// either deliver us data (recording) or request more data (playback).
if (pDevice->type == mal_device_type_playback) {
// Before playing anything we need to grab an initial fragment of sample data from the client.
if (mal_device__read_fragment_from_client__dsound(pDevice, 0) != MAL_SUCCESS) {
continue; // Just cancel playback and go back to the start of the loop.
}
IDirectSoundBuffer_Play((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer, 0, 0, DSBPLAY_LOOPING);
} else {
IDirectSoundCaptureBuffer8_Start((LPDIRECTSOUNDCAPTUREBUFFER8)pDevice->dsound.pCaptureBuffer, DSCBSTART_LOOPING);
}
for (;;) {
// Wait for a notification. Notifications are tied to fragments.
unsigned int eventCount = pDevice->fragmentCount + 1;
HANDLE eventHandles[MAL_MAX_FRAGMENTS_DSOUND + 1]; // +1 for the stop event.
mal_copy_memory(eventHandles, pDevice->dsound.pNotifyEvents, sizeof(HANDLE) * pDevice->fragmentCount);
eventHandles[eventCount-1] = pDevice->dsound.hStopEvent;
DWORD rc = WaitForMultipleObjects(eventCount, eventHandles, FALSE, INFINITE);
if (rc < WAIT_OBJECT_0 || rc >= WAIT_OBJECT_0 + eventCount) {
break;
}
unsigned int eventIndex = rc - WAIT_OBJECT_0;
HANDLE hEvent = eventHandles[eventIndex];
// Has the device been stopped? If so, need to get out of this loop.
if (hEvent == pDevice->dsound.hStopEvent) {
break;
}
// If we get here it means the event that's been signaled represents a fragment.
unsigned int fragmentIndex = eventIndex; // <-- Just for clarity.
mal_assert(fragmentIndex < pDevice->fragmentCount);
if (pDevice->type == mal_device_type_playback) {
mal_device__read_fragment_from_client__dsound(pDevice, (fragmentIndex + 1) % pDevice->fragmentCount);
} else {
mal_device__send_fragment_to_client__dsound(pDevice, fragmentIndex);
}
}
}
return (mal_thread_result)0;
}
typedef struct
{
mal_uint32 deviceCount;
mal_uint32 infoCount;
mal_device_info* pInfo;
} mal_device_enum_data__dsound;
static BOOL CALLBACK mal_enum_devices_callback__dsound(LPGUID lpGuid, LPCSTR lpcstrDescription, LPCSTR lpcstrModule, LPVOID lpContext)
{
(void)lpcstrModule;
mal_device_enum_data__dsound* pData = (mal_device_enum_data__dsound*)lpContext;
assert(pData != NULL);
if (pData->pInfo != NULL) {
if (pData->infoCount > 0) {
mal_strncpy_s(pData->pInfo->description, sizeof(pData->pInfo->description), lpcstrDescription, (size_t)-1);
if (lpGuid != NULL) {
mal_copy_memory(pData->pInfo->id.guid, lpGuid, 16);
} else {
mal_zero_memory(pData->pInfo->id.guid, 16);
}
pData->pInfo += 1;
pData->infoCount -= 1;
pData->deviceCount += 1;
}
} else {
pData->deviceCount += 1;
}
return TRUE;
}
mal_result mal_enumerate_devices__dsound(mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo)
{
mal_uint32 infoSize = *pCount;
*pCount = 0;
mal_device_enum_data__dsound enumData;
enumData.deviceCount = 0;
enumData.infoCount = infoSize;
enumData.pInfo = pInfo;
HMODULE dsoundDLL = mal_open_dsound_dll();
if (dsoundDLL == NULL) {
return MAL_NO_BACKEND;
}
if (type == mal_device_type_playback) {
mal_DirectSoundEnumerateAProc pDirectSoundEnumerateA = (mal_DirectSoundEnumerateAProc)GetProcAddress(dsoundDLL, "DirectSoundEnumerateA");
if (pDirectSoundEnumerateA) {
pDirectSoundEnumerateA(mal_enum_devices_callback__dsound, &enumData);
}
} else {
mal_DirectSoundCaptureEnumerateAProc pDirectSoundCaptureEnumerateA = (mal_DirectSoundCaptureEnumerateAProc)GetProcAddress(dsoundDLL, "DirectSoundCaptureEnumerateA");
if (pDirectSoundCaptureEnumerateA) {
pDirectSoundCaptureEnumerateA(mal_enum_devices_callback__dsound, &enumData);
}
}
mal_close_dsound_dll(dsoundDLL);
*pCount = enumData.deviceCount;
return MAL_SUCCESS;
}
void mal_device_uninit__dsound(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
pDevice->flags |= MAL_FLAG_TERMINATING;
if (pDevice->flags & MAL_FLAG_DSOUND_HAS_THREAD) {
mal_semaphore_release(&pDevice->dsound.semaphore);
mal_thread_wait(&pDevice->dsound.thread);
}
if (pDevice->flags & MAL_FLAG_DSOUND_HAS_SEMAPHORE) {
mal_semaphore_delete(&pDevice->dsound.semaphore);
}
if (pDevice->dsound.hDSoundDLL != NULL) {
if (pDevice->dsound.hStopEvent) {
CloseHandle(pDevice->dsound.hStopEvent);
}
for (mal_uint32 i = 0; i < pDevice->fragmentCount; ++i) {
if (pDevice->dsound.pNotifyEvents[i]) {
CloseHandle(pDevice->dsound.pNotifyEvents[i]);
}
}
if (pDevice->dsound.pCaptureBuffer) {
IDirectSoundCaptureBuffer8_Release((LPDIRECTSOUNDBUFFER8)pDevice->dsound.pCaptureBuffer);
}
if (pDevice->dsound.pCapture) {
IDirectSoundCapture_Release((LPDIRECTSOUNDCAPTURE8)pDevice->dsound.pCaptureBuffer);
}
if (pDevice->dsound.pPlaybackBuffer) {
IDirectSoundBuffer_Release((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer);
}
if (pDevice->dsound.pPlaybackPrimaryBuffer) {
IDirectSoundBuffer_Release((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackPrimaryBuffer);
}
if (pDevice->dsound.pPlayback != NULL) {
IDirectSound_Release((LPDIRECTSOUND8)pDevice->dsound.pPlayback);
}
mal_close_dsound_dll((HMODULE)pDevice->dsound.hDSoundDLL);
}
}
mal_result mal_device_init__dsound(mal_device* pDevice, mal_device_type type, mal_device_id* pDeviceID, mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_uint32 fragmentSizeInFrames, mal_uint32 fragmentCount)
{
mal_assert(pDevice != NULL);
pDevice->api = mal_api_dsound;
pDevice->dsound.hDSoundDLL = (mal_handle)mal_open_dsound_dll();
if (pDevice->dsound.hDSoundDLL == NULL) {
return MAL_NO_BACKEND;
}
// Check that we have a valid format.
GUID subformat;
switch (format)
{
case mal_format_u8:
case mal_format_s16:
case mal_format_s32:
{
subformat = _g_mal_GUID_KSDATAFORMAT_SUBTYPE_PCM;
} break;
case mal_format_f32:
case mal_format_f64:
{
subformat = _g_mal_GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
} break;
case mal_format_alaw:
{
subformat = _g_mal_GUID_KSDATAFORMAT_SUBTYPE_ALAW;
} break;
case mal_format_mulaw:
{
subformat = _g_mal_GUID_KSDATAFORMAT_SUBTYPE_MULAW;
} break;
case mal_format_s24: // <-- Untested. Not quite sure on alignment.
default:
return MAL_FORMAT_NOT_SUPPORTED;
}
if (fragmentCount > MAL_MAX_FRAGMENTS_DSOUND) {
fragmentCount = MAL_MAX_FRAGMENTS_DSOUND;
}
WAVEFORMATEXTENSIBLE wf;
mal_zero_object(&wf);
wf.Format.cbSize = sizeof(wf);
wf.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
wf.Format.nChannels = (WORD)channels;
wf.Format.nSamplesPerSec = (DWORD)sampleRate;
wf.Format.wBitsPerSample = mal_get_sample_size_in_bytes(format)*8;
wf.Format.nBlockAlign = (wf.Format.nChannels * wf.Format.wBitsPerSample) / 8;
wf.Format.nAvgBytesPerSec = wf.Format.nBlockAlign * wf.Format.nSamplesPerSec;
wf.Samples.wValidBitsPerSample = wf.Format.wBitsPerSample;
wf.dwChannelMask = (channels <= 2) ? 0 : ~(((DWORD)-1) << channels);
wf.SubFormat = subformat;
DWORD fragmentSizeInBytes = mal_device_get_fragment_size_in_bytes(pDevice);
// Unfortunately DirectSound uses different APIs and data structures for playback and catpure devices :(
if (type == mal_device_type_playback) {
mal_DirectSoundCreate8Proc pDirectSoundCreate8 = (mal_DirectSoundCreate8Proc)GetProcAddress(pDevice->dsound.hDSoundDLL, "DirectSoundCreate8");
if (pDirectSoundCreate8 == NULL) {
mal_device_uninit__dsound(pDevice);
return MAL_NO_BACKEND;
}
if (FAILED(pDirectSoundCreate8((pDeviceID == NULL) ? NULL : (LPCGUID)pDeviceID->guid, (LPDIRECTSOUND8*)&pDevice->dsound.pPlayback, NULL))) {
mal_device_uninit__dsound(pDevice);
return MAL_NO_BACKEND;
}
// The cooperative level must be set before doing anything else.
if (FAILED(IDirectSound_SetCooperativeLevel((LPDIRECTSOUND8)pDevice->dsound.pPlayback, GetForegroundWindow(), DSSCL_PRIORITY))) {
mal_device_uninit__dsound(pDevice);
return MAL_NO_BACKEND;
}
DSBUFFERDESC descDSPrimary;
mal_zero_object(&descDSPrimary);
descDSPrimary.dwSize = sizeof(DSBUFFERDESC);
descDSPrimary.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_CTRLVOLUME;
if (FAILED(IDirectSound_CreateSoundBuffer((LPDIRECTSOUND8)pDevice->dsound.pPlayback, &descDSPrimary, (LPDIRECTSOUNDBUFFER*)&pDevice->dsound.pPlaybackPrimaryBuffer, NULL))) {
mal_device_uninit__dsound(pDevice);
return MAL_NO_BACKEND;
}
// From MSDN:
//
// The method succeeds even if the hardware does not support the requested format; DirectSound sets the buffer to the closest
// supported format. To determine whether this has happened, an application can call the GetFormat method for the primary buffer
// and compare the result with the format that was requested with the SetFormat method.
if (FAILED(IDirectSoundBuffer_SetFormat((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackPrimaryBuffer, (WAVEFORMATEX*)&wf))) {
mal_device_uninit__dsound(pDevice);
return MAL_NO_BACKEND;
}
// Get the _actual_ properties of the buffer. This is silly API design...
DWORD requiredSize;
if (FAILED(IDirectSoundBuffer_GetFormat((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackPrimaryBuffer, NULL, 0, &requiredSize))) {
mal_device_uninit__dsound(pDevice);
return MAL_NO_BACKEND;
}
char rawdata[1024];
WAVEFORMATEXTENSIBLE* pActualFormat = (WAVEFORMATEXTENSIBLE*)rawdata;
if (FAILED(IDirectSoundBuffer_GetFormat((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackPrimaryBuffer, (WAVEFORMATEX*)pActualFormat, requiredSize, NULL))) {
mal_device_uninit__dsound(pDevice);
return MAL_NO_BACKEND;
}
pDevice->channels = pActualFormat->Format.nChannels;
pDevice->sampleRate = pActualFormat->Format.nSamplesPerSec;
pDevice->fragmentCount = fragmentCount;
pDevice->fragmentSizeInFrames = mal_next_power_of_2(fragmentSizeInFrames); // Keeping the fragment size a multiple of 2 just for consistency with ALSA.
// Meaning of dwFlags (from MSDN):
//
// DSBCAPS_CTRLPOSITIONNOTIFY
// The buffer has position notification capability.
//
// DSBCAPS_GLOBALFOCUS
// With this flag set, an application using DirectSound can continue to play its buffers if the user switches focus to
// another application, even if the new application uses DirectSound.
//
// DSBCAPS_GETCURRENTPOSITION2
// In the first version of DirectSound, the play cursor was significantly ahead of the actual playing sound on emulated
// sound cards; it was directly behind the write cursor. Now, if the DSBCAPS_GETCURRENTPOSITION2 flag is specified, the
// application can get a more accurate play cursor.
DSBUFFERDESC descDS;
memset(&descDS, 0, sizeof(DSBUFFERDESC));
descDS.dwSize = sizeof(DSBUFFERDESC);
descDS.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2;
descDS.dwBufferBytes = fragmentSizeInBytes * pDevice->fragmentCount;
descDS.lpwfxFormat = (WAVEFORMATEX*)&wf;
if (FAILED(IDirectSound_CreateSoundBuffer((LPDIRECTSOUND8)pDevice->dsound.pPlayback, &descDS, (LPDIRECTSOUNDBUFFER*)&pDevice->dsound.pPlaybackBuffer, NULL))) {
mal_device_uninit__dsound(pDevice);
return MAL_NO_BACKEND;
}
// Notifications are set up via a DIRECTSOUNDNOTIFY object which is retrieved from the buffer.
if (FAILED(IDirectSoundBuffer8_QueryInterface((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer, g_mal_GUID_IID_DirectSoundNotify, (void**)&pDevice->dsound.pNotify))) {
mal_device_uninit__dsound(pDevice);
return MAL_NO_BACKEND;
}
} else {
mal_DirectSoundCaptureCreate8Proc pDirectSoundCaptureCreate8 = (mal_DirectSoundCaptureCreate8Proc)GetProcAddress(pDevice->dsound.hDSoundDLL, "DirectSoundCaptureCreate8");
if (pDirectSoundCaptureCreate8 == NULL) {
mal_device_uninit__dsound(pDevice);
return MAL_NO_BACKEND;
}
if (FAILED(pDirectSoundCaptureCreate8((pDeviceID == NULL) ? NULL : (LPCGUID)pDeviceID->guid, (LPDIRECTSOUNDCAPTURE8*)&pDevice->dsound.pCapture, NULL))) {
mal_device_uninit__dsound(pDevice);
return MAL_NO_BACKEND;
}
DSCBUFFERDESC descDS;
mal_zero_object(&descDS);
descDS.dwSize = sizeof(descDS);
descDS.dwFlags = 0;
descDS.dwBufferBytes = fragmentSizeInBytes * pDevice->fragmentCount;
descDS.lpwfxFormat = (WAVEFORMATEX*)&wf;
LPDIRECTSOUNDCAPTUREBUFFER pDSCB_Temp;
if (FAILED(IDirectSoundCapture_CreateCaptureBuffer((LPDIRECTSOUNDCAPTURE8)pDevice->dsound.pCapture, &descDS, &pDSCB_Temp, NULL))) {
mal_device_uninit__dsound(pDevice);
return MAL_NO_BACKEND;
}
HRESULT hr = IDirectSoundCapture_QueryInterface(pDSCB_Temp, g_mal_GUID_IID_IDirectSoundCaptureBuffer8, (LPVOID*)&pDevice->dsound.pCaptureBuffer);
IDirectSoundCaptureBuffer_Release(pDSCB_Temp);
if (FAILED(hr)) {
mal_device_uninit__dsound(pDevice);
return MAL_NO_BACKEND;
}
// Notifications are set up via a DIRECTSOUNDNOTIFY object which is retrieved from the buffer.
if (FAILED(IDirectSoundCaptureBuffer8_QueryInterface((LPDIRECTSOUNDCAPTUREBUFFER)pDevice->dsound.pCaptureBuffer, g_mal_GUID_IID_DirectSoundNotify, (void**)&pDevice->dsound.pNotify))) {
mal_device_uninit__dsound(pDevice);
return MAL_NO_BACKEND;
}
}
// We need a notification for each fragment. The notification offset is slightly different depending on whether or not the
// device is a playback or capture device. For a playback device we want to be notified when a fragment just starts playing,
// whereas for a capture device we want to be notified when a fragment has just _finished_ capturing.
DSBPOSITIONNOTIFY notifyPoints[MAL_MAX_FRAGMENTS_DSOUND]; // One notification event for each fragment.
for (mal_uint32 i = 0; i < pDevice->fragmentCount; ++i) {
pDevice->dsound.pNotifyEvents[i] = CreateEventA(NULL, FALSE, FALSE, NULL);
if (pDevice->dsound.pNotifyEvents[i] == NULL) {
mal_device_uninit__dsound(pDevice);
return MAL_NO_BACKEND;
}
// The notification offset is in bytes.
DWORD dwOffset;
if (type == mal_device_type_playback) {
dwOffset = i * fragmentSizeInBytes;
} else {
dwOffset = ((i+1)*fragmentSizeInBytes) % (fragmentSizeInBytes*pDevice->fragmentCount);
}
notifyPoints[i].dwOffset = dwOffset;
notifyPoints[i].hEventNotify = pDevice->dsound.pNotifyEvents[i];
}
if (FAILED(IDirectSoundNotify_SetNotificationPositions((LPDIRECTSOUNDNOTIFY)pDevice->dsound.pNotify, pDevice->fragmentCount, notifyPoints))) {
mal_device_uninit__dsound(pDevice);
return MAL_NO_BACKEND;
}
// When the device is playing the worker thread will be waiting on a bunch of notification events. To return from
// this wait state we need to signal a special event.
pDevice->dsound.hStopEvent = CreateEventA(NULL, FALSE, FALSE, NULL);
if (pDevice->dsound.hStopEvent == NULL) {
mal_device_uninit__dsound(pDevice);
return MAL_FAILED_TO_INIT_BACKEND;
}
if (!mal_semaphore_create(&pDevice->dsound.semaphore, 0)) {
mal_device_uninit__dsound(pDevice);
return MAL_FAILED_TO_INIT_BACKEND;
}
pDevice->flags |= MAL_FLAG_DSOUND_HAS_SEMAPHORE;
if (!mal_thread_create(&pDevice->dsound.thread, mal_worker_thread__dsound, pDevice)) {
mal_device_uninit__dsound(pDevice);
return MAL_FAILED_TO_INIT_BACKEND;
}
pDevice->flags |= MAL_FLAG_DSOUND_HAS_THREAD;
return MAL_SUCCESS;
}
mal_result mal_device_start__dsound(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
// We don't actually start the device here. We instead signal the semaphore on the worker thread and
// let that thread play the device.
pDevice->flags |= MAL_FLAG_STARTED;
mal_semaphore_release(&pDevice->dsound.semaphore);
// Make sure the signal used to stop the worker thread is no longer signaled.
ResetEvent(pDevice->dsound.hStopEvent);
return MAL_SUCCESS;
}
mal_result mal_device_stop__dsound(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
// The device is not stopped here. We instead mark the device as stopped and signal the semaphore
// on the worker thread.
pDevice->flags &= ~MAL_FLAG_STARTED;
mal_semaphore_release(&pDevice->dsound.semaphore);
// The worker thread is likely waiting on
SetEvent(pDevice->dsound.hStopEvent);
return MAL_SUCCESS;
}
#endif
///////////////////////////////////////////////////////////////////////////////
//
// ALSA Backend
//
///////////////////////////////////////////////////////////////////////////////
#ifdef MAL_ENABLE_ALSA
#include <alsa/asoundlib.h>
#define MAL_FLAG_ALSA_USING_MMAP (1 << 15)
#define MAL_FLAG_ALSA_HAS_SEMAPHORE (1 << 16) // Used for cleanup.
#define MAL_FLAG_ALSA_HAS_THREAD (1 << 17) // Used for cleanup.
mal_bool32 mal_device_write__alsa(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
if (!mal_device_is_started(pDevice)) {
return MAL_FALSE;
}
void* pBuffer = NULL;
if (pDevice->alsa.pIntermediaryBuffer == NULL) {
// mmap.
return MAL_FALSE;
} else {
// readi/writei.
pBuffer = pDevice->alsa.pIntermediaryBuffer;
}
mal_uint32 desiredSampleCount = pDevice->fragmentSizeInFrames * pDevice->channels;
mal_uint32 samplesRead = 0;
if (pDevice->onSend) {
samplesRead = pDevice->onSend(pDevice, desiredSampleCount, pBuffer);
if (samplesRead != desiredSampleCount) {
// Not enough samples were read. Fill the remainder with silence.
mal_uint32 sampleSize = mal_get_sample_size_in_bytes(pDevice->format);
mal_uint32 consumedBytes = samplesRead*sampleSize;
mal_uint32 remainingBytes = (desiredSampleCount-samplesRead)*sampleSize;
mal_zero_memory((mal_uint8*)pBuffer + consumedBytes, remainingBytes);
}
}
if (pDevice->alsa.pIntermediaryBuffer == NULL) {
// mmap.
} else {
// readi/writei.
for (;;) {
snd_pcm_sframes_t framesWritten = snd_pcm_writei(pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, pDevice->fragmentSizeInFrames);
if (framesWritten < 0) {
if (framesWritten == -EAGAIN) {
continue; // Just keep trying...
} else if (framesWritten == -EPIPE) {
// Underrun. Just recover and try writing again.
if (snd_pcm_recover(pDevice->alsa.pPCM, framesWritten, MAL_TRUE) < 0) {
return MAL_FALSE;
}
framesWritten = snd_pcm_writei(pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, pDevice->fragmentSizeInFrames);
if (framesWritten < 0) {
return MAL_FALSE;
}
break; // Success.
} else {
return MAL_FALSE;
}
} else {
break; // Success.
}
}
}
return MAL_TRUE;
}
mal_bool32 mal_device_read__alsa(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
if (!mal_device_is_started(pDevice)) {
return MAL_FALSE;
}
mal_uint32 samplesToSend = 0;
void* pBuffer = NULL;
if (pDevice->alsa.pIntermediaryBuffer == NULL) {
// mmap.
return MAL_FALSE;
} else {
// readi/writei.
snd_pcm_sframes_t framesRead = 0;
for (;;) {
framesRead = snd_pcm_readi(pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, pDevice->fragmentSizeInFrames);
if (framesRead < 0) {
if (framesRead == -EAGAIN) {
continue; // Just keep trying...
} else if (framesRead == -EPIPE) {
// Overrun. Just recover and try reading again.
if (snd_pcm_recover(pDevice->alsa.pPCM, framesRead, MAL_TRUE) < 0) {
return MAL_FALSE;
}
snd_pcm_sframes_t framesRead = snd_pcm_readi(pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, pDevice->fragmentSizeInFrames);
if (framesRead < 0) {
return MAL_FALSE;
}
break; // Success.
} else {
return MAL_FALSE;
}
} else {
break; // Success.
}
}
samplesToSend = framesRead * pDevice->channels;
pBuffer = pDevice->alsa.pIntermediaryBuffer;
}
if (pDevice->onRecv && samplesToSend > 0) {
pDevice->onRecv(pDevice, samplesToSend, pBuffer);
}
if (pDevice->alsa.pIntermediaryBuffer == NULL) {
// mmap.
} else {
// readi/writei.
}
return MAL_TRUE;
}
mal_thread_result MAL_THREADCALL mal_worker_thread__alsa(void* pData)
{
mal_device* pDevice = (mal_device*)pData;
mal_assert(pDevice != NULL);
for (;;) {
mal_semaphore_wait(&pDevice->alsa.semaphore);
// Just break if we're terminating.
if (pDevice->flags & MAL_FLAG_TERMINATING) {
break;
}
// Continue if the device has been stopped.
if (!mal_device_is_started(pDevice)) {
snd_pcm_drop(pDevice->alsa.pPCM);
continue;
}
// Getting here means we just started the device and we need to wait for the device to
// either deliver us data (recording) or request more data (playback).
snd_pcm_prepare(pDevice->alsa.pPCM);
if (pDevice->type == mal_device_type_playback) {
// Playback. Read from client, write to device.
while (mal_device_write__alsa(pDevice)) {
}
} else {
// Playback. Read from device, write to client.
while (mal_device_read__alsa(pDevice)) {
}
}
}
return (mal_thread_result)0;
}
mal_result mal_enumerate_devices__alsa(mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo)
{
mal_uint32 infoSize = *pCount;
*pCount = 0;
char** ppDeviceHints;
if (snd_device_name_hint(-1, "pcm", (void***)&ppDeviceHints) < 0) {
return MAL_UNKNOWN_ERROR;
}
char** ppNextDeviceHint = ppDeviceHints;
while (*ppNextDeviceHint != NULL) {
char* NAME = snd_device_name_get_hint(*ppNextDeviceHint, "NAME");
char* DESC = snd_device_name_get_hint(*ppNextDeviceHint, "DESC");
char* IOID = snd_device_name_get_hint(*ppNextDeviceHint, "IOID");
if (IOID == NULL ||
(type == mal_device_type_playback && strcmp(IOID, "Output") == 0) ||
(type == mal_device_type_capture && strcmp(IOID, "Input" ) == 0))
{
if (pInfo != NULL) {
if (infoSize > 0) {
mal_strncpy_s(pInfo->id.name, sizeof(pInfo->id.name), NAME ? NAME : "", (size_t)-1);
mal_strncpy_s(pInfo->description, sizeof(pInfo->description), DESC ? DESC : "", (size_t)-1);
pInfo += 1;
infoSize -= 1;
*pCount += 1;
}
} else {
*pCount += 1;
}
}
free(NAME);
free(DESC);
free(IOID);
ppNextDeviceHint += 1;
}
snd_device_name_free_hint((void**)ppDeviceHints);
return MAL_SUCCESS;
}
void mal_device_uninit__alsa(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
pDevice->flags |= MAL_FLAG_TERMINATING;
if (pDevice->flags & MAL_FLAG_ALSA_HAS_THREAD) {
mal_semaphore_release(&pDevice->alsa.semaphore);
mal_thread_wait(&pDevice->alsa.thread);
}
if (pDevice->flags & MAL_FLAG_ALSA_HAS_SEMAPHORE) {
mal_semaphore_delete(&pDevice->alsa.semaphore);
}
if (pDevice->alsa.pPCM) {
snd_pcm_close((snd_pcm_t*)pDevice->alsa.pPCM);
if (pDevice->alsa.pIntermediaryBuffer == NULL) {
mal_free(pDevice->alsa.pIntermediaryBuffer);
}
}
}
mal_result mal_device_init__alsa(mal_device* pDevice, mal_device_type type, mal_device_id* pDeviceID, mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_uint32 fragmentSizeInFrames, mal_uint32 fragmentCount)
{
mal_assert(pDevice != NULL);
pDevice->api = mal_api_alsa;
char deviceName[256];
if (pDeviceID == NULL) {
strcpy(deviceName, "default");
} else {
strcpy(deviceName, pDeviceID->name);
}
snd_pcm_format_t formatALSA;
switch (format)
{
case mal_format_u8: formatALSA = SND_PCM_FORMAT_U8; break;
case mal_format_s16: formatALSA = SND_PCM_FORMAT_S16_LE; break;
case mal_format_s32: formatALSA = SND_PCM_FORMAT_S32_LE; break;
case mal_format_f32: formatALSA = SND_PCM_FORMAT_FLOAT_LE; break;
case mal_format_alaw: formatALSA = SND_PCM_FORMAT_A_LAW; break;
case mal_format_mulaw: formatALSA = SND_PCM_FORMAT_MU_LAW; break;
default: return MAL_FORMAT_NOT_SUPPORTED;
}
if (snd_pcm_open((snd_pcm_t**)&pDevice->alsa.pPCM, deviceName, (type == mal_device_type_playback) ? SND_PCM_STREAM_PLAYBACK : SND_PCM_STREAM_CAPTURE, 0) < 0) {
mal_device_uninit__alsa(pDevice);
return MAL_FAILED_TO_INIT_BACKEND;
}
snd_pcm_hw_params_t* pHWParams = NULL;
if (snd_pcm_hw_params_malloc(&pHWParams) < 0) {
mal_device_uninit__alsa(pDevice);
return MAL_OUT_OF_MEMORY;
}
if (snd_pcm_hw_params_any((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams) < 0) {
snd_pcm_hw_params_free(pHWParams);
mal_device_uninit__alsa(pDevice);
return MAL_FAILED_TO_INIT_BACKEND;
}
if (snd_pcm_hw_params_set_access((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {
snd_pcm_hw_params_free(pHWParams);
mal_device_uninit__alsa(pDevice);
return MAL_FAILED_TO_INIT_BACKEND;
}
if (snd_pcm_hw_params_set_format((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, formatALSA) < 0) {
snd_pcm_hw_params_free(pHWParams);
mal_device_uninit__alsa(pDevice);
return MAL_FAILED_TO_INIT_BACKEND;
}
if (snd_pcm_hw_params_set_rate_near((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, &sampleRate, 0) < 0) {
snd_pcm_hw_params_free(pHWParams);
mal_device_uninit__alsa(pDevice);
return MAL_FAILED_TO_INIT_BACKEND;
}
if (snd_pcm_hw_params_set_channels_near((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, &channels) < 0) {
snd_pcm_hw_params_free(pHWParams);
mal_device_uninit__alsa(pDevice);
return MAL_FAILED_TO_INIT_BACKEND;
}
int dir = 0;
if (snd_pcm_hw_params_set_periods_near((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, &fragmentCount, &dir) < 0) {
snd_pcm_hw_params_free(pHWParams);
mal_device_uninit__alsa(pDevice);
return MAL_FAILED_TO_INIT_BACKEND;
}
// A few properties may have been adjusted so we need to make sure the device object is aware.
pDevice->channels = channels;
pDevice->sampleRate = sampleRate;
pDevice->fragmentCount = fragmentCount;
// According to the ALSA documentation, the value passed to snd_pcm_sw_params_set_avail_min() must be a power
// of 2 on some hardware. The value passed to this function is the size in frames of a fragment. Thus, to be
// as robust as possible the size of the hardware buffer should be sized based on the size of the next power-
// of-two frame count.
pDevice->fragmentSizeInFrames = mal_next_power_of_2(fragmentSizeInFrames);
if (snd_pcm_hw_params_set_buffer_size((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, pDevice->fragmentSizeInFrames * pDevice->fragmentCount) < 0) {
snd_pcm_hw_params_free(pHWParams);
mal_device_uninit__alsa(pDevice);
return MAL_FAILED_TO_INIT_BACKEND;
}
if (snd_pcm_hw_params((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams) < 0) {
snd_pcm_hw_params_free(pHWParams);
mal_device_uninit__alsa(pDevice);
return MAL_FAILED_TO_INIT_BACKEND;
}
snd_pcm_hw_params_free(pHWParams);
snd_pcm_sw_params_t* pSWParams = NULL;
if (snd_pcm_sw_params_malloc(&pSWParams) < 0) {
mal_device_uninit__alsa(pDevice);
return MAL_FAILED_TO_INIT_BACKEND;
}
if (snd_pcm_sw_params_current((snd_pcm_t*)pDevice->alsa.pPCM, pSWParams) != 0) {
snd_pcm_sw_params_free(pSWParams);
mal_device_uninit__alsa(pDevice);
return MAL_FAILED_TO_INIT_BACKEND;
}
if (snd_pcm_sw_params_set_avail_min((snd_pcm_t*)pDevice->alsa.pPCM, pSWParams, pDevice->fragmentSizeInFrames) != 0) {
snd_pcm_sw_params_free(pSWParams);
mal_device_uninit__alsa(pDevice);
return MAL_FAILED_TO_INIT_BACKEND;
}
if (type == mal_device_type_playback) {
if (snd_pcm_sw_params_set_start_threshold((snd_pcm_t*)pDevice->alsa.pPCM, pSWParams, pDevice->fragmentSizeInFrames) != 0) {
snd_pcm_sw_params_free(pSWParams);
mal_device_uninit__alsa(pDevice);
return MAL_FAILED_TO_INIT_BACKEND;
}
}
if (snd_pcm_sw_params((snd_pcm_t*)pDevice->alsa.pPCM, pSWParams) != 0) {
snd_pcm_sw_params_free(pSWParams);
mal_device_uninit__alsa(pDevice);
return MAL_FAILED_TO_INIT_BACKEND;
}
snd_pcm_sw_params_free(pSWParams);
// If we're _not_ using mmap we need to use an intermediary buffer.
if (!(pDevice->flags & MAL_FLAG_ALSA_USING_MMAP)) {
pDevice->alsa.pIntermediaryBuffer = mal_malloc(pDevice->fragmentSizeInFrames * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format));
if (pDevice->alsa.pIntermediaryBuffer == NULL) {
mal_device_uninit__alsa(pDevice);
return MAL_FAILED_TO_INIT_BACKEND;
}
}
if (!mal_semaphore_create(&pDevice->alsa.semaphore, 0)) {
mal_device_uninit__alsa(pDevice);
return MAL_FAILED_TO_INIT_BACKEND;
}
pDevice->flags |= MAL_FLAG_ALSA_HAS_SEMAPHORE;
if (!mal_thread_create(&pDevice->alsa.thread, mal_worker_thread__alsa, pDevice)) {
mal_device_uninit__alsa(pDevice);
return MAL_FAILED_TO_INIT_BACKEND;
}
pDevice->flags |= MAL_FLAG_ALSA_HAS_THREAD;
return MAL_SUCCESS;
}
mal_result mal_device_start__alsa(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
// We don't actually start the device here. We instead signal the semaphore on the worker thread and
// let that thread do the device preparation.
pDevice->flags |= MAL_FLAG_STARTED;
mal_semaphore_release(&pDevice->alsa.semaphore);
return MAL_SUCCESS;
}
mal_result mal_device_stop__alsa(mal_device* pDevice)
{
mal_assert(pDevice != NULL);
// The device is not stopped here. We instead mark the device as stopped and signal the semaphore
// on the worker thread.
pDevice->flags &= ~MAL_FLAG_STARTED;
mal_semaphore_release(&pDevice->alsa.semaphore);
// If we are in snd_pcm_writei()/snd_pcm_readi() we won't return from it until it's signaled. It
// appears from my admittedly limited research and experimentation that we can signal the PCM with
// snd_pcm_prepare(). If don't do this the thread will still return, but it'll wait for the next
// fragment to be processed which might take some time depending on the size of the fragment.
//
// I'm not sure if this is the proper way to do this so best look into this.
snd_pcm_prepare(pDevice->alsa.pPCM);
return MAL_SUCCESS;
}
#endif
// Helper for determining whether or not the given device is initialized.
mal_bool32 mal_device__is_initialized(mal_device* pDevice)
{
if (pDevice == NULL) return MAL_FALSE;
return (pDevice->flags & MAL_FLAG_INITIALIZED) != 0;
}
mal_result mal_enumerate_devices(mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo)
{
if (pCount == NULL) return MAL_INVALID_ARGS;
mal_result result = MAL_NO_BACKEND;
#ifdef MAL_ENABLE_DSOUND
if (result != MAL_SUCCESS) {
result = mal_enumerate_devices__dsound(type, pCount, pInfo);
}
#endif
#ifdef MAL_ENABLE_ALSA
if (result != MAL_SUCCESS) {
result = mal_enumerate_devices__alsa(type, pCount, pInfo);
}
#endif
#ifdef MAL_ENABLE_NULL
if (result != MAL_SUCCESS) {
result = mal_enumerate_devices__null(type, pCount, pInfo);
}
#endif
return result;
}
mal_result mal_device_init(mal_device* pDevice, mal_device_type type, mal_device_id* pDeviceID, mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_uint32 fragmentSizeInFrames, mal_uint32 fragmentCount)
{
if (pDevice == NULL) return MAL_INVALID_ARGS;
mal_zero_object(pDevice);
if (channels == 0 || sampleRate == 0 || fragmentSizeInFrames == 0 || fragmentCount == 0) return MAL_INVALID_ARGS;
pDevice->type = type;
pDevice->format = format;
pDevice->channels = channels;
pDevice->sampleRate = sampleRate;
pDevice->fragmentSizeInFrames = fragmentSizeInFrames;
pDevice->fragmentCount = fragmentCount;
mal_result result = MAL_NO_BACKEND;
#ifdef MAL_ENABLE_DSOUND
if (result != MAL_SUCCESS) {
result = mal_device_init__dsound(pDevice, type, pDeviceID, format, channels, sampleRate, fragmentSizeInFrames, fragmentCount);
}
#endif
#ifdef MAL_ENABLE_ALSA
if (result != MAL_SUCCESS) {
result = mal_device_init__alsa(pDevice, type, pDeviceID, format, channels, sampleRate, fragmentSizeInFrames, fragmentCount);
}
#endif
#ifdef MAL_ENABLE_NULL
if (result != MAL_SUCCESS) {
result = mal_device_init__null(pDevice, type, pDeviceID, format, channels, sampleRate, fragmentSizeInFrames, fragmentCount);
}
#endif
if (result != MAL_SUCCESS) {
return MAL_NO_BACKEND;
}
pDevice->flags |= MAL_FLAG_INITIALIZED;
return MAL_SUCCESS;
}
void mal_device_uninit(mal_device* pDevice)
{
if (!mal_device__is_initialized(pDevice)) return;
// Make sure the device is stopped first. The backends will probably handle this naturally,
// but I like to do it explicitly for my own sanity.
mal_device_stop(pDevice);
#ifdef MAL_ENABLE_DSOUND
if (pDevice->api == mal_api_dsound) {
mal_device_uninit__dsound(pDevice);
}
#endif
#ifdef MAL_ENABLE_ALSA
if (pDevice->api == mal_api_alsa) {
mal_device_uninit__alsa(pDevice);
}
#endif
#ifdef MAL_ENABLE_NULL
if (pDevice->api == mal_api_null) {
mal_device_uninit__null(pDevice);
}
#endif
mal_zero_object(pDevice);
}
void mal_device_set_recv_callback(mal_device* pDevice, mal_recv_proc proc)
{
if (pDevice == NULL) return;
pDevice->onRecv = proc;
}
void mal_device_set_send_callback(mal_device* pDevice, mal_send_proc proc)
{
if (pDevice == NULL) return;
pDevice->onSend = proc;
}
mal_result mal_device_start(mal_device* pDevice)
{
if (pDevice == NULL) return MAL_INVALID_ARGS;
if (mal_device_is_started(pDevice)) {
return MAL_DEVICE_ALREADY_STARTED;
}
mal_result result = MAL_NO_BACKEND;
#ifdef MAL_ENABLE_DSOUND
if (pDevice->api == mal_api_dsound) {
result = mal_device_start__dsound(pDevice);
}
#endif
#ifdef MAL_ENABLE_ALSA
if (pDevice->api == mal_api_alsa) {
result = mal_device_start__alsa(pDevice);
}
#endif
#ifdef MAL_ENABLE_NULL
if (pDevice->api == mal_api_null) {
result = mal_device_start__null(pDevice);
}
#endif
if (result == MAL_SUCCESS) {
pDevice->flags |= MAL_FLAG_STARTED;
}
return result;
}
mal_result mal_device_stop(mal_device* pDevice)
{
if (pDevice == NULL) return MAL_INVALID_ARGS;
if (!mal_device_is_started(pDevice)) {
return MAL_DEVICE_ALREADY_STOPPED;
}
mal_result result = MAL_NO_BACKEND;
#ifdef MAL_ENABLE_DSOUND
if (pDevice->api == mal_api_dsound) {
result = mal_device_stop__dsound(pDevice);
}
#endif
#ifdef MAL_ENABLE_ALSA
if (pDevice->api == mal_api_alsa) {
result = mal_device_stop__alsa(pDevice);
}
#endif
#ifdef MAL_ENABLE_NULL
if (pDevice->api == mal_api_null) {
result = mal_device_stop__null(pDevice);
}
#endif
if (result == MAL_SUCCESS) {
pDevice->flags &= ~MAL_FLAG_STARTED;
}
return result;
}
mal_bool32 mal_device_is_started(mal_device* pDevice)
{
if (pDevice == NULL) return MAL_FALSE;
return (pDevice->flags & MAL_FLAG_STARTED) != 0;
}
mal_uint32 mal_device_get_fragment_size_in_bytes(mal_device* pDevice)
{
if (pDevice == NULL) return 0;
return pDevice->fragmentSizeInFrames * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format);
}
mal_uint32 mal_get_sample_size_in_bytes(mal_format format)
{
mal_uint32 sizes[] = {
1, // u8
2, // s16
3, // s24
4, // s32
4, // f32
8, // f64
1, // alaw
1 // mulaw
};
return sizes[format];
}
#endif
// REVISION HISTORY
// ================
//
// v0.1 - TBD
// - Initial versioned release
// TODO
// ====
// - Error handling in worker threads isn't quite right. At the top of the main loop, before the semaphore wait, I think
// the device needs to be unmarked as playing.
// - mal_device_start() and mal_device_stop() need improving:
// - Need to wait for a synchronization primitive on the worker thread to signal that the operation is complete.
// - Need a more accurate return code.
// - Make starting and stopping thread-safe
// - More error codes
// - Logging
// - Implement mmap mode for ALSA.
// - Make device initialization more robust for ALSA
// - Clamp period sizes to their min/max.
// - Support rewinding. This will enable applications to employ better anti-latency.
// DEVELOPMENT NOTES
// =================
//
// ALSA
// ----
// - [DONE] Use snd_pcm_recover() when snd_pcm_writei() or snd_pcm_readi() fails.
/*
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
*/
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment