soundEngine.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. 
  2. #include "soundEngine.h"
  3. #include "../../DFPSR/api/soundAPI.h"
  4. #include "../../DFPSR/api/drawAPI.h"
  5. #include "../../DFPSR/api/fileAPI.h"
  6. #include "../../DFPSR/api/fontAPI.h"
  7. #include "../../DFPSR/base/virtualStack.h"
  8. #include "../../DFPSR/base/simd.h"
  9. #include "SoundPlayer.h"
  10. #include <future>
  11. #include <atomic>
  12. namespace dsr {
  13. static const int maxChannels = 2;
  14. static const int outputChannels = 2;
  15. static const int outputSampleRate = 44100;
  16. double outputSoundStep = 1.0 / (double)outputSampleRate;
  17. std::future<void> soundFuture;
  18. static std::atomic<bool> soundRunning{true};
  19. static std::mutex soundMutex;
  20. static void minMax(float &minimum, float &maximum, float value) {
  21. if (value < minimum) { minimum = value; }
  22. if (value > maximum) { maximum = value; }
  23. }
  24. struct Sound {
  25. SoundBuffer buffer;
  26. String name;
  27. bool fromFile;
  28. Sound(const SoundBuffer &buffer, const ReadableString &name, bool fromFile)
  29. : buffer(buffer), name(name), fromFile(fromFile) {}
  30. Sound(const ReadableString &name, bool fromFile, int32_t samplesPerChannel, int32_t channelCount, int32_t sampleRate)
  31. : buffer(samplesPerChannel, channelCount, sampleRate), name(name), fromFile(fromFile) {}
  32. float sampleLinear(int32_t leftIndex, int32_t rightIndex, double ratio, int32_t channel) {
  33. int64_t leftOffset = leftIndex * sound_getChannelCount(this->buffer) + channel;
  34. int64_t rightOffset = rightIndex * sound_getChannelCount(this->buffer) + channel;
  35. float a = 0.0, b = 0.0;
  36. SafePointer<float> source = sound_getSafePointer(this->buffer);
  37. a = source[leftOffset];
  38. b = source[rightOffset];
  39. return b * ratio + a * (1.0 - ratio);
  40. }
  41. float sampleLinear_cyclic(double location, int32_t channel) {
  42. int32_t truncated = (int64_t)location;
  43. int32_t floor = truncated % sound_getSamplesPerChannel(this->buffer);
  44. int32_t ceiling = floor + 1; if (ceiling == sound_getSamplesPerChannel(this->buffer)) { ceiling = 0; }
  45. double ratio = location - truncated;
  46. return this->sampleLinear(floor, ceiling, ratio, channel);
  47. }
  48. float sampleLinear_clamped(double location, int32_t channel) {
  49. int32_t truncated = (int64_t)location;
  50. int32_t floor = truncated; if (floor >= sound_getSamplesPerChannel(this->buffer)) { floor = sound_getSamplesPerChannel(this->buffer) - 1; }
  51. int32_t ceiling = floor + 1; if (ceiling >= sound_getSamplesPerChannel(this->buffer)) { ceiling = sound_getSamplesPerChannel(this->buffer) - 1; }
  52. double ratio = location - truncated;
  53. return this->sampleLinear(floor, ceiling, ratio, channel);
  54. }
  55. void sampleMinMax(float &minimum, float &maximum, int startSample, int endSample, int channel) {
  56. if (startSample < 0) { startSample = 0; }
  57. if (endSample >= sound_getSamplesPerChannel(this->buffer)) { endSample = sound_getSamplesPerChannel(this->buffer) - 1; }
  58. if (channel < 0) { channel = 0; }
  59. if (channel >= sound_getChannelCount(this->buffer)) { channel = sound_getChannelCount(this->buffer) - 1; }
  60. int bufferIndex = startSample * sound_getChannelCount(this->buffer) + channel;
  61. SafePointer<float> source = sound_getSafePointer(this->buffer);
  62. for (int s = startSample; s <= endSample; s++) {
  63. minMax(minimum, maximum, source[bufferIndex]);
  64. bufferIndex += sound_getChannelCount(this->buffer);
  65. }
  66. }
  67. };
  68. List<Sound> sounds;
  69. int soundEngine_loadSoundFromFile(const ReadableString &filename, bool mustExist) {
  70. // Try to reuse any previously instance of the file before accessing the file system
  71. for (int s = 0; s < sounds.length(); s++) {
  72. if (sounds[s].fromFile && string_match(sounds[s].name, filename)) {
  73. return s;
  74. }
  75. }
  76. return soundEngine_insertSoundBuffer(sound_load(filename, mustExist), filename, true);
  77. }
  78. int soundEngine_getSoundBufferCount() {
  79. return sounds.length();
  80. }
  81. List<SoundPlayer> fixedPlayers;
  82. int64_t nextPlayerID = 0;
  83. int soundEngine_playSound(int soundIndex, bool repeat, float leftVolume, float rightVolume, const EnvelopeSettings &envelopeSettings) {
  84. int result;
  85. if (soundIndex < 0 || soundIndex >= sounds.length()) {
  86. sendWarning(U"playSound_simple: Sound index ", soundIndex, U" does not exist!\n");
  87. return -1;
  88. }
  89. Sound *sound = &(sounds[soundIndex]);
  90. if (!sound_exists(sound->buffer)) {
  91. // Nothing to play.
  92. return -1;
  93. }
  94. int soundSampleRate = sound_getSampleRate(sound->buffer);
  95. if (soundSampleRate != outputSampleRate) {
  96. throwError(U"playSound_simple: The sound ", sound->name, U" has ", soundSampleRate, U" samples per second in each channel, but the sound engine samples output at ", outputSampleRate, U" samples per second!\n");
  97. }
  98. int soundChannel = sound_getChannelCount(sound->buffer);
  99. if (soundChannel > maxChannels) {
  100. throwError(U"playSound_simple: The sound ", sound->name, U" has ", soundChannel, U" channels, but the sound engine can not play more than ", maxChannels, U"channels!\n");
  101. }
  102. soundMutex.lock();
  103. result = nextPlayerID;
  104. fixedPlayers.pushConstruct(sounds[soundIndex].buffer, soundIndex, result, repeat, 0u, leftVolume, rightVolume, envelopeSettings);
  105. nextPlayerID++;
  106. soundMutex.unlock();
  107. return result;
  108. }
  109. static int findFixedPlayer(int64_t playerID) {
  110. for (int p = 0; p < fixedPlayers.length(); p++) {
  111. if (fixedPlayers[p].playerID == playerID) {
  112. return p;
  113. }
  114. }
  115. return -1;
  116. }
  117. void soundEngine_releaseSound(int64_t playerID) {
  118. if (playerID != -1) {
  119. soundMutex.lock();
  120. int index = findFixedPlayer(playerID);
  121. if (index > -1) {
  122. fixedPlayers[index].sustained = false;
  123. }
  124. soundMutex.unlock();
  125. }
  126. }
  127. void soundEngine_stopSound(int64_t playerID) {
  128. if (playerID != -1) {
  129. soundMutex.lock();
  130. int index = findFixedPlayer(playerID);
  131. if (index > -1) {
  132. fixedPlayers.remove(index);
  133. }
  134. soundMutex.unlock();
  135. }
  136. }
  137. void soundEngine_stopAllSounds() {
  138. soundMutex.lock();
  139. fixedPlayers.clear();
  140. soundMutex.unlock();
  141. }
  142. // By using a fixed period size independently of the hardware's period size with sound_streamToSpeakers_fixed, we can reduce waste from SIMD padding and context switches.
  143. static const int32_t periodSize = 1024;
  144. void soundEngine_initialize() {
  145. // Start a worker thread mixing sounds in realtime
  146. std::function<void()> task = []() {
  147. //sound_streamToSpeakers(outputChannels, outputSampleRate, [](SafePointer<float> target, int32_t requestedSamplesPerChannel) -> bool {
  148. sound_streamToSpeakers_fixed(outputChannels, outputSampleRate, periodSize, [](SafePointer<float> target) -> bool {
  149. // TODO: Create a thread-safe swap chain or input queue, so that the main thread and sound thread can work at the same time.
  150. // Anyone wanting to change the played sounds from another thread will have to wait until this section has finished processing
  151. soundMutex.lock();
  152. VirtualStackAllocation<float> playerBuffer(periodSize * maxChannels, "Sound target buffer", memory_createAlignmentAndMask(sizeof(DSR_FLOAT_VECTOR_SIZE)));
  153. for (int32_t p = fixedPlayers.length() - 1; p >= 0; p--) {
  154. SoundPlayer *player = &(fixedPlayers[p]);
  155. SoundBuffer *sound = &(player->soundBuffer);
  156. // Get samples from the player.
  157. player_getNextSamples(*player, playerBuffer, periodSize, 1.0 / (double)outputSampleRate);
  158. // TODO: Use a swap chain to update volumes for sound players without stalling.
  159. // TODO: Fade volume transitions by assigning soft targets to slowly move towards.
  160. // Apply any volume scaling.
  161. //if (player->leftVolume < 0.999f || player->leftVolume > 1.001f || player->rightVolume < 0.999f || player->rightVolume > 1.001f) {
  162. //
  163. //}
  164. //
  165. if (sound_getChannelCount(*sound) == 1) { // Mono source to stereo target
  166. // TODO: Use a zip/shuffle operation for duplicating channels when available in hardware.
  167. SafePointer<float> sourceBlock = playerBuffer;
  168. SafePointer<float> targetBlock = target;
  169. const bool multiplyLeft = player->fadeLeft;
  170. const bool multiplyRight = player->fadeRight;
  171. for (int32_t t = 0; t < periodSize; t++) {
  172. float value = *sourceBlock;
  173. if (multiplyLeft) {
  174. targetBlock[0] += value * player->leftVolume;
  175. } else {
  176. targetBlock[0] += value;
  177. }
  178. if (multiplyRight) {
  179. targetBlock[1] += value * player->rightVolume;
  180. } else {
  181. targetBlock[1] += value;
  182. }
  183. sourceBlock += 1;
  184. targetBlock += 2;
  185. }
  186. } else if (sound_getChannelCount(*sound) == 2) { // Stereo source to stereo target
  187. // Accumulating sound samples with the same number of channels in and out.
  188. SafePointer<const float> sourceBlock = playerBuffer;
  189. SafePointer<float> targetBlock = target;
  190. if (player->fadeLeft || player->fadeRight) {
  191. for (int32_t t = 0; t < periodSize; t++) {
  192. targetBlock[0] += sourceBlock[0] * player->leftVolume;
  193. targetBlock[1] += sourceBlock[1] * player->rightVolume;
  194. sourceBlock += 2;
  195. targetBlock += 2;
  196. }
  197. } else {
  198. for (int32_t t = 0; t < periodSize * outputChannels; t += laneCountF) {
  199. F32xF packedSamples = F32xF::readAligned(sourceBlock, "Reading stereo sound samples");
  200. F32xF oldTarget = F32xF::readAligned(targetBlock, "Reading stereo sound samples");
  201. F32xF result = oldTarget + packedSamples;
  202. result.writeAligned(targetBlock, "Incrementing stereo samples");
  203. // Move pointers to the next block of input and output data.
  204. sourceBlock.increaseBytes(DSR_FLOAT_VECTOR_SIZE);
  205. targetBlock.increaseBytes(DSR_FLOAT_VECTOR_SIZE);
  206. }
  207. }
  208. } else {
  209. // TODO: How should more input than output channels be handled?
  210. }
  211. // Remove players that are done.
  212. if (player->envelope.envelopeSettings.used) {
  213. // Remove after fading out when an envelope is used.
  214. if (player->envelope.done()) {
  215. fixedPlayers.remove(p);
  216. }
  217. } else {
  218. // Remove instantly on release if there is no envelope.
  219. if (!(player->sustained)) {
  220. fixedPlayers.remove(p);
  221. }
  222. }
  223. }
  224. soundMutex.unlock();
  225. return soundRunning;
  226. });
  227. };
  228. soundFuture = std::async(std::launch::async, task);
  229. }
  230. void soundEngine_terminate() {
  231. if (soundRunning) {
  232. soundRunning = false;
  233. if (soundFuture.valid()) {
  234. soundFuture.wait();
  235. }
  236. }
  237. }
  238. void soundEngine_drawSound(dsr::ImageRgbaU8 target, const dsr::IRect &region, int32_t soundIndex, bool selected) {
  239. uint32_t playerCount = 0u;
  240. for (int32_t p = 0; p < fixedPlayers.length(); p++) {
  241. if (fixedPlayers[p].soundIndex == soundIndex) {
  242. playerCount++;
  243. }
  244. }
  245. draw_rectangle(target, region, selected ? ColorRgbaI32(128, 255, 128, 255) : ColorRgbaI32(40, 40, 40, 255));
  246. Sound *sound = &(sounds[soundIndex]);
  247. int32_t innerHeight = region.height() / sound_getChannelCount(sound->buffer);
  248. ColorRgbaI32 foreColor = selected ? ColorRgbaI32(200, 255, 200, 255) : ColorRgbaI32(200, 200, 200, 255);
  249. for (int32_t c = 0; c < sound_getChannelCount(sound->buffer); c++) {
  250. IRect innerBound = IRect(region.left() + 1, region.top() + 1, region.width() - 2, innerHeight - 2);
  251. draw_rectangle(target, innerBound, playerCount ? ColorRgbaI32(40, 40, 0, 255) : selected ? ColorRgbaI32(0, 0, 0, 255) : ColorRgbaI32(20, 20, 20, 255));
  252. double strideX = ((double)sound_getSamplesPerChannel(sound->buffer) - 1.0) / (double)innerBound.width();
  253. double scale = innerBound.height() * 0.5;
  254. double center = innerBound.top() + scale;
  255. draw_line(target, innerBound.left(), center, innerBound.right() - 1, center, ColorRgbaI32(0, 0, 255, 255));
  256. if (strideX > 1.0) {
  257. double startSample = 0.0;
  258. double endSample = strideX;
  259. for (int32_t x = innerBound.left(); x < innerBound.right(); x++) {
  260. float minimum = 1.0, maximum = -1.0;
  261. // TODO: Switch between min-max sampling (denser) and linear interpolation (sparser)
  262. sound->sampleMinMax(minimum, maximum, (int32_t)startSample, (int32_t)endSample, c);
  263. draw_line(target, x, center - (minimum * scale), x, center - (maximum * scale), foreColor);
  264. startSample = endSample;
  265. endSample = endSample + strideX;
  266. }
  267. } else {
  268. double sampleX = 0.0;
  269. for (int32_t x = innerBound.left(); x < innerBound.right(); x++) {
  270. float valueLeft = sound->sampleLinear_clamped(sampleX, c);
  271. sampleX += strideX;
  272. float valueRight = sound->sampleLinear_clamped(sampleX, c);
  273. draw_line(target, x, center - (valueLeft * scale), x, center - (valueRight * scale), foreColor);
  274. }
  275. }
  276. }
  277. // Draw a location for each player using the sound.
  278. double pixelsPerSample = (double)(region.width()) / (double)sound_getSamplesPerChannel(sound->buffer);
  279. for (int32_t p = 0; p < fixedPlayers.length(); p++) {
  280. SoundPlayer *player = &(fixedPlayers[p]);
  281. if (player->soundIndex == soundIndex) {
  282. // TODO: Display a line along the sound for each player.
  283. int32_t pixelX = region.left() + int32_t(double(player->location) * pixelsPerSample);
  284. draw_line(target, pixelX, region.top(), pixelX, region.bottom(), foreColor);
  285. playerCount++;
  286. }
  287. }
  288. font_printLine(target, font_getDefault(), sound->name, IVector2D(region.left() + 5, region.top() + 5), foreColor);
  289. }
  290. int32_t soundEngine_insertSoundBuffer(const SoundBuffer &buffer, const ReadableString &name, bool fromFile) {
  291. return sounds.pushConstructGetIndex(buffer, name, fromFile);
  292. }
  293. SoundBuffer soundEngine_getSound(int32_t soundIndex) {
  294. if (soundIndex < 0 || soundIndex >= sounds.length()) {
  295. return SoundBuffer();
  296. } else {
  297. return sounds[soundIndex].buffer;
  298. }
  299. }
  300. }