Browse Source

Made a first working CoreAudio backend.

David Piuva 8 months ago
parent
commit
47f2ea9270

+ 11 - 1
Source/DFPSR/DFPSR.DsrHead

@@ -1,4 +1,4 @@
-# A project header for using the DFPSR library.
+# A project header for using the DFPSR library.
 #   Backends:
 #     * Give the Graphics flag if the application should be able to create a window.
 #     * Give the Sound flag if the application should be able to generate sounds.
@@ -62,6 +62,16 @@ if Sound
 		Message "  Using Alsa\n"
 		Link "asound"
 		SoundManager = "../soundManagers/AlsaSound.cpp"
+	end if
+	if MacOS
+		Message "  Using CoreAudio\n"
+		# -framework and CoreAudio must be sent as individual arguments to Clang.
+		#TODO: Modify the build system to allow sending multiple arguments as groups to the compiler.
+		#LinkerFlag "-framework"
+		#  LinkerFlag "CoreAudio"
+		LinkerFlag "-framework"
+		  LinkerFlag "AudioUnit"
+		SoundManager = "../soundManagers/CoreAudioSound.cpp"
 	end if
 	if Windows
 		Message "  Using WinMM\n"

+ 2 - 0
Source/SDK/music/Music.DsrProj

@@ -16,3 +16,5 @@ StaticRuntime
 # Uncomment to enable debug mode, which is slower
 Debug
 CompilerFlag "-g"
+
+ListDependencies

+ 8 - 0
Source/SDK/music/build_macos.sh

@@ -0,0 +1,8 @@
+# Hack to use X11 on MacOS
+export CPATH=/opt/homebrew/include:$CPATH
+export LIBRARY_PATH=/opt/homebrew/lib:$LIBRARY_PATH
+
+# Launch the build system with Music.DsrProj and MacOS selected as the platform.
+echo "Running build_linux.sh $@"
+chmod +x ../../tools/builder/buildProject.sh;
+../../tools/builder/buildProject.sh Music.DsrProj MacOS $@;

+ 122 - 0
Source/soundManagers/CoreAudioSound.cpp

