Skip to content

Instantly share code, notes, and snippets.

@kode54

kode54/hesuvi_convert.c

Last active Jan 12, 2021
Embed
What would you like to do?
This fairly simple tool converts all HeSuVi 14 channel presets into 7.0 formatted _L/_R stereo pairs, for use with the soon to be updated PulseAudio module-virtual-surround-sink, which I've updated with a faster FFT overlap-save convolver, eliminated the sample length limits for impulses, and added support for asymmetrical/dual impulse mode.

To convert an impulse from HeSuVi, for example, the base GSX filter:

hesuvi_convert gsx.wav gsx_L.wav gsx_R.wav

And to use it:

pacmd load-module module-virtual-surround-sink sink_name=VirtualSurround \
    sink_master=<output device> hrir_left=$PWD/gsx_L.wav hrir_right=$PWD/gsx_R.wav \
    channel_map=front-left,front-right,rear-left,rear-right,front-center,lfe

(Suggested channel_map override added, since either Wine or some Wine games barf with 7.1 instead of 5.1)

Merge request !240 on pulseaudio/pulseaudio at Freedesktop.org Gitlab

Thread I started on Reddit about this

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#define SPEAKER_FRONT_LEFT 0x1
#define SPEAKER_FRONT_RIGHT 0x2
#define SPEAKER_FRONT_CENTER 0x4
#define SPEAKER_LFE 0x8
#define SPEAKER_BACK_LEFT 0x10
#define SPEAKER_BACK_RIGHT 0x20
#define SPEAKER_SIDE_LEFT 0x200
#define SPEAKER_SIDE_RIGHT 0x400
#define OUTPUTS 8
#define SPEAKERS_HESUVI \
(SPEAKER_FRONT_LEFT| \
SPEAKER_FRONT_RIGHT| \
SPEAKER_FRONT_CENTER| \
SPEAKER_LFE| \
SPEAKER_BACK_LEFT| \
SPEAKER_BACK_RIGHT| \
SPEAKER_SIDE_LEFT| \
SPEAKER_SIDE_RIGHT)
static const uint32_t speakers_output[OUTPUTS] =
{
SPEAKER_FRONT_LEFT,
SPEAKER_FRONT_RIGHT,
SPEAKER_FRONT_CENTER,
SPEAKER_FRONT_CENTER, /* duplicate LFE into center preset */
SPEAKER_BACK_LEFT,
SPEAKER_BACK_RIGHT,
SPEAKER_SIDE_LEFT,
SPEAKER_SIDE_RIGHT,
};
static const uint32_t speakers_hesuvi_to_left[14] =
{
SPEAKER_FRONT_LEFT,
0,
SPEAKER_SIDE_LEFT,
0,
SPEAKER_BACK_LEFT,
0,
SPEAKER_FRONT_CENTER,
0,
SPEAKER_FRONT_RIGHT,
0,
SPEAKER_SIDE_RIGHT,
0,
SPEAKER_BACK_RIGHT,
0,
};
static const uint32_t speakers_hesuvi_to_right[14] =
{
0,
SPEAKER_FRONT_LEFT,
0,
SPEAKER_SIDE_LEFT,
0,
SPEAKER_BACK_LEFT,
0,
SPEAKER_FRONT_RIGHT,
0,
SPEAKER_SIDE_RIGHT,
0,
SPEAKER_BACK_RIGHT,
0,
SPEAKER_FRONT_CENTER,
};
typedef struct __attribute__((packed)) tWAVEFORMATEX {
uint16_t wFormatTag;
uint16_t nChannels;
uint32_t nSamplesPerSec;
uint32_t nAvgBytesPerSec;
uint16_t nBlockAlign;
uint16_t wBitsPerSample;
uint16_t cbSize;
} WAVEFORMATEX, *PWAVEFORMATEX, *NPWAVEFORMATEX, *LPWAVEFORMATEX;
typedef struct __attribute__((packed)) _GUID {
uint32_t Data1;
uint16_t Data2;
uint16_t Data3;
uint8_t Data4[8];
} GUID;
static const GUID KSDATAFORMAT_SUBTYPE_IEEE_FLOAT =
{ 3,
0,
16,
{128, 0, 0, 170, 0, 56, 155, 113}
};
typedef struct __attribute__((packed)) {
WAVEFORMATEX Format;
union {
uint16_t wValidBitsPerSample;
uint16_t wSamplesPerBlock;
uint16_t wReserved;
} Samples;
uint32_t dwChannelMask;
GUID SubFormat;
} WAVEFORMATEXTENSIBLE, *PWAVEFORMATEXTENSIBLE;
static uint16_t get_le16_ptr(const uint8_t * in)
{
return (uint16_t)in[0] + (uint16_t)in[1] * 0x100;
}
static void put_le16_ptr(uint8_t * out, uint16_t value)
{
out[0] = (uint8_t)(value & 0xFF);
out[1] = (uint8_t)((value >> 8) & 0xFF);
}
static uint16_t get_le16(uint16_t in)
{
union {
uint16_t val16;
uint8_t val8[2];
} temp;
temp.val16 = in;
return get_le16_ptr(temp.val8);
}
static uint16_t put_le16(uint16_t in)
{
union {
uint16_t val16;
uint8_t val8[2];
} temp;
put_le16_ptr(temp.val8, in);
return temp.val16;
}
static uint32_t get_le32_ptr(const uint8_t * in)
{
return (uint32_t)in[0] + (uint32_t)in[1] * 0x100 +
(uint32_t)in[2] * 0x10000 + (uint32_t)in[3] * 0x1000000;
}
static void put_le32_ptr(uint8_t * out, uint32_t val)
{
out[0] = (uint8_t)(val & 0xFF);
out[1] = (uint8_t)((val >> 8) & 0xFF);
out[2] = (uint8_t)((val >> 16) & 0xFF);
out[3] = (uint8_t)((val >> 24) & 0xFF);
}
static uint32_t get_le32(uint32_t in)
{
union {
uint32_t val32;
uint8_t val8[4];
} temp;
temp.val32 = in;
return get_le32_ptr(temp.val8);
}
static uint32_t put_le32(uint32_t in)
{
union {
uint32_t val32;
uint8_t val8[4];
} temp;
put_le32_ptr(temp.val8, in);
return temp.val32;
}
static void put_guid_ptr(uint8_t * out, GUID in)
{
put_le32_ptr(out, in.Data1);
put_le16_ptr(out + 4, in.Data2);
put_le16_ptr(out + 6, in.Data3);
memcpy(out + 8, in.Data4, 8);
}
static GUID put_guid(GUID value)
{
union {
GUID valGuid;
uint8_t val8[16];
} temp;
put_guid_ptr(temp.val8, value);
return temp.valGuid;
}
int main(int argc, char ** argv)
{
FILE *f;
uint8_t * input_wav;
uint8_t * output_wav_left, * output_wav_right;
size_t input_size;
size_t output_size;
uint32_t i, j, k, mask;
uint32_t val32;
uint32_t format_size;
uint32_t sample_count;
size_t fact_chunk_offset;
size_t sample_data_offset;
size_t sample_data_size;
size_t sample_out_offset;
PWAVEFORMATEX wavHeader;
WAVEFORMATEXTENSIBLE outHeader = {0};
if (argc != 4) {
fprintf(stderr, "Usage:\thesuvi_convert <input.wav> <output_left.wav> <output_right.wav>\n");
return 1;
}
f = fopen(argv[1], "rb");
if (!f) {
fprintf(stderr, "Unable to open output file: %s\n", argv[1]);
return 1;
}
fseek(f, 0, SEEK_END);
input_size = ftell(f);
fseek(f, 0, SEEK_SET);
input_wav = malloc(input_size + 1);
if (!input_wav) {
fprintf(stderr, "Unable to allocate memory for input file\n");
fclose(f);
return 1;
}
if (fread(input_wav, 1, input_size, f) != input_size) {
fprintf(stderr, "Unable to read input file\n");
free(input_wav);
fclose(f);
return 1;
}
fclose(f);
if (memcmp(input_wav, "RIFF", 4) != 0) {
fprintf(stderr, "Invalid input file signature\n");
free(input_wav);
return 1;
}
val32 = get_le32_ptr(input_wav + 4);
if (val32 + 8 > input_size) {
fprintf(stderr, "Invalid RIFF chunk size in input\n");
free(input_wav);
return 1;
}
if (memcmp(input_wav + 8, "WAVE", 4) != 0) {
fprintf(stderr, "Not a WAV file\n");
free(input_wav);
return 1;
}
if (memcmp(input_wav + 12, "fmt ", 4) != 0) {
fprintf(stderr, "WAV format chunk not where expected\n");
free(input_wav);
return 1;
}
format_size = get_le32_ptr(input_wav + 16);
if (format_size < 16) {
fprintf(stderr, "WAV format chunk not large enough\n");
}
wavHeader = (PWAVEFORMATEX)(input_wav + 20);
if (get_le16(wavHeader->wFormatTag) != 3 ||
get_le16(wavHeader->nChannels) != 14 ||
get_le16(wavHeader->wBitsPerSample) != 32 ||
get_le16(wavHeader->nBlockAlign) != get_le16(wavHeader->nChannels) * 4 ||
get_le32(wavHeader->nSamplesPerSec) != get_le32(wavHeader->nAvgBytesPerSec) / get_le16(wavHeader->nBlockAlign)) {
fprintf(stderr, "WAV has unexpected format\n");
free(input_wav);
return 1;
}
memset(&outHeader, 0, sizeof(outHeader));
memcpy(&outHeader.Format, wavHeader, format_size);
outHeader.Format.wFormatTag = put_le16(0xFFFE);
outHeader.Format.cbSize = put_le16(sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX));
outHeader.Format.nChannels = put_le16(OUTPUTS);
outHeader.Format.nBlockAlign = put_le16(OUTPUTS * 4);
outHeader.Format.nAvgBytesPerSec = put_le32(get_le32(wavHeader->nSamplesPerSec) * OUTPUTS * 4);
outHeader.Samples.wValidBitsPerSample = put_le16(32);
outHeader.SubFormat = put_guid(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT);
outHeader.dwChannelMask = put_le32(SPEAKERS_HESUVI);
if (memcmp(input_wav + 20 + format_size, "fact", 4) != 0) {
fprintf(stderr, "Input WAV is missing fact chunk\n");
free(input_wav);
return 1;
}
val32 = get_le32_ptr(input_wav + 20 + format_size + 4);
if (val32 != 4) {
fprintf(stderr, "Input WAV fact chunk is wrong size\n");
free(input_wav);
return 1;
}
fact_chunk_offset = 20 + format_size;
sample_count = get_le32_ptr(input_wav + fact_chunk_offset + 8);
sample_data_offset = 20 + format_size + 12;
while (sample_data_offset < input_size) {
if (memcmp(input_wav + sample_data_offset, "data", 4) == 0)
break;
val32 = get_le32_ptr(input_wav + sample_data_offset + 4);
sample_data_offset += 8 + val32;
}
if (sample_data_offset >= input_size) {
fprintf(stderr, "WAV data chunk not found\n");
free(input_wav);
return 1;
}
sample_data_size = get_le32_ptr(input_wav + sample_data_offset + 4);
sample_data_offset += 8;
if (sample_data_size < sample_count * 14 * 4) {
fprintf(stderr, "WAV data too small for specified sample count\n");
free(input_wav);
return 1;
}
/* RIFF + fmt + fact + data */
output_size = 8 + 4 + 8 + sizeof(outHeader) + 8 + 4 + 8 + sample_count * OUTPUTS * 4;
output_wav_left = calloc(1, output_size);
if (!output_wav_left) {
fprintf(stderr, "Unable to allocate output buffer\n");
free(input_wav);
return 1;
}
output_wav_right = calloc(1, output_size);
if (!output_wav_right) {
fprintf(stderr, "Unable to allocate output buffer\n");
free(output_wav_left);
free(input_wav);
return 1;
}
memcpy(output_wav_left, "RIFF", 4);
memcpy(output_wav_right, "RIFF", 4);
put_le32_ptr(output_wav_left + 4, output_size - 8);
put_le32_ptr(output_wav_right + 4, output_size - 8);
memcpy(output_wav_left + 8, "WAVE", 4);
memcpy(output_wav_right + 8, "WAVE", 4);
memcpy(output_wav_left + 12, "fmt ", 4);
memcpy(output_wav_right + 12, "fmt ", 4);
put_le32_ptr(output_wav_left + 16, sizeof(outHeader));
put_le32_ptr(output_wav_right + 16, sizeof(outHeader));
memcpy(output_wav_left + 20, &outHeader, sizeof(outHeader));
memcpy(output_wav_right + 20, &outHeader, sizeof(outHeader));
memcpy(output_wav_left + 20 + sizeof(outHeader), input_wav + fact_chunk_offset, 12);
memcpy(output_wav_right + 20 + sizeof(outHeader), input_wav + fact_chunk_offset, 12);
memcpy(output_wav_left + 20 + sizeof(outHeader) + 12, "data", 4);
memcpy(output_wav_right + 20 + sizeof(outHeader) + 12, "data", 4);
put_le32_ptr(output_wav_left + 20 + sizeof(outHeader) + 12 + 4, sample_count * OUTPUTS * 4);
put_le32_ptr(output_wav_right + 20 + sizeof(outHeader) + 12 + 4, sample_count * OUTPUTS * 4);
sample_out_offset = 20 + sizeof(outHeader) + 12 + 8;
for (i = 0; i < sample_count; i++) {
uint32_t * frame_in = (uint32_t *)(input_wav + sample_data_offset + i * 14 * 4);
uint32_t * frame_out_left = (uint32_t *)(output_wav_left + sample_out_offset + i * OUTPUTS * 4);
uint32_t * frame_out_right = (uint32_t *)(output_wav_right + sample_out_offset + i * OUTPUTS * 4);
for (j = 0; j < OUTPUTS; j++) {
mask = speakers_output[j];
for (k = 0; k < 14; k++) {
if (speakers_hesuvi_to_left[k] == mask) {
frame_out_left[j] = frame_in[k];
}
if (speakers_hesuvi_to_right[k] == mask) {
frame_out_right[j] = frame_in[k];
}
}
}
}
free(input_wav);
f = fopen(argv[2], "wb");
if (!f) {
fprintf(stderr, "Unable to open output file: %s\n", argv[2]);
free(output_wav_right);
free(output_wav_left);
return 1;
}
if (fwrite(output_wav_left, 1, output_size, f) != output_size) {
fprintf(stderr, "Unable to write output file\n");
free(output_wav_right);
free(output_wav_left);
return 1;
}
fclose(f);
free(output_wav_left);
f = fopen(argv[3], "wb");
if (!f) {
fprintf(stderr, "Unable to open output file: %s\n", argv[3]);
free(output_wav_right);
return 1;
}
if (fwrite(output_wav_right, 1, output_size, f) != output_size) {
fprintf(stderr, "Unable to write output file\n");
free(output_wav_right);
fclose(f);
return 1;
}
fclose(f);
free(output_wav_right);
return 0;
}
@MagicD3VIL

This comment has been minimized.

Copy link

@MagicD3VIL MagicD3VIL commented Jan 11, 2021

Thanks for the tool! I just noticed that it does not recognize any of the HeSuVi's HRIRs that does not contain reverb. For example oal_dflt, dh- or sonic-.

@kode54

This comment has been minimized.

Copy link
Owner Author

@kode54 kode54 commented Jan 12, 2021

I need to update it to support those, but those are symmetrical responses, so therefore, they only need to be converted to a single output file, and used with the hrir= parameter.

@MagicD3VIL

This comment has been minimized.

Copy link

@MagicD3VIL MagicD3VIL commented Jan 12, 2021

Again thanks for your work on this. I used your converter and tried out a lot of different HeSuVi HRIRs and I must say that cmss_game sounds absolutely great on 8 channels while gaming.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment