Browse Source

Sound abstraction layer with backends for Linux and MS-Windows.

David Piuva 3 years ago
parent
commit
7d9c2b16da

+ 96 - 0
Source/soundManagers/AlsaSound.cpp

@@ -0,0 +1,96 @@
+
+// Use -lasound for linking to the winmm library in GCC/G++
+//   Install on Arch: sudo pacman -S libasound-dev
+//   Install on Debian: sudo apt-get install libasound-dev
+
+#include "../../dfpsr/Source/DFPSR/includeFramework.h"
+#include "soundManagers.h"
+#include <alsa/asoundlib.h>
+
+using namespace dsr;
+
+snd_pcm_t *pcm = nullptr;
+static int bufferElements = 0;
+static int16_t *outputBuffer = nullptr;
+static float *floatBuffer = nullptr;
+static void allocateBuffers(int neededElements) {
+	// TODO: Use aligned memory with Buffer
+	outputBuffer = (int16_t *)calloc(roundUp(neededElements, 8), sizeof(int16_t));
+	floatBuffer = (float *)calloc(roundUp(neededElements, 8), sizeof(float));
+	bufferElements = neededElements;
+}
+
+static void terminateSound() {
+	if (pcm) {
+		snd_pcm_drop(pcm);
+		snd_pcm_drain(pcm);
+		snd_pcm_close(pcm);
+		pcm = nullptr;
+	}
+	if (outputBuffer) { free(outputBuffer); }
+	if (floatBuffer) { free(floatBuffer); }
+}
+
+bool sound_streamToSpeakers(int channels, int sampleRate, std::function<bool(float*, int)> soundOutput) {
+	int errorCode;
+	if ((errorCode = snd_pcm_open(&pcm, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
+		terminateSound();
+		throwError("Cannot open sound device. (", snd_strerror(errorCode), ")\n");
+	}
+	snd_pcm_hw_params_t *hardwareParameters;
+	snd_pcm_hw_params_alloca(&hardwareParameters);
+	snd_pcm_hw_params_any(pcm, hardwareParameters);
+	if ((errorCode = snd_pcm_hw_params_set_access(pcm, hardwareParameters, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
+		terminateSound();
+		throwError("Failed to select interleaved sound. (", snd_strerror(errorCode), ")\n");
+	}
+	if ((errorCode = snd_pcm_hw_params_set_format(pcm, hardwareParameters, SND_PCM_FORMAT_S16_LE)) < 0) {
+		terminateSound();
+		throwError("Failed to select sound format. (", snd_strerror(errorCode), ")\n");
+	}
+	if ((errorCode = snd_pcm_hw_params_set_channels(pcm, hardwareParameters, channels)) < 0) {
+		terminateSound();
+		throwError("Failed to select channel count. (", snd_strerror(errorCode), ")\n");
+	}
+	if ((errorCode = snd_pcm_hw_params_set_buffer_size(pcm, hardwareParameters, 2048)) < 0) {
+		terminateSound();
+		throwError("Failed to select buffer size. (", snd_strerror(errorCode), ")\n");
+	}
+	uint rate = sampleRate;
+	if ((errorCode = snd_pcm_hw_params_set_rate_near(pcm, hardwareParameters, &rate, 0)) < 0) {
+		terminateSound();
+		throwError("Failed to select approximate sample rate. (", snd_strerror(errorCode), ")\n");
+	}
+	if ((errorCode = snd_pcm_hw_params(pcm, hardwareParameters)) < 0) {
+		terminateSound();
+		throwError("Failed to select hardware parameters. (", snd_strerror(errorCode), ")\n");
+	}
+	// Allocate a buffer for sending data to speakers
+	snd_pcm_uframes_t samplesPerChannel;
+	snd_pcm_hw_params_get_period_size(hardwareParameters, &samplesPerChannel, 0);
+	// Allocate target buffers
+	int totalSamples = samplesPerChannel * channels;
+	allocateBuffers(totalSamples);
+	while (true) {
+		memset(floatBuffer, 0, totalSamples * sizeof(float));
+		bool keepRunning = soundOutput(floatBuffer, samplesPerChannel);
+		// Convert to target format so that the sound can be played
+		// TODO: Use SIMD
+		for (uint32_t t = 0; t < samplesPerChannel * channels; t++) {
+			outputBuffer[t] = sound_convertF32ToI16(floatBuffer[t]);
+		}
+		errorCode = snd_pcm_writei(pcm, outputBuffer, samplesPerChannel);
+		if (errorCode == -EPIPE) {
+			// Came too late! Not enough written samples to play.
+			snd_pcm_prepare(pcm);
+		} else if (errorCode < 0) {
+			terminateSound();
+			throwError("Failed writing data to PCM. (", snd_strerror(errorCode), ")\n");
+		}
+		if (!keepRunning) {
+			break;
+		}
+	}
+	terminateSound();
+	return true;
+}

+ 6 - 0
Source/soundManagers/NoSound.cpp

@@ -0,0 +1,6 @@
+
+#include "soundManagers.h"
+
+bool sound_streamToSpeakers_impl(int channels, int sampleRate, std::function<bool(float*, int)> soundOutput) {
+	return false;
+}

+ 106 - 0
Source/soundManagers/WinMMSound.cpp

@@ -0,0 +1,106 @@
+
+// Use -lwinmm for linking to the winmm library in GCC/G++
+
+#include "../../dfpsr/Source/DFPSR/includeFramework.h"
+#include "soundManagers.h"
+#include <windows.h>
+#include <mmsystem.h>
+
+using namespace dsr;
+
+static const int samplesPerChannel = 2048;
+
+static int bufferElements = 0;
+static int16_t *outputBuffer[2] = {nullptr, nullptr};
+static float *floatBuffer = nullptr;
+static void allocateBuffers(int neededElements) {
+	// TODO: Use aligned memory with Buffer
+	outputBuffer[0] = (int16_t *)calloc(roundUp(neededElements, 8), sizeof(int16_t));
+	outputBuffer[1] = (int16_t *)calloc(roundUp(neededElements, 8), sizeof(int16_t));
+	floatBuffer = (float *)calloc(roundUp(neededElements, 8), sizeof(float));
+	bufferElements = neededElements;
+}
+
+static HWAVEOUT waveOutput;
+static WAVEHDR header[2] = {0};
+static HANDLE bufferEndEvent = 0;
+
+static bool running = true;
+static void terminateSound() {
+	printText("Terminating sound.\n");
+	running = false;
+	if (waveOutput) {
+		waveOutReset(waveOutput);
+		for (int b = 0; b < 2; b++) {
+			waveOutUnprepareHeader(waveOutput, &header[b], sizeof(WAVEHDR));
+		}
+		waveOutClose(waveOutput);
+		waveOutput = 0;
+	}
+	if (bufferEndEvent) {
+		CloseHandle(bufferEndEvent);
+		bufferEndEvent = 0;
+	}
+	for (int b = 0; b < 2; b++) {
+		if (outputBuffer[b]) { free(outputBuffer[b]); }
+	}
+	if (floatBuffer) { free(floatBuffer); }
+}
+
+bool sound_streamToSpeakers(int channels, int sampleRate, std::function<bool(float*, int)> soundOutput) {
+	bufferEndEvent = CreateEvent(0, FALSE, FALSE, 0);
+	if (bufferEndEvent == 0) {
+		terminateSound();
+		throwError(U"Failed to create buffer end event!");
+	}
+	int totalSamples = samplesPerChannel * channels;
+	allocateBuffers(totalSamples);
+	WAVEFORMATEX format;
+	ZeroMemory(&format, sizeof(WAVEFORMATEX));
+	format.nChannels = (WORD)channels;
+	format.nSamplesPerSec = (DWORD)sampleRate;
+	format.wFormatTag = WAVE_FORMAT_PCM;
+	format.wBitsPerSample = 16;
+	format.nBlockAlign = format.nChannels * sizeof(int16_t);
+	format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
+	format.cbSize = 0;
+	if(waveOutOpen(&waveOutput, WAVE_MAPPER, &format, (DWORD_PTR)bufferEndEvent, (DWORD_PTR)NULL, CALLBACK_EVENT) != MMSYSERR_NOERROR) {
+		terminateSound();
+		throwError(U"Failed to open wave output!");
+	}
+	if(waveOutSetVolume(waveOutput, 0xFFFFFFFF) != MMSYSERR_NOERROR) {
+		terminateSound();
+		throwError(U"Failed to set volume!");
+	}
+	for (int b = 0; b < 2; b++) {
+		ZeroMemory(&header[b], sizeof(WAVEHDR));
+		header[b].dwBufferLength = totalSamples * sizeof(int16_t);
+		header[b].lpData = (LPSTR)(outputBuffer[b]);
+		if (waveOutPrepareHeader(waveOutput, &header[b], sizeof(WAVEHDR)) != MMSYSERR_NOERROR) {
+			terminateSound();
+			throwError(U"Failed to prepare buffer for streaming!");
+		}
+	}
+	running = true;
+	while (running) {
+		for (int b = 0; b < 2; b++) {
+			if ((header[b].dwFlags & WHDR_INQUEUE) == 0) {
+				// When one of the buffers is done playing, generate new sound and write more data to the output.
+				memset(floatBuffer, 0, totalSamples * sizeof(float));
+				// TODO: Use 128-bit aligned memory
+				running = soundOutput(floatBuffer, samplesPerChannel);
+				// TODO: Use SIMD
+				for (int i = 0; i < totalSamples; i++) {
+					outputBuffer[b][i] = sound_convertF32ToI16(floatBuffer[i]);
+				}
+				if (waveOutWrite(waveOutput, &header[b], sizeof(WAVEHDR)) != MMSYSERR_NOERROR) {
+					terminateSound(); throwError(U"Failed to write wave output!");
+				}
+				if (!running) { break; }
+			}
+		}
+		WaitForSingleObject(bufferEndEvent, INFINITE);
+	}
+	terminateSound();
+	return true;
+}

+ 25 - 0
Source/soundManagers/soundManagers.h

@@ -0,0 +1,25 @@
+
+#ifndef DFPSR_SOUND_API
+#define DFPSR_SOUND_API
+
+#include <functional>
+
+inline float sound_convertI16ToF32(int64_t input) {
+	return input * (1.0f / 32767.0f);
+}
+
+inline int sound_convertF32ToI16(float input) {
+	int64_t result = input * 32767.0f;
+	if (result > 32767) { result = 32767; }
+	if (result < -32768) { result = -32768; }
+	return result;
+}
+
+// Call this function from a separate thread in a sound engine to initialize the sound system, call back with sound output requests and terminate when the callback returns false.
+// The float array given to soundOutput should be filled with samples from 0 to totalSamples - 1.
+// Channels from the same point in time are packed together without any padding in between.
+// Returns false if the backend could not be created.
+// Returns true iff the backend completed all work and terminated safely.
+bool sound_streamToSpeakers(int channels, int sampleRate, std::function<bool(float*, int)> soundOutput);
+
+#endif