wintoys

wintoys.git
git clone git://git.lenczewski.org/wintoys.git
Log | Files | Refs | README

wasapidemo.c (17762B)


      1 #include "wasapidemo.h"
      2 
      3 global_variable b32 win32_running;
      4 global_variable b32 win32_granular_sleep;
      5 global_variable u64 win32_perf_frequency;
      6 
      7 global_variable struct win32_dsound_buffer win32_dsound_buffer;
      8 global_variable struct win32_wasapi_buffer win32_wasapi_buffer;
      9 
     10 global_variable struct wasapidemo_mixer win32_mixer;
     11 
     12 int WINAPI
     13 WinMain(HINSTANCE instance, HINSTANCE prev_instance, LPSTR cmdline, int cmdshow)
     14 {
     15 	(void) prev_instance;
     16 	(void) cmdline;
     17 	(void) cmdshow;
     18 
     19 	LARGE_INTEGER perf_frequency_result;
     20 	QueryPerformanceFrequency(&perf_frequency_result);
     21 	win32_perf_frequency = perf_frequency_result.QuadPart;
     22 
     23 	const UINT scheduler_granularity_ms = 1;
     24 	win32_granular_sleep = timeBeginPeriod(scheduler_granularity_ms) == TIMERR_NOERROR;
     25 
     26 	// NOTE: set high priority for thread to reduce audio glitches :/
     27 	DWORD task_index = 0;
     28 	AvSetMmThreadCharacteristics("Pro Audio", &task_index);
     29 	(void) task_index;
     30 
     31 	const char window_class_name[] = "wasapidemo_window";
     32 
     33 	WNDCLASSEXA window_class = {
     34 		.cbSize = sizeof(WNDCLASSEX),
     35 		.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW,
     36 		.lpfnWndProc = win32_window_proc,
     37 		.hInstance = instance,
     38 		.lpszClassName = window_class_name,
     39 	};
     40 
     41 	RegisterClassExA(&window_class);
     42 
     43 	HWND window = CreateWindowExA(
     44 			0,
     45 			window_class_name,
     46 			"wasapidemo",
     47 			WS_OVERLAPPEDWINDOW | WS_VISIBLE,
     48 			CW_USEDEFAULT,
     49 			CW_USEDEFAULT,
     50 			CW_USEDEFAULT,
     51 			CW_USEDEFAULT,
     52 			0,
     53 			0,
     54 			instance,
     55 			0);
     56 
     57 	if (!window) {
     58 		fprintf(stderr, "Failed to create window\n");
     59 		exit(EXIT_FAILURE);
     60 	}
     61 
     62 	u32 target_render_rate = 60;
     63 
     64 	{
     65 		HDC dc = GetDC(window);
     66 		int actual_refresh_rate = GetDeviceCaps(dc, VREFRESH);
     67 		if (actual_refresh_rate > 1)
     68 			target_render_rate = actual_refresh_rate;
     69 		ReleaseDC(window, dc);
     70 	}
     71 
     72 	u64 target_us_per_frame = (u64) (USECS / (f32) target_render_rate);
     73 
     74 	win32_mixer = (struct wasapidemo_mixer) {
     75 		.tone_hz = WASAPIDEMO_MIXER_FUNDAMENTAL,
     76 		.tone_volume = 4000,
     77 	};
     78 
     79 #if 1
     80 	// TODO: investigate WAVEFORMATEXTENSIBLE instead for higher bitrate sound
     81 	WAVEFORMATEX sound_format = {
     82 		.wFormatTag = WAVE_FORMAT_PCM,
     83 		.nChannels = SOUND_CHANNELS,
     84 		.nSamplesPerSec = SOUND_SAMPLE_RATE,
     85 		.wBitsPerSample = SOUND_SAMPLE_BITS,
     86 		.nBlockAlign = SOUND_FRAME_BYTES,
     87 		.nAvgBytesPerSec = SOUND_SAMPLE_RATE * SOUND_FRAME_BYTES,
     88 	};
     89 #else
     90 	WAVEFORMATEXTENSIBLE sound_format = {
     91 		.Format = {
     92 			.wFormatTag = WAVE_FORMAT_EXTENSIBLE,
     93 			.cbSize = sizeof(WAVEFORMATEX),
     94 			.nChannels = SOUND_CHANNELS,
     95 			.nSamplesPerSec = SOUND_SAMPLE_RATE,
     96 			.wBitsPerSample = SOUND_SAMPLE_BITES,
     97 			.nBlockAlign = SOUND_FRAME_BYTES,
     98 			.nAvgBytesPerSec = SOUND_SAMPLE_RATE * SOUND_FRAME_BYTES,
     99 		},
    100 		.Samples.wValidBitsPerSample = sample_bits,
    101 		.SubFormat = KSDATAFORMAT_SUBTYPE_PCM,
    102 	};
    103 #endif
    104 
    105 	u32 sound_buffer_size = ((WAVEFORMATEX *) &sound_format)->nAvgBytesPerSec;
    106 
    107 #if USE_WASAPI
    108 	// u32 frame_sound_buffer_ms = 3 * ((f32) MSECS / target_render_rate);
    109 	u32 buffer_frames = ((WAVEFORMATEX *) &sound_format)->nSamplesPerSec / (f32) target_render_rate;
    110 
    111 	win32_init_wasapi((WAVEFORMATEX *) &sound_format, buffer_frames);
    112 #else
    113 	win32_init_dsound(window, (WAVEFORMATEX *) &sound_format, sound_buffer_size);
    114 #endif
    115 	init_sound(&win32_mixer);
    116 
    117 	s16 *sound_buffer = VirtualAlloc(NULL, sound_buffer_size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
    118 
    119 	b32 sound_is_valid = false;
    120 
    121 	u64 frame_flip_micros = win32_perf_micros();
    122 
    123 	win32_running = true;
    124 
    125 #if 0
    126 	win32_granular_sleep = false;
    127 #endif
    128 
    129 	f32 dt = 1.0f / target_render_rate;
    130 	while (win32_running) {
    131 		u64 start_perf_micros = win32_perf_micros();
    132 		u64 start_perf_cycles = __rdtsc();
    133 
    134 		win32_process_pending_messages();
    135 
    136 		u64 calc_audio_micros = win32_perf_micros();
    137 
    138 #if USE_WASAPI
    139 		UINT32 queued_frames;
    140 		if (!IAudioClient_GetCurrentPadding(win32_wasapi_buffer.audio_client, &queued_frames)) {
    141 			u32 required_frames = win32_wasapi_buffer.buffer_frame_count - queued_frames;
    142 
    143 			char buf[256];
    144 			snprintf(buf, ARRLEN(buf), "WASAPI: total buffered frames: %" PRIu32 ", %" PRIu32 " queued frames, %" PRIu32 " new frames to generate\n", win32_wasapi_buffer.buffer_frame_count, queued_frames, required_frames);
    145 			OutputDebugStringA(buf);
    146 
    147 			struct wasapidemo_audio_buffer audio_buffer = {
    148 				.buffer = sound_buffer,
    149 				.expected_samples = required_frames, 
    150 				.sample_rate = SOUND_SAMPLE_RATE,
    151 				.channels = SOUND_CHANNELS,
    152 			};
    153 
    154 			render_sound(&audio_buffer, &win32_mixer, dt);
    155 
    156 			win32_fill_wasapi_buffer(required_frames, &audio_buffer);
    157 		}
    158 #else
    159 		DWORD play_cursor, write_cursor;
    160 		if (SUCCEEDED(IDirectSoundBuffer_GetCurrentPosition(win32_dsound_buffer.buffer, &play_cursor, &write_cursor))) {
    161 			if (!sound_is_valid) {
    162 				win32_dsound_buffer.buffer_cursor = write_cursor;
    163 				sound_is_valid = true;
    164 			}
    165 
    166 			u32 target_sample_bytes_per_frame = (win32_dsound_buffer.sample_rate * win32_dsound_buffer.block_size) / target_render_rate;
    167 			u32 frame_flip_latency_safety_margin_bytes = target_sample_bytes_per_frame / 2.0f;
    168 
    169 			u64 audio_micros = calc_audio_micros - frame_flip_micros;
    170 			f32 frame_time_remaining = (f32) (target_us_per_frame - audio_micros) / (f32) target_us_per_frame;
    171 			DWORD expected_bytes_until_next_frame_flip = (DWORD) (frame_time_remaining * target_sample_bytes_per_frame);
    172 			DWORD next_frame_flip_cursor = play_cursor + expected_bytes_until_next_frame_flip;
    173 
    174 			DWORD unwrapped_write_cursor = write_cursor;
    175 			if (unwrapped_write_cursor < play_cursor)
    176 				unwrapped_write_cursor += win32_dsound_buffer.buffer_size;
    177 			ASSERT(play_cursor <= unwrapped_write_cursor);
    178 			unwrapped_write_cursor += frame_flip_latency_safety_margin_bytes;
    179 
    180 			b32 audio_card_is_low_latency = unwrapped_write_cursor < next_frame_flip_cursor;
    181 
    182 			DWORD target_cursor;
    183 			if (audio_card_is_low_latency) {
    184 				target_cursor = next_frame_flip_cursor + target_sample_bytes_per_frame;
    185 			} else {
    186 				target_cursor = unwrapped_write_cursor + target_sample_bytes_per_frame + frame_flip_latency_safety_margin_bytes;
    187 			}
    188 			target_cursor %= win32_dsound_buffer.buffer_size;
    189 
    190 			DWORD expected_bytes = 0;
    191 			if (win32_dsound_buffer.buffer_cursor > target_cursor) {
    192 				expected_bytes = (win32_dsound_buffer.buffer_size - win32_dsound_buffer.buffer_cursor) + target_cursor;
    193 			} else /* win32_dsound_buffer.buffer_cursor <= target_cursor */ {
    194 				expected_bytes = target_cursor - win32_dsound_buffer.buffer_cursor;
    195 			}	
    196 
    197 			struct wasapidemo_audio_buffer audio_buffer = {
    198 				.buffer = sound_buffer,
    199 				.expected_samples = expected_bytes / win32_dsound_buffer.block_size,
    200 				.sample_rate = win32_dsound_buffer.sample_rate,
    201 				.channels = win32_dsound_buffer.channels,
    202 			};
    203 
    204 			render_sound(&audio_buffer, &win32_mixer, dt);
    205 
    206 			win32_fill_dsound_buffer(win32_dsound_buffer.buffer_cursor, expected_bytes, &audio_buffer);
    207 		} else {
    208 			sound_is_valid = false;	
    209 		}
    210 #endif
    211 
    212 		frame_flip_micros = win32_perf_micros();
    213 
    214 		u64 elapsed_cycles = __rdtsc() - start_perf_cycles;
    215 		u64 elapsed_us = win32_perf_micros() - start_perf_micros;
    216 
    217 #if 1
    218 		if (elapsed_us < target_us_per_frame) {
    219 			if (win32_granular_sleep) {
    220 				DWORD sleep_ms = (DWORD) ((f32) (target_us_per_frame - elapsed_us) / MSECS);
    221 				if (sleep_ms) Sleep(sleep_ms);
    222 				elapsed_us = win32_perf_micros() - start_perf_micros;
    223 			} else {
    224 				while (elapsed_us < target_us_per_frame)
    225 					elapsed_us = win32_perf_micros() - start_perf_micros;
    226 			}
    227 		} else if (elapsed_us > target_us_per_frame) {
    228 			// TODO: missed frame, log me!
    229 		}
    230 
    231 #endif
    232 		dt = (f32) elapsed_us / USECS;
    233 
    234 		char buf[256];
    235 		snprintf(buf, ARRLEN(buf), "Timings: %" PRIu64 " us / %" PRIu64 " Cy\n", elapsed_us, elapsed_cycles);
    236 		OutputDebugStringA(buf);
    237 	}
    238 
    239 	exit(EXIT_SUCCESS);
    240 }
    241 
    242 internal u64
    243 win32_perf_micros(void)
    244 {	
    245 	LARGE_INTEGER result;
    246 	QueryPerformanceCounter(&result);
    247 
    248 	return ((u64) result.QuadPart * 1000 * 1000) / win32_perf_frequency;
    249 }
    250 
    251 internal void
    252 win32_process_pending_messages(void)
    253 {
    254 	MSG msg;
    255 	while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) {
    256 		switch (msg.message) {
    257 		case WM_QUIT: {
    258 			win32_running = false;
    259 
    260 #if USE_WASAPI
    261 	win32_wasapi_stop();
    262 #else
    263 	win32_dsound_stop();
    264 #endif
    265 		} break;
    266 
    267 		case WM_SYSKEYDOWN:
    268 		case WM_SYSKEYUP:
    269 		case WM_KEYDOWN:
    270 		case WM_KEYUP: {
    271 			u32 vkeycode = (u32) msg.wParam;
    272 			b32 was_down = (msg.lParam & (1 << 30));
    273 			b32 is_down = (msg.lParam & (1 << 31)) == 0;
    274 
    275 			win32_mixer.tone_hz = WASAPIDEMO_MIXER_FUNDAMENTAL;
    276 
    277 			if (is_down != was_down) {
    278 				switch (vkeycode) {
    279 				case 'L':
    280 					if (is_down) win32_mixer.tone_hz = WASAPIDEMO_MIXER_FUNDAMENTAL / 2;
    281 					break;
    282 
    283 				case 'H':
    284 					if (is_down) win32_mixer.tone_hz = 2 * WASAPIDEMO_MIXER_FUNDAMENTAL;
    285 					break;
    286 				}
    287 			}
    288 		} break;
    289 
    290 		default: {
    291 			TranslateMessage(&msg);
    292 			DispatchMessageA(&msg);
    293 		} break;
    294 		}
    295 	}
    296 }
    297 
    298 LRESULT CALLBACK
    299 win32_window_proc(HWND window, UINT msg, WPARAM wparam, LPARAM lparam)
    300 {
    301 	LRESULT res = 0;
    302 
    303 	switch (msg) {
    304 	case WM_DESTROY:
    305 	case WM_CLOSE: {
    306 		win32_running = false;
    307 
    308 #if USE_WASAPI
    309 	win32_wasapi_stop();
    310 #else
    311 	win32_dsound_stop();
    312 #endif
    313 	} break;
    314 
    315 	default: {
    316 		res = DefWindowProc(window, msg, wparam, lparam);
    317 	} break;
    318 	}
    319 
    320 	return res;
    321 }
    322 
    323 #define DIRECT_SOUND_CREATE(name) HRESULT WINAPI name(LPCGUID pcGuidDevice, LPDIRECTSOUND *ppDS, LPUNKNOWN pUnkOuter)
    324 typedef DIRECT_SOUND_CREATE(direct_sound_create_t);
    325 
    326 internal void
    327 win32_init_dsound(HWND window, WAVEFORMATEX *sound_format, u32 buffer_size)
    328 {
    329 	HMODULE lib = LoadLibraryA("dsound.dll");
    330 	if (!lib) {
    331 		fprintf(stderr, "Failed to load DirectSound!\n");
    332 		return;
    333 	}
    334 
    335 	direct_sound_create_t *direct_sound_create_fn = (direct_sound_create_t *) GetProcAddress(lib, "DirectSoundCreate");
    336 	if (!direct_sound_create_fn) {
    337 		fprintf(stderr, "Failed to get handle to DirectSoundCreate!\n");
    338 		return;
    339 	}
    340 
    341 	LPDIRECTSOUND direct_sound;
    342 	if (!SUCCEEDED(direct_sound_create_fn(0, &direct_sound, 0))) {
    343 		fprintf(stderr, "Failed to create DirectSound instance!\n");
    344 		return;
    345 	}
    346 
    347 	if (!SUCCEEDED(IDirectSound_SetCooperativeLevel(direct_sound, window, DSSCL_PRIORITY))) {
    348 		fprintf(stderr, "Failed to set DirectSound cooperative level!\n");
    349 		return;
    350 	}
    351 
    352 	win32_dsound_buffer.buffer_size = buffer_size;
    353 	win32_dsound_buffer.buffer_cursor = 0;
    354 	win32_dsound_buffer.sample_rate = sound_format->nSamplesPerSec;
    355 	win32_dsound_buffer.sample_bits = sound_format->wBitsPerSample;
    356 	win32_dsound_buffer.channels = sound_format->nChannels;
    357 	win32_dsound_buffer.block_size = sound_format->nBlockAlign;
    358 
    359 	DSBUFFERDESC primary_buffer_desc = {
    360 		.dwSize = sizeof(DSBUFFERDESC),
    361 		.dwFlags = DSBCAPS_PRIMARYBUFFER,
    362 	};
    363 
    364 	LPDIRECTSOUNDBUFFER primary_buffer;
    365 	if (!SUCCEEDED(IDirectSound_CreateSoundBuffer(direct_sound, &primary_buffer_desc, &primary_buffer, 0))) {
    366 		fprintf(stderr, "Failed to create DirectSound primary buffer!\n");
    367 		return;
    368 	}
    369 
    370 	if (!SUCCEEDED(IDirectSoundBuffer_SetFormat(primary_buffer, sound_format))) {
    371 		fprintf(stderr, "Failed to set format for DirectSound primary buffer!\n");
    372 		return;
    373 	}
    374 
    375 	DSBUFFERDESC secondary_buffer_desc = {
    376 		.dwSize = sizeof(DSBUFFERDESC),
    377 		.dwBufferBytes = win32_dsound_buffer.buffer_size,
    378 		.lpwfxFormat = sound_format,
    379 	};
    380 
    381 	if (!SUCCEEDED(IDirectSound_CreateSoundBuffer(direct_sound, &secondary_buffer_desc, &win32_dsound_buffer.buffer, 0))) {
    382 		fprintf(stderr, "Failed to create DirectSound secondary buffer!\n");
    383 		return;
    384 	}
    385 
    386 	win32_clear_dsound_buffer();
    387 
    388 	IDirectSoundBuffer_Play(win32_dsound_buffer.buffer, 0, 0, DSBPLAY_LOOPING);
    389 }
    390 
    391 internal void
    392 win32_clear_dsound_buffer(void)
    393 {
    394 	void *region1, *region2;
    395 	DWORD region1_size, region2_size;
    396 	if (SUCCEEDED(IDirectSoundBuffer_Lock(win32_dsound_buffer.buffer, 0, win32_dsound_buffer.buffer_size, &region1, &region1_size, &region2, &region2_size, 0))) {
    397 		u8 *dst;
    398 
    399 		dst = region1;
    400 		for (u32 i = 0; i < region1_size; i++)
    401 			*dst++ = 0;
    402 
    403 		dst = region2;
    404 		for (u8 i = 0; i < region2_size; i++)
    405 			*dst++ = 0;
    406 
    407 		IDirectSoundBuffer_Unlock(win32_dsound_buffer.buffer, region1, region1_size, region2, region2_size);
    408 	}
    409 }
    410 
    411 internal void
    412 win32_fill_dsound_buffer(DWORD ptr, DWORD size, struct wasapidemo_audio_buffer const *source)
    413 {
    414 	ASSERT(win32_dsound_buffer.channels == source->channels);
    415 
    416 	void *region1, *region2;
    417 	DWORD region1_size, region2_size;
    418 	if (SUCCEEDED(IDirectSoundBuffer_Lock(win32_dsound_buffer.buffer, ptr, size, &region1, &region1_size, &region2, &region2_size, 0))) {
    419 		s16 *dst, *src = source->buffer;
    420 
    421 		DWORD region1_samples = region1_size / win32_dsound_buffer.block_size;
    422 		dst = region1;
    423 		for (DWORD i = 0; i < region1_samples; i++) {
    424 			for (u32 j = 0; j < win32_dsound_buffer.channels; j++)
    425 				*dst++ = *src++;
    426 		}
    427 
    428 		DWORD region2_samples = region2_size / win32_dsound_buffer.block_size;
    429 		dst = region2;
    430 		for (DWORD i = 0; i < region2_samples; i++) {
    431 			for (u32 j = 0; j < win32_dsound_buffer.channels; j++)
    432 				*dst++ = *src++;
    433 		}
    434 
    435 		win32_dsound_buffer.buffer_cursor += region1_size + region2_size;
    436 		win32_dsound_buffer.buffer_cursor %= win32_dsound_buffer.buffer_size;
    437 
    438 		IDirectSoundBuffer_Unlock(win32_dsound_buffer.buffer, region1, region1_size, region2, region2_size);
    439 	}
    440 }
    441 
    442 internal void
    443 win32_dsound_stop(void)
    444 {
    445 	IDirectSoundBuffer_Stop(win32_dsound_buffer.buffer);
    446 }
    447 
    448 internal void
    449 win32_init_wasapi(WAVEFORMATEX *sound_format, u32 buffer_frames)
    450 {
    451 	HRESULT hr;
    452 
    453 	CoInitialize(0);
    454 
    455 	IMMDeviceEnumerator *device_enumerator;
    456 	if (CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator, (LPVOID *) &device_enumerator)) {
    457 		fprintf(stderr, "Failed to create IMMDeviceEnumerator instance.\n");
    458 		return;
    459 	}
    460 
    461 	IMMDevice *device;
    462 	if (IMMDeviceEnumerator_GetDefaultAudioEndpoint(device_enumerator, eRender, eMultimedia, &device)) {
    463 		fprintf(stderr, "Failed to get default render audio endpoint.\n");
    464 		IMMDeviceEnumerator_Release(device_enumerator);
    465 		return;
    466 	}
    467 
    468 	IMMDeviceEnumerator_Release(device_enumerator);
    469 
    470 	IAudioClient *audio_client;
    471 	if (IMMDevice_Activate(device, &IID_IAudioClient, CLSCTX_ALL, NULL, (LPVOID *) &audio_client)) {
    472 		fprintf(stderr, "Failed to activate default render audio endpoint.\n");
    473 		goto device_error;
    474 	}
    475 
    476 	if (IAudioClient_QueryInterface(audio_client, &IID_IAudioClient3, (LPVOID *) &win32_wasapi_buffer.audio_client)) {
    477 		fprintf(stderr, "Failed to activate audio client 3 interface.\n");
    478 		IAudioClient_Release(audio_client);
    479 		goto device_error;
    480 	}
    481 
    482 	IAudioClient_Release(audio_client);
    483 
    484 	AudioClientProperties props = {
    485 		.cbSize = sizeof props,
    486 		.eCategory = AudioCategory_Media,
    487 		.Options = AUDCLNT_STREAMOPTIONS_MATCH_FORMAT,
    488 	};
    489 
    490 	IAudioClient2_SetClientProperties(win32_wasapi_buffer.audio_client, &props);
    491 
    492 	if (IAudioClient_IsFormatSupported(win32_wasapi_buffer.audio_client, AUDCLNT_SHAREMODE_EXCLUSIVE, sound_format, NULL)) {
    493 		fprintf(stderr, "Desired format not supported.\n");
    494 		goto audio_client_error;
    495 	}
    496 
    497 	UINT32 default_period, fundamental_period, min_period, max_period;
    498 	if (IAudioClient3_GetSharedModeEnginePeriod(win32_wasapi_buffer.audio_client, sound_format, &default_period, &fundamental_period, &min_period, &max_period)) {
    499 		fprintf(stderr, "Cannot fetch shared engine periods.\n");
    500 		goto audio_client_error;
    501 	}
    502 
    503 	UINT32 period_in_frames = CLAMP(buffer_frames, min_period, max_period);
    504 	if (IAudioClient3_InitializeSharedAudioStream(win32_wasapi_buffer.audio_client, 0, period_in_frames, sound_format, 0)) {
    505 		fprintf(stderr, "Failed to initialise shared audio stream.\n");
    506 		goto audio_client_error;
    507 	}
    508 
    509 	IAudioClient_GetBufferSize(win32_wasapi_buffer.audio_client, &win32_wasapi_buffer.buffer_frame_count);
    510 
    511 	if (IAudioClient_GetService(win32_wasapi_buffer.audio_client, &IID_IAudioRenderClient, (LPVOID *) &win32_wasapi_buffer.render_client)) {
    512 		fprintf(stderr, "Failed to get audio render client.\n");
    513 		goto audio_client_error;
    514 	}
    515 
    516 	BYTE *buffer;
    517 	if (IAudioRenderClient_GetBuffer(win32_wasapi_buffer.render_client, win32_wasapi_buffer.buffer_frame_count, &buffer)) {
    518 		fprintf(stderr, "Failed to get audio render client buffer.\n");
    519 		goto audio_render_client_error;
    520 	}
    521 
    522 	if (IAudioRenderClient_ReleaseBuffer(win32_wasapi_buffer.render_client, win32_wasapi_buffer.buffer_frame_count, AUDCLNT_BUFFERFLAGS_SILENT)) {
    523 		fprintf(stderr, "Failed to release audio render client buffer.\n");
    524 		goto audio_render_client_error;
    525 	}
    526 
    527 	if (IAudioClient_Start(win32_wasapi_buffer.audio_client)) {
    528 		fprintf(stderr, "Failed to start audio client playback.\n");
    529 		goto audio_render_client_error;
    530 	}
    531 
    532 	goto end;
    533 
    534 audio_render_client_error:
    535 	IAudioRenderClient_Release(win32_wasapi_buffer.render_client);
    536 
    537 audio_client_error:
    538 	IAudioClient_Release(win32_wasapi_buffer.audio_client);
    539 
    540 device_error:
    541 end:
    542 	IMMDevice_Release(device);
    543 
    544 	CoUninitialize();
    545 }
    546 
    547 internal void
    548 win32_fill_wasapi_buffer(UINT32 frames, struct wasapidemo_audio_buffer const *source)
    549 {
    550 	BYTE *data;
    551 	if (!IAudioRenderClient_GetBuffer(win32_wasapi_buffer.render_client, frames, &data)) {
    552 		s16 *dst = (s16 *) data, *src = source->buffer;
    553 		for (u32 i = 0; i < frames; i++) {
    554 			for (u32 j = 0; j < source->channels; j++)
    555 				*dst++ = *src++;
    556 		}
    557 
    558 		IAudioRenderClient_ReleaseBuffer(win32_wasapi_buffer.render_client, frames, 0);
    559 	}
    560 }
    561 
    562 internal void
    563 win32_wasapi_stop(void)
    564 {
    565 	IAudioClient_Stop(win32_wasapi_buffer.audio_client);
    566 }
    567 
    568 #define _USE_MATH_DEFINES
    569 #include <math.h>
    570 
    571 void
    572 init_sound(struct wasapidemo_mixer *mixer)
    573 {
    574 	(void) mixer;
    575 }
    576 
    577 void
    578 render_sound(struct wasapidemo_audio_buffer *buffer, struct wasapidemo_mixer *mixer, f32 dt)
    579 {
    580 	(void) dt;
    581 
    582 	const u32 wave_period = buffer->sample_rate / mixer->tone_hz;
    583 
    584 	s16 *sample = buffer->buffer;
    585 	for (u32 i = 0; i < buffer->expected_samples; i++) {
    586 		f32 v = sinf(mixer->tone_t);
    587 
    588 		s16 sample_value = (s16) (v * mixer->tone_volume);
    589 		for (u32 j = 0; j < buffer->channels; j++)
    590 			*sample++ = sample_value;
    591 
    592 		const f32 PI = M_PI;
    593 		mixer->tone_t += 2 * PI / wave_period;
    594 		if (mixer->tone_t >= 2 * PI)
    595 			mixer->tone_t -= 2 * PI;
    596 	}
    597 }
    598