
#include "HrtfData.h"
#include "Endianness.h"
#include <algorithm>
#include <cmath>

typedef struct {
	uint8_t bytes[3];
} sample_int24_t;

const double pi = 3.1415926535897932385;

template <typename T>
void read_stream(std::istream& stream, T& value) {
	stream.read(reinterpret_cast<std::istream::char_type*>(&value), sizeof(value));
	from_little_endian_inplace(value);
}

HrtfData::HrtfData(std::istream& stream) {
	const char required_magic00[] = { 'M', 'i', 'n', 'P', 'H', 'R', '0', '0' };
	const char required_magic01[] = { 'M', 'i', 'n', 'P', 'H', 'R', '0', '1' };
	const char required_magic02[] = { 'M', 'i', 'n', 'P', 'H', 'R', '0', '2' };
	const char required_magic03[] = { 'M', 'i', 'n', 'P', 'H', 'R', '0', '3' };
	char actual_magic[sizeof(required_magic03) / sizeof(required_magic03[0])];

	stream.read(actual_magic, sizeof(actual_magic));
	if(std::equal(std::begin(required_magic03), std::end(required_magic03), std::begin(actual_magic), std::end(actual_magic))) {
		LoadHrtf03(stream);
	} else if(std::equal(std::begin(required_magic02), std::end(required_magic02), std::begin(actual_magic), std::end(actual_magic))) {
		LoadHrtf02(stream);
	} else if(std::equal(std::begin(required_magic01), std::end(required_magic01), std::begin(actual_magic), std::end(actual_magic))) {
		LoadHrtf01(stream);
	} else if(std::equal(std::begin(required_magic00), std::end(required_magic00), std::begin(actual_magic), std::end(actual_magic))) {
		LoadHrtf00(stream);
	} else {
		throw std::logic_error("Bad file format.");
	}
}

void HrtfData::LoadHrtf03(std::istream& stream) {
	// const uint8_t ChanType_LeftOnly{0};
	const uint8_t ChanType_LeftRight{ 1 };

	uint32_t sample_rate;
	uint8_t channel_type;
	uint8_t impulse_response_length;
	uint8_t distances_count;

	read_stream(stream, sample_rate);
	read_stream(stream, channel_type);
	read_stream(stream, impulse_response_length);
	read_stream(stream, distances_count);

	if(!stream || stream.eof()) {
		throw std::logic_error("Failed reading file.");
	}

	if(channel_type > ChanType_LeftRight) {
		throw std::logic_error("Invalid channel format.");
	}

	int channel_count = channel_type == ChanType_LeftRight ? 2 : 1;

	std::vector<DistanceData> distances(distances_count);

	for(uint8_t i = 0; i < distances_count; i++) {
		uint16_t distance;
		read_stream(stream, distance);
		distances[i].distance = float(distance) / 1000.0f;

		uint8_t elevations_count;
		read_stream(stream, elevations_count);
		distances[i].elevations.resize(elevations_count);

		if(!stream || stream.eof()) {
			throw std::logic_error("Failed reading file.");
		}

		for(uint8_t j = 0; j < elevations_count; j++) {
			uint8_t azimuth_count;
			read_stream(stream, azimuth_count);
			distances[i].elevations[j].azimuths.resize(azimuth_count);
		}

		if(!stream || stream.eof()) {
			throw std::logic_error("Failed reading file.");
		}
	}

	const float normalization_factor = 1.0f / 8388608.0f;

	for(auto& distance : distances) {
		for(auto& elevation : distance.elevations) {
			for(auto& azimuth : elevation.azimuths) {
				azimuth.impulse_response.resize(impulse_response_length * channel_count);
				for(auto& sample : azimuth.impulse_response) {
					union {
						sample_int24_t sample;
						int32_t sample_int;
					} sample_union;
					sample_union.sample_int = 0;
					read_stream(stream, sample_union.sample);
					sample_union.sample_int <<= 8;
					sample_union.sample_int >>= 8;
					sample = sample_union.sample_int * normalization_factor;
				}
			}
		}
	}

	if(!stream || stream.eof()) {
		throw std::logic_error("Failed reading file.");
	}

	uint8_t longest_delay = 0;
	for(auto& distance : distances) {
		for(auto& elevation : distance.elevations) {
			for(auto& azimuth : elevation.azimuths) {
				uint8_t delay;
				read_stream(stream, delay);
				azimuth.delay = delay;
				longest_delay = std::max(longest_delay, delay);
				if(channel_type == ChanType_LeftRight) {
					read_stream(stream, delay);
					azimuth.delay_right = delay;
					longest_delay = std::max(longest_delay, delay);
				}
			}
		}
	}

	if(!stream || stream.eof()) {
		throw std::logic_error("Failed reading file.");
	}

	std::sort(distances.begin(), distances.end(),
	          [](const DistanceData& lhs, const DistanceData& rhs) noexcept { return lhs.distance > rhs.distance; });

	m_distances = std::move(distances);
	m_channel_count = channel_count;
	m_response_length = impulse_response_length;
	m_sample_rate = sample_rate;
	m_longest_delay = longest_delay;
}

