diff options
author | Chris Robinson <[email protected]> | 2017-08-08 20:25:31 -0700 |
---|---|---|
committer | Chris Robinson <[email protected]> | 2017-08-08 20:30:26 -0700 |
commit | ad3c4b81e2e6547fba7e66615a7ffd2fb69eb3bc (patch) | |
tree | 6101fc01ed52f459ce3d5359abe57d0113d7c047 | |
parent | 530002e168c907bd3468cd0fefaec2565e248bbd (diff) |
Add experimental support for 24-bit, dual-ear HRTFs
Currently makehrtf only handles 24-bit output, not dual-ear, and only when
given the --experimental switch. Files produced this way will not be guaranteed
future compatibility. When the mhr format is also updated with multi-distance
measurements, the experimental switch can go away.
-rw-r--r-- | Alc/hrtf.c | 233 | ||||
-rw-r--r-- | utils/makehrtf.c | 50 |
2 files changed, 272 insertions, 11 deletions
@@ -54,6 +54,8 @@ struct HrtfEntry { static const ALchar magicMarker00[8] = "MinPHR00"; static const ALchar magicMarker01[8] = "MinPHR01"; +/* FIXME: Set with the right number when finalized. */ +static const ALchar magicMarker02[18] = "MinPHRTEMPDONOTUSE"; /* First value for pass-through coefficients (remaining are 0), used for omni- * directional sounds. */ @@ -383,9 +385,16 @@ static ALushort GetLE_ALushort(const ALubyte **data, size_t *len) return ret; } -static ALint GetLE_ALuint(const ALubyte **data, size_t *len) +static ALint GetLE_ALint24(const ALubyte **data, size_t *len) { - ALint ret = (*data)[0] | ((*data)[1]<<8) | ((*data)[2]<<16) | ((*data)[3]<<24); + ALint ret = (*data)[0] | ((*data)[1]<<8) | ((*data)[2]<<16); + *data += 3; *len -= 3; + return (ret^0x800000) - 0x800000; +} + +static ALuint GetLE_ALuint(const ALubyte **data, size_t *len) +{ + ALuint ret = (*data)[0] | ((*data)[1]<<8) | ((*data)[2]<<16) | ((*data)[3]<<24); *data += 4; *len -= 4; return ret; } @@ -711,6 +720,217 @@ static struct Hrtf *LoadHrtf01(const ALubyte *data, size_t datalen, const char * return Hrtf; } +#define SAMPLETYPE_S16 0 +#define SAMPLETYPE_S24 1 + +#define CHANTYPE_LEFTONLY 0 +#define CHANTYPE_LEFTRIGHT 1 + +static struct Hrtf *LoadHrtf02(const ALubyte *data, size_t datalen, const char *filename) +{ + const ALubyte maxDelay = HRTF_HISTORY_LENGTH-1; + struct Hrtf *Hrtf = NULL; + ALboolean failed = AL_FALSE; + ALuint rate = 0; + ALubyte sampleType; + ALubyte channelType; + ALushort irCount = 0; + ALushort irSize = 0; + ALubyte evCount = 0; + const ALubyte *azCount = NULL; + ALushort *evOffset = NULL; + ALfloat (*coeffs)[2] = NULL; + ALubyte (*delays)[2] = NULL; + ALsizei i, j; + + if(datalen < 6) + { + ERR("Unexpected end of %s data (req %d, rem "SZFMT"\n", filename, 6, datalen); + return NULL; + } + + rate = GetLE_ALuint(&data, &datalen); + sampleType = GetLE_ALubyte(&data, &datalen); + channelType = GetLE_ALubyte(&data, &datalen); + + irSize = GetLE_ALubyte(&data, &datalen); + + evCount = GetLE_ALubyte(&data, &datalen); + + if(sampleType > SAMPLETYPE_S24) + { + ERR("Unsupported sample type: %d\n", sampleType); + failed = AL_TRUE; + } + if(channelType > CHANTYPE_LEFTRIGHT) + { + ERR("Unsupported channel type: %d\n", channelType); + failed = AL_TRUE; + } + + if(irSize < MIN_IR_SIZE || irSize > MAX_IR_SIZE || (irSize%MOD_IR_SIZE)) + { + ERR("Unsupported HRIR size: irSize=%d (%d to %d by %d)\n", + irSize, MIN_IR_SIZE, MAX_IR_SIZE, MOD_IR_SIZE); + failed = AL_TRUE; + } + if(evCount < MIN_EV_COUNT || evCount > MAX_EV_COUNT) + { + ERR("Unsupported elevation count: evCount=%d (%d to %d)\n", + evCount, MIN_EV_COUNT, MAX_EV_COUNT); + failed = AL_TRUE; + } + if(failed) + return NULL; + + if(datalen < evCount) + { + ERR("Unexpected end of %s data (req %d, rem "SZFMT"\n", filename, evCount, datalen); + return NULL; + } + + azCount = Get_ALubytePtr(&data, &datalen, evCount); + + evOffset = malloc(sizeof(evOffset[0])*evCount); + if(azCount == NULL || evOffset == NULL) + { + ERR("Out of memory.\n"); + failed = AL_TRUE; + } + + if(!failed) + { + for(i = 0;i < evCount;i++) + { + if(azCount[i] < MIN_AZ_COUNT || azCount[i] > MAX_AZ_COUNT) + { + ERR("Unsupported azimuth count: azCount[%d]=%d (%d to %d)\n", + i, azCount[i], MIN_AZ_COUNT, MAX_AZ_COUNT); + failed = AL_TRUE; + } + } + } + + if(!failed) + { + evOffset[0] = 0; + irCount = azCount[0]; + for(i = 1;i < evCount;i++) + { + evOffset[i] = evOffset[i-1] + azCount[i-1]; + irCount += azCount[i]; + } + + coeffs = malloc(sizeof(coeffs[0])*irSize*irCount); + delays = malloc(sizeof(delays[0])*irCount); + if(coeffs == NULL || delays == NULL) + { + ERR("Out of memory.\n"); + failed = AL_TRUE; + } + } + + if(!failed) + { + size_t reqsize = 2*irSize*irCount + irCount; + if(datalen < reqsize) + { + ERR("Unexpected end of %s data (req "SZFMT", rem "SZFMT"\n", + filename, reqsize, datalen); + failed = AL_TRUE; + } + } + + if(!failed) + { + if(channelType == CHANTYPE_LEFTONLY || channelType == CHANTYPE_LEFTRIGHT) + { + if(sampleType == SAMPLETYPE_S16) + for(i = 0;i < irCount;i++) + { + for(j = 0;j < irSize;j++) + coeffs[i*irSize + j][0] = GetLE_ALshort(&data, &datalen) / 32768.0f; + } + else if(sampleType == SAMPLETYPE_S24) + for(i = 0;i < irCount;i++) + { + for(j = 0;j < irSize;j++) + coeffs[i*irSize + j][0] = GetLE_ALint24(&data, &datalen) / 8388608.0f; + } + } + if(channelType == CHANTYPE_LEFTRIGHT) + { + if(sampleType == SAMPLETYPE_S16) + for(i = 0;i < irCount;i++) + { + for(j = 0;j < irSize;j++) + coeffs[i*irSize + j][1] = GetLE_ALshort(&data, &datalen) / 32768.0f; + } + else if(sampleType == SAMPLETYPE_S24) + for(i = 0;i < irCount;i++) + { + for(j = 0;j < irSize;j++) + coeffs[i*irSize + j][1] = GetLE_ALint24(&data, &datalen) / 8388608.0f; + } + } + if(channelType == CHANTYPE_LEFTONLY || channelType == CHANTYPE_LEFTRIGHT) + { + for(i = 0;i < irCount;i++) + { + delays[i][0] = GetLE_ALubyte(&data, &datalen); + if(delays[i][0] > maxDelay) + { + ERR("Invalid delays[%d][0]: %d (%d)\n", i, delays[i][0], maxDelay); + failed = AL_TRUE; + } + } + } + if(channelType == CHANTYPE_LEFTRIGHT) + { + for(i = 0;i < irCount;i++) + { + delays[i][1] = GetLE_ALubyte(&data, &datalen); + if(delays[i][1] > maxDelay) + { + ERR("Invalid delays[%d][1]: %d (%d)\n", i, delays[i][1], maxDelay); + failed = AL_TRUE; + } + } + } + } + + if(!failed) + { + if(channelType == CHANTYPE_LEFTONLY) + { + /* Mirror the left ear responses to the right ear. */ + for(i = 0;i < evCount;i++) + { + ALushort evoffset = evOffset[i]; + ALubyte azcount = azCount[i]; + for(j = 0;j < azcount;j++) + { + ALsizei lidx = evoffset + j; + ALsizei ridx = evoffset + ((azcount-j) % azcount); + ALsizei k; + + for(k = 0;k < irSize;k++) + coeffs[ridx*irSize + k][1] = coeffs[lidx*irSize + k][0]; + delays[ridx][1] = delays[lidx][0]; + } + } + } + + Hrtf = CreateHrtfStore(rate, irSize, evCount, irCount, azCount, + evOffset, coeffs, delays, filename); + } + + free(evOffset); + free(coeffs); + free(delays); + return Hrtf; +} + static void AddFileEntry(vector_EnumeratedHrtf *list, const_al_string filename) { @@ -1064,8 +1284,15 @@ struct Hrtf *GetLoadedHrtf(struct HrtfEntry *entry) rsize = fmap.len; } - if(rsize < sizeof(magicMarker01)) + if(rsize < sizeof(magicMarker02)) ERR("%s data is too short ("SZFMT" bytes)\n", name, rsize); + else if(memcmp(rdata, magicMarker02, sizeof(magicMarker02)) == 0) + { + TRACE("Detected data set format v2\n"); + hrtf = LoadHrtf02(rdata+sizeof(magicMarker02), + rsize-sizeof(magicMarker02), name + ); + } else if(memcmp(rdata, magicMarker01, sizeof(magicMarker01)) == 0) { TRACE("Detected data set format v1\n"); diff --git a/utils/makehrtf.c b/utils/makehrtf.c index 259b717f..963528bb 100644 --- a/utils/makehrtf.c +++ b/utils/makehrtf.c @@ -196,6 +196,19 @@ // response protocol 01. #define MHR_FORMAT ("MinPHR01") +#define MHR_FORMAT_EXPERIMENTAL ("MinPHRTEMPDONOTUSE") + +// Sample and channel type enum values +typedef enum SampleTypeT { + ST_S16 = 0, + ST_S24 = 1 +} SampleTypeT; + +typedef enum ChannelTypeT { + CT_LEFTONLY = 0, + CT_LEFTRIGHT = 1 +} ChannelTypeT; + // Byte order for the serialization routines. typedef enum ByteOrderT { BO_NONE, @@ -268,6 +281,8 @@ typedef struct SourceRefT { // the resulting HRTF. typedef struct HrirDataT { uint mIrRate; + SampleTypeT mSampleType; + ChannelTypeT mChannelType; uint mIrCount; uint mIrSize; uint mIrPoints; @@ -1869,7 +1884,7 @@ static int WriteBin4(const ByteOrderT order, const uint bytes, const uint32 in, } // Store the OpenAL Soft HRTF data set. -static int StoreMhr(const HrirDataT *hData, const char *filename) +static int StoreMhr(const HrirDataT *hData, const int experimental, const char *filename) { uint e, step, end, n, j, i; uint dither_seed; @@ -1881,10 +1896,17 @@ static int StoreMhr(const HrirDataT *hData, const char *filename) fprintf(stderr, "Error: Could not open MHR file '%s'.\n", filename); return 0; } - if(!WriteAscii(MHR_FORMAT, fp, filename)) + if(!WriteAscii(experimental ? MHR_FORMAT_EXPERIMENTAL : MHR_FORMAT, fp, filename)) return 0; if(!WriteBin4(BO_LITTLE, 4, (uint32)hData->mIrRate, fp, filename)) return 0; + if(experimental) + { + if(!WriteBin4(BO_LITTLE, 1, (uint32)hData->mSampleType, fp, filename)) + return 0; + if(!WriteBin4(BO_LITTLE, 1, (uint32)hData->mChannelType, fp, filename)) + return 0; + } if(!WriteBin4(BO_LITTLE, 1, (uint32)hData->mIrPoints, fp, filename)) return 0; if(!WriteBin4(BO_LITTLE, 1, (uint32)hData->mEvCount, fp, filename)) @@ -1900,13 +1922,17 @@ static int StoreMhr(const HrirDataT *hData, const char *filename) dither_seed = 22222; for(j = 0;j < end;j += step) { + const double scale = (!experimental || hData->mSampleType == ST_S16) ? 32767.0 : + ((hData->mSampleType == ST_S24) ? 8388607.0 : 0.0); + const int bps = (!experimental || hData->mSampleType == ST_S16) ? 2 : + ((hData->mSampleType == ST_S24) ? 3 : 0); double out[MAX_TRUNCSIZE]; for(i = 0;i < n;i++) - out[i] = TpdfDither(32767.0 * hData->mHrirs[j+i], &dither_seed); + out[i] = TpdfDither(scale * hData->mHrirs[j+i], &dither_seed); for(i = 0;i < n;i++) { - v = (int)Clamp(out[i], -32768.0, 32767.0); - if(!WriteBin4(BO_LITTLE, 2, (uint32)v, fp, filename)) + v = (int)Clamp(out[i], -scale-1.0, scale); + if(!WriteBin4(BO_LITTLE, bps, (uint32)v, fp, filename)) return 0; } } @@ -2697,7 +2723,7 @@ error: * resulting data set as desired. If the input name is NULL it will read * from standard input. */ -static int ProcessDefinition(const char *inName, const uint outRate, const uint fftSize, const int equalize, const int surface, const double limit, const uint truncSize, const HeadModelT model, const double radius, const OutputFormatT outFormat, const char *outName) +static int ProcessDefinition(const char *inName, const uint outRate, const uint fftSize, const int equalize, const int surface, const double limit, const uint truncSize, const HeadModelT model, const double radius, const OutputFormatT outFormat, const int experimental, const char *outName) { char rateStr[8+1], expName[MAX_PATH_LEN]; TokenReaderT tr; @@ -2706,6 +2732,8 @@ static int ProcessDefinition(const char *inName, const uint outRate, const uint FILE *fp; hData.mIrRate = 0; + hData.mSampleType = ST_S24; + hData.mChannelType = CT_LEFTONLY; hData.mIrPoints = 0; hData.mFftSize = 0; hData.mIrSize = 0; @@ -2779,7 +2807,7 @@ static int ProcessDefinition(const char *inName, const uint outRate, const uint { case OF_MHR: fprintf(stdout, "Creating MHR data set file...\n"); - if(!StoreMhr(&hData, expName)) + if(!StoreMhr(&hData, experimental, expName)) { DestroyArray(hData.mHrtds); DestroyArray(hData.mHrirs); @@ -2826,6 +2854,7 @@ int main(const int argc, const char *argv[]) OutputFormatT outFormat; uint outRate, fftSize; int equalize, surface; + int experimental; char *end = NULL; HeadModelT model; uint truncSize; @@ -2860,6 +2889,7 @@ int main(const int argc, const char *argv[]) truncSize = DEFAULT_TRUNCSIZE; model = DEFAULT_HEAD_MODEL; radius = DEFAULT_CUSTOM_RADIUS; + experimental = 0; argi = 2; while(argi < argc) @@ -2954,6 +2984,8 @@ int main(const int argc, const char *argv[]) inName = &argv[argi][3]; else if(strncmp(argv[argi], "-o=", 3) == 0) outName = &argv[argi][3]; + else if(strcmp(argv[argi], "--experimental") == 0) + experimental = 1; else { fprintf(stderr, "Error: Invalid option '%s'.\n", argv[argi]); @@ -2961,7 +2993,9 @@ int main(const int argc, const char *argv[]) } argi++; } - if(!ProcessDefinition(inName, outRate, fftSize, equalize, surface, limit, truncSize, model, radius, outFormat, outName)) + if(!ProcessDefinition(inName, outRate, fftSize, equalize, surface, limit, + truncSize, model, radius, outFormat, experimental, + outName)) return -1; fprintf(stdout, "Operation completed.\n"); return 0; |