#ifndef DFPSR_SOUND_API #define DFPSR_SOUND_API #include "bufferAPI.h" #include "stringAPI.h" namespace dsr { // 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 sound_streamToSpeakers function returns false if the backend could not be created, and true iff the backend completed all work and terminated safely. // Channels: The number of virtual speakers to send samples to. // 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. // sampleRate: The number of ticks per second for each channel. // soundOutput: A callback that requests length number of ticks generated by the sound engine and written in a packed format into the target array. // 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. // 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. // The audio backend is responsible for converting the 32-bit float samples into a bit-depth chosen by the backend. // 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. // 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. // Padding elements will not add to the time passed in the sound engine, for only played elements increment time. // length: The number of samples per channel. The total number of elements to write into target is channels * length. // How to use: // Call sound_streamToSpeakers with desired channels and sampleRate from a separate thread. // Handle callbacks to soundOutput by feeding the next packed sound samples and letting it return false when done. // Close the thread and let the sound engine clean up resources. bool sound_streamToSpeakers(int32_t channels, int32_t sampleRate, std::function target, int32_t length)> soundOutput); // Wrapper for sound_streamToSpeakers to allow working with a fixed size period for better determinism across different hardware. // The target elements should be filled for indices 0 to (periodSamplesPerChannel * channels) - 1 // 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. // A fixed period can also be used for perfect timing when playing music. // Pre-condition: periodSamplesPerChannel must be a power of two. bool sound_streamToSpeakers_fixed(int32_t channels, int32_t sampleRate, int32_t periodSamplesPerChannel, std::function target)> soundOutput); // A sound buffer with packed channels of 32-bit floats. // The duration in seconds equals samplesPerChannel / sampleRate struct SoundBuffer { Buffer impl_samples; // The packed samples. uint32_t impl_samplesPerChannel = 0u; // Number of samples per channel. uint32_t impl_channelCount = 0u; // Number of channels packed into the sound format. uint32_t impl_sampleRate = 0u; // How many samples each channel will play per second. SoundBuffer(uint32_t samplesPerChannel, uint32_t channelCount, uint32_t sampleRate); SoundBuffer() {} }; inline SoundBuffer sound_create(uint32_t samplesPerChannel, uint32_t channelCount, uint32_t sampleRate) { return SoundBuffer(samplesPerChannel, channelCount, sampleRate); } inline bool sound_exists(const SoundBuffer &sound) { return sound.impl_samples.isNotNull(); } inline int32_t sound_getSamplesPerChannel(const SoundBuffer &sound) { return sound.impl_samplesPerChannel; } inline int32_t sound_getChannelCount(const SoundBuffer &sound) { return sound.impl_channelCount; } inline int32_t sound_getSampleRate(const SoundBuffer &sound) { return sound.impl_sampleRate; } inline SafePointer sound_getSafePointer(const SoundBuffer &sound) { return buffer_getSafeData(sound.impl_samples, "Sound buffer"); } SoundBuffer sound_generate_function(uint32_t samplesPerChannel, uint32_t channelCount, uint32_t sampleRate, std::function generator); enum class RiffWaveFormat { RawU8, RawI16, RawI24, RawI32 // Floating-point sounds can currently be loaded but not saved. }; // TODO: Implement random dither patterns? enum class RoundingMethod { Truncate, Nearest }; // Encodes a Riff wave file into a file buffer. Buffer sound_encode_RiffWave(const SoundBuffer &sound, RiffWaveFormat format, RoundingMethod roundingMethod = RoundingMethod::Nearest); // 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. SoundBuffer sound_decode_RiffWave(const Buffer &fileBuffer); SoundBuffer sound_load(const ReadableString& filename, bool mustExist = true); // Save the sound buffer to the path specified by filename and return true iff the operation was successful. // The file extension is case insensitive after the last dot in filename. // Accepted file extensions: // *.wav // If mustWork is true, an exception will be raised on failure. // If mustWork is false, failure will return false. bool sound_save(const ReadableString& filename, const SoundBuffer &sound, bool mustWork = true); // When you want to select a spefific version of the RIFF wave format. bool sound_save_RiffWave(const ReadableString& filename, const SoundBuffer &sound, RiffWaveFormat format, RoundingMethod roundingMethod = RoundingMethod::Nearest, bool mustWork = true); // TODO: // * Resample sound buffers in a different speed, so that tones are generated in advance. // Limit sound buffers to 2^32 total samples (17 GB with 27 hours in a single sound) and try to use gather functions. /* float sampleLinear(int32_t leftIndex, int32_t rightIndex, double ratio, int32_t channel) { int64_t leftOffset = leftIndex * sound_getChannelCount(this->buffer) + channel; int64_t rightOffset = rightIndex * sound_getChannelCount(this->buffer) + channel; float a = 0.0, b = 0.0; SafePointer source = sound_getSafePointer(this->buffer); a = source[leftOffset]; b = source[rightOffset]; return b * ratio + a * (1.0 - ratio); } float sampleLinear_cyclic(double location, int32_t channel) { int32_t truncated = (int64_t)location; int32_t floor = truncated % sound_getSamplesPerChannel(this->buffer); int32_t ceiling = floor + 1; if (ceiling == sound_getSamplesPerChannel(this->buffer)) { ceiling = 0; } double ratio = location - truncated; return this->sampleLinear(floor, ceiling, ratio, channel); } float sampleLinear_clamped(double location, int32_t channel) { int32_t truncated = (int64_t)location; int32_t floor = truncated; if (floor >= sound_getSamplesPerChannel(this->buffer)) { floor = sound_getSamplesPerChannel(this->buffer) - 1; } int32_t ceiling = floor + 1; if (ceiling >= sound_getSamplesPerChannel(this->buffer)) { ceiling = sound_getSamplesPerChannel(this->buffer) - 1; } double ratio = location - truncated; return this->sampleLinear(floor, ceiling, ratio, channel); } */ // * Repeat sound buffers along time and channels for fast playback with a memory copy. // Balance shifts and random variations can be applied after the duplication to make use of all the memory. // * Convert between power ratios and decibels. // * Compress sounds using a bruteforce search for patterns. // Start with a low-pass filter separating high and low frequencies. // Analyze local minima and maxima to initialize an initial guess. // Compute error gradients to adjust frequencies, offsets and amplitudes. // Repeat for the higher frequencies until all tones have been matched and only some noise remains to compress or discard. } #endif