void HrtfData::LoadHrtf02(std::istream& stream) {
	// const uint8_t SampleType_S16{0};
	const uint8_t SampleType_S24{ 1 };
	// const uint8_t ChanType_LeftOnly{0};
	const uint8_t ChanType_LeftRight{ 1 };

	uint32_t sample_rate;
	uint8_t sample_type;
	uint8_t channel_type;
	uint8_t impulse_response_length;
	uint8_t distances_count;

	read_stream(stream, sample_rate);
	read_stream(stream, sample_type);
	read_stream(stream, channel_type);
	read_stream(stream, impulse_response_length);
	read_stream(stream, distances_count);

	if(!stream || stream.eof()) {
		throw std::logic_error("Failed reading file.");
	}

	if(sample_type > SampleType_S24) {
		throw std::logic_error("Invalid sample type.");
	}

	if(channel_type > ChanType_LeftRight) {
		throw std::logic_error("Invalid channel format.");
	}

	int channel_count = channel_type == ChanType_LeftRight ? 2 : 1;

	std::vector<DistanceData> distances(distances_count);

	for(uint8_t i = 0; i < distances_count; i++) {
		uint16_t distance;
		read_stream(stream, distance);
		distances[i].distance = float(distance) / 1000.0f;

		uint8_t elevations_count;
		read_stream(stream, elevations_count);
		distances[i].elevations.resize(elevations_count);

		if(!stream || stream.eof()) {
			throw std::logic_error("Failed reading file.");
		}

		for(uint8_t j = 0; j < elevations_count; j++) {
			uint8_t azimuth_count;
			read_stream(stream, azimuth_count);
			distances[i].elevations[j].azimuths.resize(azimuth_count);
		}

		if(!stream || stream.eof()) {
			throw std::logic_error("Failed reading file.");
		}
	}

	const float normalization_factor = (sample_type == SampleType_S24) ? 1.0f / 8388608.0f : 1.0f / 32768.0f;

	for(auto& distance : distances) {
		for(auto& elevation : distance.elevations) {
			for(auto& azimuth : elevation.azimuths) {
				azimuth.impulse_response.resize(impulse_response_length * channel_count);
				if(sample_type == SampleType_S24) {
					for(auto& sample : azimuth.impulse_response) {
						union {
							sample_int24_t sample;
							int32_t sample_int;
						} sample_union;
						sample_union.sample_int = 0;
						read_stream(stream, sample_union.sample);
						sample_union.sample_int <<= 8;
						sample_union.sample_int >>= 8;
						sample = sample_union.sample_int * normalization_factor;
					}
				} else {
					for(auto& sample : azimuth.impulse_response) {
						int16_t sample_from_file;
						read_stream(stream, sample_from_file);
						sample = sample_from_file * normalization_factor;
					}
				}
			}
		}
	}

	if(!stream || stream.eof()) {
		throw std::logic_error("Failed reading file.");
	}

	uint8_t longest_delay = 0;
	for(auto& distance : distances) {
		for(auto& elevation : distance.elevations) {
			for(auto& azimuth : elevation.azimuths) {
				uint8_t delay;
				read_stream(stream, delay);
				azimuth.delay = delay;
				longest_delay = std::max(longest_delay, delay);
				if(channel_type == ChanType_LeftRight) {
					read_stream(stream, delay);
					azimuth.delay_right = delay;
					longest_delay = std::max(longest_delay, delay);
				}
			}
		}
	}

	if(!stream || stream.eof()) {
		throw std::logic_error("Failed reading file.");
	}

	std::sort(distances.begin(), distances.end(),
	          [](const DistanceData& lhs, const DistanceData& rhs) noexcept { return lhs.distance > rhs.distance; });

	m_distances = std::move(distances);
	m_channel_count = channel_count;
	m_response_length = impulse_response_length;
	m_sample_rate = sample_rate;
	m_longest_delay = longest_delay;
}

void HrtfData::LoadHrtf01(std::istream& stream) {
	uint32_t sample_rate;
	uint8_t impulse_response_length;

	read_stream(stream, sample_rate);
	read_stream(stream, impulse_response_length);

	if(!stream || stream.eof()) {
		throw std::logic_error("Failed reading file.");
	}

	std::vector<DistanceData> distances(1);

	distances[0].distance = 1.0;

	uint8_t elevations_count;
	read_stream(stream, elevations_count);
	distances[0].elevations.resize(elevations_count);

	if(!stream || stream.eof()) {
		throw std::logic_error("Failed reading file.");
	}

	for(uint8_t i = 0; i < elevations_count; i++) {
		uint8_t azimuth_count;
		read_stream(stream, azimuth_count);
		distances[0].elevations[i].azimuths.resize(azimuth_count);
	}

	if(!stream || stream.eof()) {
		throw std::logic_error("Failed reading file.");
	}

	const float normalization_factor = 1.0f / 32768.0f;

	for(auto& elevation : distances[0].elevations) {
		for(auto& azimuth : elevation.azimuths) {
			azimuth.impulse_response.resize(impulse_response_length);
			for(auto& sample : azimuth.impulse_response) {
				int16_t sample_from_file;
				read_stream(stream, sample_from_file);
				sample = sample_from_file * normalization_factor;
			}
		}
	}

	if(!stream || stream.eof()) {
		throw std::logic_error("Failed reading file.");
	}

	uint8_t longest_delay = 0;
	for(auto& elevation : distances[0].elevations) {
		for(auto& azimuth : elevation.azimuths) {
			uint8_t delay;
			read_stream(stream, delay);
			delay <<= 2;
			azimuth.delay = delay;
			longest_delay = std::max(longest_delay, delay);
		}
	}

	if(!stream || stream.eof()) {
		throw std::logic_error("Failed reading file.");
	}

	m_distances = std::move(distances);
	m_channel_count = 1;
	m_response_length = impulse_response_length;
	m_sample_rate = sample_rate;
	m_longest_delay = longest_delay;
}

void HrtfData::LoadHrtf00(std::istream& stream) {
	uint32_t sample_rate;
	uint16_t impulse_response_count;
	uint16_t impulse_response_length;

	read_stream(stream, sample_rate);
	read_stream(stream, impulse_response_count);
	read_stream(stream, impulse_response_length);

	if(!stream || stream.eof()) {
		throw std::logic_error("Failed reading file.");
	}

	std::vector<DistanceData> distances(1);

	distances[0].distance = 1.0;

	uint8_t elevations_count;
	read_stream(stream, elevations_count);
	distances[0].elevations.resize(elevations_count);

	if(!stream || stream.eof()) {
		throw std::logic_error("Failed reading file.");
	}

	std::vector<uint16_t> irOffsets(elevations_count);

	for(uint8_t i = 0; i < elevations_count; i++) {
		read_stream(stream, irOffsets[i]);
	}

	if(!stream || stream.eof()) {
		throw std::logic_error("Failed reading file.");
	}

	for(size_t i = 1; i < elevations_count; i++) {
		if(irOffsets[i] <= irOffsets[i - 1]) {
			throw std::logic_error("Invalid elevation offset.");
		}
	}
	if(impulse_response_count <= irOffsets[elevations_count - 1]) {
		throw std::logic_error("Invalid elevation offset.");
	}

	for(size_t i = 1; i < elevations_count; i++) {
		distances[0].elevations[i - 1].azimuths.resize(irOffsets[i] - irOffsets[i - 1]);
	}
	distances[0].elevations[elevations_count - 1].azimuths.resize(impulse_response_count - irOffsets[elevations_count - 1]);

	const float normalization_factor = 1.0f / 32768.0f;

	for(auto& elevation : distances[0].elevations) {
		for(auto& azimuth : elevation.azimuths) {
			azimuth.impulse_response.resize(impulse_response_length);
			for(auto& sample : azimuth.impulse_response) {
				int16_t sample_from_file;
				read_stream(stream, sample_from_file);
				sample = sample_from_file * normalization_factor;
			}
		}
	}

	if(!stream || stream.eof()) {
		throw std::logic_error("Failed reading file.");
	}

	uint8_t longest_delay = 0;
	for(auto& elevation : distances[0].elevations) {
		for(auto& azimuth : elevation.azimuths) {
			uint8_t delay;
			read_stream(stream, delay);
			delay <<= 2;
			azimuth.delay = delay;
			longest_delay = std::max(longest_delay, delay);
		}
	}

	if(!stream || stream.eof()) {
		throw std::logic_error("Failed reading file.");
	}

	m_distances = std::move(distances);
	m_channel_count = 1;
	m_response_length = impulse_response_length;
	m_sample_rate = sample_rate;
	m_longest_delay = longest_delay;
}

void HrtfData::get_direction_data(angle_t elevation, angle_t azimuth, distance_t distance, uint32_t channel, DirectionData& ref_data) const {
	assert(elevation >= -angle_t(pi * 0.5));
	assert(elevation <= angle_t(pi * 0.5));
	assert(azimuth >= -angle_t(2.0 * pi));
	assert(azimuth <= angle_t(2.0 * pi));

	const float azimuth_mod = std::fmod(azimuth + angle_t(pi * 2.0), angle_t(pi * 2.0));

	size_t distance_index0 = 0;
	while(distance_index0 < m_distances.size() - 1 &&
	      m_distances[distance_index0].distance > distance) {
		distance_index0++;
	}
	const size_t distance_index1 = std::min(distance_index0 + 1, m_distances.size() - 1);
	const distance_t distance0 = m_distances[distance_index0].distance;
	const distance_t distance1 = m_distances[distance_index1].distance;
	const distance_t distance_delta = distance0 - distance1;
	const float distance_fractional_part = distance_delta ? (distance - distance1) / distance_delta : 0;

	const auto& elevations0 = m_distances[distance_index0].elevations;
	const auto& elevations1 = m_distances[distance_index1].elevations;

	const angle_t elevation_scaled0 = (elevation + angle_t(pi * 0.5)) * (elevations0.size() - 1) / angle_t(pi);
	const angle_t elevation_scaled1 = (elevation + angle_t(pi * 0.5)) * (elevations1.size() - 1) / angle_t(pi);
	const size_t elevation_index00 = static_cast<size_t>(elevation_scaled0);
	const size_t elevation_index10 = static_cast<size_t>(elevation_scaled1);
	const size_t elevation_index01 = std::min(elevation_index00 + 1, elevations0.size() - 1);
	const size_t elevation_index11 = std::min(elevation_index10 + 1, elevations1.size() - 1);

	const float elevation_fractional_part0 = std::fmod(elevation_scaled0, 1.0);
	const float elevation_fractional_part1 = std::fmod(elevation_scaled1, 1.0);

	const angle_t azimuth_scaled00 = azimuth_mod * elevations0[elevation_index00].azimuths.size() / angle_t(2 * pi);
	const size_t azimuth_index000 = static_cast<size_t>(azimuth_scaled00) % elevations0[elevation_index00].azimuths.size();
	const size_t azimuth_index001 = static_cast<size_t>(azimuth_scaled00 + 1) % elevations0[elevation_index00].azimuths.size();
	const float azimuth_fractional_part00 = std::fmod(azimuth_scaled00, 1.0);

	const angle_t azimuth_scaled10 = azimuth_mod * elevations1[elevation_index10].azimuths.size() / angle_t(2 * pi);
	const size_t azimuth_index100 = static_cast<size_t>(azimuth_scaled10) % elevations1[elevation_index10].azimuths.size();
	const size_t azimuth_index101 = static_cast<size_t>(azimuth_scaled10 + 1) % elevations1[elevation_index10].azimuths.size();
	const float azimuth_fractional_part10 = std::fmod(azimuth_scaled10, 1.0);

	const angle_t azimuth_scaled01 = azimuth_mod * elevations0[elevation_index01].azimuths.size() / angle_t(2 * pi);
	const size_t azimuth_index010 = static_cast<size_t>(azimuth_scaled01) % elevations0[elevation_index01].azimuths.size();
	const size_t azimuth_index011 = static_cast<size_t>(azimuth_scaled01 + 1) % elevations0[elevation_index01].azimuths.size();
	const float azimuth_fractional_part01 = std::fmod(azimuth_scaled01, 1.0);

	const angle_t azimuth_scaled11 = azimuth_mod * elevations1[elevation_index11].azimuths.size() / angle_t(2 * pi);
	const size_t azimuth_index110 = static_cast<size_t>(azimuth_scaled11) % elevations1[elevation_index11].azimuths.size();
	const size_t azimuth_index111 = static_cast<size_t>(azimuth_scaled11 + 1) % elevations1[elevation_index11].azimuths.size();
	const float azimuth_fractional_part11 = std::fmod(azimuth_scaled11, 1.0);

	const float blend_factor_000 = (1.0f - elevation_fractional_part0) * (1.0f - azimuth_fractional_part00) * distance_fractional_part;
	const float blend_factor_001 = (1.0f - elevation_fractional_part0) * azimuth_fractional_part00 * distance_fractional_part;
	const float blend_factor_010 = elevation_fractional_part0 * (1.0f - azimuth_fractional_part01) * distance_fractional_part;
	const float blend_factor_011 = elevation_fractional_part0 * azimuth_fractional_part01 * distance_fractional_part;

	const float blend_factor_100 = (1.0f - elevation_fractional_part1) * (1.0f - azimuth_fractional_part10) * (1.0f - distance_fractional_part);
	const float blend_factor_101 = (1.0f - elevation_fractional_part1) * azimuth_fractional_part10 * (1.0f - distance_fractional_part);
	const float blend_factor_110 = elevation_fractional_part1 * (1.0f - azimuth_fractional_part11) * (1.0f - distance_fractional_part);
	const float blend_factor_111 = elevation_fractional_part1 * azimuth_fractional_part11 * (1.0f - distance_fractional_part);

	delay_t delay0;
	delay_t delay1;

	if(channel == 0) {
		delay0 =
		elevations0[elevation_index00].azimuths[azimuth_index000].delay * blend_factor_000 + elevations0[elevation_index00].azimuths[azimuth_index001].delay * blend_factor_001 + elevations0[elevation_index01].azimuths[azimuth_index010].delay * blend_factor_010 + elevations0[elevation_index01].azimuths[azimuth_index011].delay * blend_factor_011;

		delay1 =
		elevations1[elevation_index10].azimuths[azimuth_index100].delay * blend_factor_100 + elevations1[elevation_index10].azimuths[azimuth_index101].delay * blend_factor_101 + elevations1[elevation_index11].azimuths[azimuth_index110].delay * blend_factor_110 + elevations1[elevation_index11].azimuths[azimuth_index111].delay * blend_factor_111;
	} else {
		delay0 =
		elevations0[elevation_index00].azimuths[azimuth_index000].delay_right * blend_factor_000 + elevations0[elevation_index00].azimuths[azimuth_index001].delay_right * blend_factor_001 + elevations0[elevation_index01].azimuths[azimuth_index010].delay_right * blend_factor_010 + elevations0[elevation_index01].azimuths[azimuth_index011].delay_right * blend_factor_011;

		delay1 =
		elevations1[elevation_index10].azimuths[azimuth_index100].delay_right * blend_factor_100 + elevations1[elevation_index10].azimuths[azimuth_index101].delay_right * blend_factor_101 + elevations1[elevation_index11].azimuths[azimuth_index110].delay_right * blend_factor_110 + elevations1[elevation_index11].azimuths[azimuth_index111].delay_right * blend_factor_111;
	}

	ref_data.delay = delay0 + delay1;

	if(ref_data.impulse_response.size() < m_response_length)
		ref_data.impulse_response.resize(m_response_length);

	for(size_t i = 0, j = channel; i < m_response_length; i++, j += m_channel_count) {
		float sample0 =
		elevations0[elevation_index00].azimuths[azimuth_index000].impulse_response[j] * blend_factor_000 + elevations0[elevation_index00].azimuths[azimuth_index001].impulse_response[j] * blend_factor_001 + elevations0[elevation_index01].azimuths[azimuth_index010].impulse_response[j] * blend_factor_010 + elevations0[elevation_index01].azimuths[azimuth_index011].impulse_response[j] * blend_factor_011;
		float sample1 =
		elevations1[elevation_index10].azimuths[azimuth_index100].impulse_response[j] * blend_factor_100 + elevations1[elevation_index10].azimuths[azimuth_index101].impulse_response[j] * blend_factor_101 + elevations1[elevation_index11].azimuths[azimuth_index110].impulse_response[j] * blend_factor_110 + elevations1[elevation_index11].azimuths[azimuth_index111].impulse_response[j] * blend_factor_111;

		ref_data.impulse_response[i] = sample0 + sample1;
	}
}

void HrtfData::get_direction_data(angle_t elevation, angle_t azimuth, distance_t distance, DirectionData& ref_data_left, DirectionData& ref_data_right) const {
	assert(elevation >= -angle_t(pi * 0.5));
	assert(elevation <= angle_t(pi * 0.5));
	assert(azimuth >= -angle_t(2.0 * pi));
	assert(azimuth <= angle_t(2.0 * pi));

	get_direction_data(elevation, azimuth, distance, 0, ref_data_left);
	if(m_channel_count == 1) {
		get_direction_data(elevation, -azimuth, distance, 0, ref_data_right);
	} else {
		get_direction_data(elevation, azimuth, distance, 1, ref_data_right);
	}
}

void HrtfData::sample_direction(angle_t elevation, angle_t azimuth, distance_t distance, uint32_t sample, uint32_t channel, float& value, float& delay) const {
	assert(elevation >= -angle_t(pi * 0.5));
	assert(elevation <= angle_t(pi * 0.5));
	assert(azimuth >= -angle_t(2.0 * pi));
	assert(azimuth <= angle_t(2.0 * pi));

	size_t distance_index0 = 0;
	while(distance_index0 < m_distances.size() - 1 &&
	      m_distances[distance_index0].distance > distance) {
		distance_index0++;
	}
	const size_t distance_index1 = std::min(distance_index0 + 1, m_distances.size() - 1);
	const distance_t distance0 = m_distances[distance_index0].distance;
	const distance_t distance1 = m_distances[distance_index1].distance;
	const distance_t distance_delta = distance0 - distance1;
	const float distance_fractional_part = distance_delta ? (distance - distance1) / distance_delta : 0;

	const auto& elevations0 = m_distances[distance_index0].elevations;
	const auto& elevations1 = m_distances[distance_index1].elevations;

	const float azimuth_mod = std::fmod(azimuth + angle_t(pi * 2.0), angle_t(pi * 2.0));

	const angle_t elevation_scaled0 = (elevation + angle_t(pi * 0.5)) * (elevations0.size() - 1) / angle_t(pi);
	const angle_t elevation_scaled1 = (elevation + angle_t(pi * 0.5)) * (elevations1.size() - 1) / angle_t(pi);
	const size_t elevation_index00 = static_cast<size_t>(elevation_scaled0);
	const size_t elevation_index10 = static_cast<size_t>(elevation_scaled1);
	const size_t elevation_index01 = std::min(elevation_index00 + 1, elevations0.size() - 1);
	const size_t elevation_index11 = std::min(elevation_index10 + 1, elevations1.size() - 1);

	const float elevation_fractional_part0 = std::fmod(elevation_scaled0, 1.0);
	const float elevation_fractional_part1 = std::fmod(elevation_scaled1, 1.0);

	const angle_t azimuth_scaled00 = azimuth_mod * elevations0[elevation_index00].azimuths.size() / angle_t(2 * pi);
	const size_t azimuth_index000 = static_cast<size_t>(azimuth_scaled00) % elevations0[elevation_index00].azimuths.size();
	const size_t azimuth_index001 = static_cast<size_t>(azimuth_scaled00 + 1) % elevations0[elevation_index00].azimuths.size();
	const float azimuth_fractional_part00 = std::fmod(azimuth_scaled00, 1.0);

	const angle_t azimuth_scaled10 = azimuth_mod * elevations1[elevation_index10].azimuths.size() / angle_t(2 * pi);
	const size_t azimuth_index100 = static_cast<size_t>(azimuth_scaled10) % elevations1[elevation_index10].azimuths.size();
	const size_t azimuth_index101 = static_cast<size_t>(azimuth_scaled10 + 1) % elevations1[elevation_index10].azimuths.size();
	const float azimuth_fractional_part10 = std::fmod(azimuth_scaled10, 1.0);

	const angle_t azimuth_scaled01 = azimuth_mod * elevations0[elevation_index01].azimuths.size() / angle_t(2 * pi);
	const size_t azimuth_index010 = static_cast<size_t>(azimuth_scaled01) % elevations0[elevation_index01].azimuths.size();
	const size_t azimuth_index011 = static_cast<size_t>(azimuth_scaled01 + 1) % elevations0[elevation_index01].azimuths.size();
	const float azimuth_fractional_part01 = std::fmod(azimuth_scaled01, 1.0);

	const angle_t azimuth_scaled11 = azimuth_mod * elevations1[elevation_index11].azimuths.size() / angle_t(2 * pi);
	const size_t azimuth_index110 = static_cast<size_t>(azimuth_scaled11) % elevations1[elevation_index11].azimuths.size();
	const size_t azimuth_index111 = static_cast<size_t>(azimuth_scaled11 + 1) % elevations1[elevation_index11].azimuths.size();
	const float azimuth_fractional_part11 = std::fmod(azimuth_scaled11, 1.0);

	const float blend_factor_000 = (1.0f - elevation_fractional_part0) * (1.0f - azimuth_fractional_part00) * distance_fractional_part;
	const float blend_factor_001 = (1.0f - elevation_fractional_part0) * azimuth_fractional_part00 * distance_fractional_part;
	const float blend_factor_010 = elevation_fractional_part0 * (1.0f - azimuth_fractional_part01) * distance_fractional_part;
	const float blend_factor_011 = elevation_fractional_part0 * azimuth_fractional_part01 * distance_fractional_part;

	const float blend_factor_100 = (1.0f - elevation_fractional_part1) * (1.0f - azimuth_fractional_part10) * (1.0f - distance_fractional_part);
	const float blend_factor_101 = (1.0f - elevation_fractional_part1) * azimuth_fractional_part10 * (1.0f - distance_fractional_part);
	const float blend_factor_110 = elevation_fractional_part1 * (1.0f - azimuth_fractional_part11) * (1.0f - distance_fractional_part);
	const float blend_factor_111 = elevation_fractional_part1 * azimuth_fractional_part11 * (1.0f - distance_fractional_part);

	float delay0;
	float delay1;

	if(channel == 0) {
		delay0 =
		elevations0[elevation_index00].azimuths[azimuth_index000].delay * blend_factor_000 + elevations0[elevation_index00].azimuths[azimuth_index001].delay * blend_factor_001 + elevations0[elevation_index01].azimuths[azimuth_index010].delay * blend_factor_010 + elevations0[elevation_index01].azimuths[azimuth_index011].delay * blend_factor_011;

		delay1 =
		elevations1[elevation_index10].azimuths[azimuth_index100].delay * blend_factor_100 + elevations1[elevation_index10].azimuths[azimuth_index101].delay * blend_factor_101 + elevations1[elevation_index11].azimuths[azimuth_index110].delay * blend_factor_110 + elevations1[elevation_index11].azimuths[azimuth_index111].delay * blend_factor_111;
	} else {
		delay0 =
		elevations0[elevation_index00].azimuths[azimuth_index000].delay_right * blend_factor_000 + elevations0[elevation_index00].azimuths[azimuth_index001].delay_right * blend_factor_001 + elevations0[elevation_index01].azimuths[azimuth_index010].delay_right * blend_factor_010 + elevations0[elevation_index01].azimuths[azimuth_index011].delay_right * blend_factor_011;

		delay1 =
		elevations1[elevation_index10].azimuths[azimuth_index100].delay_right * blend_factor_100 + elevations1[elevation_index10].azimuths[azimuth_index101].delay_right * blend_factor_101 + elevations1[elevation_index11].azimuths[azimuth_index110].delay_right * blend_factor_110 + elevations1[elevation_index11].azimuths[azimuth_index111].delay_right * blend_factor_111;
	}

	delay = delay0 + delay1;

	sample = sample * m_channel_count + channel;

	float value0 =
	elevations0[elevation_index00].azimuths[azimuth_index000].impulse_response[sample] * blend_factor_000 + elevations0[elevation_index00].azimuths[azimuth_index001].impulse_response[sample] * blend_factor_001 + elevations0[elevation_index01].azimuths[azimuth_index010].impulse_response[sample] * blend_factor_010 + elevations0[elevation_index01].azimuths[azimuth_index011].impulse_response[sample] * blend_factor_011;

	float value1 =
	elevations1[elevation_index10].azimuths[azimuth_index100].impulse_response[sample] * blend_factor_100 + elevations1[elevation_index10].azimuths[azimuth_index101].impulse_response[sample] * blend_factor_101 + elevations1[elevation_index11].azimuths[azimuth_index110].impulse_response[sample] * blend_factor_110 + elevations1[elevation_index11].azimuths[azimuth_index111].impulse_response[sample] * blend_factor_111;

	value = value0 + value1;
}

void HrtfData::sample_direction(angle_t elevation, angle_t azimuth, distance_t distance, uint32_t sample, float& value_left, float& delay_left, float& value_right, float& delay_right) const {
	assert(elevation >= -angle_t(pi * 0.5));
	assert(elevation <= angle_t(pi * 0.5));
	assert(azimuth >= -angle_t(2.0 * pi));
	assert(azimuth <= angle_t(2.0 * pi));

	sample_direction(elevation, azimuth, distance, sample, 0, value_left, delay_left);
	if(m_channel_count == 1) {
		sample_direction(elevation, -azimuth, distance, sample, 0, value_right, delay_right);
	} else {
		sample_direction(elevation, azimuth, distance, sample, 1, value_right, delay_right);
	}
}
