Skip to content

Instantly share code, notes, and snippets.

@Wassimulator
Last active April 2, 2025 19:47
Show Gist options
  • Save Wassimulator/87d25dfd7ea4fb1df7f563e72168ae0f to your computer and use it in GitHub Desktop.
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)
/*
# 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;
}
#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