
#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<bool(dsr::SafePointer<float> 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<bool(dsr::SafePointer<float> 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<float> sound_getSafePointer(const SoundBuffer &sound) { return buffer_getSafeData<float>(sound.impl_samples, "Sound buffer"); }

	SoundBuffer sound_generate_function(uint32_t samplesPerChannel, uint32_t channelCount, uint32_t sampleRate, std::function<float(double time, uint32_t channelIndex)> 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<float> 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
