add a realtime scheduler class and priority-inheritance mutexes with timeouts and abandonment, add an HDA audio driver and a SYS_AUDIO_* syscall family, flesh out kernel32 with Win32 threads, events, mutexes, critical sections, and condition variables, add SYS_RESUME_THREAD for suspended thread creation, add KeInitializeMemoryTypes to reprogram MTRRs and install a canonical PAT for WB/WC/UC control, rework the display pipeline with a pitched present path and console output flush, bring back disk.vhd with mtools since vvfat fucking sucks, harden AHCI with serialized cache flush, USB mass storage with SCSI sense-data decoding, and xHCI with cleaner DMA teardown, fix the last stray for(;;) in HalStopProcessor to while(true), pile on smoke tests for audio, win32 sync primitives, and thread suspend, implement AHCI timeout, kill readahead since it was desperetaly trying to murder me, fix doom sound x2.

This commit is contained in:
maksw committed 2026-08-11 18:38:36 +02:00
1 parent 8b254f84f4
commit 4add6789ba
177 files changed
+22565 -3070

No files matched your search

+159 -115
View File
@@ -7549,15 +7549,25 @@ void doom_init(int argc, const char** argv, int flags)
if (!doom_exit) doom_exit = doom_exit_impl;
if (!doom_getenv) doom_getenv = doom_getenv_impl;
doom_print("doom_init: allocating screen buffer\n");
screen_buffer = (unsigned char*)doom_malloc(SCREENWIDTH * SCREENHEIGHT);
doom_print("doom_init: screen buffer ready\n");
doom_print("doom_init: allocating final screen buffer\n");
final_screen_buffer = (unsigned char*)doom_malloc(SCREENWIDTH * SCREENHEIGHT * 4);
doom_print("doom_init: final screen buffer ready\n");
doom_print("doom_init: reading initial time\n");
last_update_time = I_GetTime();
doom_print("doom_init: initial time ready\n");
myargc = argc;
myargv = (char**)argv; // unsafe my ass
doom_flags = flags;
doom_print("doom_init: entering D_DoomMain\n");
D_DoomMain();
doom_print("doom_init: D_DoomMain returned\n");
}
@@ -9790,7 +9800,27 @@ void FindResponseFile(void)
{
int i;
doom_print("FindResponseFile: entered\n");
if (myargc <= 0)
{
doom_print("FindResponseFile: argc <= 0\n");
return;
}
if (myargc == 1)
{
doom_print("FindResponseFile: argc == 1, returning\n");
return;
}
doom_print("FindResponseFile: argc > 1\n");
for (i = 1; i < myargc; i++)
{
doom_print("FindResponseFile: checking argument\n");
if (!myargv || !myargv[i])
{
doom_print("FindResponseFile: invalid argv entry\n");
return;
}
if (myargv[i][0] == '@')
{
void* handle = 0;
@@ -9860,6 +9890,9 @@ void FindResponseFile(void)
break;
}
}
doom_print("FindResponseFile: scan complete\n");
}
@@ -9871,9 +9904,13 @@ void D_DoomMain(void)
int p;
char file[256];
doom_print("D_DoomMain: FindResponseFile\n");
FindResponseFile();
doom_print("D_DoomMain: FindResponseFile done\n");
doom_print("D_DoomMain: IdentifyVersion\n");
IdentifyVersion();
doom_print("D_DoomMain: IdentifyVersion done\n");
modifiedgame = doom_false;
@@ -23741,19 +23778,67 @@ void M_SaveDefaults(void)
//
// M_LoadDefaults
//
// Reads one complete defaults line without allowing malformed files to escape the caller's buffer.
static int M_ReadDefaultsLine(void* handle, char* line, int capacity)
{
int length = 0;
doom_boolean overflow = doom_false;
if (!handle || !line || capacity < 2)
return 0;
for (;;)
{
char c = 0;
int read = doom_read(handle, &c, 1);
if (read != 1)
{
if (!length && !overflow)
return 0;
break;
}
if (c == '\n')
break;
if (c == '\r')
continue;
if (length + 1 < capacity)
line[length++] = c;
else
overflow = doom_true;
}
line[length] = 0;
return overflow ? -1 : 1;
}
// Returns the first non-whitespace byte in a mutable defaults token.
static char* M_SkipDefaultWhitespace(char* text)
{
while (*text == ' ' || *text == '\t')
text++;
return text;
}
// Removes horizontal whitespace from the end of a mutable defaults token.
static void M_TrimDefaultWhitespace(char* text)
{
int length = (int)doom_strlen(text);
while (length > 0 && (text[length - 1] == ' ' || text[length - 1] == '\t'))
text[--length] = 0;
}
// Loads bounded name/value records from the optional defaults file.
void M_LoadDefaults(void)
{
char line[384];
int i;
int len;
void* f;
char def[80];
char strparm[100];
char* newstring;
int parm;
doom_boolean isstring;
// set everything to base values
// numdefaults = sizeof(defaults)/sizeof(defaults[0]);
// Set everything to base values.
for (i = 0; i < numdefaults; i++)
{
if (defaults[i].defaultvalue == 0xFFFF)
@@ -23762,104 +23847,80 @@ void M_LoadDefaults(void)
*defaults[i].location = (int)defaults[i].defaultvalue;
}
// check for a custom default file
// Check for a custom default file.
i = M_CheckParm((char*)"-config");
if (i && i < myargc - 1)
{
defaultfile = myargv[i + 1];
//doom_print(" default file: %s\n", defaultfile);
doom_print(" default file: ");
doom_print(defaultfile);
doom_print("\n");
}
else
defaultfile = basedefault;
// read the file in, overriding any set defaults
f = doom_open(defaultfile, "r");
if (f)
{
while (!doom_eof(f))
defaultfile = basedefault;
}
void* f = doom_open(defaultfile, "r");
if (!f)
return;
for (;;)
{
int lineStatus = M_ReadDefaultsLine(f, line, (int)sizeof(line));
if (!lineStatus)
break;
if (lineStatus < 0)
{
// def
int arg_read = 0;
char c;
for (i = 0; i < 79; ++i)
{
doom_read(f, &c, 1);
if (c == ' ' || c == '\n' || c == '\t')
{
if (i > 0) arg_read++;
break;
}
def[i] = c;
}
def[i] = '\0';
// Ignore spaces
if (c != '\n')
{
while (1)
{
doom_read(f, &c, 1);
if (c != ' ' && c != '\t') break;
}
// strparam
i = 0;
if (c != '\n')
{
for (; i < 260;)
{
strparm[i++] = c;
doom_read(f, &c, 1);
if (c == '\n')
{
if (i > 0) arg_read++;
break;
}
}
}
strparm[i] = '\0';
}
isstring = doom_false;
//if (fscanf(f, "%79s %[^\n]\n", def, strparm) == 2)
if (arg_read == 2)
{
if (strparm[0] == '"')
{
// get a string default
isstring = doom_true;
len = (int)doom_strlen(strparm);
newstring = (char*)doom_malloc(len);
strparm[len - 1] = 0;
doom_strcpy(newstring, strparm + 1);
}
else if (strparm[0] == '0' && strparm[1] == 'x')
{
//sscanf(strparm + 2, "%x", &parm);
parm = doom_atox(strparm + 2);
}
else
{
//sscanf(strparm, "%i", &parm);
parm = doom_atoi(strparm);
}
for (i = 0; i < numdefaults; i++)
if (!doom_strcmp(def, defaults[i].name))
{
if (!isstring)
*defaults[i].location = parm;
else
*defaults[i].text_location = newstring;
break;
}
}
doom_print("M_LoadDefaults: ignored an overlong defaults line.\n");
continue;
}
doom_close(f);
char* name = M_SkipDefaultWhitespace(line);
if (!*name || *name == '#')
continue;
char* value = name;
while (*value && *value != ' ' && *value != '\t')
value++;
if (!*value)
continue;
*value++ = 0;
value = M_SkipDefaultWhitespace(value);
M_TrimDefaultWhitespace(value);
if (!*value)
continue;
default_t* setting = get_default(name);
if (!setting)
continue;
if (value[0] == '"')
{
int length = (int)doom_strlen(value);
if (length < 2 || value[length - 1] != '"' || !setting->text_location)
continue;
int textLength = length - 2;
char* newstring = (char*)doom_malloc(textLength + 1);
if (!newstring)
continue;
doom_memcpy(newstring, value + 1, textLength);
newstring[textLength] = 0;
*setting->text_location = newstring;
}
else if (setting->location)
{
int parsed;
if (value[0] == '0' && value[1] == 'x')
parsed = doom_atox(value + 2);
else
parsed = doom_atoi(value);
*setting->location = parsed;
}
}
doom_close(f);
}
@@ -37068,10 +37129,6 @@ void R_InitTextures(void)
int* directory;
int temp1;
int temp2;
int temp3;
// Load the patch names from pnames.lmp.
name[8] = 0;
names = (char*)W_CacheLumpName((char*)"PNAMES", PU_STATIC);
@@ -37118,23 +37175,10 @@ void R_InitTextures(void)
totalwidth = 0;
// Really complex printing shit...
temp1 = W_GetNumForName((char*)"S_START"); // P_???????
temp2 = W_GetNumForName((char*)"S_END") - 1;
temp3 = ((temp2 - temp1 + 63) / 64) + ((numtextures + 63) / 64);
doom_print("[");
for (i = 0; i < temp3; i++)
doom_print(" ");
doom_print(" ]");
for (i = 0; i < temp3; i++)
doom_print("\x8");
doom_print("\x8\x8\x8\x8\x8\x8\x8\x8\x8\x8");
// Cursor-control progress output can block simple platform consoles.
for (i = 0; i < numtextures; i++, directory++)
{
if (!(i & 63))
doom_print(".");
if (i == numtextures1)
{
// Start looking in second texture file.
@@ -37285,14 +37329,14 @@ void R_InitColormaps(void)
//
void R_InitData(void)
{
R_InitTextures();
doom_print("\nInitTextures");
R_InitFlats();
R_InitTextures();
doom_print("\nInitFlats");
R_InitSpriteLumps();
R_InitFlats();
doom_print("\nInitSprites");
R_InitColormaps();
R_InitSpriteLumps();
doom_print("\nInitColormaps");
R_InitColormaps();
}
+18 -3
View File
@@ -1,6 +1,6 @@
// doom.c — PureDOOM port
// Native personality carrying all the weight.
// Drop doom1.wad (or doom.wad) next to this binary on your FAT32 volume.
// Place the Doom WAD and optional doom.sf2 in the process working directory.
#include <stdint.h>
#include <stddef.h>
@@ -10,6 +10,8 @@
#include <intrin.h>
#include <ntlite/syscall.h>
#include "doom_audio.h"
#define DOOM_IMPLEMENTATION
#include "PureDOOM.h"
@@ -189,6 +191,7 @@ static void doom_gettime_wrapper(int* sec, int* usec)
// ---------------------------------------------------------------------------
static void doom_exit_wrapper(int code)
{
DoomAudioAbort();
SysExitProcess(code);
}
@@ -445,7 +448,7 @@ static int doom_now_tic(void)
// Waits until Doom has a new simulation tic to render.
static int wait_for_next_doom_tic(int lastTic)
{
for (;;) {
while (true) {
int now = doom_now_tic();
if (now != lastTic)
return now;
@@ -495,9 +498,18 @@ int main(void)
char argvStorage[512];
const char* argv[32];
int argc = build_argv_from_command_line(cmdline, argvStorage, sizeof(argvStorage), argv, 32);
if (argc == 1)
doom_print_wrapper("doom: parsed argc == 1\n");
else if (argc > 1)
doom_print_wrapper("doom: parsed argc > 1\n");
else
doom_print_wrapper("doom: parsed argc <= 0\n");
init_surface();
init_display_clock();
do { SYS_WRITE_CONSOLE_REQUEST req = { SYS_SV_LITERAL("doom: starting doom\n") }; SysWriteConsole(&req); } while (0);
doom_init(argc, argv, 0);
do { SYS_WRITE_CONSOLE_REQUEST req = { SYS_SV_LITERAL("doom: starting audio\n") }; SysWriteConsole(&req); } while (0);
DoomAudioStart();
do { SYS_WRITE_CONSOLE_REQUEST req = { SYS_SV_LITERAL("doom: entering game loop\n") }; SysWriteConsole(&req); } while (0);
@@ -508,9 +520,12 @@ int main(void)
int frameTic = wait_for_next_doom_tic(lastFrameTic);
lastFrameTic = frameTic;
DoomAudioLockCore();
poll_input();
doom_update();
render_frame((const uint8_t*)doom_get_framebuffer(4 /* RGBA */));
const uint8_t* framebuffer = (const uint8_t*)doom_get_framebuffer(4 /* RGBA */);
DoomAudioUnlockCore();
render_frame(framebuffer);
}
SysExitProcess(0);
+802
View File
@@ -0,0 +1,802 @@
// 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 "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);
}
+18
View File
@@ -0,0 +1,18 @@
// Doom audio owns PureDOOM sound extraction, MIDI synthesis, and native stream submission.
#pragma once
// Starts the optional audio worker after PureDOOM has initialized its sound state.
void DoomAudioStart(void);
// Stops playback immediately without waiting for the worker.
void DoomAudioAbort(void);
// Stops the audio worker and releases its process-owned resources.
void DoomAudioShutdown(void);
// Enters the lock shared by the game loop and PureDOOM audio callbacks.
void DoomAudioLockCore(void);
// Leaves the lock shared by the game loop and PureDOOM audio callbacks.
void DoomAudioUnlockCore(void);
+19
View File
@@ -0,0 +1,19 @@
Copyright (C) 2017-2023 Bernhard Schelling (Based on SFZero, Copyright (C) 2012 Steve Folta, https://github.com/stevefolta/SFZero)
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+11
View File
@@ -0,0 +1,11 @@
TinySoundFont v0.9
Upstream: https://github.com/schellingb/TinySoundFont
File: tsf.h
License: MIT; see LICENSE.tsf.
The vendored header is unchanged apart from normalized line endings. nt-lite
supplies allocator, memory, and math macros from doom_audio.c so the synthesizer
stays within the native user runtime. Doom loads the optional small `doom.sf2`
asset asynchronously, then releases the input mapping after TinySoundFont has
copied the sample bank into its owned representation.
File diff suppressed because it is too large. Load diff