sound.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. 
  2. #include "sound.h"
  3. #include "../../DFPSR/api/soundAPI.h"
  4. #include <future>
  5. #include <atomic>
  6. namespace dsr {
  7. static const int outputChannels = 2;
  8. static const int outputSampleRate = 44100;
  9. double outputSoundStep = 1.0 / (double)outputSampleRate;
  10. double shortestTime = outputSoundStep * 0.01;
  11. std::future<void> soundFuture;
  12. static std::atomic<bool> soundRunning{true};
  13. static std::mutex soundMutex;
  14. static void minMax(float &minimum, float &maximum, float value) {
  15. if (value < minimum) { minimum = value; }
  16. if (value > maximum) { maximum = value; }
  17. }
  18. struct Sound {
  19. SoundBuffer buffer;
  20. String name;
  21. bool fromFile;
  22. Sound(const SoundBuffer &buffer, const ReadableString &name, bool fromFile)
  23. : buffer(buffer), name(name), fromFile(fromFile) {}
  24. Sound(const ReadableString &name, bool fromFile, int32_t samplesPerChannel, int32_t channelCount, int32_t sampleRate)
  25. : buffer(samplesPerChannel, channelCount, sampleRate), name(name), fromFile(fromFile) {}
  26. float sampleLinear(int32_t leftIndex, int32_t rightIndex, double ratio, int32_t channel) {
  27. int64_t leftOffset = leftIndex * sound_getChannelCount(this->buffer) + channel;
  28. int64_t rightOffset = rightIndex * sound_getChannelCount(this->buffer) + channel;
  29. float a = 0.0, b = 0.0;
  30. SafePointer<float> source = sound_getSafePointer(this->buffer);
  31. a = source[leftOffset];
  32. b = source[rightOffset];
  33. return b * ratio + a * (1.0 - ratio);
  34. }
  35. float sampleLinear_cyclic(double location, int32_t channel) {
  36. int32_t truncated = (int64_t)location;
  37. int32_t floor = truncated % sound_getSamplesPerChannel(this->buffer);
  38. int32_t ceiling = floor + 1; if (ceiling == sound_getSamplesPerChannel(this->buffer)) { ceiling = 0; }
  39. double ratio = location - truncated;
  40. return this->sampleLinear(floor, ceiling, ratio, channel);
  41. }
  42. float sampleLinear_clamped(double location, int32_t channel) {
  43. int32_t truncated = (int64_t)location;
  44. int32_t floor = truncated; if (floor >= sound_getSamplesPerChannel(this->buffer)) { floor = sound_getSamplesPerChannel(this->buffer) - 1; }
  45. int32_t ceiling = floor + 1; if (ceiling >= sound_getSamplesPerChannel(this->buffer)) { ceiling = sound_getSamplesPerChannel(this->buffer) - 1; }
  46. double ratio = location - truncated;
  47. return this->sampleLinear(floor, ceiling, ratio, channel);
  48. }
  49. void sampleMinMax(float &minimum, float &maximum, int startSample, int endSample, int channel) {
  50. if (startSample < 0) { startSample = 0; }
  51. if (endSample >= sound_getSamplesPerChannel(this->buffer)) { endSample = sound_getSamplesPerChannel(this->buffer) - 1; }
  52. if (channel < 0) { channel = 0; }
  53. if (channel >= sound_getChannelCount(this->buffer)) { channel = sound_getChannelCount(this->buffer) - 1; }
  54. int bufferIndex = startSample * sound_getChannelCount(this->buffer) + channel;
  55. SafePointer<float> source = sound_getSafePointer(this->buffer);
  56. for (int s = startSample; s <= endSample; s++) {
  57. minMax(minimum, maximum, source[bufferIndex]);
  58. bufferIndex += sound_getChannelCount(this->buffer);
  59. }
  60. }
  61. };
  62. List<Sound> sounds;
  63. static int createEmptySoundBuffer(const ReadableString &name, bool fromFile, int samplesPerChannel, int sampleRate, int channelCount) {
  64. if (samplesPerChannel < 1) { throwError("Cannot create sound buffer without and length!\n");}
  65. if (channelCount < 1) { throwError("Cannot create sound buffer without any channels!\n");}
  66. if (sampleRate < 1) { throwError("Cannot create sound buffer without any sample rate!\n");}
  67. return sounds.pushConstructGetIndex(name, fromFile, samplesPerChannel, channelCount, sampleRate);
  68. }
  69. int generateMonoSoundBuffer(const ReadableString &name, int samplesPerChannel, int sampleRate, std::function<float(double time)> generator) {
  70. int result = createEmptySoundBuffer(name, false, samplesPerChannel, sampleRate, 1);
  71. double time = 0.0;
  72. double soundStep = 1.0 / (double)sampleRate;
  73. SafePointer<float> target = sound_getSafePointer(sounds.last().buffer);
  74. for (int s = 0; s < samplesPerChannel; s++) {
  75. target[s] = generator(time);
  76. time += soundStep;
  77. }
  78. return result;
  79. }
  80. int loadSoundFromFile(const ReadableString &filename, bool mustExist) {
  81. // Try to reuse any previously instance of the file before accessing the file system
  82. for (int s = 0; s < sounds.length(); s++) {
  83. if (sounds[s].fromFile && string_match(sounds[s].name, filename)) {
  84. return s;
  85. }
  86. }
  87. return sounds.pushConstructGetIndex(Sound(sound_decode_RiffWave(file_loadBuffer(filename, mustExist)), filename, true));
  88. }
  89. int getSoundBufferCount() {
  90. return sounds.length();
  91. }
  92. EnvelopeSettings::EnvelopeSettings()
  93. : attack(0.0), decay(0.0), sustain(1.0), release(0.0), hold(0.0), rise(0.0), sustainedSmooth(0.0), releasedSmooth(0.0) {}
  94. EnvelopeSettings::EnvelopeSettings(double attack, double decay, double sustain, double release, double hold, double rise, double sustainedSmooth, double releasedSmooth)
  95. : attack(attack), decay(decay), sustain(sustain), release(release), hold(hold), rise(rise), sustainedSmooth(sustainedSmooth), releasedSmooth(releasedSmooth) {}
  96. static double closerLinear(double &ref, double goal, double maxStep) {
  97. double difference;
  98. if (ref + maxStep < goal) {
  99. difference = maxStep;
  100. ref += maxStep;
  101. } else if (ref - maxStep > goal) {
  102. difference = -maxStep;
  103. ref -= maxStep;
  104. } else {
  105. difference = goal - ref;
  106. ref = goal;
  107. }
  108. return difference;
  109. }
  110. struct Envelope {
  111. // Settings
  112. EnvelopeSettings envelopeSettings;
  113. // TODO: Add different types of smoothing filters and interpolation methods
  114. // Dynamic
  115. int state = 0;
  116. double currentVolume = 0.0, currentGoal = 0.0, releaseVolume = 0.0, timeSinceChange = 0.0;
  117. bool lastSustained = true;
  118. Envelope(const EnvelopeSettings &envelopeSettings)
  119. : envelopeSettings(envelopeSettings) {
  120. // Avoiding division by zero using very short fades
  121. if (this->envelopeSettings.attack < shortestTime) { this->envelopeSettings.attack = shortestTime; }
  122. if (this->envelopeSettings.hold < shortestTime) { this->envelopeSettings.hold = shortestTime; }
  123. if (this->envelopeSettings.decay < shortestTime) { this->envelopeSettings.decay = shortestTime; }
  124. if (this->envelopeSettings.release < shortestTime) { this->envelopeSettings.release = shortestTime; }
  125. }
  126. double getVolume(bool sustained, double seconds) {
  127. if (sustained) {
  128. if (state == 0) {
  129. // Attack
  130. this->currentGoal += seconds / this->envelopeSettings.attack;
  131. if (this->currentGoal > 1.0) {
  132. this->currentGoal = 1.0;
  133. state = 1; this->timeSinceChange = 0.0;
  134. }
  135. } else if (state == 1) {
  136. // Hold
  137. if (this->timeSinceChange < this->envelopeSettings.hold) {
  138. this->currentGoal = 1.0;
  139. } else {
  140. state = 2; this->timeSinceChange = 0.0;
  141. }
  142. } else if (state == 2) {
  143. // Decay
  144. this->currentGoal += (this->envelopeSettings.sustain - 1.0) * seconds / this->envelopeSettings.decay;
  145. if (this->currentGoal < this->envelopeSettings.sustain) {
  146. this->currentGoal = this->envelopeSettings.sustain;
  147. state = 3; this->timeSinceChange = 0.0;
  148. }
  149. } else if (state == 3) {
  150. // Sustain / rise
  151. this->currentGoal += this->envelopeSettings.rise * seconds / this->envelopeSettings.decay;
  152. if (this->currentGoal < 0.0) {
  153. this->currentGoal = 0.0;
  154. } else if (this->currentGoal > 1.0) {
  155. this->currentGoal = 1.0;
  156. }
  157. }
  158. } else {
  159. // Release
  160. if (this->lastSustained) {
  161. this->releaseVolume = this->currentGoal;
  162. }
  163. // Linear release, using releaseVolume to calculate the slope needed for the current release time
  164. this->currentGoal -= this->releaseVolume * seconds / this->envelopeSettings.release;
  165. if (this->currentGoal < 0.0) {
  166. this->currentGoal = 0.0;
  167. }
  168. this->lastSustained = false;
  169. }
  170. double smooth = sustained ? this->envelopeSettings.sustainedSmooth : this->envelopeSettings.releasedSmooth;
  171. if (smooth > 0.0) {
  172. // Move faster to the goal the further away it is
  173. double change = seconds / smooth;
  174. if (change > 1.0) { change = 1.0; }
  175. double keep = 1.0 - change;
  176. this->currentVolume = this->currentVolume * keep + this->currentGoal * change;
  177. // Move slowly towards the goal with a fixed speed to finally reach zero and stop sampling the sound
  178. closerLinear(this->currentVolume, this->currentGoal, seconds * 0.01);
  179. } else {
  180. this->currentVolume = this->currentGoal;
  181. }
  182. this->timeSinceChange += seconds;
  183. return this->currentVolume;
  184. }
  185. bool done() {
  186. return this->currentVolume <= 0.0000000001 && !this->lastSustained;
  187. }
  188. };
  189. // Currently playing sounds
  190. struct Player {
  191. // Unique identifier
  192. int64_t playerID;
  193. // Assigned from instrument
  194. int soundIndex;
  195. Envelope envelope;
  196. bool repeat;
  197. double leftVolume, rightVolume;
  198. double speed; // TODO: Use for playing with interpolation
  199. double location = 0; // Floating sample index
  200. bool sustained = true; // If the sound is still being generated
  201. Player(int64_t playerID, int soundIndex, bool repeat, double leftVolume, double rightVolume, double speed, const EnvelopeSettings &envelopeSettings)
  202. : playerID(playerID), soundIndex(soundIndex), envelope(envelopeSettings), repeat(repeat), leftVolume(leftVolume), rightVolume(rightVolume), speed(speed) {}
  203. };
  204. List<Player> players;
  205. int64_t nextPlayerID = 0;
  206. int playSound(int soundIndex, bool repeat, double leftVolume, double rightVolume, double speed, const EnvelopeSettings &envelopeSettings) {
  207. int result;
  208. soundMutex.lock();
  209. result = nextPlayerID;
  210. players.pushConstruct(nextPlayerID, soundIndex, repeat, leftVolume, rightVolume, speed, envelopeSettings);
  211. nextPlayerID++;
  212. soundMutex.unlock();
  213. return result;
  214. }
  215. int playSound(int soundIndex, bool repeat, double leftVolume, double rightVolume, double speed) {
  216. return playSound(soundIndex, repeat, leftVolume, rightVolume, speed, EnvelopeSettings());
  217. }
  218. static int findSound(int64_t playerID) {
  219. for (int p = 0; p < players.length(); p++) {
  220. if (players[p].playerID == playerID) {
  221. return p;
  222. }
  223. }
  224. return -1;
  225. }
  226. void releaseSound(int64_t playerID) {
  227. if (playerID != -1) {
  228. soundMutex.lock();
  229. int index = findSound(playerID);
  230. if (index > -1) {
  231. players[index].sustained = false;;
  232. }
  233. soundMutex.unlock();
  234. }
  235. }
  236. void stopSound(int64_t playerID) {
  237. if (playerID != -1) {
  238. soundMutex.lock();
  239. int index = findSound(playerID);
  240. if (index > -1) {
  241. players.remove(index);
  242. }
  243. soundMutex.unlock();
  244. }
  245. }
  246. void stopAllSounds() {
  247. soundMutex.lock();
  248. players.clear();
  249. soundMutex.unlock();
  250. }
  251. #define PREPARE_SAMPLE \
  252. double envelope = player->envelope.getVolume(player->sustained, outputSoundStep);
  253. #define NEXT_SAMPLE_CYCLIC \
  254. player->location += sampleStep; \
  255. if (player->location >= sourceSampleCount) { \
  256. player->location -= sourceSampleCount; \
  257. } \
  258. if (player->envelope.done()) { \
  259. players.remove(p); \
  260. break; \
  261. }
  262. #define NEXT_SAMPLE_ONCE \
  263. player->location += sampleStep; \
  264. if (player->location >= sourceSampleCount) { \
  265. players.remove(p); \
  266. break; \
  267. } \
  268. if (player->envelope.done()) { \
  269. players.remove(p); \
  270. break; \
  271. }
  272. void sound_initialize() {
  273. // Start a worker thread mixing sounds in realtime
  274. std::function<void()> task = []() {
  275. sound_streamToSpeakers(outputChannels, outputSampleRate, [](SafePointer<float> target, int requestedSamples) -> bool {
  276. // Anyone wanting to change the played sounds from another thread will have to wait until this section has finished processing
  277. soundMutex.lock();
  278. // TODO: Create a graph of filters for different instruments
  279. // TODO: Let the output buffer be just another sound buffer, so that a reusable function can stream to sections of larger sound buffers
  280. for (int p = players.length() - 1; p >= 0; p--) {
  281. Player *player = &(players[p]);
  282. int soundIndex = player->soundIndex;
  283. Sound *sound = &(sounds[soundIndex]);
  284. int sourceSampleCount = sound_getSamplesPerChannel(sound->buffer);
  285. double sampleStep = player->speed * sound_getSampleRate(sound->buffer) * outputSoundStep;
  286. if (player->repeat) {
  287. if (sound_getChannelCount(sound->buffer) == 1) { // Mono source
  288. for (int t = 0; t < requestedSamples; t++) {
  289. PREPARE_SAMPLE
  290. float monoSource = sound->sampleLinear_cyclic(player->location, 0) * envelope;
  291. target[t * outputChannels + 0] += monoSource * player->leftVolume;
  292. target[t * outputChannels + 1] += monoSource * player->rightVolume;
  293. NEXT_SAMPLE_CYCLIC
  294. }
  295. } else if (sound_getChannelCount(sound->buffer) == 2) { // Stereo source
  296. for (int t = 0; t < requestedSamples; t++) {
  297. PREPARE_SAMPLE
  298. target[t * outputChannels + 0] += sound->sampleLinear_cyclic(player->location, 0) * envelope * player->leftVolume;
  299. target[t * outputChannels + 1] += sound->sampleLinear_cyclic(player->location, 1) * envelope * player->rightVolume;
  300. NEXT_SAMPLE_CYCLIC
  301. }
  302. }
  303. } else {
  304. if (sound_getChannelCount(sound->buffer) == 1) { // Mono source
  305. for (int t = 0; t < requestedSamples; t++) {
  306. PREPARE_SAMPLE
  307. float monoSource = sound->sampleLinear_clamped(player->location, 0) * envelope;
  308. target[t * outputChannels + 0] += monoSource * player->leftVolume;
  309. target[t * outputChannels + 1] += monoSource * player->rightVolume;
  310. NEXT_SAMPLE_ONCE
  311. }
  312. } else if (sound_getChannelCount(sound->buffer) == 2) { // Stereo source
  313. for (int t = 0; t < requestedSamples; t++) {
  314. PREPARE_SAMPLE
  315. target[t * outputChannels + 0] += sound->sampleLinear_clamped(player->location, 0) * envelope * player->leftVolume;
  316. target[t * outputChannels + 1] += sound->sampleLinear_clamped(player->location, 1) * envelope * player->rightVolume;
  317. NEXT_SAMPLE_ONCE
  318. }
  319. }
  320. }
  321. }
  322. soundMutex.unlock();
  323. return soundRunning;
  324. });
  325. };
  326. soundFuture = std::async(std::launch::async, task);
  327. }
  328. void sound_terminate() {
  329. if (soundRunning) {
  330. soundRunning = false;
  331. if (soundFuture.valid()) {
  332. soundFuture.wait();
  333. }
  334. }
  335. }
  336. void drawEnvelope(ImageRgbaU8 target, const IRect &region, const EnvelopeSettings &envelopeSettings, double releaseTime, double viewTime) {
  337. int top = region.top();
  338. int bottom = region.bottom() - 1;
  339. Envelope envelope = Envelope(envelopeSettings);
  340. double secondsPerPixel = viewTime / region.width();
  341. draw_rectangle(target, region, ColorRgbaI32(0, 0, 0, 255));
  342. draw_rectangle(target, IRect(region.left(), region.top(), region.width() * (releaseTime / viewTime), region.height() / 8), ColorRgbaI32(0, 128, 128, 255));
  343. int oldHardY = bottom;
  344. for (int s = 0; s < region.width(); s++) {
  345. int x = s + region.left();
  346. double time = s * secondsPerPixel;
  347. double smoothLevel = envelope.getVolume(time < releaseTime, secondsPerPixel);
  348. double hardLevel = envelope.currentGoal;
  349. if (envelope.done()) {
  350. draw_line(target, x, top, x, (top * 7 + bottom) / 8, ColorRgbaI32(128, 0, 0, 255));
  351. } else {
  352. draw_line(target, x, (top * smoothLevel) + (bottom * (1.0 - smoothLevel)), x, bottom, ColorRgbaI32(64, 64, 0, 255));
  353. int hardY = (top * hardLevel) + (bottom * (1.0 - hardLevel));
  354. draw_line(target, x, oldHardY, x, hardY, ColorRgbaI32(255, 255, 255, 255));
  355. oldHardY = hardY;
  356. }
  357. }
  358. }
  359. void drawSound(dsr::ImageRgbaU8 target, const dsr::IRect &region, int soundIndex, bool selected) {
  360. draw_rectangle(target, region, selected ? ColorRgbaI32(128, 255, 128, 255) : ColorRgbaI32(40, 40, 40, 255));
  361. Sound *sound = &(sounds[soundIndex]);
  362. int innerHeight = region.height() / sound_getChannelCount(sound->buffer);
  363. ColorRgbaI32 foreColor = selected ? ColorRgbaI32(200, 255, 200, 255) : ColorRgbaI32(200, 200, 200, 255);
  364. for (int c = 0; c < sound_getChannelCount(sound->buffer); c++) {
  365. IRect innerBound = IRect(region.left() + 1, region.top() + 1, region.width() - 2, innerHeight - 2);
  366. draw_rectangle(target, innerBound, selected ? ColorRgbaI32(0, 0, 0, 255) : ColorRgbaI32(20, 20, 20, 255));
  367. double strideX = ((double)sound_getSamplesPerChannel(sound->buffer) - 1.0) / (double)innerBound.width();
  368. double scale = innerBound.height() * 0.5;
  369. double center = innerBound.top() + scale;
  370. draw_line(target, innerBound.left(), center, innerBound.right() - 1, center, ColorRgbaI32(0, 0, 255, 255));
  371. if (strideX > 1.0) {
  372. double startSample = 0.0;
  373. double endSample = strideX;
  374. for (int x = innerBound.left(); x < innerBound.right(); x++) {
  375. float minimum = 1.0, maximum = -1.0;
  376. // TODO: Switch between min-max sampling (denser) and linear interpolation (sparser)
  377. sound->sampleMinMax(minimum, maximum, (int)startSample, (int)endSample, c);
  378. draw_line(target, x, center - (minimum * scale), x, center - (maximum * scale), foreColor);
  379. startSample = endSample;
  380. endSample = endSample + strideX;
  381. }
  382. } else {
  383. double sampleX = 0.0;
  384. for (int x = innerBound.left(); x < innerBound.right(); x++) {
  385. float valueLeft = sound->sampleLinear_clamped(sampleX, c);
  386. sampleX += strideX;
  387. float valueRight = sound->sampleLinear_clamped(sampleX, c);
  388. draw_line(target, x, center - (valueLeft * scale), x, center - (valueRight * scale), foreColor);
  389. }
  390. }
  391. }
  392. font_printLine(target, font_getDefault(), sound->name, IVector2D(region.left() + 5, region.top() + 5), foreColor);
  393. }
  394. }