|
#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; |
|
} |
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-.