Files
nt-lite/code/user/apps/doom/doom_audio.c
T
maksw 73abd6f90d isolate POSIX builds from the host UCRT, wire lwext4 and ntfs-3g as ext4.sys and ntfs.sys, split the FAT driver into directory/file/async/driver/mount units, add posixsrv and dwm plus input/console/desktop syscall clients and a pile of new smoke targets, rename serial.sys to cereal.sys, name HDA/USB/lwIP/AHCI workers, harden ATA probing/reset/removal around actual hardware deadlines, absent-channel detection, diagnostics and explicit worker lifetime, redo ACPI S5 shutdown around GAS/MMIO and HW-reduced firmware, scan SSDTs for _S5 and deal with Intel SMI sleep trapping, make serial and SMP timeouts use real monotonic deadlines, add thread names and fix terminal thread teardown, scheduler and APC prepare/commit races, make advisory file locks queryable and preserve split ranges and add mutable file append state, add shared-memory pipes with kernel-assisted wait/wake, introduce capability-scoped input and display managers, split surface map rights into read/write, add explicit unmap and replace present with commit/generation/damage tracking while keeping physical presentation under the display manager, decouple native exception classes from Win32 exception numbers and translate them in kernel32, add opaque process startup data, bump the resource protocol to v3 with namespace directories, symlinks and enumeration and service control to v2 with generic READY handle exports, add the new POSIX errno/status surface and rebuild fds around posixsrv-owned shared open descriptions, descriptor-local flags, exact inheritance snapshots, dup/fcntl/record locks and shared-ring pipes, add waitpid, POSIX credentials/groups/sessions/passwd/group lookups and move sockets onto stable open descriptions, port the shells and apps to the new console/input services and surface commit API, teach init to load services.ini and start/supervise RSSRV, DWM and generic services under restricted capabilities while publishing their endpoints through RSSRV, split RSSRV into namespace, volume, display, session, input, console and resource components and make /SystemRoot a real symlink to the independently published system volume, shift the syscall table again, expand lock/resource/surface/POSIX/service/thread smoke coverage, make docs scream ASK QUESTIONS and DISCUSS at LLMs, replace MIT No Attribution with WTFPL+.
2026-09-16 10:33:20 +02:00

808 lines
27 KiB
C

// Doom audio mixes PureDOOM SFX with optional SoundFont MIDI into one native render stream.
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <intrin.h>
#include <ntlite/syscall.h>
#include <ntlite/console_service.h>
#include <ntlite/path.h>
#include "doom_audio.h"
#include "PureDOOM.h"
#define DOOM_AUDIO_SAMPLE_RATE DOOM_SAMPLERATE
#define DOOM_AUDIO_CHANNELS 2u
#define DOOM_AUDIO_BLOCK_FRAMES 512u
#define DOOM_AUDIO_WRITE_QUANTUM_FRAMES 256u
#define DOOM_AUDIO_PREFILL_FRAMES DOOM_AUDIO_BLOCK_FRAMES
#define DOOM_AUDIO_REFILL_WATERMARK_FRAMES DOOM_AUDIO_WRITE_QUANTUM_FRAMES
#define DOOM_AUDIO_MIDI_BATCH_MESSAGES 256u
#define DOOM_AUDIO_SOUNDFONT_PATH "doom.sf2"
#define DOOM_AUDIO_MAX_SOUNDFONT_BYTES (64u * 1024u * 1024u)
#define DOOM_AUDIO_WAIT_MILLISECONDS 100u
#define DOOM_AUDIO_WORKER_PRIORITY 2u
#define DOOM_AUDIO_SYNTH_LOADER_PRIORITY 0u
#define DOOM_AUDIO_MAX_VOICES 128
#define DOOM_AUDIO_MUSIC_GAIN 1.0f
// Computes a natural logarithm for the positive finite values used by TinySoundFont.
static double DoomAudioLog(double value) {
union {
double Value;
uint64_t Bits;
} bits;
const double ln2 = 0.69314718055994530942;
int exponent;
double mantissa;
double ratio;
double ratio2;
double term;
double sum;
if (value <= 0.0)
return -1.0e300;
bits.Value = value;
exponent = (int)((bits.Bits >> 52) & 0x7FFu) - 1023;
bits.Bits = (bits.Bits & UINT64_C(0x000FFFFFFFFFFFFF)) |
UINT64_C(0x3FF0000000000000);
mantissa = bits.Value;
ratio = (mantissa - 1.0) / (mantissa + 1.0);
ratio2 = ratio * ratio;
term = ratio;
sum = term;
term *= ratio2;
sum += term / 3.0;
term *= ratio2;
sum += term / 5.0;
term *= ratio2;
sum += term / 7.0;
term *= ratio2;
sum += term / 9.0;
term *= ratio2;
sum += term / 11.0;
term *= ratio2;
sum += term / 13.0;
return (double)exponent * ln2 + 2.0 * sum;
}
// Scales a normal double by an integral power of two without a runtime math library.
static double DoomAudioScalePowerOfTwo(double value, int exponent) {
union {
double Value;
uint64_t Bits;
} scale;
if (exponent > 1023)
return 1.0e300;
if (exponent < -1022)
return 0.0;
scale.Bits = (uint64_t)(exponent + 1023) << 52;
return value * scale.Value;
}
// Computes an exponential over the bounded ranges used by TinySoundFont envelopes.
static double DoomAudioExp(double value) {
const double ln2 = 0.69314718055994530942;
const double inverseLn2 = 1.4426950408889634074;
int exponent;
double reduced;
double result;
if (value >= 700.0)
return 1.0e300;
if (value <= -700.0)
return 0.0;
exponent = (int)(value * inverseLn2 + (value >= 0.0 ? 0.5 : -0.5));
reduced = value - (double)exponent * ln2;
result = 1.0 + reduced *
(1.0 + reduced *
(0.5 + reduced *
(0.16666666666666666667 + reduced *
(0.04166666666666666667 + reduced *
(0.00833333333333333333 + reduced *
(0.00138888888888888889 + reduced *
0.00019841269841269841))))));
return DoomAudioScalePowerOfTwo(result, exponent);
}
// Computes a positive real power for TinySoundFont frequency and gain conversion.
static double DoomAudioPow(double base, double exponent) {
if (base <= 0.0)
return 0.0;
return DoomAudioExp(exponent * DoomAudioLog(base));
}
// Computes the float exponential required by TinySoundFont envelopes.
static float DoomAudioExpf(float value) {
return (float)DoomAudioExp((double)value);
}
// Computes the float power required by TinySoundFont frequency and gain conversion.
static float DoomAudioPowf(float base, float exponent) {
return (float)DoomAudioPow((double)base, (double)exponent);
}
// Computes base-ten logarithms from the shared natural logarithm approximation.
static double DoomAudioLog10(double value) {
return DoomAudioLog(value) * 0.43429448190325182765;
}
// Computes square roots with a bit-derived seed and stable Newton refinement.
static double DoomAudioSqrt(double value) {
union {
double Value;
uint64_t Bits;
} estimate;
if (value <= 0.0)
return 0.0;
estimate.Value = value;
estimate.Bits = (estimate.Bits >> 1) + UINT64_C(0x1FF8000000000000);
for (int iteration = 0; iteration < 6; iteration++)
estimate.Value = 0.5 * (estimate.Value + value / estimate.Value);
return estimate.Value;
}
// Computes the float square root required by TinySoundFont panning.
static float DoomAudioSqrtf(float value) {
return (float)DoomAudioSqrt((double)value);
}
// Computes tangent accurately over TinySoundFont's normalized low-pass range.
static double DoomAudioTan(double value) {
double squared = value * value;
double sine = value *
(1.0 + squared *
(-0.16666666666666666667 + squared *
(0.00833333333333333333 + squared *
(-0.00019841269841269841 + squared *
(0.00000275573192239859 + squared *
(-0.00000002505210838544 + squared *
0.00000000016059043837))))));
double cosine = 1.0 + squared *
(-0.5 + squared *
(0.04166666666666666667 + squared *
(-0.00138888888888888889 + squared *
(0.00002480158730158730 + squared *
(-0.00000027557319223986 + squared *
(0.00000000208767569879 + squared *
-0.00000000001147074560))))));
if (cosine > -1.0e-12 && cosine < 1.0e-12)
cosine = cosine < 0.0 ? -1.0e-12 : 1.0e-12;
return sine / cosine;
}
#pragma warning(push)
#pragma warning(disable: 4244 4267 4706)
#define TSF_NO_STDIO
#define TSF_MALLOC malloc
#define TSF_REALLOC realloc
#define TSF_FREE free
#define TSF_MEMCPY memcpy
#define TSF_MEMSET memset
#define TSF_POW DoomAudioPow
#define TSF_POWF DoomAudioPowf
#define TSF_EXPF DoomAudioExpf
#define TSF_LOG DoomAudioLog
#define TSF_TAN DoomAudioTan
#define TSF_LOG10 DoomAudioLog10
#define TSF_SQRT DoomAudioSqrt
#define TSF_SQRTF DoomAudioSqrtf
#define TSF_IMPLEMENTATION
#include "third_party/tsf.h"
#pragma warning(pop)
typedef struct DOOM_AUDIO_STATE {
uint32_t CoreMutex;
uint32_t StreamHandle;
uint32_t WorkerHandle;
uint32_t WorkerId;
uint32_t SynthLoaderHandle;
uint32_t SynthLoaderId;
volatile long StopRequested;
tsf* volatile Synth;
} DOOM_AUDIO_STATE;
static DOOM_AUDIO_STATE g_doomAudio;
// Writes one complete diagnostic line to the process console.
static void DoomAudioPrint(const char* text) {
if (text) (void)NtlConsoleWrite(SYS_HANDLE_INVALID, text, strlen(text), NULL);
}
// Opens the default available native render endpoint.
static KSTATUS DoomAudioFindEndpoint(uint32_t* endpointIdOut) {
SYS_AUDIO_ENDPOINT_INFO endpoints[SYS_AUDIO_MAX_ENDPOINTS];
SYS_AUDIO_ENUMERATE_ENDPOINTS_REQUEST request;
uint32_t count = 0;
const SYS_AUDIO_ENDPOINT_INFO* fallback = NULL;
KSTATUS status;
if (!endpointIdOut)
return NTLSTATUS_INVALID_PARAMETER;
*endpointIdOut = 0;
memset(endpoints, 0, sizeof(endpoints));
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.Endpoints = endpoints;
request.Capacity = SYS_AUDIO_MAX_ENDPOINTS;
request.CountOut = &count;
status = SysAudioEnumerateEndpoints(&request);
if (status != NTLSTATUS_SUCCESS)
return status;
if (count > SYS_AUDIO_MAX_ENDPOINTS)
count = SYS_AUDIO_MAX_ENDPOINTS;
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;
if (endpoint->Flags & SYS_AUDIO_ENDPOINT_FLAG_DEFAULT) {
*endpointIdOut = endpoint->EndpointId;
return NTLSTATUS_SUCCESS;
}
}
if (!fallback)
return NTLSTATUS_NOT_FOUND;
*endpointIdOut = fallback->EndpointId;
return NTLSTATUS_SUCCESS;
}
// Returns the currently published synthesizer without taking the PureDOOM lock.
static tsf* DoomAudioGetSynth(void) {
return (tsf*)_InterlockedCompareExchangePointer(
(void* volatile*)&g_doomAudio.Synth, NULL, NULL);
}
// Maps the optional SoundFont so TinySoundFont can parse it without a separate input copy.
static KSTATUS DoomAudioMapSoundFont(void** dataOut, uint32_t* sizeOut) {
uint32_t fileHandle = 0;
uint32_t mappingHandle = 0;
uint64_t fileSize = 0;
void* base = NULL;
KSTATUS status;
if (!dataOut || !sizeOut)
return NTLSTATUS_INVALID_PARAMETER;
*dataOut = NULL;
*sizeOut = 0;
status = NtlOpenFile(SysSvFromCstr(DOOM_AUDIO_SOUNDFONT_PATH),
SYS_FILE_ACCESS_QUERY | SYS_FILE_ACCESS_READ,
SYS_FILE_OPEN_EXISTING, SYS_FILE_FLAG_NONE, &fileHandle);
if (status != NTLSTATUS_SUCCESS)
return status;
status = SysGetFileSize(fileHandle, &fileSize);
if (status != NTLSTATUS_SUCCESS || fileSize == 0 ||
fileSize > DOOM_AUDIO_MAX_SOUNDFONT_BYTES ||
fileSize > INT32_MAX) {
(void)SysCloseHandle(fileHandle);
return status == NTLSTATUS_SUCCESS ? NTLSTATUS_INVALID_PARAMETER : status;
}
SYS_CREATE_FILE_MAPPING_REQUEST createMapping = {
sizeof(SYS_CREATE_FILE_MAPPING_REQUEST), fileHandle, SYS_VM_READ,
SYS_MAP_FLAG_NONE, fileSize, &mappingHandle
};
status = SysCreateFileMapping(&createMapping);
if (status != NTLSTATUS_SUCCESS || !mappingHandle) {
(void)SysCloseHandle(fileHandle);
return status == NTLSTATUS_SUCCESS ? NTLSTATUS_FAILURE : status;
}
SYS_MAP_FILE_REQUEST mapFile = {
sizeof(SYS_MAP_FILE_REQUEST), mappingHandle, SYS_MAP_ACCESS_READ,
SYS_MAP_FLAG_NONE, 0, (size_t)fileSize, &base
};
status = SysMapFile(&mapFile);
(void)SysCloseHandle(mappingHandle);
(void)SysCloseHandle(fileHandle);
if (status != NTLSTATUS_SUCCESS || !base)
return status == NTLSTATUS_SUCCESS ? NTLSTATUS_FAILURE : status;
*dataOut = base;
*sizeOut = (uint32_t)fileSize;
return NTLSTATUS_SUCCESS;
}
// Configures one parsed General MIDI synthesizer for PureDOOM's native sample rate.
static KSTATUS DoomAudioConfigureSynth(tsf* synth) {
if (!synth)
return NTLSTATUS_INVALID_PARAMETER;
tsf_set_output(synth, TSF_STEREO_INTERLEAVED,
(int)DOOM_AUDIO_SAMPLE_RATE, 0.0f);
if (!tsf_set_max_voices(synth, DOOM_AUDIO_MAX_VOICES))
return NTLSTATUS_NO_MEMORY;
for (int channel = 0; channel < 16; channel++) {
if (!tsf_channel_set_presetnumber(synth, channel, 0, channel == 9))
return NTLSTATUS_FAILURE;
}
return NTLSTATUS_SUCCESS;
}
// Loads optional MIDI state at low priority without blocking Doom or SFX startup.
static void DoomAudioSynthLoader(uintptr_t argument) {
uint32_t soundFontSize = 0;
void* soundFontBase = NULL;
tsf* synth = NULL;
KSTATUS status;
(void)argument;
status = DoomAudioMapSoundFont(&soundFontBase, &soundFontSize);
if (status == NTLSTATUS_NOT_FOUND) {
DoomAudioPrint("doom: doom.sf2 unavailable; continuing with sound effects only\n");
SysExitThread(0);
}
if (status != NTLSTATUS_SUCCESS) {
DoomAudioPrint("doom: doom.sf2 could not be mapped; continuing with sound effects only\n");
SysExitThread(0);
}
if (_InterlockedCompareExchange(&g_doomAudio.StopRequested, 0, 0)) {
(void)SysFreeVm(soundFontBase);
SysExitThread(0);
}
synth = tsf_load_memory(soundFontBase, (int)soundFontSize);
(void)SysFreeVm(soundFontBase);
if (!synth) {
DoomAudioPrint("doom: doom.sf2 is invalid or synth allocation failed; continuing with sound effects only\n");
SysExitThread(0);
}
status = DoomAudioConfigureSynth(synth);
if (status != NTLSTATUS_SUCCESS ||
_InterlockedCompareExchange(&g_doomAudio.StopRequested, 0, 0)) {
if (status != NTLSTATUS_SUCCESS)
DoomAudioPrint("doom: MIDI synthesizer initialization failed; continuing with sound effects only\n");
tsf_close(synth);
SysExitThread(0);
}
(void)_InterlockedExchangePointer(
(void* volatile*)&g_doomAudio.Synth, synth);
DoomAudioPrint("doom: MIDI synthesizer ready\n");
SysExitThread(0);
}
// Applies one packed WinMM-style short MIDI message to TinySoundFont.
static void DoomAudioDispatchMidi(tsf* synth, uint32_t message) {
uint8_t status = (uint8_t)(message & 0xFFu);
uint8_t data1 = (uint8_t)((message >> 8) & 0x7Fu);
uint8_t data2 = (uint8_t)((message >> 16) & 0x7Fu);
int channel = status & 0x0F;
if (!synth)
return;
switch (status & 0xF0) {
case 0x80:
tsf_channel_note_off(synth, channel, data1);
break;
case 0x90:
if (data2)
(void)tsf_channel_note_on(synth, channel, data1,
(float)data2 / 127.0f);
else
tsf_channel_note_off(synth, channel, data1);
break;
case 0xB0:
(void)tsf_channel_midi_control(synth, channel, data1, data2);
break;
case 0xC0:
(void)tsf_channel_set_presetnumber(synth, channel, data1,
channel == 9);
break;
case 0xE0:
(void)tsf_channel_set_pitchwheel(synth, channel,
(int)data1 | ((int)data2 << 7));
break;
default:
break;
}
}
// Advances one PureDOOM MIDI tick and dispatches every event outside the core lock.
static void DoomAudioTickMidi(tsf* synth) {
uint32_t messages[DOOM_AUDIO_MIDI_BATCH_MESSAGES];
int tickComplete = 0;
if (!synth)
return;
while (!tickComplete) {
uint32_t count = 0;
DoomAudioLockCore();
while (count < DOOM_AUDIO_MIDI_BATCH_MESSAGES) {
uint32_t message = (uint32_t)doom_tick_midi();
if (!message) {
tickComplete = 1;
break;
}
messages[count++] = message;
}
DoomAudioUnlockCore();
for (uint32_t index = 0; index < count; index++)
DoomAudioDispatchMidi(synth, messages[index]);
}
}
// Copies one complete PureDOOM SFX block while holding the shared core lock.
static void DoomAudioReadSfxBlock(int16_t* frames) {
short* source;
DoomAudioLockCore();
source = doom_get_sound_buffer();
memcpy(frames, source,
DOOM_AUDIO_BLOCK_FRAMES * DOOM_AUDIO_CHANNELS * sizeof(int16_t));
DoomAudioUnlockCore();
}
// Mixes rendered floating-point music into signed 16-bit PureDOOM SFX.
static void DoomAudioMixMusic(int16_t* destination,
const float* music,
uint32_t frameOffset,
uint32_t frameCount) {
uint32_t firstSample = frameOffset * DOOM_AUDIO_CHANNELS;
uint32_t sampleCount = frameCount * DOOM_AUDIO_CHANNELS;
for (uint32_t index = 0; index < sampleCount; index++) {
float scaled = music[index] * (32767.0f * DOOM_AUDIO_MUSIC_GAIN);
int32_t synthSample;
int32_t mixed;
if (scaled > 32767.0f)
synthSample = 32767;
else if (scaled < -32768.0f)
synthSample = -32768;
else
synthSample = (int32_t)scaled;
mixed = (int32_t)destination[firstSample + index] + synthSample;
if (mixed > 32767)
mixed = 32767;
else if (mixed < -32768)
mixed = -32768;
destination[firstSample + index] = (int16_t)mixed;
}
}
// Renders MIDI at exactly 140 ticks per second into one native PureDOOM block.
static void DoomAudioRenderMusic(tsf* synth,
int16_t* frames,
uint32_t* phaseInOut) {
float music[DOOM_AUDIO_BLOCK_FRAMES * DOOM_AUDIO_CHANNELS];
uint32_t frameOffset = 0;
memset(music, 0, sizeof(music));
while (frameOffset < DOOM_AUDIO_BLOCK_FRAMES) {
uint32_t remaining;
uint32_t framesUntilTick;
while (*phaseInOut >= DOOM_AUDIO_SAMPLE_RATE) {
*phaseInOut -= DOOM_AUDIO_SAMPLE_RATE;
DoomAudioTickMidi(synth);
}
remaining = DOOM_AUDIO_BLOCK_FRAMES - frameOffset;
framesUntilTick =
(DOOM_AUDIO_SAMPLE_RATE - *phaseInOut + DOOM_MIDI_RATE - 1u) /
DOOM_MIDI_RATE;
if (framesUntilTick > remaining)
framesUntilTick = remaining;
tsf_render_float(synth,
music + frameOffset * DOOM_AUDIO_CHANNELS,
(int)framesUntilTick,
0);
DoomAudioMixMusic(frames, music + frameOffset * DOOM_AUDIO_CHANNELS,
frameOffset, framesUntilTick);
frameOffset += framesUntilTick;
*phaseInOut += framesUntilTick * DOOM_MIDI_RATE;
}
}
// Renders one complete native-rate block containing synchronized music and SFX.
static void DoomAudioRenderBlock(int16_t* frames,
tsf** activeSynthInOut,
uint32_t* midiPhaseInOut) {
tsf* synth;
DoomAudioReadSfxBlock(frames);
synth = DoomAudioGetSynth();
if (synth != *activeSynthInOut) {
*activeSynthInOut = synth;
*midiPhaseInOut = DOOM_AUDIO_SAMPLE_RATE;
}
if (synth)
DoomAudioRenderMusic(synth, frames, midiPhaseInOut);
}
// Queries one coherent native stream snapshot.
static KSTATUS DoomAudioQueryStream(SYS_AUDIO_STREAM_INFO* infoOut) {
if (!infoOut)
return NTLSTATUS_INVALID_PARAMETER;
memset(infoOut, 0, sizeof(*infoOut));
infoOut->Size = sizeof(*infoOut);
return SysAudioQueryStream(g_doomAudio.StreamHandle, infoOut);
}
// Arms a wait for writable space or a terminal stream condition.
static KSTATUS DoomAudioWaitWritable(const SYS_AUDIO_STREAM_INFO* info,
uint32_t writableFrames) {
SYS_AUDIO_ARM_WAIT_REQUEST request;
KSTATUS status;
if (!info || !writableFrames || writableFrames > info->CapacityFrames)
return NTLSTATUS_INVALID_PARAMETER;
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.StreamHandle = g_doomAudio.StreamHandle;
request.Mask = SYS_AUDIO_WAIT_WRITABLE |
SYS_AUDIO_WAIT_FAILED |
SYS_AUDIO_WAIT_DISCONTINUITY;
request.WritableFrames = writableFrames;
request.DiscontinuityGeneration = info->DiscontinuityGeneration;
status = SysAudioArmWait(&request);
if (status != NTLSTATUS_SUCCESS)
return status;
return SysWaitSingle(g_doomAudio.StreamHandle,
SYS_WAIT_FLAG_NONE,
DOOM_AUDIO_WAIT_MILLISECONDS);
}
// Waits until the buffered source has fallen to the low-latency refill watermark.
static KSTATUS DoomAudioWaitForRefill(const SYS_AUDIO_STREAM_INFO* info) {
if (!info || info->CapacityFrames <= DOOM_AUDIO_REFILL_WATERMARK_FRAMES)
return NTLSTATUS_INVALID_PARAMETER;
return DoomAudioWaitWritable(
info, info->CapacityFrames - DOOM_AUDIO_REFILL_WATERMARK_FRAMES);
}
// Writes as much of one pending block as the nonblocking stream accepts.
static KSTATUS DoomAudioWritePending(const int16_t* frames,
uint32_t* frameOffsetInOut) {
SYS_AUDIO_WRITE_REQUEST request;
uint32_t written = 0;
KSTATUS status;
if (!frames || !frameOffsetInOut ||
*frameOffsetInOut >= DOOM_AUDIO_BLOCK_FRAMES)
return NTLSTATUS_INVALID_PARAMETER;
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.StreamHandle = g_doomAudio.StreamHandle;
request.FrameCount = DOOM_AUDIO_BLOCK_FRAMES - *frameOffsetInOut;
if (request.FrameCount > DOOM_AUDIO_WRITE_QUANTUM_FRAMES)
request.FrameCount = DOOM_AUDIO_WRITE_QUANTUM_FRAMES;
request.Frames = frames + *frameOffsetInOut * DOOM_AUDIO_CHANNELS;
request.WrittenFramesOut = &written;
status = SysAudioWrite(&request);
if (status != NTLSTATUS_SUCCESS)
return status;
if (!written)
return NTLSTATUS_WOULD_BLOCK;
*frameOffsetInOut += written;
return NTLSTATUS_SUCCESS;
}
// Keeps a bounded low-latency stream queue at PureDOOM's native cadence.
static void DoomAudioWorker(uintptr_t argument) {
int16_t block[DOOM_AUDIO_BLOCK_FRAMES * DOOM_AUDIO_CHANNELS];
uint32_t pendingOffset = DOOM_AUDIO_BLOCK_FRAMES;
uint32_t midiPhase = 0;
tsf* activeSynth = NULL;
uint8_t started = 0;
(void)argument;
while (true) {
SYS_AUDIO_STREAM_INFO info;
uint32_t requiredFrames;
KSTATUS status;
if (_InterlockedCompareExchange(&g_doomAudio.StopRequested, 0, 0))
break;
status = DoomAudioQueryStream(&info);
if (status != NTLSTATUS_SUCCESS || info.State == SysAudioStreamFailed) {
DoomAudioPrint("doom: native audio stream failed\n");
break;
}
if (!started && info.BufferedFrames >= DOOM_AUDIO_PREFILL_FRAMES) {
status = SysAudioStart(g_doomAudio.StreamHandle);
if (status != NTLSTATUS_SUCCESS) {
DoomAudioPrint("doom: native audio start failed\n");
break;
}
started = 1;
continue;
}
if (pendingOffset == DOOM_AUDIO_BLOCK_FRAMES) {
if (started &&
info.BufferedFrames > DOOM_AUDIO_REFILL_WATERMARK_FRAMES) {
status = DoomAudioWaitForRefill(&info);
if (status != NTLSTATUS_SUCCESS && status != NTLSTATUS_TIMEOUT) {
DoomAudioPrint("doom: native audio wait failed\n");
break;
}
continue;
}
DoomAudioRenderBlock(block, &activeSynth, &midiPhase);
pendingOffset = 0;
}
requiredFrames = DOOM_AUDIO_BLOCK_FRAMES - pendingOffset;
if (requiredFrames > DOOM_AUDIO_WRITE_QUANTUM_FRAMES)
requiredFrames = DOOM_AUDIO_WRITE_QUANTUM_FRAMES;
if (info.WritableFrames < requiredFrames) {
status = DoomAudioWaitWritable(&info, requiredFrames);
if (status != NTLSTATUS_SUCCESS && status != NTLSTATUS_TIMEOUT) {
DoomAudioPrint("doom: native audio wait failed\n");
break;
}
continue;
}
status = DoomAudioWritePending(block, &pendingOffset);
if (status == NTLSTATUS_WOULD_BLOCK)
continue;
if (status != NTLSTATUS_SUCCESS) {
DoomAudioPrint("doom: native audio write failed\n");
break;
}
}
if (started)
(void)SysAudioStop(g_doomAudio.StreamHandle);
SysExitThread(0);
}
// Releases resources owned before or after the producer thread lifetime.
static void DoomAudioReleaseResources(void) {
if (g_doomAudio.StreamHandle) {
(void)SysCloseHandle(g_doomAudio.StreamHandle);
g_doomAudio.StreamHandle = 0;
}
tsf* synth = (tsf*)_InterlockedExchangePointer(
(void* volatile*)&g_doomAudio.Synth, NULL);
if (synth)
tsf_close(synth);
if (g_doomAudio.CoreMutex) {
(void)SysCloseHandle(g_doomAudio.CoreMutex);
g_doomAudio.CoreMutex = 0;
}
}
// Starts one process-owned audio stream and its exclusive producer thread.
void DoomAudioStart(void) {
uint32_t endpointId = 0;
uint32_t streamHandle = 0;
KSTATUS status;
if (DOOM_AUDIO_PREFILL_FRAMES >= SYS_AUDIO_STREAM_CAPACITY_FRAMES ||
DOOM_AUDIO_REFILL_WATERMARK_FRAMES >= DOOM_AUDIO_PREFILL_FRAMES ||
DOOM_AUDIO_WRITE_QUANTUM_FRAMES > DOOM_AUDIO_BLOCK_FRAMES) {
DoomAudioPrint("doom: invalid native audio buffering configuration\n");
return;
}
memset(&g_doomAudio, 0, sizeof(g_doomAudio));
status = SysCreateMutex(&g_doomAudio.CoreMutex);
if (status != NTLSTATUS_SUCCESS) {
DoomAudioPrint("doom: audio synchronization unavailable; continuing without sound\n");
return;
}
status = DoomAudioFindEndpoint(&endpointId);
if (status != NTLSTATUS_SUCCESS) {
DoomAudioPrint("doom: no audio endpoint; continuing without sound\n");
DoomAudioReleaseResources();
return;
}
SYS_AUDIO_OPEN_STREAM_REQUEST open;
memset(&open, 0, sizeof(open));
open.Size = sizeof(open);
open.EndpointId = endpointId;
open.Format.SampleRate = DOOM_AUDIO_SAMPLE_RATE;
open.Format.ChannelCount = DOOM_AUDIO_CHANNELS;
open.Format.SampleFormat = SysAudioSampleS16;
open.StreamHandleOut = &streamHandle;
status = SysAudioOpenStream(&open);
if (status != NTLSTATUS_SUCCESS) {
DoomAudioPrint("doom: native audio open failed; continuing without sound\n");
DoomAudioReleaseResources();
return;
}
g_doomAudio.StreamHandle = streamHandle;
SYS_CREATE_THREAD_REQUEST thread;
memset(&thread, 0, sizeof(thread));
thread.Size = sizeof(thread);
thread.Entry = (uintptr_t)DoomAudioWorker;
thread.Priority = DOOM_AUDIO_WORKER_PRIORITY;
thread.ThreadHandleOut = &g_doomAudio.WorkerHandle;
thread.ThreadIdOut = &g_doomAudio.WorkerId;
status = SysCreateThread(&thread);
if (status != NTLSTATUS_SUCCESS) {
DoomAudioPrint("doom: audio worker creation failed; continuing without sound\n");
DoomAudioReleaseResources();
return;
}
memset(&thread, 0, sizeof(thread));
thread.Size = sizeof(thread);
thread.Entry = (uintptr_t)DoomAudioSynthLoader;
thread.Priority = DOOM_AUDIO_SYNTH_LOADER_PRIORITY;
thread.ThreadHandleOut = &g_doomAudio.SynthLoaderHandle;
thread.ThreadIdOut = &g_doomAudio.SynthLoaderId;
status = SysCreateThread(&thread);
if (status != NTLSTATUS_SUCCESS)
DoomAudioPrint("doom: MIDI loader creation failed; continuing with sound effects only\n");
}
// Stops playback immediately without waiting for the producer thread.
void DoomAudioAbort(void) {
_InterlockedExchange(&g_doomAudio.StopRequested, 1);
if (g_doomAudio.StreamHandle)
(void)SysAudioStop(g_doomAudio.StreamHandle);
}
// Stops the producer before releasing its stream, synthesizer, and core lock.
void DoomAudioShutdown(void) {
DoomAudioAbort();
if (g_doomAudio.WorkerHandle) {
(void)SysWaitSingle(g_doomAudio.WorkerHandle,
SYS_WAIT_FLAG_NONE,
SYS_WAIT_TIMEOUT_INFINITE);
(void)SysCloseHandle(g_doomAudio.WorkerHandle);
g_doomAudio.WorkerHandle = 0;
}
if (g_doomAudio.SynthLoaderHandle) {
(void)SysWaitSingle(g_doomAudio.SynthLoaderHandle,
SYS_WAIT_FLAG_NONE,
SYS_WAIT_TIMEOUT_INFINITE);
(void)SysCloseHandle(g_doomAudio.SynthLoaderHandle);
g_doomAudio.SynthLoaderHandle = 0;
}
DoomAudioReleaseResources();
}
// Enters the lock shared by the game loop and PureDOOM audio callbacks.
void DoomAudioLockCore(void) {
if (g_doomAudio.CoreMutex)
(void)SysWaitSingle(g_doomAudio.CoreMutex,
SYS_WAIT_FLAG_NONE,
SYS_WAIT_TIMEOUT_INFINITE);
}
// Leaves the lock shared by the game loop and PureDOOM audio callbacks.
void DoomAudioUnlockCore(void) {
if (g_doomAudio.CoreMutex)
(void)SysReleaseMutex(g_doomAudio.CoreMutex);
}