@@ -0,0 +1,122 @@
+
+#include "../DFPSR/api/soundAPI.h"
+#include "../DFPSR/api/timeAPI.h"
+#include "../DFPSR/base/noSimd.h"
+//#include <CoreAudio/CoreAudio.h>
+#include <AudioUnit/AudioUnit.h>
+
+namespace dsr {
+
+static AudioUnit audioUnit;
+static bool playing = false;
+static void terminateSound() {
+	AudioOutputUnitStop(audioUnit);
+	AudioUnitUninitialize(audioUnit);
+	AudioComponentInstanceDispose(audioUnit);
+}
+
+static uint32_t allocatedElements = 0u;
+static Buffer floatBuffer;
+static SafePointer<float> floatData;
+
+static void allocateBuffers(int neededElements) {
+	if (neededElements > allocatedElements) {
+		floatBuffer = buffer_create(neededElements * sizeof(float));
+		floatData = buffer_getSafeData<float>(floatBuffer, "Output data");
+		allocatedElements = neededElements;
+	}
+}
+
+int32_t engineChannels;
+int32_t engineSampleRate;
+static std::function<bool(SafePointer<float> data, int32_t length)> engineCallback;
+
+OSStatus coreAudioCallback(void*, AudioUnitRenderActionFlags*, const AudioTimeStamp*, uint32_t, uint32_t samplesPerChannel, AudioBufferList *outputBuffers) {
+	if (playing) {
+		// Calculate the number of samples from all channels.
+		uint32_t totalSamples = samplesPerChannel * engineChannels;
+		// Make sure that we have enough memory in the float buffer to get sounds from the engine.
+		allocateBuffers(totalSamples);
+		// Set all elements to zero, so that the engine does not have to do it before accumulating sounds.
+		safeMemorySet(floatData, 0, totalSamples * sizeof(float));
+		// Get samples from the sound engine and check if we are done playing sounds.
+		bool keepRunning = engineCallback(floatData, samplesPerChannel);
+		// Convert from float to 16-bit signed PCM format.
+		int16_t *outputData = (int16_t*)(outputBuffers->mBuffers[0].mData);
+		for (uint32_t t = 0; t < samplesPerChannel * engineChannels; t++) {
+			outputData[t] = truncateToI32(clamp(-32768.0f, floatData[t] * 32767.0f, 32767.0f));
+		}	
+		// If the engine is done taking requests, then this backend can terminate.
+		if (!keepRunning) {
+			playing = false;
+		}
+	}
+	return noErr;
+}
+
+static void initializeSound() {
+	OSErr errorCode;
+	AudioComponentDescription audioComponentDescription;
+	audioComponentDescription.componentType = kAudioUnitType_Output;
+	audioComponentDescription.componentSubType = kAudioUnitSubType_DefaultOutput;
+	audioComponentDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
+	AudioComponent audioOutputDevice = AudioComponentFindNext(nullptr, &audioComponentDescription);
+	if (audioOutputDevice == nullptr) {
+		throwError("Failed to find any CoreAudio output device!\n");
+		return;
+	}
+	errorCode = AudioComponentInstanceNew(audioOutputDevice, &audioUnit);
+	if (errorCode != 0) {
+		throwError("Failed to create the CoreAudio audio unit! Error code: ", errorCode, "!\n");
+		return;
+	}
+	AURenderCallbackStruct audioUnitCallbackStruct;
+	audioUnitCallbackStruct.inputProc = coreAudioCallback;
+	errorCode = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &audioUnitCallbackStruct, sizeof(audioUnitCallbackStruct));
+	if (errorCode != 0) {
+		throwError("Failed to assign the CoreAudio audio unit callback! Error code: ", errorCode, "!\n");
+		return;
+	}
+	AudioStreamBasicDescription audioStreamBasicDescription;
+	audioStreamBasicDescription.mFormatID = kAudioFormatLinearPCM;
+	audioStreamBasicDescription.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
+	audioStreamBasicDescription.mSampleRate = engineSampleRate;
+	audioStreamBasicDescription.mBitsPerChannel = 16;
+	audioStreamBasicDescription.mChannelsPerFrame = engineChannels;
+	audioStreamBasicDescription.mFramesPerPacket = 1;
+	audioStreamBasicDescription.mBytesPerFrame = engineChannels * sizeof(int16_t);
+	audioStreamBasicDescription.mBytesPerPacket = engineChannels * sizeof(int16_t);
+	errorCode = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &audioStreamBasicDescription, sizeof(audioStreamBasicDescription));
+	if (errorCode != 0) {
+		throwError("Failed to set the CoreAudio audio properties! Error code: ", errorCode, "!\n");
+		return;
+	}
+	errorCode = AudioUnitInitialize(audioUnit);
+	if (errorCode != 0) {
+		throwError("Failed to initialize the CoreAudio audio unit! Error code: ", errorCode, "!\n");
+		return;
+	}
+	errorCode = AudioOutputUnitStart(audioUnit);
+	if (errorCode != 0) {
+		throwError("Failed to start the CoreAudio audio unit! Error code: ", errorCode, "!\n");
+		return;
+	}
+	playing = true;
+}
+
+bool sound_streamToSpeakers(int32_t channels, int32_t sampleRate, std::function<bool(SafePointer<float> data, int32_t length)> soundOutput) {
+	engineChannels = channels;
+	engineSampleRate = sampleRate;
+	engineCallback = soundOutput;
+	initializeSound();
+	// For consistent thread behavior between operating systems and letting callbacks finish before terminating CoreAudio's audio unit,
+	//   a loop will check if it is time to terminate the sound engine's thread once in a while.
+	// TODO: Make it faster and more efficient, by using a mutex that is locked while playing.
+	while (playing) {
+		time_sleepSeconds(0.2);
+	}
+	terminateSound();
+	return true;
+}
+
+}