Last active
May 23, 2025 19:27
-
-
Save gcatlin/0dd61f19d40804173d015c01a80461b8 to your computer and use it in GitHub Desktop.
Core Audio sine wave example
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
// 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); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@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).