Reapply "Death to C++"

This reverts commit 13f856cfbc.
This commit is contained in:
2026-03-12 16:30:04 -04:00
parent d5d2f6db8e
commit 8695f69282
80 changed files with 5671 additions and 39268 deletions

342
src/audio/audio_coreaudio.c Normal file
View File

@@ -0,0 +1,342 @@
#include "audio/audio.h"
#include <AudioToolbox/AudioToolbox.h>
#include <CoreAudio/CoreAudio.h>
#include <string.h>
#include <math.h>
#include <stdatomic.h>
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#define AUDIO_MAX_DEVICES 32
#define AUDIO_MAX_CHANNELS 32
#define AUDIO_TEST_TONE_HZ 440.0
#define AUDIO_TEST_TONE_SEC 2.0
#define AUDIO_PI 3.14159265358979323846
typedef struct CoreAudioDeviceInfo {
AudioDeviceID device_id;
} CoreAudioDeviceInfo;
struct AudioEngine {
AudioDeviceInfo devices[AUDIO_MAX_DEVICES];
CoreAudioDeviceInfo ca_devices[AUDIO_MAX_DEVICES];
S32 device_count;
AUGraph graph;
AudioUnit output_unit;
S32 active_device_index;
F64 sample_rate;
S32 num_channels;
// Test tone state (accessed from audio render thread)
_Atomic S32 test_tone_active;
_Atomic S32 test_tone_samples_remaining;
F64 test_tone_phase;
};
////////////////////////////////
// Audio render callback
static AudioEngine *g_audio_engine = NULL;
static OSStatus audio_render_callback(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData)
{
(void)inRefCon; (void)ioActionFlags; (void)inTimeStamp; (void)inBusNumber;
AudioEngine *engine = g_audio_engine;
if (!engine) {
for (UInt32 buf = 0; buf < ioData->mNumberBuffers; buf++)
memset(ioData->mBuffers[buf].mData, 0, ioData->mBuffers[buf].mDataByteSize);
return noErr;
}
S32 tone_active = atomic_load(&engine->test_tone_active);
if (!tone_active) {
for (UInt32 buf = 0; buf < ioData->mNumberBuffers; buf++)
memset(ioData->mBuffers[buf].mData, 0, ioData->mBuffers[buf].mDataByteSize);
return noErr;
}
F64 phase = engine->test_tone_phase;
F64 phase_inc = 2.0 * AUDIO_PI * AUDIO_TEST_TONE_HZ / engine->sample_rate;
S32 remaining = atomic_load(&engine->test_tone_samples_remaining);
S32 samples_to_gen = (remaining < (S32)inNumberFrames) ? remaining : (S32)inNumberFrames;
// CoreAudio with kAudioFormatFlagIsNonInterleaved: each buffer = one channel
for (UInt32 buf = 0; buf < ioData->mNumberBuffers; buf++) {
F32 *out = (F32 *)ioData->mBuffers[buf].mData;
F64 p = phase;
for (UInt32 s = 0; s < inNumberFrames; s++) {
if ((S32)s < samples_to_gen) {
out[s] = (F32)(sin(p) * 0.5); // -6dB
p += phase_inc;
if (p >= 2.0 * AUDIO_PI) p -= 2.0 * AUDIO_PI;
} else {
out[s] = 0.0f;
}
}
}
// Advance phase using first channel's traversal
phase += phase_inc * samples_to_gen;
while (phase >= 2.0 * AUDIO_PI) phase -= 2.0 * AUDIO_PI;
engine->test_tone_phase = phase;
S32 new_remaining = atomic_fetch_sub(&engine->test_tone_samples_remaining, samples_to_gen);
if (new_remaining <= samples_to_gen) {
atomic_store(&engine->test_tone_active, 0);
atomic_store(&engine->test_tone_samples_remaining, 0);
}
return noErr;
}
////////////////////////////////
// Device enumeration
static void enumerate_output_devices(AudioEngine *engine) {
engine->device_count = 0;
AudioObjectPropertyAddress prop = {
kAudioHardwarePropertyDevices,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMain
};
UInt32 data_size = 0;
if (AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &prop, 0, NULL, &data_size) != noErr)
return;
S32 device_count = (S32)(data_size / sizeof(AudioDeviceID));
if (device_count <= 0) return;
AudioDeviceID *device_ids = (AudioDeviceID *)malloc(data_size);
if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &prop, 0, NULL, &data_size, device_ids) != noErr) {
free(device_ids);
return;
}
for (S32 i = 0; i < device_count && engine->device_count < AUDIO_MAX_DEVICES; i++) {
// Check if device has output channels
AudioObjectPropertyAddress stream_prop = {
kAudioDevicePropertyStreamConfiguration,
kAudioDevicePropertyScopeOutput,
kAudioObjectPropertyElementMain
};
UInt32 stream_size = 0;
if (AudioObjectGetPropertyDataSize(device_ids[i], &stream_prop, 0, NULL, &stream_size) != noErr)
continue;
AudioBufferList *buf_list = (AudioBufferList *)malloc(stream_size);
if (AudioObjectGetPropertyData(device_ids[i], &stream_prop, 0, NULL, &stream_size, buf_list) != noErr) {
free(buf_list);
continue;
}
S32 output_channels = 0;
for (UInt32 b = 0; b < buf_list->mNumberBuffers; b++)
output_channels += (S32)buf_list->mBuffers[b].mNumberChannels;
free(buf_list);
if (output_channels == 0) continue;
// Get device name
AudioObjectPropertyAddress name_prop = {
kAudioObjectPropertyName,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMain
};
CFStringRef name_ref = NULL;
UInt32 name_size = sizeof(name_ref);
if (AudioObjectGetPropertyData(device_ids[i], &name_prop, 0, NULL, &name_size, &name_ref) != noErr)
continue;
S32 idx = engine->device_count++;
CFStringGetCString(name_ref, engine->devices[idx].name, sizeof(engine->devices[idx].name),
kCFStringEncodingUTF8);
CFRelease(name_ref);
engine->devices[idx].id = idx;
engine->ca_devices[idx].device_id = device_ids[i];
}
free(device_ids);
}
////////////////////////////////
// Public API
AudioEngine *audio_create(void *hwnd) {
(void)hwnd;
AudioEngine *engine = (AudioEngine *)calloc(1, sizeof(AudioEngine));
engine->active_device_index = -1;
atomic_store(&engine->test_tone_active, 0);
atomic_store(&engine->test_tone_samples_remaining, 0);
g_audio_engine = engine;
enumerate_output_devices(engine);
return engine;
}
void audio_destroy(AudioEngine *engine) {
audio_close_device(engine);
if (g_audio_engine == engine) g_audio_engine = NULL;
free(engine);
}
void audio_refresh_devices(AudioEngine *engine) {
audio_close_device(engine);
enumerate_output_devices(engine);
}
S32 audio_get_device_count(AudioEngine *engine) {
return engine->device_count;
}
AudioDeviceInfo *audio_get_device(AudioEngine *engine, S32 index) {
if (index < 0 || index >= engine->device_count) return NULL;
return &engine->devices[index];
}
B32 audio_open_device(AudioEngine *engine, S32 index) {
audio_close_device(engine);
if (index < 0 || index >= engine->device_count) return false;
AudioDeviceID device_id = engine->ca_devices[index].device_id;
// Create AUGraph
if (NewAUGraph(&engine->graph) != noErr) return false;
// Add HAL output node
AudioComponentDescription output_desc = {0};
output_desc.componentType = kAudioUnitType_Output;
output_desc.componentSubType = kAudioUnitSubType_HALOutput;
output_desc.componentManufacturer = kAudioUnitManufacturer_Apple;
AUNode output_node;
if (AUGraphAddNode(engine->graph, &output_desc, &output_node) != noErr) {
DisposeAUGraph(engine->graph);
engine->graph = NULL;
return false;
}
if (AUGraphOpen(engine->graph) != noErr) {
DisposeAUGraph(engine->graph);
engine->graph = NULL;
return false;
}
if (AUGraphNodeInfo(engine->graph, output_node, NULL, &engine->output_unit) != noErr) {
DisposeAUGraph(engine->graph);
engine->graph = NULL;
return false;
}
// Set device
if (AudioUnitSetProperty(engine->output_unit, kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global, 0, &device_id, sizeof(device_id)) != noErr) {
DisposeAUGraph(engine->graph);
engine->graph = NULL;
return false;
}
// Get device sample rate
AudioObjectPropertyAddress rate_prop = {
kAudioDevicePropertyNominalSampleRate,
kAudioDevicePropertyScopeOutput,
kAudioObjectPropertyElementMain
};
Float64 sample_rate = 44100.0;
UInt32 rate_size = sizeof(sample_rate);
AudioObjectGetPropertyData(device_id, &rate_prop, 0, NULL, &rate_size, &sample_rate);
engine->sample_rate = sample_rate;
// Set stream format: Float32, non-interleaved
AudioStreamBasicDescription fmt = {0};
fmt.mSampleRate = sample_rate;
fmt.mFormatID = kAudioFormatLinearPCM;
fmt.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsNonInterleaved | kAudioFormatFlagIsPacked;
fmt.mBitsPerChannel = 32;
fmt.mChannelsPerFrame = 2;
fmt.mFramesPerPacket = 1;
fmt.mBytesPerFrame = 4;
fmt.mBytesPerPacket = 4;
engine->num_channels = 2;
AudioUnitSetProperty(engine->output_unit, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, 0, &fmt, sizeof(fmt));
// Set render callback
AURenderCallbackStruct cb = {0};
cb.inputProc = audio_render_callback;
cb.inputProcRefCon = engine;
if (AudioUnitSetProperty(engine->output_unit, kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input, 0, &cb, sizeof(cb)) != noErr) {
DisposeAUGraph(engine->graph);
engine->graph = NULL;
return false;
}
// Initialize and start
if (AUGraphInitialize(engine->graph) != noErr) {
DisposeAUGraph(engine->graph);
engine->graph = NULL;
return false;
}
if (AUGraphStart(engine->graph) != noErr) {
DisposeAUGraph(engine->graph);
engine->graph = NULL;
return false;
}
engine->active_device_index = index;
engine->test_tone_phase = 0.0;
atomic_store(&engine->test_tone_active, 0);
atomic_store(&engine->test_tone_samples_remaining, 0);
return true;
}
void audio_close_device(AudioEngine *engine) {
if (!engine->graph) return;
atomic_store(&engine->test_tone_active, 0);
atomic_store(&engine->test_tone_samples_remaining, 0);
AUGraphStop(engine->graph);
AUGraphUninitialize(engine->graph);
DisposeAUGraph(engine->graph);
engine->graph = NULL;
engine->output_unit = NULL;
engine->active_device_index = -1;
engine->test_tone_phase = 0.0;
}
void audio_play_test_tone(AudioEngine *engine) {
if (!engine->graph) return;
engine->test_tone_phase = 0.0;
S32 total_samples = (S32)(engine->sample_rate * AUDIO_TEST_TONE_SEC);
atomic_store(&engine->test_tone_samples_remaining, total_samples);
atomic_store(&engine->test_tone_active, 1);
}
B32 audio_is_test_tone_playing(AudioEngine *engine) {
return atomic_load(&engine->test_tone_active) != 0;
}
void audio_update(AudioEngine *engine, F32 dt) {
(void)engine;
(void)dt;
}