1511 lines
55 KiB
C
1511 lines
55 KiB
C
// WinMM waveform output over the native render-stream syscall ABI.
|
|
|
|
#include <stdint.h>
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <intrin.h>
|
|
#include <ntlite/syscall.h>
|
|
|
|
typedef unsigned int MMRESULT;
|
|
typedef void* HWAVEOUT;
|
|
typedef unsigned int UINT;
|
|
typedef uint32_t DWORD;
|
|
typedef uintptr_t DWORD_PTR;
|
|
typedef unsigned short WORD;
|
|
typedef int BOOL;
|
|
|
|
#define FALSE 0
|
|
#define TRUE 1
|
|
|
|
#define MMSYSERR_NOERROR 0u
|
|
#define MMSYSERR_ERROR 1u
|
|
#define MMSYSERR_BADDEVICEID 2u
|
|
#define MMSYSERR_ALLOCATED 4u
|
|
#define MMSYSERR_INVALHANDLE 5u
|
|
#define MMSYSERR_NOMEM 7u
|
|
#define MMSYSERR_NOTSUPPORTED 8u
|
|
#define MMSYSERR_INVALFLAG 10u
|
|
#define MMSYSERR_INVALPARAM 11u
|
|
#define MMSYSERR_HANDLEBUSY 12u
|
|
|
|
#define WAVERR_BADFORMAT 32u
|
|
#define WAVERR_STILLPLAYING 33u
|
|
#define WAVERR_UNPREPARED 34u
|
|
|
|
#define WAVE_FORMAT_PCM 1u
|
|
#define WAVE_MAPPER 0xFFFFFFFFu
|
|
|
|
#define WAVE_FORMAT_QUERY 0x00000001u
|
|
#define WAVE_ALLOWSYNC 0x00000002u
|
|
#define WAVE_FORMAT_DIRECT 0x00000008u
|
|
#define CALLBACK_TYPEMASK 0x00070000u
|
|
#define CALLBACK_NULL 0x00000000u
|
|
#define CALLBACK_WINDOW 0x00010000u
|
|
#define CALLBACK_THREAD 0x00020000u
|
|
#define CALLBACK_FUNCTION 0x00030000u
|
|
#define CALLBACK_EVENT 0x00050000u
|
|
#define WINMM_WAVE_OPEN_SUPPORTED_FLAGS \
|
|
(WAVE_FORMAT_QUERY | WAVE_ALLOWSYNC | WAVE_FORMAT_DIRECT | CALLBACK_TYPEMASK)
|
|
|
|
#define WHDR_DONE 0x00000001u
|
|
#define WHDR_PREPARED 0x00000002u
|
|
#define WHDR_BEGINLOOP 0x00000004u
|
|
#define WHDR_ENDLOOP 0x00000008u
|
|
#define WHDR_INQUEUE 0x00000010u
|
|
|
|
#define WOM_OPEN 0x03BBu
|
|
#define WOM_CLOSE 0x03BCu
|
|
#define WOM_DONE 0x03BDu
|
|
|
|
#define TIME_MS 0x0001u
|
|
#define TIME_SAMPLES 0x0002u
|
|
#define TIME_BYTES 0x0004u
|
|
|
|
#define WAVECAPS_VOLUME 0x0001u
|
|
#define WAVECAPS_LRVOLUME 0x0002u
|
|
|
|
#define WAVE_FORMAT_1M16 0x00000004u
|
|
#define WAVE_FORMAT_1S16 0x00000008u
|
|
#define WAVE_FORMAT_2M16 0x00000040u
|
|
#define WAVE_FORMAT_2S16 0x00000080u
|
|
#define WAVE_FORMAT_4M16 0x00000400u
|
|
#define WAVE_FORMAT_4S16 0x00000800u
|
|
#define WAVE_FORMAT_48M16 0x00004000u
|
|
#define WAVE_FORMAT_48S16 0x00008000u
|
|
|
|
#define WINMM_WAVEOUT_MAGIC 0x4F564157u
|
|
#define WINMM_WAIT_MILLISECONDS 5000ull
|
|
#define WINMM_WORKER_PRIORITY 1u
|
|
#define WINMM_WRITE_CHUNK_FRAMES 1024u
|
|
|
|
typedef struct WAVEFORMATEX {
|
|
WORD wFormatTag;
|
|
WORD nChannels;
|
|
DWORD nSamplesPerSec;
|
|
DWORD nAvgBytesPerSec;
|
|
WORD nBlockAlign;
|
|
WORD wBitsPerSample;
|
|
WORD cbSize;
|
|
} WAVEFORMATEX;
|
|
|
|
typedef struct WAVEHDR {
|
|
char* lpData;
|
|
DWORD dwBufferLength;
|
|
DWORD dwBytesRecorded;
|
|
DWORD_PTR dwUser;
|
|
DWORD dwFlags;
|
|
DWORD dwLoops;
|
|
struct WAVEHDR* lpNext;
|
|
DWORD_PTR reserved;
|
|
} WAVEHDR;
|
|
|
|
typedef struct MMTIME {
|
|
UINT wType;
|
|
union {
|
|
DWORD ms;
|
|
DWORD sample;
|
|
DWORD cb;
|
|
DWORD ticks;
|
|
} u;
|
|
} MMTIME;
|
|
|
|
typedef struct WAVEOUTCAPSA {
|
|
WORD wMid;
|
|
WORD wPid;
|
|
DWORD vDriverVersion;
|
|
char szPname[32];
|
|
DWORD dwFormats;
|
|
WORD wChannels;
|
|
WORD wReserved1;
|
|
DWORD dwSupport;
|
|
} WAVEOUTCAPSA;
|
|
|
|
typedef struct WAVEOUTCAPSW {
|
|
WORD wMid;
|
|
WORD wPid;
|
|
DWORD vDriverVersion;
|
|
unsigned short szPname[32];
|
|
DWORD dwFormats;
|
|
WORD wChannels;
|
|
WORD wReserved1;
|
|
DWORD dwSupport;
|
|
} WAVEOUTCAPSW;
|
|
|
|
typedef char WINMM_WAVEFORMATEX_SIZE[(sizeof(WAVEFORMATEX) == 20u) ? 1 : -1];
|
|
typedef char WINMM_WAVEHDR_SIZE[(sizeof(WAVEHDR) == 48u) ? 1 : -1];
|
|
typedef char WINMM_MMTIME_SIZE[(sizeof(MMTIME) == 8u) ? 1 : -1];
|
|
typedef char WINMM_WAVEOUTCAPSA_SIZE[(sizeof(WAVEOUTCAPSA) == 52u) ? 1 : -1];
|
|
typedef char WINMM_WAVEOUTCAPSW_SIZE[(sizeof(WAVEOUTCAPSW) == 84u) ? 1 : -1];
|
|
|
|
typedef void (__stdcall *WAVEOUTPROC)(HWAVEOUT waveOut,
|
|
UINT message,
|
|
DWORD_PTR instance,
|
|
DWORD_PTR parameter1,
|
|
DWORD_PTR parameter2);
|
|
|
|
typedef enum WINMM_WAVE_COMMAND {
|
|
WinmmWaveCommandNone = 0,
|
|
WinmmWaveCommandPause,
|
|
WinmmWaveCommandRestart,
|
|
WinmmWaveCommandReset
|
|
} WINMM_WAVE_COMMAND;
|
|
|
|
typedef struct WINMM_WAVE_BUFFER {
|
|
struct WINMM_WAVE_BUFFER* Next;
|
|
WAVEHDR* Header;
|
|
int16_t* Frames;
|
|
uint32_t FrameCount;
|
|
uint32_t SubmittedFrames;
|
|
uint64_t PresentedTarget;
|
|
uint8_t FullySubmitted;
|
|
uint64_t Sequence;
|
|
} WINMM_WAVE_BUFFER;
|
|
|
|
typedef struct WINMM_WAVEOUT {
|
|
struct WINMM_WAVEOUT* RegistryNext;
|
|
uint32_t Magic;
|
|
volatile long Lock;
|
|
volatile long ApiUsers;
|
|
uint32_t NativeStream;
|
|
uint32_t WakeEvent;
|
|
uint32_t CommandEvent;
|
|
uint32_t ReadyEvent;
|
|
uint32_t WorkerHandle;
|
|
uint32_t WorkerTid;
|
|
uint64_t EndpointId;
|
|
WAVEFORMATEX SourceFormat;
|
|
uintptr_t Callback;
|
|
uintptr_t CallbackInstance;
|
|
uint32_t CallbackMode;
|
|
uint32_t DeviceId;
|
|
uint32_t Volume;
|
|
uint32_t Closing;
|
|
uint32_t UserPaused;
|
|
uint32_t AutoPaused;
|
|
uint32_t Started;
|
|
uint32_t TerminalError;
|
|
WINMM_WAVE_COMMAND Command;
|
|
MMRESULT CommandResult;
|
|
WINMM_WAVE_BUFFER* QueueHead;
|
|
WINMM_WAVE_BUFFER* QueueTail;
|
|
WINMM_WAVE_BUFFER* SubmitNode;
|
|
uint64_t NativeSubmitted;
|
|
uint64_t KnownDiscontinuity;
|
|
uint64_t HeadersQueued;
|
|
uint64_t HeadersCompleted;
|
|
uint64_t FramesQueued;
|
|
} WINMM_WAVEOUT;
|
|
|
|
static WINMM_WAVEOUT* gWaveOutRegistry;
|
|
static volatile long gWaveOutRegistryLock;
|
|
|
|
|
|
typedef struct WINMM_DEBUG_LINE {
|
|
char Buffer[192];
|
|
size_t Length;
|
|
} WINMM_DEBUG_LINE;
|
|
|
|
// Appends text to one bounded WinMM diagnostic line.
|
|
static void WinmmDebugText(WINMM_DEBUG_LINE* line, const char* text) {
|
|
if (!line || !text)
|
|
return;
|
|
while (*text && line->Length + 1 < sizeof(line->Buffer))
|
|
line->Buffer[line->Length++] = *text++;
|
|
}
|
|
|
|
// Appends one unsigned decimal value to a WinMM diagnostic line.
|
|
static void WinmmDebugU64(WINMM_DEBUG_LINE* line, uint64_t value) {
|
|
char digits[24];
|
|
size_t count = 0;
|
|
do {
|
|
digits[count++] = (char)('0' + value % 10u);
|
|
value /= 10u;
|
|
} while (value && count < sizeof(digits));
|
|
while (count && line->Length + 1 < sizeof(line->Buffer))
|
|
line->Buffer[line->Length++] = digits[--count];
|
|
}
|
|
|
|
// Appends one fixed-width hexadecimal value to a WinMM diagnostic line.
|
|
static void WinmmDebugHex(WINMM_DEBUG_LINE* line, uint64_t value) {
|
|
static const char digits[] = "0123456789ABCDEF";
|
|
WinmmDebugText(line, "0x");
|
|
int started = 0;
|
|
for (int shift = 60; shift >= 0; shift -= 4) {
|
|
uint8_t digit = (uint8_t)((value >> shift) & 0xFu);
|
|
if (!started && digit == 0 && shift)
|
|
continue;
|
|
started = 1;
|
|
if (line->Length + 1 < sizeof(line->Buffer))
|
|
line->Buffer[line->Length++] = digits[digit];
|
|
}
|
|
}
|
|
|
|
// Emits one complete WinMM diagnostic line through the native debug console.
|
|
static void WinmmDebugCommit(WINMM_DEBUG_LINE* line) {
|
|
if (!line)
|
|
return;
|
|
if (line->Length + 1 < sizeof(line->Buffer))
|
|
line->Buffer[line->Length++] = '\n';
|
|
line->Buffer[line->Length] = '\0';
|
|
SYS_PRINT_DEBUG_REQUEST request = { SysSvFromParts(line->Buffer, line->Length) };
|
|
(void)SysPrintDebug(&request);
|
|
}
|
|
|
|
// Logs one waveform-open request before validation changes its control flow.
|
|
static void WinmmDebugOpenRequest(UINT deviceId,
|
|
const WAVEFORMATEX* format,
|
|
DWORD flags) {
|
|
WINMM_DEBUG_LINE line = {0};
|
|
WinmmDebugText(&line, "WINMM: open request device=");
|
|
WinmmDebugU64(&line, deviceId);
|
|
WinmmDebugText(&line, " tag=");
|
|
WinmmDebugU64(&line, format ? format->wFormatTag : 0);
|
|
WinmmDebugText(&line, " rate=");
|
|
WinmmDebugU64(&line, format ? format->nSamplesPerSec : 0);
|
|
WinmmDebugText(&line, " channels=");
|
|
WinmmDebugU64(&line, format ? format->nChannels : 0);
|
|
WinmmDebugText(&line, " bits=");
|
|
WinmmDebugU64(&line, format ? format->wBitsPerSample : 0);
|
|
WinmmDebugText(&line, " align=");
|
|
WinmmDebugU64(&line, format ? format->nBlockAlign : 0);
|
|
WinmmDebugText(&line, " avg=");
|
|
WinmmDebugU64(&line, format ? format->nAvgBytesPerSec : 0);
|
|
WinmmDebugText(&line, " flags=");
|
|
WinmmDebugHex(&line, flags);
|
|
WinmmDebugCommit(&line);
|
|
}
|
|
|
|
// Logs one WinMM result with its handle and native stream identity.
|
|
static void WinmmDebugResult(const char* operation,
|
|
const WINMM_WAVEOUT* waveOut,
|
|
uint64_t value) {
|
|
WINMM_DEBUG_LINE line = {0};
|
|
WinmmDebugText(&line, "WINMM: ");
|
|
WinmmDebugText(&line, operation);
|
|
if (waveOut) {
|
|
WinmmDebugText(&line, " handle=");
|
|
WinmmDebugHex(&line, (uintptr_t)waveOut);
|
|
WinmmDebugText(&line, " stream=");
|
|
WinmmDebugU64(&line, waveOut->NativeStream);
|
|
}
|
|
WinmmDebugText(&line, " value=");
|
|
WinmmDebugU64(&line, value);
|
|
WinmmDebugCommit(&line);
|
|
}
|
|
|
|
// Acquires one short user-mode spin lock without entering the kernel.
|
|
static void WinmmLock(volatile long* lock) {
|
|
while (_InterlockedCompareExchange(lock, 1, 0) != 0)
|
|
(void)SysYield();
|
|
}
|
|
|
|
// Releases one short user-mode spin lock.
|
|
static void WinmmUnlock(volatile long* lock) {
|
|
_InterlockedExchange(lock, 0);
|
|
}
|
|
|
|
// Maps one native status to the closest waveform-open result.
|
|
static MMRESULT WinmmMapNativeStatus(KSTATUS status) {
|
|
if (status == NTLSTATUS_SUCCESS)
|
|
return MMSYSERR_NOERROR;
|
|
if (status == NTLSTATUS_NO_MEMORY)
|
|
return MMSYSERR_NOMEM;
|
|
if (status == NTLSTATUS_NOT_FOUND || status == NTLSTATUS_DEVICE_REMOVED)
|
|
return MMSYSERR_BADDEVICEID;
|
|
if (status == NTLSTATUS_NOT_SUPPORTED)
|
|
return WAVERR_BADFORMAT;
|
|
if (status == NTLSTATUS_INVALID_PARAMETER)
|
|
return MMSYSERR_INVALPARAM;
|
|
return MMSYSERR_ERROR;
|
|
}
|
|
|
|
// Returns whether one source rate is accepted by the native stream ABI.
|
|
static int WinmmRateIsSupported(uint32_t sampleRate) {
|
|
return sampleRate == 11025u || sampleRate == 22050u ||
|
|
sampleRate == 44100u || sampleRate == 48000u;
|
|
}
|
|
|
|
// Validates the narrow PCM surface implemented by this personality.
|
|
static MMRESULT WinmmValidateFormat(const WAVEFORMATEX* format) {
|
|
if (!format)
|
|
return MMSYSERR_INVALPARAM;
|
|
if (format->wFormatTag != WAVE_FORMAT_PCM ||
|
|
(format->nChannels != 1u && format->nChannels != 2u) ||
|
|
!WinmmRateIsSupported(format->nSamplesPerSec) ||
|
|
(format->wBitsPerSample != 16u && format->wBitsPerSample != 24u))
|
|
return WAVERR_BADFORMAT;
|
|
|
|
uint32_t bytesPerSample = format->wBitsPerSample / 8u;
|
|
uint32_t blockAlign = (uint32_t)format->nChannels * bytesPerSample;
|
|
uint64_t averageBytes = (uint64_t)format->nSamplesPerSec * blockAlign;
|
|
if (format->nBlockAlign != blockAlign ||
|
|
averageBytes > UINT32_MAX ||
|
|
format->nAvgBytesPerSec != (uint32_t)averageBytes)
|
|
return WAVERR_BADFORMAT;
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
// Enumerates available native endpoints into the caller's fixed snapshot.
|
|
static MMRESULT WinmmEnumerateEndpoints(SYS_AUDIO_ENDPOINT_INFO* endpoints,
|
|
uint32_t capacity,
|
|
uint32_t* countOut) {
|
|
SYS_AUDIO_ENUMERATE_ENDPOINTS_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.Endpoints = endpoints;
|
|
request.Capacity = capacity;
|
|
request.CountOut = countOut;
|
|
return WinmmMapNativeStatus(SysAudioEnumerateEndpoints(&request));
|
|
}
|
|
|
|
// Counts endpoints that are currently ready and available.
|
|
static uint32_t WinmmAvailableEndpointCount(void) {
|
|
SYS_AUDIO_ENDPOINT_INFO endpoints[SYS_AUDIO_MAX_ENDPOINTS];
|
|
uint32_t count = 0;
|
|
if (WinmmEnumerateEndpoints(endpoints, SYS_AUDIO_MAX_ENDPOINTS, &count) != MMSYSERR_NOERROR)
|
|
return 0;
|
|
if (count > SYS_AUDIO_MAX_ENDPOINTS)
|
|
count = SYS_AUDIO_MAX_ENDPOINTS;
|
|
|
|
uint32_t available = 0;
|
|
for (uint32_t index = 0; index < count; index++) {
|
|
if (endpoints[index].State == SysAudioEndpointReady &&
|
|
(endpoints[index].Flags & SYS_AUDIO_ENDPOINT_FLAG_AVAILABLE))
|
|
available++;
|
|
}
|
|
return available;
|
|
}
|
|
|
|
// Resolves WAVE_MAPPER or one available-device index to a stable endpoint ID.
|
|
static MMRESULT WinmmResolveEndpoint(uint32_t deviceId,
|
|
uint64_t* endpointIdOut,
|
|
uint32_t* resolvedDeviceIdOut) {
|
|
SYS_AUDIO_ENDPOINT_INFO endpoints[SYS_AUDIO_MAX_ENDPOINTS];
|
|
uint32_t count = 0;
|
|
MMRESULT result = WinmmEnumerateEndpoints(endpoints, SYS_AUDIO_MAX_ENDPOINTS, &count);
|
|
if (result != MMSYSERR_NOERROR)
|
|
return result;
|
|
if (count > SYS_AUDIO_MAX_ENDPOINTS)
|
|
count = SYS_AUDIO_MAX_ENDPOINTS;
|
|
|
|
const SYS_AUDIO_ENDPOINT_INFO* fallback = NULL;
|
|
uint32_t fallbackIndex = 0;
|
|
uint32_t availableIndex = 0;
|
|
for (uint32_t index = 0; index < count; index++) {
|
|
const SYS_AUDIO_ENDPOINT_INFO* endpoint = &endpoints[index];
|
|
if (endpoint->State != SysAudioEndpointReady ||
|
|
(endpoint->Flags & SYS_AUDIO_ENDPOINT_FLAG_AVAILABLE) == 0)
|
|
continue;
|
|
if (!fallback) {
|
|
fallback = endpoint;
|
|
fallbackIndex = availableIndex;
|
|
}
|
|
if (deviceId == WAVE_MAPPER &&
|
|
(endpoint->Flags & SYS_AUDIO_ENDPOINT_FLAG_DEFAULT)) {
|
|
*endpointIdOut = endpoint->EndpointId;
|
|
*resolvedDeviceIdOut = availableIndex;
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
if (deviceId != WAVE_MAPPER && availableIndex == deviceId) {
|
|
*endpointIdOut = endpoint->EndpointId;
|
|
*resolvedDeviceIdOut = availableIndex;
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
availableIndex++;
|
|
}
|
|
|
|
if (deviceId == WAVE_MAPPER && fallback) {
|
|
*endpointIdOut = fallback->EndpointId;
|
|
*resolvedDeviceIdOut = fallbackIndex;
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
return MMSYSERR_BADDEVICEID;
|
|
}
|
|
|
|
// Adds one live waveform handle to the process-local registry.
|
|
static void WinmmRegisterWaveOut(WINMM_WAVEOUT* waveOut) {
|
|
WinmmLock(&gWaveOutRegistryLock);
|
|
waveOut->RegistryNext = gWaveOutRegistry;
|
|
gWaveOutRegistry = waveOut;
|
|
WinmmUnlock(&gWaveOutRegistryLock);
|
|
}
|
|
|
|
// References one registered waveform handle without dereferencing unknown pointers.
|
|
static WINMM_WAVEOUT* WinmmReferenceWaveOut(HWAVEOUT handle) {
|
|
WINMM_WAVEOUT* found = NULL;
|
|
WinmmLock(&gWaveOutRegistryLock);
|
|
for (WINMM_WAVEOUT* current = gWaveOutRegistry;
|
|
current;
|
|
current = current->RegistryNext) {
|
|
if ((HWAVEOUT)current == handle && current->Magic == WINMM_WAVEOUT_MAGIC) {
|
|
_InterlockedIncrement(¤t->ApiUsers);
|
|
found = current;
|
|
break;
|
|
}
|
|
}
|
|
WinmmUnlock(&gWaveOutRegistryLock);
|
|
return found;
|
|
}
|
|
|
|
// Releases one API-use reference acquired through the registry.
|
|
static void WinmmReleaseWaveOut(WINMM_WAVEOUT* waveOut) {
|
|
if (waveOut)
|
|
_InterlockedDecrement(&waveOut->ApiUsers);
|
|
}
|
|
|
|
// Removes one waveform handle so no new API references can be acquired.
|
|
static int WinmmRemoveWaveOut(WINMM_WAVEOUT* waveOut) {
|
|
int removed = 0;
|
|
WinmmLock(&gWaveOutRegistryLock);
|
|
WINMM_WAVEOUT** link = &gWaveOutRegistry;
|
|
while (*link) {
|
|
if (*link == waveOut) {
|
|
*link = waveOut->RegistryNext;
|
|
waveOut->RegistryNext = NULL;
|
|
removed = 1;
|
|
break;
|
|
}
|
|
link = &(*link)->RegistryNext;
|
|
}
|
|
WinmmUnlock(&gWaveOutRegistryLock);
|
|
return removed;
|
|
}
|
|
|
|
// Delivers one WinMM callback through a supported user-mode mechanism.
|
|
static void WinmmNotify(WINMM_WAVEOUT* waveOut,
|
|
uint32_t message,
|
|
uintptr_t parameter1,
|
|
uintptr_t parameter2) {
|
|
if (waveOut->CallbackMode == CALLBACK_FUNCTION) {
|
|
WAVEOUTPROC callback = (WAVEOUTPROC)waveOut->Callback;
|
|
callback((HWAVEOUT)waveOut,
|
|
message,
|
|
waveOut->CallbackInstance,
|
|
parameter1,
|
|
parameter2);
|
|
} else if (waveOut->CallbackMode == CALLBACK_EVENT) {
|
|
(void)SysSignalEvent((uint32_t)waveOut->Callback);
|
|
}
|
|
}
|
|
|
|
// Converts one packed PCM buffer into personality-owned native S16 frames.
|
|
static MMRESULT WinmmCopyFrames(const WINMM_WAVEOUT* waveOut,
|
|
const WAVEHDR* header,
|
|
int16_t** framesOut,
|
|
uint32_t* frameCountOut) {
|
|
if (!header || !framesOut || !frameCountOut)
|
|
return MMSYSERR_INVALPARAM;
|
|
if (header->dwBufferLength && !header->lpData)
|
|
return MMSYSERR_INVALPARAM;
|
|
if (header->dwBufferLength % waveOut->SourceFormat.nBlockAlign)
|
|
return WAVERR_BADFORMAT;
|
|
|
|
uint32_t frameCount = header->dwBufferLength / waveOut->SourceFormat.nBlockAlign;
|
|
size_t sampleCount = (size_t)frameCount * waveOut->SourceFormat.nChannels;
|
|
if (frameCount && sampleCount > SIZE_MAX / sizeof(int16_t))
|
|
return MMSYSERR_NOMEM;
|
|
int16_t* output = sampleCount ? (int16_t*)malloc(sampleCount * sizeof(int16_t)) : NULL;
|
|
if (sampleCount && !output)
|
|
return MMSYSERR_NOMEM;
|
|
|
|
if (waveOut->SourceFormat.wBitsPerSample == 16u) {
|
|
if (sampleCount)
|
|
memcpy(output, header->lpData, sampleCount * sizeof(int16_t));
|
|
} else {
|
|
const uint8_t* input = (const uint8_t*)header->lpData;
|
|
for (size_t sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++) {
|
|
uint32_t packed = (uint32_t)input[sampleIndex * 3u] |
|
|
((uint32_t)input[sampleIndex * 3u + 1u] << 8u) |
|
|
((uint32_t)input[sampleIndex * 3u + 2u] << 16u);
|
|
int32_t signedSample = (packed & 0x00800000u)
|
|
? (int32_t)(packed | 0xFF000000u)
|
|
: (int32_t)packed;
|
|
output[sampleIndex] = (int16_t)(signedSample / 256);
|
|
}
|
|
}
|
|
|
|
*framesOut = output;
|
|
*frameCountOut = frameCount;
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
// Pops and completes every header whose source frames reached hardware presentation.
|
|
static void WinmmCompletePresentedBuffers(WINMM_WAVEOUT* waveOut,
|
|
uint64_t framesPresented) {
|
|
while (true) {
|
|
WinmmLock(&waveOut->Lock);
|
|
WINMM_WAVE_BUFFER* buffer = waveOut->QueueHead;
|
|
if (!buffer || !buffer->FullySubmitted ||
|
|
buffer->PresentedTarget > framesPresented) {
|
|
WinmmUnlock(&waveOut->Lock);
|
|
return;
|
|
}
|
|
waveOut->QueueHead = buffer->Next;
|
|
if (!waveOut->QueueHead)
|
|
waveOut->QueueTail = NULL;
|
|
if (waveOut->SubmitNode == buffer)
|
|
waveOut->SubmitNode = buffer->Next;
|
|
buffer->Header->dwFlags &= ~WHDR_INQUEUE;
|
|
buffer->Header->dwFlags |= WHDR_DONE;
|
|
waveOut->HeadersCompleted++;
|
|
WinmmUnlock(&waveOut->Lock);
|
|
WinmmNotify(waveOut, WOM_DONE, (uintptr_t)buffer->Header, 0);
|
|
free(buffer->Frames);
|
|
free(buffer);
|
|
}
|
|
}
|
|
|
|
// Detaches every queued buffer for reset or terminal failure completion.
|
|
static WINMM_WAVE_BUFFER* WinmmDetachAllBuffers(WINMM_WAVEOUT* waveOut) {
|
|
WinmmLock(&waveOut->Lock);
|
|
WINMM_WAVE_BUFFER* buffers = waveOut->QueueHead;
|
|
waveOut->QueueHead = NULL;
|
|
waveOut->QueueTail = NULL;
|
|
waveOut->SubmitNode = NULL;
|
|
WinmmUnlock(&waveOut->Lock);
|
|
return buffers;
|
|
}
|
|
|
|
// Completes and releases one detached buffer list in queue order.
|
|
static void WinmmCompleteDetachedBuffers(WINMM_WAVEOUT* waveOut,
|
|
WINMM_WAVE_BUFFER* buffers) {
|
|
while (buffers) {
|
|
WINMM_WAVE_BUFFER* next = buffers->Next;
|
|
buffers->Header->dwFlags &= ~WHDR_INQUEUE;
|
|
buffers->Header->dwFlags |= WHDR_DONE;
|
|
waveOut->HeadersCompleted++;
|
|
WinmmNotify(waveOut, WOM_DONE, (uintptr_t)buffers->Header, 0);
|
|
free(buffers->Frames);
|
|
free(buffers);
|
|
buffers = next;
|
|
}
|
|
}
|
|
|
|
// Queries one coherent native stream snapshot.
|
|
static KSTATUS WinmmQueryNativeStream(WINMM_WAVEOUT* waveOut,
|
|
SYS_AUDIO_STREAM_INFO* info) {
|
|
memset(info, 0, sizeof(*info));
|
|
info->Size = sizeof(*info);
|
|
return SysAudioQueryStream(waveOut->NativeStream, info);
|
|
}
|
|
|
|
// Updates the controlled discontinuity baseline after stop or reset.
|
|
static void WinmmRefreshDiscontinuity(WINMM_WAVEOUT* waveOut) {
|
|
SYS_AUDIO_STREAM_INFO info;
|
|
if (WinmmQueryNativeStream(waveOut, &info) == NTLSTATUS_SUCCESS)
|
|
waveOut->KnownDiscontinuity = info.DiscontinuityGeneration;
|
|
}
|
|
|
|
// Processes one synchronous control command on the worker thread.
|
|
static void WinmmProcessCommand(WINMM_WAVEOUT* waveOut) {
|
|
WinmmLock(&waveOut->Lock);
|
|
WINMM_WAVE_COMMAND command = waveOut->Command;
|
|
WinmmUnlock(&waveOut->Lock);
|
|
if (command == WinmmWaveCommandNone)
|
|
return;
|
|
|
|
MMRESULT result = MMSYSERR_NOERROR;
|
|
WINMM_WAVE_BUFFER* resetBuffers = NULL;
|
|
if (command == WinmmWaveCommandPause) {
|
|
if (!waveOut->UserPaused) {
|
|
waveOut->UserPaused = 1;
|
|
if (waveOut->Started && !waveOut->AutoPaused) {
|
|
SYS_AUDIO_STREAM_INFO info;
|
|
KSTATUS status = WinmmQueryNativeStream(waveOut, &info);
|
|
if (status == NTLSTATUS_SUCCESS && info.State == SysAudioStreamRunning)
|
|
status = SysAudioPause(waveOut->NativeStream);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
result = WinmmMapNativeStatus(status);
|
|
}
|
|
}
|
|
} else if (command == WinmmWaveCommandRestart) {
|
|
if (waveOut->UserPaused) {
|
|
waveOut->UserPaused = 0;
|
|
if (waveOut->Started && !waveOut->AutoPaused) {
|
|
SYS_AUDIO_STREAM_INFO info;
|
|
KSTATUS status = WinmmQueryNativeStream(waveOut, &info);
|
|
if (status == NTLSTATUS_SUCCESS && info.State == SysAudioStreamPaused)
|
|
status = SysAudioResume(waveOut->NativeStream);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
result = WinmmMapNativeStatus(status);
|
|
}
|
|
}
|
|
} else if (command == WinmmWaveCommandReset) {
|
|
(void)SysAudioStop(waveOut->NativeStream);
|
|
KSTATUS status = SysAudioReset(waveOut->NativeStream);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
result = WinmmMapNativeStatus(status);
|
|
else
|
|
waveOut->TerminalError = 0;
|
|
resetBuffers = WinmmDetachAllBuffers(waveOut);
|
|
waveOut->NativeSubmitted = 0;
|
|
waveOut->Started = 0;
|
|
waveOut->UserPaused = 0;
|
|
waveOut->AutoPaused = 0;
|
|
WinmmRefreshDiscontinuity(waveOut);
|
|
}
|
|
|
|
if (resetBuffers)
|
|
WinmmCompleteDetachedBuffers(waveOut, resetBuffers);
|
|
|
|
WinmmLock(&waveOut->Lock);
|
|
waveOut->CommandResult = result;
|
|
waveOut->Command = WinmmWaveCommandNone;
|
|
WinmmUnlock(&waveOut->Lock);
|
|
(void)SysSignalEvent(waveOut->CommandEvent);
|
|
}
|
|
|
|
// Returns the next queued node that still has source frames to submit.
|
|
static WINMM_WAVE_BUFFER* WinmmFindSubmitNode(WINMM_WAVEOUT* waveOut) {
|
|
WinmmLock(&waveOut->Lock);
|
|
WINMM_WAVE_BUFFER* buffer = waveOut->SubmitNode;
|
|
while (buffer && buffer->FullySubmitted)
|
|
buffer = buffer->Next;
|
|
waveOut->SubmitNode = buffer;
|
|
WinmmUnlock(&waveOut->Lock);
|
|
return buffer;
|
|
}
|
|
|
|
// Feeds queued buffers into the native ring until it becomes full or empty.
|
|
static KSTATUS WinmmFeedNativeStream(WINMM_WAVEOUT* waveOut,
|
|
uint32_t* madeProgressOut) {
|
|
*madeProgressOut = 0;
|
|
while (true) {
|
|
WINMM_WAVE_BUFFER* buffer = WinmmFindSubmitNode(waveOut);
|
|
if (!buffer)
|
|
return NTLSTATUS_SUCCESS;
|
|
|
|
uint32_t remaining = buffer->FrameCount - buffer->SubmittedFrames;
|
|
if (!remaining) {
|
|
buffer->FullySubmitted = 1;
|
|
buffer->PresentedTarget = waveOut->NativeSubmitted;
|
|
continue;
|
|
}
|
|
uint32_t requestFrames = remaining < WINMM_WRITE_CHUNK_FRAMES
|
|
? remaining
|
|
: WINMM_WRITE_CHUNK_FRAMES;
|
|
uint32_t writtenFrames = 0;
|
|
SYS_AUDIO_WRITE_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.StreamHandle = waveOut->NativeStream;
|
|
request.FrameCount = requestFrames;
|
|
request.Frames = buffer->Frames +
|
|
(size_t)buffer->SubmittedFrames * waveOut->SourceFormat.nChannels;
|
|
request.WrittenFramesOut = &writtenFrames;
|
|
KSTATUS status = SysAudioWrite(&request);
|
|
if (status == NTLSTATUS_WOULD_BLOCK)
|
|
return status;
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
return status;
|
|
if (!writtenFrames)
|
|
return NTLSTATUS_WOULD_BLOCK;
|
|
|
|
buffer->SubmittedFrames += writtenFrames;
|
|
waveOut->NativeSubmitted += writtenFrames;
|
|
*madeProgressOut = 1;
|
|
if (buffer->SubmittedFrames == buffer->FrameCount) {
|
|
buffer->FullySubmitted = 1;
|
|
buffer->PresentedTarget = waveOut->NativeSubmitted;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Determines whether any queued buffer still needs native-ring submission.
|
|
static int WinmmHasUnsubmittedBuffer(WINMM_WAVEOUT* waveOut) {
|
|
return WinmmFindSubmitNode(waveOut) != NULL;
|
|
}
|
|
|
|
// Starts or resumes playback after a useful prefill is available.
|
|
static KSTATUS WinmmEnsureStarted(WINMM_WAVEOUT* waveOut) {
|
|
if (waveOut->UserPaused)
|
|
return NTLSTATUS_SUCCESS;
|
|
if (!waveOut->Started) {
|
|
KSTATUS status = SysAudioStart(waveOut->NativeStream);
|
|
if (status == NTLSTATUS_SUCCESS) {
|
|
waveOut->Started = 1;
|
|
waveOut->AutoPaused = 0;
|
|
}
|
|
return status;
|
|
}
|
|
if (waveOut->AutoPaused) {
|
|
KSTATUS status = SysAudioResume(waveOut->NativeStream);
|
|
if (status == NTLSTATUS_SUCCESS)
|
|
waveOut->AutoPaused = 0;
|
|
return status;
|
|
}
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Stops a completed empty stream without resetting its public position counters.
|
|
static void WinmmStopEmptyStream(WINMM_WAVEOUT* waveOut) {
|
|
if (!waveOut->Started)
|
|
return;
|
|
(void)SysAudioStop(waveOut->NativeStream);
|
|
waveOut->Started = 0;
|
|
waveOut->AutoPaused = 0;
|
|
WinmmRefreshDiscontinuity(waveOut);
|
|
}
|
|
|
|
// Arms native progress conditions and waits for either stream progress or new work.
|
|
static KSTATUS WinmmWaitForWork(WINMM_WAVEOUT* waveOut,
|
|
const SYS_AUDIO_STREAM_INFO* info,
|
|
int hasUnsubmitted) {
|
|
SYS_AUDIO_ARM_WAIT_REQUEST arm;
|
|
memset(&arm, 0, sizeof(arm));
|
|
arm.Size = sizeof(arm);
|
|
arm.StreamHandle = waveOut->NativeStream;
|
|
arm.Mask = SYS_AUDIO_WAIT_FAILED | SYS_AUDIO_WAIT_DISCONTINUITY;
|
|
arm.DiscontinuityGeneration = waveOut->KnownDiscontinuity;
|
|
|
|
if (hasUnsubmitted && !waveOut->UserPaused) {
|
|
arm.Mask |= SYS_AUDIO_WAIT_WRITABLE;
|
|
arm.WritableFrames = 1;
|
|
}
|
|
WinmmLock(&waveOut->Lock);
|
|
WINMM_WAVE_BUFFER* head = waveOut->QueueHead;
|
|
if (head && head->FullySubmitted) {
|
|
arm.Mask |= SYS_AUDIO_WAIT_PRESENTED;
|
|
arm.PresentedFrames = head->PresentedTarget;
|
|
}
|
|
WinmmUnlock(&waveOut->Lock);
|
|
if (!hasUnsubmitted && waveOut->Started &&
|
|
info->FramesConsumed < waveOut->NativeSubmitted) {
|
|
arm.Mask |= SYS_AUDIO_WAIT_CONSUMED;
|
|
arm.ConsumedFrames = waveOut->NativeSubmitted;
|
|
}
|
|
|
|
KSTATUS status = SysAudioArmWait(&arm);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
return status;
|
|
|
|
uint32_t handles[2] = { waveOut->WakeEvent, waveOut->NativeStream };
|
|
uint32_t selected = 0;
|
|
SYS_WAIT_MANY_REQUEST wait;
|
|
memset(&wait, 0, sizeof(wait));
|
|
wait.Size = sizeof(wait);
|
|
wait.Handles = handles;
|
|
wait.Count = 2;
|
|
wait.SignaledIndexOut = &selected;
|
|
wait.TimeoutMilliseconds = SYS_WAIT_TIMEOUT_INFINITE;
|
|
return SysWaitMany(&wait);
|
|
}
|
|
|
|
// Retires a failed stream and returns all queued headers to the application.
|
|
static void WinmmFailStream(WINMM_WAVEOUT* waveOut, MMRESULT error) {
|
|
waveOut->TerminalError = error ? error : MMSYSERR_ERROR;
|
|
WinmmDebugResult("stream failure", waveOut, waveOut->TerminalError);
|
|
(void)SysAudioStop(waveOut->NativeStream);
|
|
waveOut->Started = 0;
|
|
waveOut->AutoPaused = 0;
|
|
WINMM_WAVE_BUFFER* buffers = WinmmDetachAllBuffers(waveOut);
|
|
WinmmCompleteDetachedBuffers(waveOut, buffers);
|
|
}
|
|
|
|
// Runs one waveform-output worker until close or process teardown.
|
|
static void WinmmWaveWorker(uintptr_t argument) {
|
|
WINMM_WAVEOUT* waveOut = (WINMM_WAVEOUT*)argument;
|
|
waveOut->WorkerTid = SysGetCurrentTid();
|
|
WinmmNotify(waveOut, WOM_OPEN, 0, 0);
|
|
(void)SysSignalEvent(waveOut->ReadyEvent);
|
|
|
|
while (true) {
|
|
WinmmProcessCommand(waveOut);
|
|
if (waveOut->Closing) {
|
|
WinmmStopEmptyStream(waveOut);
|
|
WinmmNotify(waveOut, WOM_CLOSE, 0, 0);
|
|
SysExitThread(0);
|
|
}
|
|
if (waveOut->TerminalError) {
|
|
uint32_t handles[1] = { waveOut->WakeEvent };
|
|
uint32_t selected = 0;
|
|
SYS_WAIT_MANY_REQUEST wait = {
|
|
sizeof(wait), 0, handles, 1, &selected, SYS_WAIT_TIMEOUT_INFINITE
|
|
};
|
|
(void)SysWaitMany(&wait);
|
|
continue;
|
|
}
|
|
|
|
SYS_AUDIO_STREAM_INFO info;
|
|
KSTATUS status = WinmmQueryNativeStream(waveOut, &info);
|
|
if (status != NTLSTATUS_SUCCESS || info.State == SysAudioStreamFailed) {
|
|
WinmmFailStream(waveOut, WinmmMapNativeStatus(
|
|
status != NTLSTATUS_SUCCESS ? status : info.FailureStatus));
|
|
continue;
|
|
}
|
|
if (info.DiscontinuityGeneration != waveOut->KnownDiscontinuity) {
|
|
// A native discontinuity is a timeline notification, not a stream
|
|
// failure. The failed state remains the terminal error authority.
|
|
waveOut->KnownDiscontinuity = info.DiscontinuityGeneration;
|
|
WinmmDebugResult("stream discontinuity", waveOut,
|
|
waveOut->KnownDiscontinuity);
|
|
}
|
|
|
|
WinmmCompletePresentedBuffers(waveOut, info.FramesPresented);
|
|
WinmmLock(&waveOut->Lock);
|
|
int queueEmpty = waveOut->QueueHead == NULL;
|
|
WinmmUnlock(&waveOut->Lock);
|
|
if (queueEmpty) {
|
|
WinmmStopEmptyStream(waveOut);
|
|
status = WinmmWaitForWork(waveOut, &info, 0);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
WinmmFailStream(waveOut, WinmmMapNativeStatus(status));
|
|
continue;
|
|
}
|
|
|
|
if (!waveOut->UserPaused) {
|
|
uint32_t madeProgress = 0;
|
|
status = WinmmFeedNativeStream(waveOut, &madeProgress);
|
|
if (status != NTLSTATUS_SUCCESS && status != NTLSTATUS_WOULD_BLOCK) {
|
|
WinmmFailStream(waveOut, WinmmMapNativeStatus(status));
|
|
continue;
|
|
}
|
|
if (waveOut->NativeSubmitted && (!waveOut->Started || waveOut->AutoPaused)) {
|
|
status = WinmmEnsureStarted(waveOut);
|
|
if (status != NTLSTATUS_SUCCESS) {
|
|
WinmmFailStream(waveOut, WinmmMapNativeStatus(status));
|
|
continue;
|
|
}
|
|
}
|
|
if (madeProgress)
|
|
continue;
|
|
}
|
|
|
|
status = WinmmQueryNativeStream(waveOut, &info);
|
|
if (status != NTLSTATUS_SUCCESS) {
|
|
WinmmFailStream(waveOut, WinmmMapNativeStatus(status));
|
|
continue;
|
|
}
|
|
int hasUnsubmitted = WinmmHasUnsubmittedBuffer(waveOut);
|
|
if (!hasUnsubmitted && waveOut->Started && !waveOut->UserPaused &&
|
|
!waveOut->AutoPaused && info.FramesConsumed >= waveOut->NativeSubmitted) {
|
|
status = SysAudioPause(waveOut->NativeStream);
|
|
if (status == NTLSTATUS_SUCCESS)
|
|
waveOut->AutoPaused = 1;
|
|
else {
|
|
WinmmFailStream(waveOut, WinmmMapNativeStatus(status));
|
|
continue;
|
|
}
|
|
}
|
|
|
|
status = WinmmWaitForWork(waveOut, &info, hasUnsubmitted);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
WinmmFailStream(waveOut, WinmmMapNativeStatus(status));
|
|
}
|
|
}
|
|
|
|
// Submits one synchronous worker command and waits for its completion.
|
|
static MMRESULT WinmmSubmitCommand(WINMM_WAVEOUT* waveOut,
|
|
WINMM_WAVE_COMMAND command) {
|
|
if (waveOut->WorkerTid == SysGetCurrentTid())
|
|
return MMSYSERR_HANDLEBUSY;
|
|
|
|
WinmmLock(&waveOut->Lock);
|
|
if (waveOut->Command != WinmmWaveCommandNone) {
|
|
WinmmUnlock(&waveOut->Lock);
|
|
return MMSYSERR_ALLOCATED;
|
|
}
|
|
waveOut->Command = command;
|
|
waveOut->CommandResult = MMSYSERR_ERROR;
|
|
WinmmUnlock(&waveOut->Lock);
|
|
(void)SysSignalEvent(waveOut->WakeEvent);
|
|
|
|
KSTATUS status = SysWait(waveOut->CommandEvent, SYS_WAIT_FLAG_NONE);
|
|
if (status != NTLSTATUS_SUCCESS)
|
|
return WinmmMapNativeStatus(status);
|
|
return waveOut->CommandResult;
|
|
}
|
|
|
|
// Returns the number of available logical waveform output devices.
|
|
__declspec(dllexport) UINT waveOutGetNumDevs(void) {
|
|
return WinmmAvailableEndpointCount();
|
|
}
|
|
|
|
// Opens one waveform output handle or validates one format query.
|
|
__declspec(dllexport) MMRESULT waveOutOpen(HWAVEOUT* handleOut,
|
|
UINT deviceId,
|
|
const WAVEFORMATEX* format,
|
|
DWORD_PTR callback,
|
|
DWORD_PTR callbackInstance,
|
|
DWORD flags) {
|
|
WinmmDebugOpenRequest(deviceId, format, flags);
|
|
MMRESULT result = WinmmValidateFormat(format);
|
|
if (result != MMSYSERR_NOERROR) {
|
|
WinmmDebugResult("open rejected format", NULL, result);
|
|
return result;
|
|
}
|
|
if (flags & ~WINMM_WAVE_OPEN_SUPPORTED_FLAGS) {
|
|
WinmmDebugResult("open rejected flags", NULL, flags & ~WINMM_WAVE_OPEN_SUPPORTED_FLAGS);
|
|
return MMSYSERR_INVALFLAG;
|
|
}
|
|
|
|
uint32_t callbackMode = flags & CALLBACK_TYPEMASK;
|
|
if (callbackMode == CALLBACK_WINDOW || callbackMode == CALLBACK_THREAD) {
|
|
WinmmDebugResult("open rejected callback", NULL, callbackMode);
|
|
return MMSYSERR_NOTSUPPORTED;
|
|
}
|
|
if (callbackMode != CALLBACK_NULL &&
|
|
callbackMode != CALLBACK_FUNCTION &&
|
|
callbackMode != CALLBACK_EVENT)
|
|
return MMSYSERR_INVALFLAG;
|
|
if ((callbackMode == CALLBACK_FUNCTION || callbackMode == CALLBACK_EVENT) && !callback)
|
|
return MMSYSERR_INVALPARAM;
|
|
|
|
uint64_t endpointId = 0;
|
|
uint32_t resolvedDeviceId = 0;
|
|
result = WinmmResolveEndpoint(deviceId, &endpointId, &resolvedDeviceId);
|
|
if (result != MMSYSERR_NOERROR) {
|
|
WinmmDebugResult("open rejected endpoint", NULL, result);
|
|
return result;
|
|
}
|
|
if (flags & WAVE_FORMAT_QUERY) {
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
if (!handleOut)
|
|
return MMSYSERR_INVALPARAM;
|
|
*handleOut = NULL;
|
|
|
|
WINMM_WAVEOUT* waveOut = (WINMM_WAVEOUT*)calloc(1, sizeof(*waveOut));
|
|
if (!waveOut)
|
|
return MMSYSERR_NOMEM;
|
|
waveOut->Magic = WINMM_WAVEOUT_MAGIC;
|
|
waveOut->EndpointId = endpointId;
|
|
waveOut->SourceFormat = *format;
|
|
waveOut->Callback = callback;
|
|
waveOut->CallbackInstance = callbackInstance;
|
|
waveOut->CallbackMode = callbackMode;
|
|
waveOut->DeviceId = resolvedDeviceId;
|
|
waveOut->Volume = 0xFFFFFFFFu;
|
|
|
|
SYS_AUDIO_OPEN_STREAM_REQUEST open;
|
|
memset(&open, 0, sizeof(open));
|
|
open.Size = sizeof(open);
|
|
open.EndpointId = endpointId;
|
|
open.Format.SampleRate = format->nSamplesPerSec;
|
|
open.Format.ChannelCount = format->nChannels;
|
|
open.Format.SampleFormat = SysAudioSampleS16;
|
|
open.StreamHandleOut = &waveOut->NativeStream;
|
|
KSTATUS status = SysAudioOpenStream(&open);
|
|
if (status != NTLSTATUS_SUCCESS) {
|
|
result = WinmmMapNativeStatus(status);
|
|
WinmmDebugResult("native stream open failed", waveOut, status);
|
|
free(waveOut);
|
|
return result;
|
|
}
|
|
|
|
status = SysCreateEvent(0, 0, &waveOut->WakeEvent);
|
|
if (status == NTLSTATUS_SUCCESS)
|
|
status = SysCreateEvent(0, 0, &waveOut->CommandEvent);
|
|
if (status == NTLSTATUS_SUCCESS)
|
|
status = SysCreateEvent(0, 0, &waveOut->ReadyEvent);
|
|
if (status != NTLSTATUS_SUCCESS) {
|
|
result = WinmmMapNativeStatus(status);
|
|
WinmmDebugResult("event creation failed", waveOut, status);
|
|
if (waveOut->ReadyEvent) (void)SysCloseHandle(waveOut->ReadyEvent);
|
|
if (waveOut->CommandEvent) (void)SysCloseHandle(waveOut->CommandEvent);
|
|
if (waveOut->WakeEvent) (void)SysCloseHandle(waveOut->WakeEvent);
|
|
(void)SysCloseHandle(waveOut->NativeStream);
|
|
free(waveOut);
|
|
return result;
|
|
}
|
|
|
|
SYS_AUDIO_STREAM_INFO info;
|
|
status = WinmmQueryNativeStream(waveOut, &info);
|
|
if (status != NTLSTATUS_SUCCESS) {
|
|
result = WinmmMapNativeStatus(status);
|
|
(void)SysCloseHandle(waveOut->ReadyEvent);
|
|
(void)SysCloseHandle(waveOut->CommandEvent);
|
|
(void)SysCloseHandle(waveOut->WakeEvent);
|
|
(void)SysCloseHandle(waveOut->NativeStream);
|
|
free(waveOut);
|
|
return result;
|
|
}
|
|
waveOut->KnownDiscontinuity = info.DiscontinuityGeneration;
|
|
|
|
WinmmRegisterWaveOut(waveOut);
|
|
SYS_CREATE_THREAD_REQUEST thread;
|
|
memset(&thread, 0, sizeof(thread));
|
|
thread.Entry = (uintptr_t)WinmmWaveWorker;
|
|
thread.Argument = (uintptr_t)waveOut;
|
|
thread.Priority = WINMM_WORKER_PRIORITY;
|
|
thread.ThreadHandleOut = &waveOut->WorkerHandle;
|
|
thread.ThreadIdOut = &waveOut->WorkerTid;
|
|
status = SysCreateThread(&thread);
|
|
if (status != NTLSTATUS_SUCCESS) {
|
|
(void)WinmmRemoveWaveOut(waveOut);
|
|
result = WinmmMapNativeStatus(status);
|
|
WinmmDebugResult("worker creation failed", waveOut, status);
|
|
(void)SysCloseHandle(waveOut->ReadyEvent);
|
|
(void)SysCloseHandle(waveOut->CommandEvent);
|
|
(void)SysCloseHandle(waveOut->WakeEvent);
|
|
(void)SysCloseHandle(waveOut->NativeStream);
|
|
free(waveOut);
|
|
return result;
|
|
}
|
|
|
|
status = SysWaitSingle(waveOut->ReadyEvent,
|
|
SYS_WAIT_FLAG_NONE,
|
|
WINMM_WAIT_MILLISECONDS);
|
|
(void)SysCloseHandle(waveOut->ReadyEvent);
|
|
waveOut->ReadyEvent = 0;
|
|
if (status != NTLSTATUS_SUCCESS) {
|
|
waveOut->Closing = 1;
|
|
(void)SysSignalEvent(waveOut->WakeEvent);
|
|
(void)SysWait(waveOut->WorkerHandle, SYS_WAIT_FLAG_NONE);
|
|
(void)WinmmRemoveWaveOut(waveOut);
|
|
(void)SysCloseHandle(waveOut->WorkerHandle);
|
|
(void)SysCloseHandle(waveOut->CommandEvent);
|
|
(void)SysCloseHandle(waveOut->WakeEvent);
|
|
(void)SysCloseHandle(waveOut->NativeStream);
|
|
free(waveOut);
|
|
return WinmmMapNativeStatus(status);
|
|
}
|
|
|
|
*handleOut = (HWAVEOUT)waveOut;
|
|
WINMM_DEBUG_LINE line = {0};
|
|
WinmmDebugText(&line, "WINMM: open success handle=");
|
|
WinmmDebugHex(&line, (uintptr_t)waveOut);
|
|
WinmmDebugText(&line, " endpoint=");
|
|
WinmmDebugU64(&line, endpointId);
|
|
WinmmDebugText(&line, " stream=");
|
|
WinmmDebugU64(&line, waveOut->NativeStream);
|
|
WinmmDebugText(&line, " worker=");
|
|
WinmmDebugU64(&line, waveOut->WorkerTid);
|
|
WinmmDebugCommit(&line);
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
// Closes one idle waveform handle after its worker sends WOM_CLOSE.
|
|
__declspec(dllexport) MMRESULT waveOutClose(HWAVEOUT handle) {
|
|
WINMM_WAVEOUT* waveOut = WinmmReferenceWaveOut(handle);
|
|
if (!waveOut)
|
|
return MMSYSERR_INVALHANDLE;
|
|
if (waveOut->WorkerTid == SysGetCurrentTid()) {
|
|
WinmmReleaseWaveOut(waveOut);
|
|
return MMSYSERR_HANDLEBUSY;
|
|
}
|
|
|
|
WinmmLock(&waveOut->Lock);
|
|
int busy = waveOut->QueueHead != NULL || waveOut->Command != WinmmWaveCommandNone;
|
|
WinmmUnlock(&waveOut->Lock);
|
|
if (busy) {
|
|
WinmmDebugResult("close still playing", waveOut, waveOut->HeadersQueued - waveOut->HeadersCompleted);
|
|
WinmmReleaseWaveOut(waveOut);
|
|
return WAVERR_STILLPLAYING;
|
|
}
|
|
if (!WinmmRemoveWaveOut(waveOut)) {
|
|
WinmmReleaseWaveOut(waveOut);
|
|
return MMSYSERR_INVALHANDLE;
|
|
}
|
|
|
|
waveOut->Closing = 1;
|
|
(void)SysSignalEvent(waveOut->WakeEvent);
|
|
KSTATUS status = SysWait(waveOut->WorkerHandle, SYS_WAIT_FLAG_NONE);
|
|
while (waveOut->ApiUsers > 1)
|
|
(void)SysYield();
|
|
|
|
waveOut->Magic = 0;
|
|
(void)SysCloseHandle(waveOut->WorkerHandle);
|
|
(void)SysCloseHandle(waveOut->CommandEvent);
|
|
(void)SysCloseHandle(waveOut->WakeEvent);
|
|
(void)SysCloseHandle(waveOut->NativeStream);
|
|
WinmmReleaseWaveOut(waveOut);
|
|
MMRESULT result = status == NTLSTATUS_SUCCESS ? MMSYSERR_NOERROR : WinmmMapNativeStatus(status);
|
|
WinmmDebugResult("close complete", waveOut, result);
|
|
free(waveOut);
|
|
return result;
|
|
}
|
|
|
|
// Marks one caller-owned header ready for waveform submission.
|
|
__declspec(dllexport) MMRESULT waveOutPrepareHeader(HWAVEOUT handle,
|
|
WAVEHDR* header,
|
|
UINT size) {
|
|
WINMM_WAVEOUT* waveOut = WinmmReferenceWaveOut(handle);
|
|
if (!waveOut)
|
|
return MMSYSERR_INVALHANDLE;
|
|
if (!header || size < sizeof(*header)) {
|
|
WinmmReleaseWaveOut(waveOut);
|
|
return MMSYSERR_INVALPARAM;
|
|
}
|
|
WinmmLock(&waveOut->Lock);
|
|
if (header->dwFlags & WHDR_INQUEUE) {
|
|
WinmmUnlock(&waveOut->Lock);
|
|
WinmmReleaseWaveOut(waveOut);
|
|
return WAVERR_STILLPLAYING;
|
|
}
|
|
if ((header->dwFlags & WHDR_PREPARED) &&
|
|
header->reserved != (DWORD_PTR)waveOut) {
|
|
WinmmUnlock(&waveOut->Lock);
|
|
WinmmReleaseWaveOut(waveOut);
|
|
return MMSYSERR_ALLOCATED;
|
|
}
|
|
header->dwFlags &= ~WHDR_DONE;
|
|
header->dwFlags |= WHDR_PREPARED;
|
|
header->reserved = (DWORD_PTR)waveOut;
|
|
WinmmUnlock(&waveOut->Lock);
|
|
WinmmReleaseWaveOut(waveOut);
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
// Releases preparation state after one header is no longer queued.
|
|
__declspec(dllexport) MMRESULT waveOutUnprepareHeader(HWAVEOUT handle,
|
|
WAVEHDR* header,
|
|
UINT size) {
|
|
WINMM_WAVEOUT* waveOut = WinmmReferenceWaveOut(handle);
|
|
if (!waveOut)
|
|
return MMSYSERR_INVALHANDLE;
|
|
if (!header || size < sizeof(*header)) {
|
|
WinmmReleaseWaveOut(waveOut);
|
|
return MMSYSERR_INVALPARAM;
|
|
}
|
|
WinmmLock(&waveOut->Lock);
|
|
if (header->dwFlags & WHDR_INQUEUE) {
|
|
WinmmUnlock(&waveOut->Lock);
|
|
WinmmReleaseWaveOut(waveOut);
|
|
return WAVERR_STILLPLAYING;
|
|
}
|
|
if ((header->dwFlags & WHDR_PREPARED) == 0 ||
|
|
header->reserved != (DWORD_PTR)waveOut) {
|
|
WinmmUnlock(&waveOut->Lock);
|
|
WinmmReleaseWaveOut(waveOut);
|
|
return MMSYSERR_INVALPARAM;
|
|
}
|
|
header->dwFlags &= ~WHDR_PREPARED;
|
|
header->reserved = 0;
|
|
WinmmUnlock(&waveOut->Lock);
|
|
WinmmReleaseWaveOut(waveOut);
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
// Copies one prepared buffer immediately and appends it to the user-mode queue.
|
|
__declspec(dllexport) MMRESULT waveOutWrite(HWAVEOUT handle,
|
|
WAVEHDR* header,
|
|
UINT size) {
|
|
WINMM_WAVEOUT* waveOut = WinmmReferenceWaveOut(handle);
|
|
if (!waveOut)
|
|
return MMSYSERR_INVALHANDLE;
|
|
if (!header || size < sizeof(*header)) {
|
|
WinmmReleaseWaveOut(waveOut);
|
|
return MMSYSERR_INVALPARAM;
|
|
}
|
|
WinmmLock(&waveOut->Lock);
|
|
if ((header->dwFlags & WHDR_PREPARED) == 0 ||
|
|
header->reserved != (DWORD_PTR)waveOut) {
|
|
WinmmUnlock(&waveOut->Lock);
|
|
WinmmReleaseWaveOut(waveOut);
|
|
return WAVERR_UNPREPARED;
|
|
}
|
|
if (header->dwFlags & WHDR_INQUEUE) {
|
|
WinmmUnlock(&waveOut->Lock);
|
|
WinmmReleaseWaveOut(waveOut);
|
|
return WAVERR_STILLPLAYING;
|
|
}
|
|
if ((header->dwFlags & (WHDR_BEGINLOOP | WHDR_ENDLOOP)) || header->dwLoops) {
|
|
WinmmUnlock(&waveOut->Lock);
|
|
WinmmReleaseWaveOut(waveOut);
|
|
return MMSYSERR_NOTSUPPORTED;
|
|
}
|
|
if (waveOut->TerminalError) {
|
|
MMRESULT error = waveOut->TerminalError;
|
|
WinmmUnlock(&waveOut->Lock);
|
|
WinmmReleaseWaveOut(waveOut);
|
|
return error;
|
|
}
|
|
header->dwFlags &= ~WHDR_DONE;
|
|
header->dwFlags |= WHDR_INQUEUE;
|
|
WinmmUnlock(&waveOut->Lock);
|
|
|
|
WINMM_WAVE_BUFFER* buffer = (WINMM_WAVE_BUFFER*)calloc(1, sizeof(*buffer));
|
|
if (!buffer) {
|
|
WinmmLock(&waveOut->Lock);
|
|
header->dwFlags &= ~WHDR_INQUEUE;
|
|
WinmmUnlock(&waveOut->Lock);
|
|
WinmmReleaseWaveOut(waveOut);
|
|
return MMSYSERR_NOMEM;
|
|
}
|
|
MMRESULT result = WinmmCopyFrames(waveOut,
|
|
header,
|
|
&buffer->Frames,
|
|
&buffer->FrameCount);
|
|
if (result != MMSYSERR_NOERROR) {
|
|
WinmmLock(&waveOut->Lock);
|
|
header->dwFlags &= ~WHDR_INQUEUE;
|
|
WinmmUnlock(&waveOut->Lock);
|
|
free(buffer);
|
|
WinmmReleaseWaveOut(waveOut);
|
|
return result;
|
|
}
|
|
buffer->Header = header;
|
|
if (!buffer->FrameCount) {
|
|
buffer->FullySubmitted = 1;
|
|
buffer->PresentedTarget = waveOut->NativeSubmitted;
|
|
}
|
|
|
|
WinmmLock(&waveOut->Lock);
|
|
if (waveOut->Closing) {
|
|
header->dwFlags &= ~WHDR_INQUEUE;
|
|
WinmmUnlock(&waveOut->Lock);
|
|
free(buffer->Frames);
|
|
free(buffer);
|
|
WinmmReleaseWaveOut(waveOut);
|
|
return MMSYSERR_INVALHANDLE;
|
|
}
|
|
waveOut->HeadersQueued++;
|
|
waveOut->FramesQueued += buffer->FrameCount;
|
|
buffer->Sequence = waveOut->HeadersQueued;
|
|
if (waveOut->QueueTail)
|
|
waveOut->QueueTail->Next = buffer;
|
|
else
|
|
waveOut->QueueHead = buffer;
|
|
waveOut->QueueTail = buffer;
|
|
if (!waveOut->SubmitNode)
|
|
waveOut->SubmitNode = buffer;
|
|
WinmmUnlock(&waveOut->Lock);
|
|
(void)SysSignalEvent(waveOut->WakeEvent);
|
|
WinmmReleaseWaveOut(waveOut);
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
// Pauses future source consumption while preserving queued headers.
|
|
__declspec(dllexport) MMRESULT waveOutPause(HWAVEOUT handle) {
|
|
WINMM_WAVEOUT* waveOut = WinmmReferenceWaveOut(handle);
|
|
if (!waveOut)
|
|
return MMSYSERR_INVALHANDLE;
|
|
MMRESULT result = WinmmSubmitCommand(waveOut, WinmmWaveCommandPause);
|
|
WinmmReleaseWaveOut(waveOut);
|
|
return result;
|
|
}
|
|
|
|
// Resumes a user-paused waveform queue.
|
|
__declspec(dllexport) MMRESULT waveOutRestart(HWAVEOUT handle) {
|
|
WINMM_WAVEOUT* waveOut = WinmmReferenceWaveOut(handle);
|
|
if (!waveOut)
|
|
return MMSYSERR_INVALHANDLE;
|
|
MMRESULT result = WinmmSubmitCommand(waveOut, WinmmWaveCommandRestart);
|
|
WinmmReleaseWaveOut(waveOut);
|
|
return result;
|
|
}
|
|
|
|
// Stops playback and returns every queued header as completed.
|
|
__declspec(dllexport) MMRESULT waveOutReset(HWAVEOUT handle) {
|
|
WINMM_WAVEOUT* waveOut = WinmmReferenceWaveOut(handle);
|
|
if (!waveOut)
|
|
return MMSYSERR_INVALHANDLE;
|
|
MMRESULT result = WinmmSubmitCommand(waveOut, WinmmWaveCommandReset);
|
|
WinmmReleaseWaveOut(waveOut);
|
|
return result;
|
|
}
|
|
|
|
// Applies independent WinMM left and right volume values through native gain and pan.
|
|
__declspec(dllexport) MMRESULT waveOutSetVolume(HWAVEOUT handle, DWORD volume) {
|
|
WINMM_WAVEOUT* waveOut = WinmmReferenceWaveOut(handle);
|
|
if (!waveOut)
|
|
return MMSYSERR_INVALHANDLE;
|
|
|
|
uint32_t left = volume & 0xFFFFu;
|
|
uint32_t right = (volume >> 16u) & 0xFFFFu;
|
|
uint32_t maximum = left > right ? left : right;
|
|
uint32_t volumeQ16 = maximum ? (uint32_t)(((uint64_t)maximum * 65536u) / 65535u) : 0u;
|
|
int32_t panQ16 = 0;
|
|
if (left > right && left)
|
|
panQ16 = -(int32_t)(((uint64_t)(left - right) * 65536u) / left);
|
|
else if (right > left && right)
|
|
panQ16 = (int32_t)(((uint64_t)(right - left) * 65536u) / right);
|
|
|
|
SYS_AUDIO_SET_VOLUME_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.StreamHandle = waveOut->NativeStream;
|
|
request.VolumeQ16 = volumeQ16;
|
|
request.PanQ16 = panQ16;
|
|
KSTATUS status = SysAudioSetVolume(&request);
|
|
if (status == NTLSTATUS_SUCCESS)
|
|
waveOut->Volume = volume;
|
|
WinmmReleaseWaveOut(waveOut);
|
|
return WinmmMapNativeStatus(status);
|
|
}
|
|
|
|
// Returns the last WinMM left and right volume value accepted for this handle.
|
|
__declspec(dllexport) MMRESULT waveOutGetVolume(HWAVEOUT handle, DWORD* volumeOut) {
|
|
WINMM_WAVEOUT* waveOut = WinmmReferenceWaveOut(handle);
|
|
if (!waveOut)
|
|
return MMSYSERR_INVALHANDLE;
|
|
if (!volumeOut) {
|
|
WinmmReleaseWaveOut(waveOut);
|
|
return MMSYSERR_INVALPARAM;
|
|
}
|
|
*volumeOut = waveOut->Volume;
|
|
WinmmReleaseWaveOut(waveOut);
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
// Reports source-frame progress using the requested MMTIME unit.
|
|
__declspec(dllexport) MMRESULT waveOutGetPosition(HWAVEOUT handle,
|
|
MMTIME* timeOut,
|
|
UINT size) {
|
|
WINMM_WAVEOUT* waveOut = WinmmReferenceWaveOut(handle);
|
|
if (!waveOut)
|
|
return MMSYSERR_INVALHANDLE;
|
|
if (!timeOut || size < sizeof(*timeOut)) {
|
|
WinmmReleaseWaveOut(waveOut);
|
|
return MMSYSERR_INVALPARAM;
|
|
}
|
|
|
|
SYS_AUDIO_STREAM_INFO info;
|
|
KSTATUS status = WinmmQueryNativeStream(waveOut, &info);
|
|
if (status != NTLSTATUS_SUCCESS) {
|
|
WinmmReleaseWaveOut(waveOut);
|
|
return WinmmMapNativeStatus(status);
|
|
}
|
|
uint64_t frames = info.FramesPresented;
|
|
if (timeOut->wType == TIME_BYTES) {
|
|
uint64_t bytes = frames * waveOut->SourceFormat.nBlockAlign;
|
|
timeOut->u.cb = bytes > UINT32_MAX ? UINT32_MAX : (uint32_t)bytes;
|
|
} else if (timeOut->wType == TIME_SAMPLES) {
|
|
timeOut->u.sample = frames > UINT32_MAX ? UINT32_MAX : (uint32_t)frames;
|
|
} else {
|
|
uint64_t milliseconds = frames * 1000u / waveOut->SourceFormat.nSamplesPerSec;
|
|
timeOut->wType = TIME_MS;
|
|
timeOut->u.ms = milliseconds > UINT32_MAX ? UINT32_MAX : (uint32_t)milliseconds;
|
|
}
|
|
WinmmReleaseWaveOut(waveOut);
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
// Returns the device index selected when the waveform handle was opened.
|
|
__declspec(dllexport) MMRESULT waveOutGetID(HWAVEOUT handle, UINT* deviceIdOut) {
|
|
WINMM_WAVEOUT* waveOut = WinmmReferenceWaveOut(handle);
|
|
if (!waveOut)
|
|
return MMSYSERR_INVALHANDLE;
|
|
if (!deviceIdOut) {
|
|
WinmmReleaseWaveOut(waveOut);
|
|
return MMSYSERR_INVALPARAM;
|
|
}
|
|
*deviceIdOut = waveOut->DeviceId;
|
|
WinmmReleaseWaveOut(waveOut);
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
// Fills the minimal ANSI device capabilities supported by this personality.
|
|
__declspec(dllexport) MMRESULT waveOutGetDevCapsA(UINT deviceId,
|
|
WAVEOUTCAPSA* caps,
|
|
UINT size) {
|
|
uint64_t endpointId = 0;
|
|
uint32_t resolvedDeviceId = 0;
|
|
MMRESULT result = WinmmResolveEndpoint(deviceId, &endpointId, &resolvedDeviceId);
|
|
if (result != MMSYSERR_NOERROR) {
|
|
WinmmDebugResult("device caps rejected", NULL, result);
|
|
return result;
|
|
}
|
|
(void)endpointId;
|
|
(void)resolvedDeviceId;
|
|
if (!caps || size < sizeof(*caps))
|
|
return MMSYSERR_INVALPARAM;
|
|
|
|
memset(caps, 0, sizeof(*caps));
|
|
caps->vDriverVersion = 0x00010000u;
|
|
memcpy(caps->szPname, "nt-lite analog output", sizeof("nt-lite analog output"));
|
|
caps->dwFormats = WAVE_FORMAT_1M16 | WAVE_FORMAT_1S16 |
|
|
WAVE_FORMAT_2M16 | WAVE_FORMAT_2S16 |
|
|
WAVE_FORMAT_4M16 | WAVE_FORMAT_4S16 |
|
|
WAVE_FORMAT_48M16 | WAVE_FORMAT_48S16;
|
|
caps->wChannels = 2;
|
|
caps->dwSupport = WAVECAPS_VOLUME | WAVECAPS_LRVOLUME;
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
// Fills the minimal wide device capabilities supported by this personality.
|
|
__declspec(dllexport) MMRESULT waveOutGetDevCapsW(UINT deviceId,
|
|
WAVEOUTCAPSW* caps,
|
|
UINT size) {
|
|
if (!caps || size < sizeof(*caps))
|
|
return MMSYSERR_INVALPARAM;
|
|
WAVEOUTCAPSA ansi;
|
|
MMRESULT result = waveOutGetDevCapsA(deviceId, &ansi, sizeof(ansi));
|
|
if (result != MMSYSERR_NOERROR)
|
|
return result;
|
|
memset(caps, 0, sizeof(*caps));
|
|
caps->wMid = ansi.wMid;
|
|
caps->wPid = ansi.wPid;
|
|
caps->vDriverVersion = ansi.vDriverVersion;
|
|
for (uint32_t index = 0; index < 32u && ansi.szPname[index]; index++)
|
|
caps->szPname[index] = (unsigned short)(uint8_t)ansi.szPname[index];
|
|
caps->dwFormats = ansi.dwFormats;
|
|
caps->wChannels = ansi.wChannels;
|
|
caps->dwSupport = ansi.dwSupport;
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
// Copies one fixed WinMM error description to an ANSI buffer.
|
|
__declspec(dllexport) MMRESULT waveOutGetErrorTextA(MMRESULT error,
|
|
char* text,
|
|
UINT characters) {
|
|
if (!text || !characters)
|
|
return MMSYSERR_INVALPARAM;
|
|
const char* message = "Unknown waveform output error";
|
|
switch (error) {
|
|
case MMSYSERR_NOERROR: message = "No error"; break;
|
|
case MMSYSERR_BADDEVICEID: message = "Bad waveform output device ID"; break;
|
|
case MMSYSERR_INVALHANDLE: message = "Invalid waveform output handle"; break;
|
|
case MMSYSERR_NOMEM: message = "Not enough memory"; break;
|
|
case MMSYSERR_NOTSUPPORTED: message = "Operation not supported"; break;
|
|
case MMSYSERR_INVALFLAG: message = "Invalid waveform output flags"; break;
|
|
case MMSYSERR_INVALPARAM: message = "Invalid waveform output parameter"; break;
|
|
case WAVERR_BADFORMAT: message = "Unsupported waveform format"; break;
|
|
case WAVERR_STILLPLAYING: message = "Waveform buffer is still playing"; break;
|
|
case WAVERR_UNPREPARED: message = "Waveform header is not prepared"; break;
|
|
default: break;
|
|
}
|
|
uint32_t index = 0;
|
|
while (index + 1u < characters && message[index]) {
|
|
text[index] = message[index];
|
|
index++;
|
|
}
|
|
text[index] = '\0';
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
// Copies one fixed WinMM error description to a wide buffer.
|
|
__declspec(dllexport) MMRESULT waveOutGetErrorTextW(MMRESULT error,
|
|
unsigned short* text,
|
|
UINT characters) {
|
|
if (!text || !characters)
|
|
return MMSYSERR_INVALPARAM;
|
|
char ansi[96];
|
|
MMRESULT result = waveOutGetErrorTextA(error, ansi, sizeof(ansi));
|
|
if (result != MMSYSERR_NOERROR)
|
|
return result;
|
|
uint32_t index = 0;
|
|
while (index + 1u < characters && ansi[index]) {
|
|
text[index] = (unsigned short)(uint8_t)ansi[index];
|
|
index++;
|
|
}
|
|
text[index] = 0;
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
// Rejects loop-breaking because looped headers are not part of this implementation.
|
|
__declspec(dllexport) MMRESULT waveOutBreakLoop(HWAVEOUT handle) {
|
|
WINMM_WAVEOUT* waveOut = WinmmReferenceWaveOut(handle);
|
|
if (!waveOut)
|
|
return MMSYSERR_INVALHANDLE;
|
|
WinmmReleaseWaveOut(waveOut);
|
|
return MMSYSERR_NOTSUPPORTED;
|
|
}
|
|
|
|
// Rejects driver-specific waveform messages instead of pretending they succeeded.
|
|
__declspec(dllexport) MMRESULT waveOutMessage(HWAVEOUT handle,
|
|
UINT message,
|
|
DWORD_PTR parameter1,
|
|
DWORD_PTR parameter2) {
|
|
(void)message;
|
|
(void)parameter1;
|
|
(void)parameter2;
|
|
WINMM_WAVEOUT* waveOut = WinmmReferenceWaveOut(handle);
|
|
if (!waveOut)
|
|
return MMSYSERR_INVALHANDLE;
|
|
WinmmReleaseWaveOut(waveOut);
|
|
return MMSYSERR_NOTSUPPORTED;
|
|
}
|