soundAPI.h 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. 
  2. #ifndef DFPSR_SOUND_API
  3. #define DFPSR_SOUND_API
  4. #include "bufferAPI.h"
  5. #include "stringAPI.h"
  6. namespace dsr {
  7. // 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.
  8. // The sound_streamToSpeakers function returns false if the backend could not be created, and true iff the backend completed all work and terminated safely.
  9. // Channels: The number of virtual speakers to send samples to.
  10. // How this is mapped to physical speakers depends on the system, because surround speakers may choose to play mono and stereo sound using only the front speakers.
  11. // sampleRate: The number of ticks per second for each channel.
  12. // soundOutput: A callback that requests length number of ticks generated by the sound engine and written in a packed format into the target array.
  13. // The soundOutput function returns true iff the audio backend should keep fetching sound samples, and false iff the engine is done and ready for the call to sound_streamToSpeakers to return.
  14. // target: The target array should be filled with sound samples in the -1.0f to 1.0f range, in indices from 0 to (length * channels) - 1.
  15. // The audio backend is responsible for converting the 32-bit float samples into a bit-depth chosen by the backend.
  16. // The backend is supposed to padd the SafePointer's range to at least be divisible by DSR_FLOAT_ALIGNMENT, which allow using both X vectors and F vectors.
  17. // The F vector can be larger than the X vector if building for a SIMD extension that only supports the widest vector length for floating-point operations.
  18. // Padding elements will not add to the time passed in the sound engine, for only played elements increment time.
  19. // length: The number of samples per channel. The total number of elements to write into target is channels * length.
  20. // How to use:
  21. // Call sound_streamToSpeakers with desired channels and sampleRate from a separate thread.
  22. // Handle callbacks to soundOutput by feeding the next packed sound samples and letting it return false when done.
  23. // Close the thread and let the sound engine clean up resources.
  24. bool sound_streamToSpeakers(int32_t channels, int32_t sampleRate, std::function<bool(dsr::SafePointer<float> target, int32_t length)> soundOutput);
  25. // Wrapper for sound_streamToSpeakers to allow working with a fixed size period for better determinism across different hardware.
  26. // The target elements should be filled for indices 0 to (periodSamplesPerChannel * channels) - 1
  27. // This allow using SIMD vectorization with a perfectly aligned period size without wasting any padding, even if the hardware's period size is an odd number.
  28. // A fixed period can also be used for perfect timing when playing music.
  29. bool sound_streamToSpeakers_fixed(int32_t channels, int32_t sampleRate, int32_t periodSamplesPerChannel, std::function<bool(dsr::SafePointer<float> target)> soundOutput);
  30. // A sound buffer with packed channels of 32-bit floats.
  31. // The duration in seconds equals samplesPerChannel / sampleRate
  32. struct SoundBuffer {
  33. Buffer impl_samples; // The packed samples.
  34. uint32_t impl_samplesPerChannel = 0u; // Number of samples per channel.
  35. uint32_t impl_channelCount = 0u; // Number of channels packed into the sound format.
  36. uint32_t impl_sampleRate = 0u; // How many samples each channel will play per second.
  37. SoundBuffer(uint32_t samplesPerChannel, uint32_t channelCount, uint32_t sampleRate);
  38. SoundBuffer() {}
  39. };
  40. inline SoundBuffer sound_create(uint32_t samplesPerChannel, uint32_t channelCount, uint32_t sampleRate) { return SoundBuffer(samplesPerChannel, channelCount, sampleRate); }
  41. inline bool sound_exists(const SoundBuffer &sound) { return sound.impl_samples.isNotNull(); }
  42. inline int32_t sound_getSamplesPerChannel(const SoundBuffer &sound) { return sound.impl_samplesPerChannel; }
  43. inline int32_t sound_getChannelCount(const SoundBuffer &sound) { return sound.impl_channelCount; }
  44. inline int32_t sound_getSampleRate(const SoundBuffer &sound) { return sound.impl_sampleRate; }
  45. inline SafePointer<float> sound_getSafePointer(const SoundBuffer &sound) { return buffer_getSafeData<float>(sound.impl_samples, "Sound buffer"); }
  46. SoundBuffer sound_generate_function(uint32_t samplesPerChannel, uint32_t channelCount, uint32_t sampleRate, std::function<float(double time, uint32_t channelIndex)> generator);
  47. enum class RiffWaveFormat {
  48. RawU8,
  49. RawI16,
  50. RawI24,
  51. RawI32
  52. // Floating-point sounds can currently be loaded but not saved.
  53. };
  54. // TODO: Implement random dither patterns?
  55. enum class RoundingMethod {
  56. Truncate,
  57. Nearest
  58. };
  59. // Encodes a Riff wave file into a file buffer.
  60. Buffer sound_encode_RiffWave(const SoundBuffer &sound, RiffWaveFormat format, RoundingMethod roundingMethod = RoundingMethod::Nearest);
  61. // Decodes a RIFF wave file from memory in file buffer and returns the sound as a packed 32-bit float sound in the -1 to +1 range.
  62. SoundBuffer sound_decode_RiffWave(const Buffer &fileBuffer);
  63. SoundBuffer sound_load(const ReadableString& filename, bool mustExist = true);
  64. // Save the sound buffer to the path specified by filename and return true iff the operation was successful.
  65. // The file extension is case insensitive after the last dot in filename.
  66. // Accepted file extensions:
  67. // *.wav
  68. // If mustWork is true, an exception will be raised on failure.
  69. // If mustWork is false, failure will return false.
  70. bool sound_save(const ReadableString& filename, const SoundBuffer &sound, bool mustWork = true);
  71. // When you want to select a spefific version of the RIFF wave format.
  72. bool sound_save_RiffWave(const ReadableString& filename, const SoundBuffer &sound, RiffWaveFormat format, RoundingMethod roundingMethod = RoundingMethod::Nearest, bool mustWork = true);
  73. // TODO:
  74. // * Resample sound buffers in a different speed, so that tones are generated in advance.
  75. // Limit sound buffers to 2^32 total samples (17 GB with 27 hours in a single sound) and try to use gather functions.
  76. /*
  77. float sampleLinear(int32_t leftIndex, int32_t rightIndex, double ratio, int32_t channel) {
  78. int64_t leftOffset = leftIndex * sound_getChannelCount(this->buffer) + channel;
  79. int64_t rightOffset = rightIndex * sound_getChannelCount(this->buffer) + channel;
  80. float a = 0.0, b = 0.0;
  81. SafePointer<float> source = sound_getSafePointer(this->buffer);
  82. a = source[leftOffset];
  83. b = source[rightOffset];
  84. return b * ratio + a * (1.0 - ratio);
  85. }
  86. float sampleLinear_cyclic(double location, int32_t channel) {
  87. int32_t truncated = (int64_t)location;
  88. int32_t floor = truncated % sound_getSamplesPerChannel(this->buffer);
  89. int32_t ceiling = floor + 1; if (ceiling == sound_getSamplesPerChannel(this->buffer)) { ceiling = 0; }
  90. double ratio = location - truncated;
  91. return this->sampleLinear(floor, ceiling, ratio, channel);
  92. }
  93. float sampleLinear_clamped(double location, int32_t channel) {
  94. int32_t truncated = (int64_t)location;
  95. int32_t floor = truncated; if (floor >= sound_getSamplesPerChannel(this->buffer)) { floor = sound_getSamplesPerChannel(this->buffer) - 1; }
  96. int32_t ceiling = floor + 1; if (ceiling >= sound_getSamplesPerChannel(this->buffer)) { ceiling = sound_getSamplesPerChannel(this->buffer) - 1; }
  97. double ratio = location - truncated;
  98. return this->sampleLinear(floor, ceiling, ratio, channel);
  99. }
  100. */
  101. // * Repeat sound buffers along time and channels for fast playback with a memory copy.
  102. // Balance shifts and random variations can be applied after the duplication to make use of all the memory.
  103. // * Convert between power ratios and decibels.
  104. // * Compress sounds using a bruteforce search for patterns.
  105. // Start with a low-pass filter separating high and low frequencies.
  106. // Analyze local minima and maxima to initialize an initial guess.
  107. // Compute error gradients to adjust frequencies, offsets and amplitudes.
  108. // Repeat for the higher frequencies until all tones have been matched and only some noise remains to compress or discard.
  109. }
  110. #endif