Skip to content

Instantly share code, notes, and snippets.

@gcatlin
Last active March 19, 2025 15:42
Show Gist options
  • Save gcatlin/0dd61f19d40804173d015c01a80461b8 to your computer and use it in GitHub Desktop.
Save gcatlin/0dd61f19d40804173d015c01a80461b8 to your computer and use it in GitHub Desktop.
Core Audio sine wave example
// To run:
// clang core-audio-sine-wave.c -framework AudioUnit && ./a.out
#include <AudioUnit/AudioUnit.h>
#define SAMPLE_RATE 48000
#define TONE_FREQUENCY 440
#define M_TAU 2.0 * M_PI
OSStatus RenderSineWave(
void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData)
{
static float theta;
SInt16 *left = (SInt16 *)ioData->mBuffers[0].mData;
for (UInt32 frame = 0; frame < inNumberFrames; ++frame) {
left[frame] = (SInt16)(sin(theta) * 32767.0f);
theta += M_TAU * TONE_FREQUENCY / SAMPLE_RATE;
if (theta > M_TAU) {
theta -= M_TAU;
}
}
// Copy left channel to right channel
memcpy(ioData->mBuffers[1].mData, left, ioData->mBuffers[1].mDataByteSize);
return noErr;
}
int main() {
OSErr err;
AudioComponentDescription acd = {
.componentType = kAudioUnitType_Output,
.componentSubType = kAudioUnitSubType_DefaultOutput,
.componentManufacturer = kAudioUnitManufacturer_Apple,
};
AudioComponent output = AudioComponentFindNext(NULL, &acd);
if (!output) printf("Can't find default output\n");
AudioUnit toneUnit;
err = AudioComponentInstanceNew(output, &toneUnit);
if (err) fprintf(stderr, "Error creating unit: %d\n", err);
AURenderCallbackStruct input = { .inputProc = RenderSineWave };
err = AudioUnitSetProperty(toneUnit, kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input, 0, &input, sizeof(input));
if (err) printf("Error setting callback: %d\n", err);
AudioStreamBasicDescription asbd = {
.mFormatID = kAudioFormatLinearPCM,
.mFormatFlags = 0
| kAudioFormatFlagIsSignedInteger
| kAudioFormatFlagIsPacked
| kAudioFormatFlagIsNonInterleaved,
.mSampleRate = 48000,
.mBitsPerChannel = 16,
.mChannelsPerFrame = 2,
.mFramesPerPacket = 1,
.mBytesPerFrame = 2,
.mBytesPerPacket = 2,
};
err = AudioUnitSetProperty(toneUnit, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, 0, &asbd, sizeof(asbd));
if (err) printf("Error setting stream format: %d\n", err);
err = AudioUnitInitialize(toneUnit);
if (err) printf("Error initializing unit: %d\n", err);
err = AudioOutputUnitStart(toneUnit);
if (err) printf("Error starting unit: %d\n", err);
usleep(500000);
AudioOutputUnitStop(toneUnit);
AudioUnitUninitialize(toneUnit);
AudioComponentInstanceDispose(toneUnit);
}
@emartinson
Copy link

emartinson commented Apr 3, 2024

Thanks! Great example!
I'd suggest some optimization of the rendering callback:

static float theta = 0.01;
static float amplitude = 0.25; // SignalLevel - Volume [0; 1]
static float thetaIncrement = M_TAU * TONE_FREQUENCY / SAMPLE_RATE;

float *left = (float *)ioData->mBuffers[0].mData;

for (UInt32 frame = 0; frame < inNumberFrames; ++frame) {
    left[frame] = (float)(sin(theta) * amplitude);
    theta += thetaIncrement;
}

You can replace float back to SInt16. I use float because all my audio unit logic is based on float values. I hear pure and beautiful sine wave!

@shakfu
Copy link

shakfu commented May 31, 2024

@gcatlin great example thanks

@emartinson I'm confused by your optimization which doesn't provide clean sine tone at all if implementing the callback as you have suggested and I get a fuzzy tone instead. Am I missing something?

OSStatus RenderSineWave(
        void *inRefCon,
        AudioUnitRenderActionFlags *ioActionFlags,
        const AudioTimeStamp *inTimeStamp,
        UInt32 inBusNumber,
        UInt32 inNumberFrames,
        AudioBufferList *ioData)
{
    static float theta = 0.01;
    static float amplitude = 0.25; // SignalLevel - Volume [0; 1]
    static float thetaIncrement = M_TAU * TONE_FREQUENCY / SAMPLE_RATE;

    float *left = (float *)ioData->mBuffers[0].mData;

    for (UInt32 frame = 0; frame < inNumberFrames; ++frame) {
        left[frame] = (float)(sin(theta) * amplitude);
        theta += thetaIncrement;
    }

    // Copy left channel to right channel
    memcpy(ioData->mBuffers[1].mData, left, ioData->mBuffers[1].mDataByteSize);

    return noErr;
}

@emartinson
Copy link

emartinson commented Jun 17, 2024

@shakfu
The reason of your fuzzy tone is probably related to either wrong Sample Rate or (most probable) audio buffer format. Sorry for that. I'm using Float sample data in my project and copy-pasted, but in this example uses Integer buffer values (kAudioFormatFlagIsSignedInteger) and casting to SInt16 in: (SInt16)(sin(theta) * 32767.0f) - that's why it's necessary to multiply to 32767.0f.

The main idea of the optimization is moving all unnecessary math out of for-loop:
theta += M_TAU * TONE_FREQUENCY / SAMPLE_RATE; -> this is a constant - no need to compute it in loop, no need to compute it even within renderer callback - is renderer callback must work as fast as possible (as it affects sound delay and audio accuracy - sometimes it's critical).

@shakfu
Copy link

shakfu commented Jun 17, 2024

@emartinson Thanks for taking the time to explain this. 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment