2025-01-22 16:18:30 +01:00

171 lines
4.4 KiB
Haxe
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
Specification:
V1: https://github.com/kcat/openal-soft/blob/be7938ed385e18c7800c663672262bb2976aa734/docs/hrtf.txt
V2: https://github.com/kcat/openal-soft/blob/0349bcc500fdb9b1245a5ddce01b2896bcf9bbb9/docs/hrtf.txt
V3: https://github.com/kcat/openal-soft/blob/3ef4bffaf959d06527a247faa19cc869781745e4/docs/hrtf.txt
**/
package aura.format.mhr;
import haxe.Int64;
import haxe.ds.Vector;
import haxe.io.Bytes;
import haxe.io.BytesInput;
import kha.arrays.Float32Array;
import aura.types.HRTF;
using aura.format.InputExtension;
/**
Load MHR HRTF files (format versions 13 are supported) into `HRTF` objects.
**/
class MHRReader {
public static function read(bytes: Bytes): HRTF {
final inp = new BytesInput(bytes);
inp.bigEndian = false;
final magic = inp.readString(8, UTF8);
final version = versionFromMagic(magic);
final sampleRate = Int64.toInt(inp.readUInt32());
final sampleType = switch (version) {
case V1: SampleType16Bit;
case V2: inp.readByte();
case V3: SampleType24Bit;
}
final channelType = switch (version) {
case V1: 0; // mono
case V2 | V3: inp.readByte();
}
final channels = channelType + 1;
// Samples per HRIR (head related impulse response) per channel
final hrirSize = inp.readByte();
// Number of fields used by the data set. Each field represents a
// set of points for a given distance.
final fieldCount = version == V1 ? 1 : inp.readByte();
final fields = new Vector<Field>(fieldCount);
var totalHRIRCount = 0;
for (i in 0...fieldCount) {
final field = new Field();
// 1000mm is arbitrary, but it doesn't matter since the interpolation
// can only access one distance anyway...
field.distance = version == V1 ? 1000 : inp.readUInt16();
field.evCount = inp.readByte();
field.azCount = new Vector<Int>(field.evCount);
field.evHRIROffsets = new Vector<Int>(field.evCount);
var fieldHrirCount = 0;
for (j in 0...field.evCount) {
// Calculate the offset into the HRIR arrays. Different
// elevations may have different amounts of azimuths/HRIRs
field.evHRIROffsets[j] = fieldHrirCount;
field.azCount[j] = inp.readByte();
fieldHrirCount += field.azCount[j];
}
field.hrirCount = fieldHrirCount;
totalHRIRCount += fieldHrirCount;
fields[i] = field;
}
// Read actual HRIR samples into coeffs
for (i in 0...fieldCount) {
final field = fields[i];
final hrirs = new Vector<HRIR>(field.hrirCount);
field.hrirs = hrirs;
for (j in 0...field.hrirCount) {
// Create individual HRIR
final hrir = hrirs[j] = new HRIR();
hrir.coeffs = new Float32Array(hrirSize * channels);
switch (sampleType) {
case SampleType16Bit:
for (s in 0...hrirSize) {
final coeff = inp.readInt16();
// 32768 = 2^15
hrir.coeffs[s] = coeff / (coeff < 0 ? 32768.0 : 32767.0);
}
case SampleType24Bit:
for (s in 0...hrirSize) {
final coeff = inp.readInt24();
// 8388608 = 2^23
hrir.coeffs[s] = coeff / (coeff < 0 ? 8388608.0 : 8388607.0);
}
}
}
}
// Read per-HRIR delay
var maxDelayLength = 0.0;
for (i in 0...fieldCount) {
final field = fields[i];
for (j in 0...field.hrirCount) {
final hrir = field.hrirs[j];
hrir.delays = new Vector<Float>(channels);
for (ch in 0...channels) {
// 6.2 fixed point
final delayRaw = inp.readByte();
final delayIntPart = delayRaw >> 2;
final delayFloatPart = isBitSet(delayRaw, 1) * 0.5 + isBitSet(delayRaw, 0) * 0.25;
final delay = delayIntPart + delayFloatPart;
hrir.delays[ch] = delay;
if (delay > maxDelayLength) {
maxDelayLength = delay;
}
}
}
}
// This should error if uncommented, check if we have reached the end of
// the file.
// inp.readByte();
return {
sampleRate: sampleRate,
numChannels: channels,
hrirSize: hrirSize,
hrirCount: totalHRIRCount,
fields: fields,
maxDelayLength: maxDelayLength
};
}
static inline function isBitSet(byte: Int, position: Int): Int {
return (byte & (1 << position) == 0) ? 0 : 1;
}
static inline function versionFromMagic(magic: String): MHRVersion {
return switch (magic) {
case "MinPHR01": V1;
case "MinPHR02": V2;
case "MinPHR03": V3;
default:
throw 'File is not an MHR HRTF file! Unknown magic string "$magic".';
}
}
}
private enum abstract SampleType(Int) from Int {
var SampleType16Bit;
var SampleType24Bit;
}
private enum abstract MHRVersion(Int) {
var V1;
var V2;
var V3;
}