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, ®ion1, ®ion1_size, ®ion2, ®ion2_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, ®ion1, ®ion1_size, ®ion2, ®ion2_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