Skip to content

Instantly share code, notes, and snippets.

@nickav
Last active July 28, 2025 20:17
Show Gist options
  • Select an option

  • Save nickav/f91ca22d4d8f10547ff4fbfecec7fcd5 to your computer and use it in GitHub Desktop.

Select an option

Save nickav/f91ca22d4d8f10547ff4fbfecec7fcd5 to your computer and use it in GitHub Desktop.
Audio Playback on MacOS with AudioUnit Backend
//
// Build with:
// > clang coreaudio_backend.m -o main -framework AudioUnit
//
//
// Base Types
//
#include <stdint.h>
typedef int8_t i8;
typedef int16_t i16;
typedef int32_t i32;
typedef int64_t i64;
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
typedef i8 b8;
typedef i16 b16;
typedef i32 b32;
typedef i64 b64;
typedef float f32;
typedef double f64;
//
// Shared Audio Header
//
typedef struct Audio_Context Audio_Context;
#define AUDIO_CALLBACK(name) void name(Audio_Context *ctx, void *samples, u32 sample_count, void *user)
typedef AUDIO_CALLBACK(Audio_Callback);
typedef struct Audio_Context Audio_Context;
struct Audio_Context
{
// Input
u32 num_channels;
u32 samples_per_second;
Audio_Callback *callback;
void *user_data;
// Output
u32 bits_per_channel;
u32 output_sample_position;
u32 latency_samples;
};
//
// Audio Backend: CoreAudio
//
// NOTE(nick): requires linking against -framework AudioUnit
#include <AudioUnit/AudioUnit.h>
typedef struct CoreAudio_Context CoreAudio_Context;
struct CoreAudio_Context
{
AudioUnit unit;
Audio_Context ctx;
b32 initted;
b32 running;
};
static CoreAudio_Context g_audio = {0};
OSStatus macos_audio_callback(
void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData)
{
if (!g_audio.running)
{
printf("[CoreAudio] Running...\n");
g_audio.running = true;
}
SInt16 *output = (SInt16 *)ioData->mBuffers[0].mData;
Audio_Context *ctx = &g_audio.ctx;
assert(ctx->callback);
if (ctx->callback)
{
ctx->callback(ctx, output, inNumberFrames, ctx->user_data);
}
return noErr;
}
bool audio_init(Audio_Context *ctx)
{
// @Robustness: prevent this function from being called twice??
memcpy(&g_audio.ctx, ctx, sizeof(Audio_Context));
assert(ctx->samples_per_second == 44100);
assert(ctx->bits_per_channel == 16);
assert(ctx->num_channels == 2);
ctx->output_sample_position = 0;
ctx->latency_samples = 0;
//
// NOTE(nick): kick this off on a thread because it's slow otherwise
// Perhaps specifically when using my Bluetooth headphones...
//
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
OSErr err;
AudioComponentDescription acd = {
.componentType = kAudioUnitType_Output,
.componentSubType = kAudioUnitSubType_DefaultOutput,
.componentManufacturer = kAudioUnitManufacturer_Apple,
};
AudioComponent output = AudioComponentFindNext(NULL, &acd);
if (!output) printf("[CoreAudio] Can't find default output\n");
err = AudioComponentInstanceNew(output, &g_audio.unit);
if (err) printf("[CoreAudio] Error creating audio unit: %d\n", err);
AURenderCallbackStruct input = { .inputProc = macos_audio_callback };
err = AudioUnitSetProperty(g_audio.unit, kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input, 0, &input, sizeof(input));
if (err) printf("[CoreAudio] Error setting callback: %d\n", err);
AudioStreamBasicDescription asbd = {
.mFormatID = kAudioFormatLinearPCM,
.mFormatFlags = 0
| kAudioFormatFlagIsSignedInteger
| kAudioFormatFlagIsPacked,
.mSampleRate = (double)ctx->samples_per_second,
.mBitsPerChannel = 16,
.mChannelsPerFrame = 2,
.mFramesPerPacket = 1,
.mBytesPerFrame = 4,
.mBytesPerPacket = 4,
};
err = AudioUnitSetProperty(g_audio.unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &asbd, sizeof(asbd));
if (err) printf("[CoreAudio] Error setting stream format: %d\n", err);
err = AudioUnitInitialize(g_audio.unit);
if (err) printf("[CoreAudio] Error initializing unit: %d\n", err);
err = AudioOutputUnitStart(g_audio.unit);
if (err) printf("[CoreAudio] Error starting unit: %d\n", err);
g_audio.initted = true;
printf("[CoreAudio] CoreAudio initted\n");
});
return true;
}
void audio_free()
{
if (g_audio.unit)
{
AudioOutputUnitStop(g_audio.unit);
AudioUnitUninitialize(g_audio.unit);
AudioComponentInstanceDispose(g_audio.unit);
g_audio.unit = 0;
}
}
//
// NOTE(nick): demo code
//
#include <math.h>
#define TAU 6.28318530717958647692f
void audio_output_sine_wave_i16(u32 samples_per_second, u32 sample_count, f32 tone_hz, i32 tone_volume, i16 *samples)
{
static f32 t_sine = 0;
i32 wave_period = (i32)(samples_per_second / tone_hz);
i16 *sample_out = samples;
for (i32 sample_index = 0; sample_index < (i32)sample_count; sample_index++)
{
f32 sine_value = sin(t_sine);
i16 sample_value = (i16)(sine_value * tone_volume);
*sample_out++ = sample_value;
*sample_out++ = sample_value;
t_sine += TAU / (f32)wave_period;
if (t_sine >= TAU) {
t_sine -= TAU;
}
}
}
void audio_test_jingle(Audio_Context *ctx, void *samples, u32 sample_count, void *user)
{
u32 samples_per_second = ctx->samples_per_second;
f32 tone_hz = 440;
{
static u64 total_samples_written = 0;
i32 buffer_index = total_samples_written % samples_per_second;
i32 buffer_size = samples_per_second;
tone_hz = 440;
if (total_samples_written % (samples_per_second * 8) >= samples_per_second * 4) tone_hz = 466.16;
if (buffer_index > buffer_size * 1 / 4) tone_hz = 523.25;
if (buffer_index > buffer_size * 2 / 4) tone_hz = 659.25;
if (buffer_index > buffer_size * 3 / 4) tone_hz = 783.99;
total_samples_written += sample_count;
}
i32 tone_volume = 3000;
audio_output_sine_wave_i16(samples_per_second, sample_count, tone_hz, tone_volume, (i16 *)samples);
}
int main()
{
Audio_Context ctx = {0};
ctx.num_channels = 2;
ctx.bits_per_channel = 16;
ctx.samples_per_second = 44100;
ctx.callback = audio_test_jingle;
audio_init(&ctx);
while (!g_audio.running) {
usleep(10000);
}
usleep(8 * 1000000);
audio_free();
printf("[CoreAudio] Closing...\n");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment