Last active
April 2, 2025 19:47
-
-
Save Wassimulator/87d25dfd7ea4fb1df7f563e72168ae0f to your computer and use it in GitHub Desktop.
Simple WASAPI example (copied over from a little game engine project, uses an external library for loading WAV files, not included, but the main concepts of using WASAPI are here)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
# example 1: 1 second of sound at 48000 Hz stereo 16 bit integer: | |
| ////////////////////////////// 1 Second = 48000 frames = 48k Hz ////////////////////////////////// | | |
| 48k * 4 bytes = 192k bytes per second = 1536 kbps bitrate | | |
| ///////////// FRAME ///////////// | ///////////// FRAME ///////////// | FRAME | FRAME | | | | | | |
| 4 bytes = 32 bits | 4 bytes = 32 bits | ... | ... | | | | | | |
| //// SAMPLE L//// | //// SAMPLE R//// | //// SAMPLE L//// | //// SAMPLE R//// | ... | ... | | | | | | |
| u16 = 2 bytes | u16 = 2 bytes | u16 = 2 bytes | u16 = 2 bytes | ... | ... | | | | | | |
# example 2: 1 second of sound at 44100 Hz mono 32 bit floats: | |
| ////////////////////////////// 1 Second = 44100 frames = 44.1k Hz //////////////////////////////// | | |
| 44.1k * 4 bytes = 176.4k bytes / second = 1411.2 kbps bitrate | | |
| ///////////// FRAME ///////////// | ///////////// FRAME ///////////// | FRAME | FRAME | | | | | | |
| 4 bytes = 32 bits | 4 bytes = 32 bits | ... | ... | | | | | | |
| ///////////// SAMPLE ///////////// | ///////////// SAMPLE ///////////// | ... | ... | | | | | | |
| f32 = 4 bytes | f32 = 4 bytes | ... | ... | | | | | | |
# example 3: 1 second of sound at 44100 Hz stereo 16 bit integer: | |
| ///////////////////////////// 1 Second = 44100 frames = 44.1k Hz ///////////////////////////////// | | |
| 44.1k * 4 bytes = 176.4k bytes / second = 1411.2 kbps bitrate | | |
| ///////////// FRAME ///////////// | ///////////// FRAME ///////////// | FRAME | FRAME | | | | | | |
| 4 bytes = 32 bits | 4 bytes = 32 bits | ... | ... | | | | | | |
| //// SAMPLE L//// | //// SAMPLE R//// | //// SAMPLE L//// | //// SAMPLE R//// | ... | ... | | | | | | |
| u16 = 2 bytes | u16 = 2 bytes | u16 = 2 bytes | u16 = 2 bytes | ... | ... | | | | | | |
*/ | |
// SimplyTone audio library | |
#define AUDIO_TRAIL_OFF_DISTANCE 100 | |
float ST_distance_gain(v3 source, v3 listener) { | |
f32 distance = (source - listener).length(); | |
float gain = 0; | |
if (distance <= AUDIO_TRAIL_OFF_DISTANCE) { | |
// Calculate the gain using the inverse square law | |
gain = 1.0 / (4.0 * M_PI * pow(distance, 2)) * 1000; | |
} | |
return clamp(gain, 0.0f, 1.0f); | |
} | |
void ST_audio_mix(ST_Context *ctx, void* buffer, u32 frame_count) { | |
u32 channel_count = ctx->wave_format.nChannels; | |
i16* buffer_data = (i16*)buffer; | |
f32 master_volume = logarithmic_scale(ctx->volume); | |
for(int f = 0; f < frame_count; f++) { | |
buffer_data[channel_count * f + 0] = 0; | |
buffer_data[channel_count * f + 1] = 0; | |
for (int c = 0; c < ctx->commands.count; c++) { | |
ST_Command *C = &ctx->commands[c]; | |
if (!C->play) continue; // it's paused, but don't purge it | |
if (C->channeled) { | |
ST_Channel *channel = &ctx->channels[C->channel_i]; | |
if (channel->occupied && channel->command_i != c) { | |
continue; | |
} else { | |
channel->occupied = true; | |
channel->command_i = c; | |
} | |
} | |
f32 command_volume = logarithmic_scale(C->volume); | |
if (C->distanced) | |
command_volume *= ST_distance_gain(C->source, ctx->listener); | |
if (C->frame_i >= C->file->frame_count || C->purge) { | |
C->purge = true; | |
if (C->channeled) { | |
ctx->channels[C->channel_i].occupied = false; | |
} | |
continue; | |
} | |
for (int channel_i = 0; channel_i < channel_count; channel_i++) { | |
i16 pcm = 0; | |
if (C->frame_i < C->file->frame_count) { | |
pcm = C->file->data[C->frame_i * channel_count + channel_i]; | |
} | |
buffer_data[channel_count * f + channel_i] += pcm * command_volume; | |
} | |
if (C->frame_i < C->file->frame_count) { | |
C->frame_i++; | |
} | |
if (C->frame_i >= C->file->frame_count && C->loop) { | |
C->frame_i = 0; // Reset frame_i to 0 for looping | |
} | |
} | |
// mixing master: | |
buffer_data[channel_count * f + 0] *= master_volume; | |
buffer_data[channel_count * f + 1] *= master_volume; | |
} | |
} | |
DWORD WINAPI ST_main_thread(LPVOID lpParam) { | |
ST_Context *ctx = (ST_Context*)lpParam; | |
bool first = true; | |
float t = 0.0f; | |
u32 sample_i = 0; | |
ctx->audio_client->Start(); | |
while (WaitForSingleObject(ctx->wait_event_handle, INFINITE) == WAIT_OBJECT_0) { | |
wait_loop: | |
for (int c = 0; c < ctx->commands.count; c++) | |
if (ctx->commands[c].frame_i < ctx->commands[c].file->frame_count && !ctx->commands[c].purge) | |
goto play_sound; | |
goto wait_loop; | |
play_sound: | |
u32 chunk_size = ctx->wave_format.nSamplesPerSec / 20; // 2400 frames | |
u32 padding = 0; | |
// do { | |
// win32_get_error(ctx->audio_client->GetCurrentPadding(&padding)); | |
// //Sleep(1); | |
// } while (padding != 0); | |
ctx->audio_client->GetCurrentPadding(&padding); | |
u32 frame_count = chunk_size - padding; | |
u8 *buffer = nullptr; | |
win32_get_error(ctx->render_client->GetBuffer(frame_count, &buffer)); | |
ST_audio_mix(ctx, buffer, frame_count); | |
t += 1.0f / f32(ctx->wave_format.nSamplesPerSec); | |
win32_get_error(ctx->render_client->ReleaseBuffer(frame_count, 0)); | |
} | |
return 0; | |
} | |
ST_File_Wav* ST_load_wav(ST_Context* ctx, char* path) { | |
drwav wav_data; | |
bool result = drwav_init_file(&wav_data, path, NULL); | |
if (result == false) { | |
printf("ST:ERROR: failed load audio file %s.\n", path); | |
return nullptr; | |
} | |
ST_File_Wav file; | |
assert(wav_data.channels == 2); | |
file.data = (i16 *)malloc(wav_data.totalPCMFrameCount * wav_data.channels * sizeof(i16)); | |
file.frame_count = drwav_read_pcm_frames_s16(&wav_data, wav_data.totalPCMFrameCount, (i16 *)file.data); | |
drwav_uninit(&wav_data); | |
ctx->source_files.push_back(file); | |
return &ctx->source_files.back(); | |
} | |
// if the `channel` param is > -1, the command become channeled and commmands queue rather than mix | |
ST_Command* ST_push_command(ST_Context* ctx, ST_File_Wav* file, bool loop, f32 volume, i32 channel = -1, ST_Channeled_Command_Type type = ST_interrupt) { | |
ST_Command* target = nullptr; | |
if (file == nullptr) { | |
assert(!"ST:ERROR: Audio command with null ptr file, check if file was loaded correctly!"); | |
return nullptr; | |
} | |
for (int i = 0; i < ctx->commands.count; i++) { | |
if (ctx->commands[i].purge) { | |
target = &ctx->commands[i]; | |
break; | |
} | |
} | |
if (target == nullptr) { | |
ST_Command command; | |
target = ctx->commands.push_back(command); | |
} | |
target->purge = false; | |
target->file = file; | |
target->frame_i = 0; | |
target->volume = volume; | |
target->loop = loop; | |
target->distanced = false; | |
target->channeled = false; | |
target->play = true; | |
if (channel > -1) { | |
assert(channel < ST_MAX_CHANNELS); | |
target->channeled = true; | |
target->channel_i = channel; | |
switch (type) { | |
case ST_elective: | |
if (ctx->channels[channel].occupied) | |
ctx->commands.pop_back(); | |
return nullptr; | |
case ST_interrupt: | |
if (ctx->channels[channel].occupied) { | |
ctx->commands[ctx->channels[channel].command_i].purge = true; | |
} | |
break; | |
case ST_queue: break; | |
} | |
} | |
return target; | |
} | |
ST_Context *ST_init() { | |
ST_Context *ctx = (ST_Context *)malloc(sizeof(ST_Context)); | |
memset(ctx, 0, sizeof(ST_Context)); | |
ctx->source_files.reserve(100); | |
ctx->source_files.one_timer = true; | |
ctx->commands.reserve(100); | |
ctx->commands.one_timer = true; | |
// Initialize COM | |
win32_get_error(CoInitializeEx(NULL, COINIT_MULTITHREADED)); | |
// Create the IMMDeviceenumerator object | |
IMMDeviceEnumerator* enumerator = NULL; | |
win32_get_error(CoCreateInstance( | |
__uuidof(MMDeviceEnumerator), NULL, | |
CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), | |
(void**)&enumerator)); | |
// Get the default audio endpoint | |
IMMDevice* device = NULL; | |
win32_get_error(enumerator->GetDefaultAudioEndpoint( | |
eRender, eMultimedia, &device)); | |
// Activate the IAudioClient interface | |
ctx->audio_client = NULL; | |
win32_get_error(device->Activate( | |
__uuidof(IAudioClient), CLSCTX_ALL, | |
NULL, (void**)&ctx->audio_client)); | |
WAVEFORMATEX *wave_format = &ctx->wave_format; | |
wave_format->wFormatTag = WAVE_FORMAT_PCM; | |
wave_format->nChannels = 2; | |
wave_format->nSamplesPerSec = 48000 ;// samples PER CHANNEL per sec | |
wave_format->nAvgBytesPerSec = 48000 * 2 * 2; // nSamplesPerSec * nChannels * nBlockAlign | |
wave_format->nBlockAlign = 2 * 2; // nChannels * (nBitsPerSample / 8), the number of bytes occupied by one sample frame across all channels | |
wave_format->wBitsPerSample = 16; | |
wave_format->cbSize = 0; | |
printf("wFormatTag = %i\nnChannels = %i\nnSamplesPerSec = %i\nnAvgBytesPerSec = %i\nnBlockAlign = %i\nwBitsPerSample = %i\ncbSize = %i\n", | |
wave_format->wFormatTag, | |
wave_format->nChannels, | |
wave_format->nSamplesPerSec, | |
wave_format->nAvgBytesPerSec, | |
wave_format->nBlockAlign, | |
wave_format->wBitsPerSample, | |
wave_format->cbSize); | |
DWORD flags = | |
AUDCLNT_STREAMFLAGS_RATEADJUST | | |
AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | | |
AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY | | |
AUDCLNT_STREAMFLAGS_EVENTCALLBACK; | |
// Initialize the audio stream | |
win32_get_error(ctx->audio_client->Initialize( | |
AUDCLNT_SHAREMODE_SHARED, | |
flags, | |
10000000, | |
0, | |
wave_format, | |
NULL)); | |
ctx->wait_event_handle = CreateEventW(0, 0, 0, 0); | |
win32_get_error(ctx->audio_client->SetEventHandle(ctx->wait_event_handle)); | |
// Get the audio buffer size | |
win32_get_error(ctx->audio_client->GetBufferSize(&ctx->buffer_frame_count)); | |
printf("buffer_frame_count = %i\n", ctx->buffer_frame_count); | |
// Get the audio rendering endpoint buffer | |
ctx->render_client = NULL; | |
win32_get_error(ctx->audio_client->GetService( | |
__uuidof(IAudioRenderClient), | |
(void**)&ctx->render_client)); | |
ctx->volume = 1; | |
CreateThread(NULL, 0, ST_main_thread, (LPVOID)ctx, 0, NULL); | |
return ctx; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <mmdeviceapi.h> | |
#include <audioclient.h> | |
#define DR_WAV_IMPLEMENTATION | |
#include <dr_wav.h> | |
#define ST_MAX_CHANNELS 10 | |
struct ST_File_Wav { | |
size_t frame_count; | |
i16 *data; | |
}; | |
enum ST_Channeled_Command_Type { | |
ST_interrupt, | |
ST_queue, | |
ST_elective, // if channel is not occpied, plays, otherwise discarded. | |
}; | |
struct ST_Command { | |
ST_File_Wav* file; | |
u32 frame_i; | |
u32 channel_i; | |
f32 volume; | |
v3 source; | |
bool distanced; | |
bool loop; | |
bool purge; | |
bool channeled; | |
bool play; | |
}; | |
struct ST_Channel { | |
bool occupied; | |
i32 command_i; | |
}; | |
struct ST_Context { | |
IAudioClient2* audio_client; | |
IAudioRenderClient* render_client; | |
WAVEFORMATEX wave_format; | |
u32 buffer_frame_count; | |
f32 volume; | |
v3 listener; | |
HANDLE wait_event_handle; | |
Dynarray<ST_File_Wav> source_files; | |
Dynarray<ST_Command> commands; | |
ST_Channel channels[ST_MAX_CHANNELS]; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment