Skip to content

Instantly share code, notes, and snippets.

@pervognsen
Last active December 15, 2023 14:43
Show Gist options
  • Star 34 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save pervognsen/6a67966c5dc4247a0021b95c8d0a7b72 to your computer and use it in GitHub Desktop.
Save pervognsen/6a67966c5dc4247a0021b95c8d0a7b72 to your computer and use it in GitHub Desktop.
Mu as of the second stream
#include "mu.h"
#define _CRT_SECURE_NO_WARNINGS
#include <malloc.h>
#define _USE_MATH_DEFINES
#include <math.h>
#define _NO_CRT_STDIO_INLINE
#include <stdio.h>
#include <stdarg.h>
#define NO_STRICT
#include <windows.h>
#include <wincodec.h>
#include <xinput.h>
#include <xaudio2.h>
#include <mmdeviceapi.h>
#include <audioclient.h>
#include <audiosessiontypes.h>
#include <wincodec.h>
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#ifndef AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM
#define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000
#endif
#ifdef _DEBUG
#define Assert(x) if (!(x)) { MessageBoxA(0, x, "Assertion Failed", MB_OK); __debugbreak(); }
#else
#define Assert(x)
#endif
#include "mu.h"
MU_EXTERN_BEGIN
void Mu_ExitWithError(Mu *mu) {
MessageBoxA(0, mu->error, "Mu Error", MB_OK);
ExitProcess(1);
}
void Mu_UpdateDigitalButton(Mu_DigitalButton *button, Mu_Bool down) {
Mu_Bool was_down = button->down;
button->down = down;
button->pressed = !was_down && down;
button->released = was_down && !down;
}
void Mu_UpdateAnalogButton(Mu_AnalogButton *button, float value) {
button->value = value;
Mu_Bool was_down = button->down;
button->down = (value >= button->threshold);
button->pressed = !was_down && button->down;
button->released = was_down && !button->down;
}
void Mu_UpdateStick(Mu_Stick *stick, float x, float y) {
if (fabs(x) <= stick->threshold) {
x = 0.0f;
}
stick->x = x;
if (fabs(y) <= stick->threshold) {
y = 0.0f;
}
stick->y = y;
}
// Mu_Window
static LRESULT CALLBACK Mu_Window_Proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) {
LRESULT result = 0;
Mu *mu = (Mu *)GetWindowLongPtrA(window, GWLP_USERDATA);
switch (message) {
case WM_SIZE:
mu->window.resized = MU_TRUE;
break;
case WM_INPUT: {
UINT size;
GetRawInputData((HRAWINPUT)lparam, RID_INPUT, 0, &size, sizeof(RAWINPUTHEADER));
void *buffer = _alloca(size);
if (GetRawInputData((HRAWINPUT)lparam, RID_INPUT, buffer, &size, sizeof(RAWINPUTHEADER)) == size) {
RAWINPUT *raw_input = (RAWINPUT *)buffer;
if (raw_input->header.dwType == RIM_TYPEMOUSE && raw_input->data.mouse.usFlags == MOUSE_MOVE_RELATIVE) {
mu->mouse.delta_position.x += raw_input->data.mouse.lLastX;
mu->mouse.delta_position.y += raw_input->data.mouse.lLastY;
USHORT button_flags = raw_input->data.mouse.usButtonFlags;
Mu_Bool left_button_down = mu->mouse.left_button.down;
if (button_flags & RI_MOUSE_LEFT_BUTTON_DOWN) {
left_button_down = MU_TRUE;
}
if (button_flags & RI_MOUSE_LEFT_BUTTON_UP) {
left_button_down = MU_FALSE;
}
Mu_UpdateDigitalButton(&mu->mouse.left_button, left_button_down);
Mu_Bool right_button_down = mu->mouse.right_button.down;
if (button_flags & RI_MOUSE_RIGHT_BUTTON_DOWN) {
right_button_down = MU_TRUE;
}
if (button_flags & RI_MOUSE_RIGHT_BUTTON_UP) {
right_button_down = MU_FALSE;
}
Mu_UpdateDigitalButton(&mu->mouse.right_button, right_button_down);
if (button_flags & RI_MOUSE_WHEEL) {
mu->mouse.delta_wheel += ((SHORT)raw_input->data.mouse.usButtonData) / WHEEL_DELTA;
}
}
}
result = DefWindowProcA(window, message, wparam, lparam);
break;
}
case WM_CHAR: {
WCHAR utf16_character = (WCHAR)wparam;
char ascii_character;
uint32_t ascii_length = WideCharToMultiByte(CP_ACP, 0, &utf16_character, 1, &ascii_character, 1, 0, 0);
if (ascii_length == 1 && mu->text_length + 1 < sizeof(mu->text) - 1) {
mu->text[mu->text_length] = ascii_character;
mu->text[mu->text_length + 1] = 0;
mu->text_length += ascii_length;
}
break;
}
case WM_DESTROY:
mu->quit = MU_TRUE;
break;
case WM_TIMER:
SwitchToFiber(mu->win32.main_fiber);
break;
default:
result = DefWindowProcA(window, message, wparam, lparam);
}
return result;
}
static void CALLBACK Mu_Window_MessageFiberProc(Mu *mu) {
SetTimer(mu->win32.window, 1, 1, 0);
for (;;) {
MSG message;
while (PeekMessage(&message, 0, 0, 0, PM_REMOVE)) {
TranslateMessage(&message);
DispatchMessage(&message);
}
SwitchToFiber(mu->win32.main_fiber);
}
}
Mu_Bool Mu_Window_Initialize(Mu *mu) {
if (!mu->window.title) {
mu->window.title = "Mu";
}
int window_x;
if (mu->window.position.x) {
window_x = mu->window.position.x;
} else {
window_x = CW_USEDEFAULT;
}
int window_y;
if (mu->window.position.y) {
window_y = mu->window.position.y;
} else {
window_y = CW_USEDEFAULT;
}
int window_width;
if (mu->window.size.x) {
window_width = mu->window.size.x;
} else {
window_width = CW_USEDEFAULT;
}
int window_height;
if (mu->window.size.y) {
window_height = mu->window.size.y;
} else {
window_height = CW_USEDEFAULT;
}
mu->win32.main_fiber = ConvertThreadToFiber(0);
Assert(mu->win32.main_fiber);
mu->win32.message_fiber = CreateFiber(0, (PFIBER_START_ROUTINE)Mu_Window_MessageFiberProc, mu);
Assert(mu->win32.message_fiber);
if (window_height != CW_USEDEFAULT && window_width != CW_USEDEFAULT) {
RECT window_rectangle;
window_rectangle.left = 0;
window_rectangle.right = window_width;
window_rectangle.top = 0;
window_rectangle.bottom = window_height;
if (AdjustWindowRect(&window_rectangle, WS_OVERLAPPEDWINDOW, 0)) {
window_width = window_rectangle.right - window_rectangle.left;
window_height = window_rectangle.bottom - window_rectangle.top;
}
}
WNDCLASSA window_class = {0};
window_class.lpfnWndProc = Mu_Window_Proc;
window_class.lpszClassName = "mu";
window_class.style = CS_HREDRAW | CS_VREDRAW;
if (RegisterClassA(&window_class) == 0) {
mu->error = "Failed to initialize window class.";
return MU_FALSE;
}
mu->win32.window = CreateWindowA("mu", mu->window.title, WS_OVERLAPPEDWINDOW, window_x, window_y, window_width, window_height, 0, 0, 0, 0);
if (!mu->win32.window) {
mu->error = "Failed to create window.";
return MU_FALSE;
}
SetWindowLongPtr(mu->win32.window, GWLP_USERDATA, (LONG_PTR)mu);
ShowWindow(mu->win32.window, SW_SHOW);
mu->win32.device_context = GetDC(mu->win32.window);
return MU_TRUE;
}
void Mu_Window_Pull(Mu *mu) {
mu->window.resized = MU_FALSE;
mu->text[0] = 0;
mu->text_length = 0;
mu->mouse.delta_position.x = 0;
mu->mouse.delta_position.y = 0;
mu->mouse.delta_wheel = 0;
mu->mouse.left_button.pressed = MU_FALSE;
mu->mouse.left_button.released = MU_FALSE;
mu->mouse.right_button.pressed = MU_FALSE;
mu->mouse.right_button.released = MU_FALSE;
SwitchToFiber(mu->win32.message_fiber);
mu->mouse.wheel += mu->mouse.delta_wheel;
RECT client_rectangle;
GetClientRect(mu->win32.window, &client_rectangle);
mu->window.size.x = client_rectangle.right - client_rectangle.left;
mu->window.size.y = client_rectangle.bottom - client_rectangle.top;
POINT window_position = {client_rectangle.left, client_rectangle.top};
ClientToScreen(mu->win32.window, &window_position);
mu->window.position.x = window_position.x;
mu->window.position.y = window_position.y;
}
// Mu_Time
Mu_Bool Mu_Time_Initialize(Mu *mu) {
LARGE_INTEGER large_integer;
QueryPerformanceFrequency(&large_integer);
mu->time.ticks_per_second = large_integer.QuadPart;
QueryPerformanceCounter(&large_integer);
mu->time.initial_ticks = large_integer.QuadPart;
return MU_TRUE;
}
void Mu_Time_Pull(Mu *mu) {
LARGE_INTEGER large_integer;
QueryPerformanceCounter(&large_integer);
uint64_t current_ticks = large_integer.QuadPart;
mu->time.delta_ticks = (current_ticks - mu->time.initial_ticks) - mu->time.ticks;
mu->time.ticks = current_ticks - mu->time.initial_ticks;
mu->time.delta_nanoseconds = (1000 * 1000 * 1000 * mu->time.delta_ticks) / mu->time.ticks_per_second;
mu->time.delta_microseconds = mu->time.delta_nanoseconds / 1000;
mu->time.delta_milliseconds = mu->time.delta_microseconds / 1000;
mu->time.delta_seconds = (float)mu->time.delta_ticks / (float)mu->time.ticks_per_second;
mu->time.nanoseconds = (1000 * 1000 * 1000 * mu->time.ticks) / mu->time.ticks_per_second;
mu->time.microseconds = mu->time.nanoseconds / 1000;
mu->time.milliseconds = mu->time.microseconds / 1000;
mu->time.seconds = (float)mu->time.ticks / (float)mu->time.ticks_per_second;
}
// Mu_Keyboard
void Mu_Keyboard_Pull(Mu *mu) {
BYTE keyboard_state[256] = {0};
GetKeyboardState(keyboard_state);
for (int key = 0; key < 256; key++) {
Mu_UpdateDigitalButton(mu->keys + key, keyboard_state[key] >> 7);
}
}
// Mu_Mouse
Mu_Bool Mu_Mouse_Initialize(Mu *mu) {
RAWINPUTDEVICE raw_input_device = {0};
raw_input_device.usUsagePage = 0x01;
raw_input_device.usUsage = 0x02;
raw_input_device.hwndTarget = mu->win32.window;
if (!RegisterRawInputDevices(&raw_input_device, 1, sizeof(raw_input_device))) {
mu->error = "Failed to register mouse as raw input device.";
return MU_FALSE;
}
return MU_TRUE;
}
void Mu_Mouse_Pull(Mu *mu) {
POINT mouse_position;
GetCursorPos(&mouse_position);
mouse_position.x -= mu->window.position.x;
mouse_position.y -= mu->window.position.y;
mu->mouse.position.x = mouse_position.x;
mu->mouse.position.y = mouse_position.y;
}
// Mu_Gamepad
Mu_Bool Mu_Gamepad_Initialize(Mu *mu) {
HMODULE xinput_module = LoadLibraryA("xinput1_3.dll");
if (xinput_module) {
mu->win32.xinput_get_state = (XINPUTGETSTATE)GetProcAddress(xinput_module, "XInputGetState");
}
float trigger_threshold = XINPUT_GAMEPAD_TRIGGER_THRESHOLD / 255.f;
mu->gamepad.left_trigger.threshold = trigger_threshold;
mu->gamepad.right_trigger.threshold = trigger_threshold;
mu->gamepad.left_thumb_stick.threshold = XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE / 32767.f;
mu->gamepad.right_thumb_stick.threshold = XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE / 32767.f;
return MU_TRUE;
}
void Mu_Gamepad_Pull(Mu *mu) {
XINPUT_STATE xinput_state = {0};
if (!mu->win32.xinput_get_state || mu->win32.xinput_get_state(0, &xinput_state) != ERROR_SUCCESS) {
mu->gamepad.connected = MU_FALSE;
return;
}
mu->gamepad.connected = MU_TRUE;
Mu_UpdateDigitalButton(&mu->gamepad.a_button, (xinput_state.Gamepad.wButtons & XINPUT_GAMEPAD_A) != 0);
Mu_UpdateDigitalButton(&mu->gamepad.b_button, (xinput_state.Gamepad.wButtons & XINPUT_GAMEPAD_B) != 0);
Mu_UpdateDigitalButton(&mu->gamepad.x_button, (xinput_state.Gamepad.wButtons & XINPUT_GAMEPAD_X) != 0);
Mu_UpdateDigitalButton(&mu->gamepad.y_button, (xinput_state.Gamepad.wButtons & XINPUT_GAMEPAD_Y) != 0);
Mu_UpdateDigitalButton(&mu->gamepad.left_shoulder_button, (xinput_state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) != 0);
Mu_UpdateDigitalButton(&mu->gamepad.right_shoulder_button, (xinput_state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) != 0);
Mu_UpdateDigitalButton(&mu->gamepad.up_button, (xinput_state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP) != 0);
Mu_UpdateDigitalButton(&mu->gamepad.down_button, (xinput_state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN) != 0);
Mu_UpdateDigitalButton(&mu->gamepad.left_button, (xinput_state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) != 0);
Mu_UpdateDigitalButton(&mu->gamepad.right_button, (xinput_state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) != 0);
Mu_UpdateDigitalButton(&mu->gamepad.left_thumb_button, (xinput_state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB) != 0);
Mu_UpdateDigitalButton(&mu->gamepad.right_thumb_button, (xinput_state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) != 0);
Mu_UpdateDigitalButton(&mu->gamepad.back_button, (xinput_state.Gamepad.wButtons & XINPUT_GAMEPAD_BACK) != 0);
Mu_UpdateDigitalButton(&mu->gamepad.start_button, (xinput_state.Gamepad.wButtons & XINPUT_GAMEPAD_START) != 0);
Mu_UpdateAnalogButton(&mu->gamepad.left_trigger, xinput_state.Gamepad.bLeftTrigger / 255.f);
Mu_UpdateAnalogButton(&mu->gamepad.right_trigger, xinput_state.Gamepad.bRightTrigger / 255.f);
#define CONVERT(x) (2.0f * (((x + 32768) / 65535.f) - 0.5f))
Mu_UpdateStick(&mu->gamepad.left_thumb_stick, CONVERT(xinput_state.Gamepad.sThumbLX), CONVERT(xinput_state.Gamepad.sThumbLY));
Mu_UpdateStick(&mu->gamepad.right_thumb_stick, CONVERT(xinput_state.Gamepad.sThumbRX), CONVERT(xinput_state.Gamepad.sThumbRY));
#undef CONVERT
}
// Mu_Audio
static void Mu_Audio_DefaultCallback(Mu_AudioBuffer *buffer) {
FillMemory(buffer->samples, sizeof(int16_t) * buffer->samples_count, 0);
}
DWORD Mu_Audio_ThreadProc(void *parameter) {
DWORD result = -1;
Mu *mu = (Mu *)parameter;
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
HANDLE buffer_ready_event = CreateEvent(0, 0, 0, 0);
if (mu->win32.audio_client->SetEventHandle(buffer_ready_event) < 0) {
goto done;
}
uint32_t buffer_frame_count;
if (mu->win32.audio_client->GetBufferSize(&buffer_frame_count) < 0) {
goto done;
}
uint32_t buffer_sample_count = buffer_frame_count * mu->audio.format.channels;
if (mu->win32.audio_client->Start() < 0) {
goto done;
}
for (;;) {
if (WaitForSingleObject(buffer_ready_event, INFINITE) != WAIT_OBJECT_0) {
goto done;
}
uint32_t padding_frame_count;
if (mu->win32.audio_client->GetCurrentPadding(&padding_frame_count) < 0) {
goto done;
}
uint32_t fill_frame_count = buffer_frame_count - padding_frame_count;
Mu_AudioBuffer buffer;
if (mu->win32.audio_render_client->GetBuffer(fill_frame_count, (BYTE **)&buffer.samples) < 0) {
goto done;
}
buffer.format = mu->audio.format;
buffer.samples_count = fill_frame_count * buffer.format.channels;
mu->audio.callback(&buffer);
if (mu->win32.audio_render_client->ReleaseBuffer(fill_frame_count, 0) < 0) {
goto done;
}
}
result = 0;
done:
mu->win32.audio_client->Stop();
return result;
}
static WAVEFORMATEX win32_audio_format = {WAVE_FORMAT_PCM, 1, 44100, 44100 * 2, 2, 16, 0};
static Mu_AudioFormat mu_audio_format = {44100, 1, 2};
Mu_Bool Mu_Audio_Initialize(Mu *mu) {
Mu_Bool result = MU_FALSE;
IMMDeviceEnumerator *device_enumerator = 0;
IMMDevice *audio_device = 0;
if (!mu->audio.callback)
{
mu->audio.callback = Mu_Audio_DefaultCallback;
}
CoInitializeEx(0, COINITBASE_MULTITHREADED);
if (CoCreateInstance(__uuidof(MMDeviceEnumerator), 0, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&device_enumerator)) < 0) {
goto done;
}
if (device_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &audio_device) < 0) {
goto done;
}
IAudioClient *audio_client;
if (audio_device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, 0, (void **)&audio_client) < 0) {
goto done;
}
REFERENCE_TIME audio_buffer_duration = 40 * 1000 * 10; // 40 milliseconds
DWORD audio_client_flags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_RATEADJUST | AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM;
if (audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED, audio_client_flags, audio_buffer_duration, 0, &win32_audio_format, 0) < 0) {
goto done;
}
IAudioRenderClient *audio_render_client;
if (audio_client->GetService(IID_PPV_ARGS(&audio_render_client)) < 0) {
goto done;
}
mu->audio.format = mu_audio_format;
mu->win32.audio_client = audio_client;
mu->win32.audio_render_client = audio_render_client;
CreateThread(0, 0, Mu_Audio_ThreadProc, mu, 0, 0);
device_enumerator->Release();
result = MU_TRUE;
done:
if (device_enumerator) {
device_enumerator->Release();
}
if (audio_device) {
audio_device->Release();
}
return result;
}
// Mu_OpenGL
void Mu_OpenGL_Push(Mu *mu) {
SwapBuffers(mu->win32.device_context);
}
Mu_Bool Mu_OpenGL_Initialize(Mu *mu) {
PIXELFORMATDESCRIPTOR pixel_format_descriptor;
pixel_format_descriptor.nSize = sizeof(pixel_format_descriptor);
pixel_format_descriptor.dwFlags = PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW | PFD_DOUBLEBUFFER;
pixel_format_descriptor.iPixelType = PFD_TYPE_RGBA;
pixel_format_descriptor.cColorBits = 24;
pixel_format_descriptor.cAlphaBits = 0;
pixel_format_descriptor.cDepthBits = 24;
pixel_format_descriptor.cStencilBits = 8;
int pixel_format = ChoosePixelFormat(mu->win32.device_context, &pixel_format_descriptor);
if (!pixel_format) {
return MU_FALSE;
}
if (!DescribePixelFormat(mu->win32.device_context, pixel_format, sizeof(pixel_format_descriptor), &pixel_format_descriptor)) {
return MU_FALSE;
}
if (!SetPixelFormat(mu->win32.device_context, pixel_format, &pixel_format_descriptor)) {
return MU_FALSE;
}
mu->win32.wgl_context = wglCreateContext(mu->win32.device_context);
if (!mu->win32.wgl_context) {
return MU_FALSE;
}
wglMakeCurrent(mu->win32.device_context, mu->win32.wgl_context);
return MU_TRUE;
}
// Mu
Mu_Bool Mu_Pull(Mu *mu) {
if (!mu->initialized) {
if (!mu->error) {
mu->error = "Mu was not initialized.";
}
Mu_ExitWithError(mu);
return MU_FALSE;
}
Mu_Window_Pull(mu);
Mu_Time_Pull(mu);
Mu_Keyboard_Pull(mu);
Mu_Mouse_Pull(mu);
Mu_Gamepad_Pull(mu);
return !mu->quit;
}
void Mu_Push(Mu *mu) {
Mu_OpenGL_Push(mu);
}
Mu_Bool Mu_Initialize(Mu *mu) {
if (!Mu_Window_Initialize(mu)) {
return MU_FALSE;
}
if (!Mu_Time_Initialize(mu)) {
return MU_FALSE;
}
if (!Mu_Mouse_Initialize(mu)) {
return MU_FALSE;
}
if (!Mu_Gamepad_Initialize(mu)) {
return MU_FALSE;
}
if (!Mu_Audio_Initialize(mu)) {
return MU_FALSE;
}
if (!Mu_OpenGL_Initialize(mu)) {
return MU_FALSE;
}
mu->initialized = MU_TRUE;
Mu_Pull(mu);
return MU_TRUE;
}
// Utilities
static IWICImagingFactory *wic_factory;
static SRWLOCK wic_factory_lock = SRWLOCK_INIT;
Mu_Bool Mu_LoadImage(const char *filename, Mu_Image *image) {
Mu_Bool result = MU_FALSE;
IWICBitmapDecoder *image_decoder = 0;
IWICBitmapFrameDecode *image_frame = 0;
IWICBitmapSource *rgba_image_frame = 0;
if (!wic_factory) {
AcquireSRWLockExclusive(&wic_factory_lock);
if (!wic_factory) {
CoInitializeEx(0, COINITBASE_MULTITHREADED);
if (CoCreateInstance(CLSID_WICImagingFactory, 0, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&wic_factory)) < 0) {
ReleaseSRWLockExclusive(&wic_factory_lock);
goto done;
}
}
ReleaseSRWLockExclusive(&wic_factory_lock);
}
int wide_filename_length = MultiByteToWideChar(CP_UTF8, 0, filename, -1, 0, 0);
WCHAR *wide_filename = (WCHAR *)_alloca(wide_filename_length * sizeof(WCHAR));
MultiByteToWideChar(CP_UTF8, 0, filename, -1, wide_filename, wide_filename_length);
if (wic_factory->CreateDecoderFromFilename(wide_filename, 0, GENERIC_READ, WICDecodeMetadataCacheOnDemand, &image_decoder) < 0) {
goto done;
}
if (image_decoder->GetFrame(0, &image_frame) < 0) {
goto done;
}
if (WICConvertBitmapSource(GUID_WICPixelFormat32bppRGBA, image_frame, &rgba_image_frame) < 0) {
goto done;
}
uint32_t width;
uint32_t height;
image_frame->GetSize(&width, &height);
image->width = width;
image->height = height;
image->channels = 4;
uint32_t buffer_size = 4 * width * height;
image->pixels = (uint8_t *)malloc(buffer_size);
uint32_t buffer_stride = 4 * width;
if (rgba_image_frame->CopyPixels(0, buffer_stride, buffer_size, image->pixels) < 0) {
free(image->pixels);
goto done;
}
result = MU_TRUE;
done:
if (image_decoder) {
image_decoder->Release();
}
if (image_frame) {
image_frame->Release();
}
if (rgba_image_frame) {
rgba_image_frame->Release();
}
return result;
}
static bool mf_initialized;
Mu_Bool Mu_LoadAudio(const char *filename, Mu_AudioBuffer *audio) {
Mu_Bool result = MU_FALSE;
IMFSourceReader *source_reader = 0;
IMFMediaType *media_type = 0;
if (!mf_initialized) {
CoInitializeEx(0, COINIT_MULTITHREADED);
if (MFStartup(MF_VERSION, 0) < 0) {
goto done;
}
mf_initialized = true;
}
int wide_filename_length = MultiByteToWideChar(CP_UTF8, 0, filename, -1, 0, 0);
WCHAR *wide_filename = (WCHAR *)_alloca(wide_filename_length * sizeof(WCHAR));
MultiByteToWideChar(CP_UTF8, 0, filename, -1, wide_filename, wide_filename_length);
if (MFCreateSourceReaderFromURL(wide_filename, 0, &source_reader) < 0) {
goto done;
}
if (MFCreateMediaType(&media_type) < 0) {
goto done;
}
media_type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio);
media_type->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM);
media_type->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, win32_audio_format.nChannels);
media_type->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, win32_audio_format.nSamplesPerSec);
media_type->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, win32_audio_format.nBlockAlign);
media_type->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, win32_audio_format.nAvgBytesPerSec);
media_type->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, win32_audio_format.wBitsPerSample);
media_type->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE);
if (source_reader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, media_type) < 0) {
goto done;
}
size_t buffer_capacity = mu_audio_format.samples_per_second * mu_audio_format.bytes_per_sample; // 1 second of audio
size_t buffer_size = 0;
char *buffer = (char *)malloc(buffer_capacity);
for (;;) {
DWORD stream_index;
DWORD flags;
LONGLONG timestamp;
IMFSample *sample;
if (source_reader->ReadSample(MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, &stream_index, &flags, &timestamp, &sample) < 0) {
free(buffer);
goto done;
}
if (flags & MF_SOURCE_READERF_ENDOFSTREAM) {
break;
}
IMFMediaBuffer *sample_buffer;
sample->ConvertToContiguousBuffer(&sample_buffer);
DWORD sample_buffer_size;
sample_buffer->GetCurrentLength(&sample_buffer_size);
size_t new_buffer_size = buffer_size + sample_buffer_size;
if (new_buffer_size > buffer_capacity) {
buffer_capacity *= 2;
if (buffer_capacity < new_buffer_size) {
buffer_capacity = new_buffer_size;
}
buffer = (char *)realloc(buffer, buffer_capacity);
}
BYTE *sample_buffer_pointer;
sample_buffer->Lock(&sample_buffer_pointer, 0, 0);
CopyMemory(buffer + buffer_size, sample_buffer_pointer, sample_buffer_size);
buffer_size += sample_buffer_size;
sample_buffer->Unlock();
sample_buffer->Release();
sample->Release();
}
audio->format = mu_audio_format;
audio->samples = (int16_t *)realloc(buffer, buffer_size);
audio->samples_count = buffer_size / mu_audio_format.bytes_per_sample;
result = MU_TRUE;
done:
if (source_reader) {
source_reader->Release();
}
if (media_type) {
media_type->Release();
}
return result;
}
MU_EXTERN_END
#pragma once
#ifdef __cplusplus
#define MU_EXTERN_BEGIN extern "C" {
#define MU_EXTERN_END }
#else
#define MU_EXTERN_BEGIN
#define MU_EXTERN_END
#endif
#include <stdint.h>
MU_EXTERN_BEGIN
enum {
MU_FALSE = 0,
MU_TRUE = 1,
MU_MAX_KEYS = 256,
MU_MAX_TEXT = 256,
MU_MAX_ERROR = 1024,
MU_CTRL = 0x11, // VK_CONTROL
MU_ALT = 0x12, // VK_MENU
MU_SHIFT = 0x10, // VK_SHIFT
MU_MAX_AUDIO_BUFFER = 2 * 1024
};
typedef uint8_t Mu_Bool;
struct Mu_Int2 {
int x;
int y;
};
struct Mu_DigitalButton {
Mu_Bool down;
Mu_Bool pressed;
Mu_Bool released;
};
struct Mu_AnalogButton {
float threshold;
float value;
Mu_Bool down;
Mu_Bool pressed;
Mu_Bool released;
};
struct Mu_Stick {
float threshold;
float x;
float y;
};
struct Mu_Gamepad {
Mu_Bool connected;
Mu_DigitalButton a_button;
Mu_DigitalButton b_button;
Mu_DigitalButton x_button;
Mu_DigitalButton y_button;
Mu_AnalogButton left_trigger;
Mu_AnalogButton right_trigger;
Mu_DigitalButton left_shoulder_button;
Mu_DigitalButton right_shoulder_button;
Mu_DigitalButton up_button;
Mu_DigitalButton down_button;
Mu_DigitalButton left_button;
Mu_DigitalButton right_button;
Mu_Stick left_thumb_stick;
Mu_Stick right_thumb_stick;
Mu_DigitalButton left_thumb_button;
Mu_DigitalButton right_thumb_button;
Mu_DigitalButton back_button;
Mu_DigitalButton start_button;
};
struct Mu_Mouse {
Mu_DigitalButton left_button;
Mu_DigitalButton right_button;
int wheel;
int delta_wheel;
Mu_Int2 position;
Mu_Int2 delta_position;
};
struct Mu_Window {
char *title;
Mu_Int2 position;
Mu_Int2 size;
Mu_Bool resized;
};
struct Mu_AudioFormat {
uint32_t samples_per_second;
uint32_t channels;
uint32_t bytes_per_sample;
};
struct Mu_AudioBuffer {
int16_t *samples;
size_t samples_count;
Mu_AudioFormat format;
};
typedef void(*Mu_AudioCallback)(Mu_AudioBuffer *buffer);
struct Mu_Audio {
Mu_AudioFormat format;
Mu_AudioCallback callback;
};
struct Mu_Time {
uint64_t delta_ticks;
uint64_t delta_nanoseconds;
uint64_t delta_microseconds;
uint64_t delta_milliseconds;
float delta_seconds;
uint64_t ticks;
uint64_t nanoseconds;
uint64_t microseconds;
uint64_t milliseconds;
float seconds;
uint64_t initial_ticks;
uint64_t ticks_per_second;
};
typedef void *HANDLE;
typedef struct _XINPUT_STATE XINPUT_STATE;
typedef unsigned long (__stdcall *XINPUTGETSTATE)(unsigned long dwUserIndex, XINPUT_STATE* pState);
struct IAudioClient;
struct IAudioRenderClient;
struct Mu_Win32 {
HANDLE window;
HANDLE device_context;
void *main_fiber;
void *message_fiber;
XINPUTGETSTATE xinput_get_state;
IAudioClient *audio_client;
IAudioRenderClient *audio_render_client;
HANDLE wgl_context;
};
struct Mu {
Mu_Bool initialized;
Mu_Bool quit;
char *error;
char error_buffer[MU_MAX_ERROR];
Mu_Window window;
Mu_DigitalButton keys[MU_MAX_KEYS];
Mu_Gamepad gamepad;
Mu_Mouse mouse;
char text[MU_MAX_TEXT];
size_t text_length;
Mu_Time time;
Mu_Audio audio;
Mu_Win32 win32;
};
Mu_Bool Mu_Initialize(Mu *mu);
Mu_Bool Mu_Pull(Mu *mu);
void Mu_Push(Mu *mu);
struct Mu_Image {
uint8_t *pixels;
uint32_t channels;
uint32_t width;
uint32_t height;
};
Mu_Bool Mu_LoadImage(const char *filename, Mu_Image *image);
Mu_Bool Mu_LoadAudio(const char *filename, Mu_AudioBuffer *audio);
MU_EXTERN_END
@rotoglup
Copy link

FWIW, if I'm not mistaken, the stream mentionned in the title is https://www.youtube.com/watch?v=NG_mUhc8LRw

@uucidl
Copy link

uucidl commented Jul 10, 2017

Notes about this implementation:

  • the xaudio2 header is unnecessary and can be removed
  • the device_enumerator->Release() line before returning from Mu_Audio_Initialize is a double-release
  • the WM_INPUT processing means if there were more than one mouse event within one frame (such as two mouse move events) then we might lose some mouse press events. The solution is not to call Mu_UpdateDigitalButton to update the button states for every event, but instead at the edges of a frame. I.e. for any given state in the Mu struct, there should be one and only one Mu_UpdateDigitalButton to get correct pressed/released sampling

I found this out while doing my own reimplementation of the Mu api, at https://github.com/uucidl/pre.uumu

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment