// Doom audio mixes PureDOOM SFX with optional SoundFont MIDI into one native render stream. #include #include #include #include #include #include #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) { SYS_WRITE_CONSOLE_REQUEST request = { SysSvFromCstr(text) }; (void)SysWriteConsole(&request); } // Opens the default available native render endpoint. static KSTATUS DoomAudioFindEndpoint(uint64_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; SYS_OPEN_FILE_REQUEST open = SysMakeOpenFileRequest( SysSvFromCstr(DOOM_AUDIO_SOUNDFONT_PATH), SYS_FILE_ACCESS_QUERY | SYS_FILE_ACCESS_READ, SYS_FILE_OPEN_EXISTING, SYS_FILE_FLAG_NONE, &fileHandle); status = SysOpenFile(&open); 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; } status = SysCreateFileMapping(fileHandle, SYS_VM_READ, SYS_MAP_FLAG_NONE, fileSize, &mappingHandle); if (status != NTLSTATUS_SUCCESS || !mappingHandle) { (void)SysCloseHandle(fileHandle); return status == NTLSTATUS_SUCCESS ? NTLSTATUS_FAILURE : status; } status = SysMapFile(mappingHandle, 0, (size_t)fileSize, SYS_MAP_ACCESS_READ, SYS_MAP_FLAG_NONE, &base); (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) { uint64_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.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.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); }