Files
nt-lite/code/user/personalities/win32/winmm_waveout.c
T

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(&current->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;
}