// 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 "soundManagers.h" #include #include "../DFPSR/base/simd.h" using namespace dsr; snd_pcm_t *pcm = nullptr; static int bufferElements = 0; static Buffer outputBuffer, floatBuffer; static SafePointer outputData; static SafePointer floatData; // Aligning memory to allow using the widest available floating-point SIMD vector. static const int soundBufferAlignment = DSR_FLOAT_ALIGNMENT; static void allocateBuffers(int neededElements) { outputBuffer = buffer_create(neededElements * sizeof(int16_t), soundBufferAlignment); floatBuffer = buffer_create(neededElements * sizeof(float), soundBufferAlignment); outputData = buffer_getSafeData(outputBuffer, "Output data"); floatData = buffer_getSafeData(floatBuffer, "Output data"); bufferElements = neededElements; } static void terminateSound() { if (pcm) { snd_pcm_drop(pcm); snd_pcm_drain(pcm); snd_pcm_close(pcm); pcm = nullptr; } } bool sound_streamToSpeakers(int channels, int sampleRate, std::function data, int length)> soundOutput) { int errorCode; if ((errorCode = snd_pcm_open(&pcm, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) { terminateSound(); sendWarning("Cannot open sound device. (", snd_strerror(errorCode), ")\n"); return false; } 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(); sendWarning("Failed to select interleaved sound. (", snd_strerror(errorCode), ")\n"); return false; } if ((errorCode = snd_pcm_hw_params_set_format(pcm, hardwareParameters, SND_PCM_FORMAT_S16_LE)) < 0) { terminateSound(); sendWarning("Failed to select sound format. (", snd_strerror(errorCode), ")\n"); return false; } if ((errorCode = snd_pcm_hw_params_set_channels(pcm, hardwareParameters, channels)) < 0) { terminateSound(); sendWarning("Failed to select channel count. (", snd_strerror(errorCode), ")\n"); return false; } if ((errorCode = snd_pcm_hw_params_set_buffer_size(pcm, hardwareParameters, 2048)) < 0) { terminateSound(); sendWarning("Failed to select buffer size. (", snd_strerror(errorCode), ")\n"); return false; } uint rate = sampleRate; if ((errorCode = snd_pcm_hw_params_set_rate_near(pcm, hardwareParameters, &rate, 0)) < 0) { terminateSound(); sendWarning("Failed to select approximate sample rate. (", snd_strerror(errorCode), ")\n"); return false; } if ((errorCode = snd_pcm_hw_params(pcm, hardwareParameters)) < 0) { terminateSound(); sendWarning("Failed to select hardware parameters. (", snd_strerror(errorCode), ")\n"); return false; } // 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) { safeMemorySet(floatData, 0, totalSamples * sizeof(float)); bool keepRunning = soundOutput(floatData, samplesPerChannel); // Convert to target format so that the sound can be played for (uint32_t t = 0; t < samplesPerChannel * channels; t+=8) { // SIMD vectorized sound conversion with scaling and clamping to signed 16-bit integers. F32x4 lowerFloats = F32x4::readAligned(floatData + t, "sound_streamToSpeakers: Reading lower floats"); F32x4 upperFloats = F32x4::readAligned(floatData + t + 4, "sound_streamToSpeakers: Reading upper floats"); I32x4 lowerInts = truncateToI32((lowerFloats * 32767.0f).clamp(-32768.0f, 32767.0f)); I32x4 upperInts = truncateToI32((upperFloats * 32767.0f).clamp(-32768.0f, 32767.0f)); // TODO: Create I16x8 SIMD vectors for processing sound as 16-bit integers? // Or just move unzip into simd.h with a fallback solution and remove simdExtra.h. // Or just implement reading and writing of 16-bit signed integers using multiple SIMD registers or smaller memory regions. IVector4D lower = lowerInts.get(); IVector4D upper = upperInts.get(); outputData[t+0] = (int16_t)lower.x; outputData[t+1] = (int16_t)lower.y; outputData[t+2] = (int16_t)lower.z; outputData[t+3] = (int16_t)lower.w; outputData[t+4] = (int16_t)upper.x; outputData[t+5] = (int16_t)upper.y; outputData[t+6] = (int16_t)upper.z; outputData[t+7] = (int16_t)upper.w; } errorCode = snd_pcm_writei(pcm, outputData.getUnsafe(), 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; }