Convert .aif files to .wav (#711)

* Migrate .aif files to .wav

* cleanup
This commit is contained in:
Marcus Huderle
2025-12-01 09:01:35 -06:00
committed by GitHub
parent 7299efd381
commit 14b76daff8
971 changed files with 1123 additions and 1158 deletions
-2
View File
@@ -1,2 +0,0 @@
aif2pcm
-24
View File
@@ -1,24 +0,0 @@
CC ?= gcc
CFLAGS = -Wall -Wextra -Wno-switch -Werror -std=c11 -O2
LIBS = -lm
SRCS = main.c extended.c
ifeq ($(OS),Windows_NT)
EXE := .exe
else
EXE :=
endif
.PHONY: all clean
all: aif2pcm$(EXE)
@:
aif2pcm$(EXE): $(SRCS)
$(CC) $(CFLAGS) $(SRCS) -o $@ $(LDFLAGS) $(LIBS)
clean:
$(RM) aif2pcm aif2pcm.exe
-172
View File
@@ -1,172 +0,0 @@
/* $Id: extended.c,v 1.8 2006/12/23 11:17:49 toad32767 Exp $ */
/*-
* Copyright (c) 2005, 2006 by Marco Trillo <marcotrillo@gmail.com>
*
* Permission is hereby granted, free of charge, to any
* person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the
* Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice
* shall be included in all copies or substantial portions of
* the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
* KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
* OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <math.h>
#include <string.h>
#include <stdint.h>
/*
* Infinite & NAN values
* for non-IEEE systems
*/
#ifndef HUGE_VAL
#ifdef HUGE
#define INFINITE_VALUE HUGE
#define NAN_VALUE HUGE
#endif
#else
#define INFINITE_VALUE HUGE_VAL
#define NAN_VALUE HUGE_VAL
#endif
/*
* IEEE 754 Extended Precision
*
* Implementation here is the 80-bit extended precision
* format of Motorola 68881, Motorola 68882 and Motorola
* 68040 FPUs, as well as Intel 80x87 FPUs.
*
* See:
* http://www.freescale.com/files/32bit/doc/fact_sheet/BR509.pdf
*/
/*
* Exponent range: [-16383,16383]
* Precision for mantissa: 64 bits with no hidden bit
* Bias: 16383
*/
/*
* Write IEEE Extended Precision Numbers
*/
void
ieee754_write_extended(double in, uint8_t* out)
{
int sgn, exp, shift;
double fraction, t;
unsigned int lexp, hexp;
unsigned long low, high;
if (in == 0.0) {
memset(out, 0, 10);
return;
}
if (in < 0.0) {
in = fabs(in);
sgn = 1;
} else
sgn = 0;
fraction = frexp(in, &exp);
if (exp == 0 || exp > 16384) {
if (exp > 16384) /* infinite value */
low = high = 0;
else {
low = 0x80000000;
high = 0;
}
exp = 32767;
goto done;
}
fraction = ldexp(fraction, 32);
t = floor(fraction);
low = (unsigned long) t;
fraction -= t;
t = floor(ldexp(fraction, 32));
high = (unsigned long) t;
/* Convert exponents < -16382 to -16382 (then they will be
* stored as -16383) */
if (exp < -16382) {
shift = 0 - exp - 16382;
high >>= shift;
high |= (low << (32 - shift));
low >>= shift;
exp = -16382;
}
exp += 16383 - 1; /* bias */
done:
lexp = ((unsigned int) exp) >> 8;
hexp = ((unsigned int) exp) & 0xFF;
/* big endian */
out[0] = ((uint8_t) sgn) << 7;
out[0] |= (uint8_t) lexp;
out[1] = (uint8_t) hexp;
out[2] = (uint8_t) (low >> 24);
out[3] = (uint8_t) ((low >> 16) & 0xFF);
out[4] = (uint8_t) ((low >> 8) & 0xFF);
out[5] = (uint8_t) (low & 0xFF);
out[6] = (uint8_t) (high >> 24);
out[7] = (uint8_t) ((high >> 16) & 0xFF);
out[8] = (uint8_t) ((high >> 8) & 0xFF);
out[9] = (uint8_t) (high & 0xFF);
return;
}
/*
* Read IEEE Extended Precision Numbers
*/
double
ieee754_read_extended(uint8_t* in)
{
int sgn, exp;
unsigned long low, high;
double out;
/* Extract the components from the big endian buffer */
sgn = (int) (in[0] >> 7);
exp = ((int) (in[0] & 0x7F) << 8) | ((int) in[1]);
low = (((unsigned long) in[2]) << 24)
| (((unsigned long) in[3]) << 16)
| (((unsigned long) in[4]) << 8) | (unsigned long) in[5];
high = (((unsigned long) in[6]) << 24)
| (((unsigned long) in[7]) << 16)
| (((unsigned long) in[8]) << 8) | (unsigned long) in[9];
if (exp == 0 && low == 0 && high == 0)
return (sgn ? -0.0 : 0.0);
switch (exp) {
case 32767:
if (low == 0 && high == 0)
return (sgn ? -INFINITE_VALUE : INFINITE_VALUE);
else
return (sgn ? -NAN_VALUE : NAN_VALUE);
default:
exp -= 16383; /* unbias exponent */
}
out = ldexp((double) low, -31 + exp);
out += ldexp((double) high, -63 + exp);
return (sgn ? -out : out);
}
-945
View File
@@ -1,945 +0,0 @@
// Copyright(c) 2016 huderlem
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
#include <limits.h>
/* extended.c */
void ieee754_write_extended (double, uint8_t*);
double ieee754_read_extended (uint8_t*);
#ifdef _MSC_VER
#define FATAL_ERROR(format, ...) \
do \
{ \
fprintf(stderr, format, __VA_ARGS__); \
exit(1); \
} while (0)
#else
#define FATAL_ERROR(format, ...) \
do \
{ \
fprintf(stderr, format, ##__VA_ARGS__); \
exit(1); \
} while (0)
#endif // _MSC_VER
typedef struct {
unsigned long num_samples;
union {
uint8_t *samples8;
uint16_t *samples16;
};
uint8_t midi_note;
uint8_t sample_size;
bool has_loop;
unsigned long loop_offset;
double sample_rate;
unsigned long real_num_samples;
} AifData;
struct Bytes {
unsigned long length;
uint8_t *data;
};
struct Marker {
unsigned short id;
unsigned long position;
// don't care about the name
};
struct Bytes *read_bytearray(const char *filename)
{
struct Bytes *bytes = malloc(sizeof(struct Bytes));
FILE *f = fopen(filename, "rb");
if (!f)
{
FATAL_ERROR("Failed to open '%s' for reading!\n", filename);
}
fseek(f, 0, SEEK_END);
bytes->length = ftell(f);
fseek(f, 0, SEEK_SET);
bytes->data = malloc(bytes->length);
unsigned long read = fread(bytes->data, bytes->length, 1, f);
fclose(f);
if (read <= 0)
{
FATAL_ERROR("Failed to read data from '%s'!\n", filename);
}
return bytes;
}
void write_bytearray(const char *filename, struct Bytes *bytes)
{
FILE *f = fopen(filename, "wb");
if (!f)
{
FATAL_ERROR("Failed to open '%s' for writing!\n", filename);
}
fwrite(bytes->data, bytes->length, 1, f);
fclose(f);
}
void free_bytearray(struct Bytes *bytes)
{
free(bytes->data);
free(bytes);
}
char *get_file_extension(char *filename)
{
char *index = strrchr(filename, '.');
if (!index || index == filename)
{
return NULL;
}
return index + 1;
}
char *new_file_extension(char *filename, char *ext)
{
char *index = strrchr(filename, '.');
if (!index || index == filename)
{
index = filename + strlen(filename);
}
int length = index - filename;
char *new_filename = malloc(length + 1 + strlen(ext) + 1);
if (new_filename)
{
strcpy(new_filename, filename);
new_filename[length] = '.';
strcpy(new_filename + length + 1, ext);
}
return new_filename;
}
void read_aif(struct Bytes *aif, AifData *aif_data)
{
aif_data->has_loop = false;
aif_data->num_samples = 0;
unsigned long pos = 0;
char chunk_name[5]; chunk_name[4] = '\0';
char chunk_type[5]; chunk_type[4] = '\0';
// Check for FORM Chunk
memcpy(chunk_name, &aif->data[pos], 4);
pos += 4;
if (strcmp(chunk_name, "FORM") != 0)
{
FATAL_ERROR("Input .aif file has invalid header Chunk '%s'!\n", chunk_name);
}
// Read size of whole file.
unsigned long whole_chunk_size = aif->data[pos++] << 24;
whole_chunk_size |= (aif->data[pos++] << 16);
whole_chunk_size |= (aif->data[pos++] << 8);
whole_chunk_size |= (uint8_t)aif->data[pos++];
unsigned long expected_whole_chunk_size = aif->length - 8;
if (whole_chunk_size != expected_whole_chunk_size)
{
FATAL_ERROR("FORM Chunk ckSize '%lu' doesn't match actual size '%lu'!\n", whole_chunk_size, expected_whole_chunk_size);
}
// Check for AIFF Form Type
memcpy(chunk_type, &aif->data[pos], 4);
pos += 4;
if (strcmp(chunk_type, "AIFF") != 0)
{
FATAL_ERROR("FORM Type is '%s', but it must be AIFF!", chunk_type);
}
struct Marker *markers = NULL;
unsigned short num_markers = 0, loop_start = 0, loop_end = 0;
unsigned long num_sample_frames = 0;
// Read all the Chunks to populate the AifData struct.
while ((pos + 8) < aif->length)
{
// Read Chunk id
memcpy(chunk_name, &aif->data[pos], 4);
pos += 4;
unsigned long chunk_size = (aif->data[pos++] << 24);
chunk_size |= (aif->data[pos++] << 16);
chunk_size |= (aif->data[pos++] << 8);
chunk_size |= aif->data[pos++];
if ((pos + chunk_size) > aif->length)
{
FATAL_ERROR("%s chunk at 0x%lx reached end of file before finishing\n", chunk_name, pos);
}
if (strcmp(chunk_name, "COMM") == 0)
{
short num_channels = (aif->data[pos++] << 8);
num_channels |= (uint8_t)aif->data[pos++];
if (num_channels != 1)
{
FATAL_ERROR("numChannels (%d) in the COMM Chunk must be 1!\n", num_channels);
}
num_sample_frames = (aif->data[pos++] << 24);
num_sample_frames |= (aif->data[pos++] << 16);
num_sample_frames |= (aif->data[pos++] << 8);
num_sample_frames |= (uint8_t)aif->data[pos++];
aif_data->sample_size = (aif->data[pos++] << 8);
aif_data->sample_size |= (uint8_t)aif->data[pos++];
if (aif_data->sample_size != 8 && aif_data->sample_size != 16)
{
FATAL_ERROR("sampleSize (%d) in the COMM Chunk must be 8 or 16!\n", aif_data->sample_size);
}
double sample_rate = ieee754_read_extended((uint8_t*)(aif->data + pos));
pos += 10;
aif_data->sample_rate = sample_rate;
if (aif_data->num_samples == 0)
{
aif_data->num_samples = num_sample_frames;
}
}
else if (strcmp(chunk_name, "MARK") == 0)
{
num_markers = (aif->data[pos++] << 8);
num_markers |= (uint8_t)aif->data[pos++];
if (markers)
{
FATAL_ERROR("More than one MARK Chunk in file!\n");
}
markers = calloc(num_markers, sizeof(struct Marker));
// Read each marker.
for (int i = 0; i < num_markers; i++)
{
unsigned short marker_id = (aif->data[pos++] << 8);
marker_id |= (uint8_t)aif->data[pos++];
unsigned long marker_position = (aif->data[pos++] << 24);
marker_position |= (aif->data[pos++] << 16);
marker_position |= (aif->data[pos++] << 8);
marker_position |= (uint8_t)aif->data[pos++];
// Marker name is a Pascal-style string.
uint8_t marker_name_size = aif->data[pos++];
// We don't actually need the marker name for anything anymore.
/*char *marker_name = (char *)malloc((marker_name_size + 1) * sizeof(char));
memcpy(marker_name, &aif->data[pos], marker_name_size);
marker_name[marker_name_size] = '\0';*/
pos += marker_name_size + !(marker_name_size & 1);
markers[i].id = marker_id;
markers[i].position = marker_position;
}
}
else if (strcmp(chunk_name, "INST") == 0)
{
uint8_t midi_note = (uint8_t)aif->data[pos++];
aif_data->midi_note = midi_note;
// Skip over data we don't need.
pos += 7;
unsigned short loop_type = (aif->data[pos++] << 8);
loop_type |= (uint8_t)aif->data[pos++];
if (loop_type)
{
loop_start = (aif->data[pos++] << 8);
loop_start |= (uint8_t)aif->data[pos++];
loop_end = (aif->data[pos++] << 8);
loop_end |= (uint8_t)aif->data[pos++];
}
else
{
// Skip NoLooping sustain loop.
pos += 4;
}
// Skip release loop, we don't need it.
pos += 6;
}
else if (strcmp(chunk_name, "SSND") == 0)
{
// Skip offset and blockSize
pos += 8;
unsigned long num_samples = chunk_size - 8;
if (aif_data->sample_size == 8)
{
uint8_t *sample_data = (uint8_t *)malloc(num_samples * sizeof(uint8_t));
memcpy(sample_data, &aif->data[pos], num_samples);
aif_data->samples8 = sample_data;
aif_data->real_num_samples = num_samples;
}
else
{
uint16_t *sample_data = (uint16_t *)malloc(num_samples * sizeof(uint16_t));
uint16_t *sample_data_swapped = (uint16_t *)malloc(num_samples * sizeof(uint16_t));
memcpy(sample_data, &aif->data[pos], num_samples);
for (long unsigned i = 0; i < num_samples; i++)
{
sample_data_swapped[i] = __builtin_bswap16(sample_data[i]);
}
aif_data->samples16 = sample_data_swapped;
aif_data->real_num_samples = num_samples;
free(sample_data);
}
pos += chunk_size - 8;
}
else
{
// Skip over unsupported chunks.
pos += chunk_size;
}
}
if (markers)
{
// Resolve loop points.
struct Marker *cur_marker = markers;
// Grab loop start point.
for (int i = 0; i < num_markers; i++, cur_marker++)
{
if (cur_marker->id == loop_start)
{
aif_data->loop_offset = cur_marker->position;
aif_data->has_loop = true;
break;
}
}
cur_marker = markers;
// Grab loop end point.
for (int i = 0; i < num_markers; i++, cur_marker++)
{
if (cur_marker->id == loop_end)
{
if (cur_marker->position < aif_data->loop_offset) {
aif_data->loop_offset = cur_marker->position;
aif_data->has_loop = true;
}
aif_data->num_samples = cur_marker->position;
break;
}
}
free(markers);
}
}
// This is a table of deltas between sample values in compressed PCM data.
const int gDeltaEncodingTable[] = {
0, 1, 4, 9, 16, 25, 36, 49,
-64, -49, -36, -25, -16, -9, -4, -1,
};
#define POSITIVE_DELTAS_START 0
#define POSITIVE_DELTAS_END 8
#define NEGATIVE_DELTAS_START 8
#define NEGATIVE_DELTAS_END 16
struct Bytes *delta_decompress(struct Bytes *delta, unsigned int expected_length)
{
struct Bytes *pcm = malloc(sizeof(struct Bytes));
pcm->length = expected_length;
pcm->data = malloc(pcm->length + 0x40);
uint8_t hi, lo;
unsigned int i = 0;
unsigned int j = 0;
int k;
int8_t base;
while (i < delta->length)
{
base = (int8_t)delta->data[i++];
pcm->data[j++] = (uint8_t)base;
if (i >= delta->length)
{
break;
}
if (j >= pcm->length)
{
break;
}
lo = delta->data[i] & 0xf;
base += gDeltaEncodingTable[lo];
pcm->data[j++] = base;
i++;
if (i >= delta->length)
{
break;
}
if (j >= pcm->length)
{
break;
}
for (k = 0; k < 31; k++)
{
hi = (delta->data[i] >> 4) & 0xf;
base += gDeltaEncodingTable[hi];
pcm->data[j++] = base;
if (j >= pcm->length)
{
break;
}
lo = delta->data[i] & 0xf;
base += gDeltaEncodingTable[lo];
pcm->data[j++] = base;
i++;
if (i >= delta->length)
{
break;
}
if (j >= pcm->length)
{
break;
}
}
if (j >= pcm->length)
{
break;
}
}
pcm->length = j;
return pcm;
}
#define U8_TO_S8(value) ((value) < 128 ? (value) : (value) - 256)
#define ABS(value) ((value) >= 0 ? (value) : -(value))
int get_delta_index(uint8_t sample, uint8_t prev_sample)
{
int best_error = INT_MAX;
int best_index = -1;
int delta_table_start_index;
int delta_table_end_index;
int sample_signed = U8_TO_S8(sample);
int prev_sample_signed = U8_TO_S8(prev_sample);
// if we're going up (or equal), only choose positive deltas
if (prev_sample_signed <= sample_signed) {
delta_table_start_index = POSITIVE_DELTAS_START;
delta_table_end_index = POSITIVE_DELTAS_END;
} else {
delta_table_start_index = NEGATIVE_DELTAS_START;
delta_table_end_index = NEGATIVE_DELTAS_END;
}
for (int i = delta_table_start_index; i < delta_table_end_index; i++)
{
uint8_t new_sample = prev_sample + gDeltaEncodingTable[i];
int new_sample_signed = U8_TO_S8(new_sample);
int error = ABS(new_sample_signed - sample_signed);
if (error < best_error)
{
best_error = error;
best_index = i;
}
}
return best_index;
}
struct Bytes *delta_compress(struct Bytes *pcm)
{
struct Bytes *delta = malloc(sizeof(struct Bytes));
// estimate the length so we can malloc
int num_blocks = pcm->length / 64;
delta->length = num_blocks * 33;
int extra = pcm->length % 64;
if (extra)
{
delta->length += 1;
extra -= 1;
}
if (extra)
{
delta->length += 1;
extra -= 1;
}
if (extra)
{
delta->length += (extra + 1) / 2;
}
delta->data = malloc(delta->length + 33);
unsigned int i = 0;
unsigned int j = 0;
int k;
uint8_t base;
int delta_index;
while (i < pcm->length)
{
base = pcm->data[i++];
delta->data[j++] = base;
if (i >= pcm->length)
{
break;
}
delta_index = get_delta_index(pcm->data[i++], base);
base += gDeltaEncodingTable[delta_index];
delta->data[j++] = delta_index;
for (k = 0; k < 31; k++)
{
if (i >= pcm->length)
{
break;
}
delta_index = get_delta_index(pcm->data[i++], base);
base += gDeltaEncodingTable[delta_index];
delta->data[j] = (delta_index << 4);
if (i >= pcm->length)
{
break;
}
delta_index = get_delta_index(pcm->data[i++], base);
base += gDeltaEncodingTable[delta_index];
delta->data[j++] |= delta_index;
}
}
delta->length = j;
return delta;
}
#define STORE_U32_LE(dest, value) \
do { \
*(dest) = (value) & 0xff; \
*((dest) + 1) = ((value) >> 8) & 0xff; \
*((dest) + 2) = ((value) >> 16) & 0xff; \
*((dest) + 3) = ((value) >> 24) & 0xff; \
} while (0)
#define LOAD_U32_LE(var, src) \
do { \
(var) = *(src); \
(var) |= (*((src) + 1) << 8); \
(var) |= (*((src) + 2) << 16); \
(var) |= (*((src) + 3) << 24); \
} while (0)
// Reads an .aif file and produces a .pcm file containing an array of 8-bit samples.
void aif2pcm(const char *aif_filename, const char *pcm_filename, bool compress)
{
struct Bytes *aif = read_bytearray(aif_filename);
AifData aif_data = {0};
read_aif(aif, &aif_data);
// Convert 16-bit to 8-bit if necessary
if (aif_data.sample_size == 16)
{
aif_data.real_num_samples /= 2;
uint8_t *converted_samples = malloc(aif_data.real_num_samples * sizeof(uint8_t));
for (unsigned long i = 0; i < aif_data.real_num_samples; i++)
{
converted_samples[i] = aif_data.samples16[i] >> 8;
}
free(aif_data.samples16);
aif_data.samples8 = converted_samples;
}
int header_size = 0x10;
struct Bytes *pcm;
struct Bytes output = {0,0};
if (compress)
{
struct Bytes *input = malloc(sizeof(struct Bytes));
input->data = aif_data.samples8;
input->length = aif_data.real_num_samples;
pcm = delta_compress(input);
free(input);
}
else
{
pcm = malloc(sizeof(struct Bytes));
pcm->data = aif_data.samples8;
pcm->length = aif_data.real_num_samples;
}
output.length = header_size + pcm->length;
output.data = malloc(output.length);
uint32_t pitch_adjust = (uint32_t)(aif_data.sample_rate * 1024);
uint32_t loop_offset = (uint32_t)(aif_data.loop_offset);
uint32_t adjusted_num_samples = (uint32_t)(aif_data.num_samples - 1);
uint32_t flags = 0;
if (aif_data.has_loop) flags |= 0x40000000;
if (compress) flags |= 1;
STORE_U32_LE(output.data + 0, flags);
STORE_U32_LE(output.data + 4, pitch_adjust);
STORE_U32_LE(output.data + 8, loop_offset);
STORE_U32_LE(output.data + 12, adjusted_num_samples);
memcpy(&output.data[header_size], pcm->data, pcm->length);
write_bytearray(pcm_filename, &output);
free(aif->data);
free(aif);
free(pcm);
free(output.data);
free(aif_data.samples8);
}
// Reads a .pcm file containing an array of 8-bit samples and produces an .aif file.
// See http://www-mmsp.ece.mcgill.ca/documents/audioformats/aiff/Docs/AIFF-1.3.pdf for .aif file specification.
void pcm2aif(const char *pcm_filename, const char *aif_filename, uint32_t base_note)
{
struct Bytes *pcm = read_bytearray(pcm_filename);
AifData *aif_data = malloc(sizeof(AifData));
uint32_t flags;
LOAD_U32_LE(flags, pcm->data + 0);
aif_data->has_loop = flags & 0x40000000;
bool compressed = flags & 1;
uint32_t pitch_adjust;
LOAD_U32_LE(pitch_adjust, pcm->data + 4);
aif_data->sample_rate = pitch_adjust / 1024.0;
LOAD_U32_LE(aif_data->loop_offset, pcm->data + 8);
LOAD_U32_LE(aif_data->num_samples, pcm->data + 12);
aif_data->num_samples += 1;
if (compressed)
{
struct Bytes *delta = pcm;
uint8_t *pcm_data = pcm->data;
delta->length -= 0x10;
delta->data += 0x10;
pcm = delta_decompress(delta, aif_data->num_samples);
free(pcm_data);
free(delta);
}
else
{
pcm->length -= 0x10;
pcm->data += 0x10;
}
aif_data->samples8 = malloc(pcm->length);
memcpy(aif_data->samples8, pcm->data, pcm->length);
struct Bytes *aif = malloc(sizeof(struct Bytes));
aif->length = 54 + 60 + pcm->length;
aif->data = malloc(aif->length);
long pos = 0;
// First, write the FORM header chunk.
// FORM Chunk ckID
aif->data[pos++] = 'F';
aif->data[pos++] = 'O';
aif->data[pos++] = 'R';
aif->data[pos++] = 'M';
// FORM Chunk ckSize
unsigned long form_size = pos;
unsigned long data_size = aif->length - 8;
aif->data[pos++] = ((data_size >> 24) & 0xFF);
aif->data[pos++] = ((data_size >> 16) & 0xFF);
aif->data[pos++] = ((data_size >> 8) & 0xFF);
aif->data[pos++] = (data_size & 0xFF);
// FORM Chunk formType
aif->data[pos++] = 'A';
aif->data[pos++] = 'I';
aif->data[pos++] = 'F';
aif->data[pos++] = 'F';
// Next, write the Common Chunk
// Common Chunk ckID
aif->data[pos++] = 'C';
aif->data[pos++] = 'O';
aif->data[pos++] = 'M';
aif->data[pos++] = 'M';
// Common Chunk ckSize
aif->data[pos++] = 0;
aif->data[pos++] = 0;
aif->data[pos++] = 0;
aif->data[pos++] = 18;
// Common Chunk numChannels
aif->data[pos++] = 0;
aif->data[pos++] = 1; // 1 channel
// Common Chunk numSampleFrames
aif->data[pos++] = ((aif_data->num_samples >> 24) & 0xFF);
aif->data[pos++] = ((aif_data->num_samples >> 16) & 0xFF);
aif->data[pos++] = ((aif_data->num_samples >> 8) & 0xFF);
aif->data[pos++] = (aif_data->num_samples & 0xFF);
// Common Chunk sampleSize
aif->data[pos++] = 0;
aif->data[pos++] = 8; // 8 bits per sample
// Common Chunk sampleRate
//double sample_rate = pitch_adjust / 1024.0;
uint8_t sample_rate_buffer[10];
ieee754_write_extended(aif_data->sample_rate, sample_rate_buffer);
for (int i = 0; i < 10; i++)
{
aif->data[pos++] = sample_rate_buffer[i];
}
if (aif_data->has_loop)
{
// Marker Chunk ckID
aif->data[pos++] = 'M';
aif->data[pos++] = 'A';
aif->data[pos++] = 'R';
aif->data[pos++] = 'K';
// Marker Chunk ckSize
aif->data[pos++] = 0;
aif->data[pos++] = 0;
aif->data[pos++] = 0;
aif->data[pos++] = 12 + (aif_data->has_loop ? 12 : 0);
// Marker Chunk numMarkers
aif->data[pos++] = 0;
aif->data[pos++] = (aif_data->has_loop ? 2 : 1);
// Marker loop start
aif->data[pos++] = 0;
aif->data[pos++] = 1; // id = 1
long loop_start = aif_data->loop_offset;
aif->data[pos++] = ((loop_start >> 24) & 0xFF);
aif->data[pos++] = ((loop_start >> 16) & 0xFF);
aif->data[pos++] = ((loop_start >> 8) & 0xFF);
aif->data[pos++] = (loop_start & 0xFF); // position
aif->data[pos++] = 5; // pascal-style string length
aif->data[pos++] = 'S';
aif->data[pos++] = 'T';
aif->data[pos++] = 'A';
aif->data[pos++] = 'R';
aif->data[pos++] = 'T'; // markerName
// Marker loop end
aif->data[pos++] = 0;
aif->data[pos++] = (aif_data->has_loop ? 2 : 1); // id = 2
long loop_end = aif_data->num_samples;
aif->data[pos++] = ((loop_end >> 24) & 0xFF);
aif->data[pos++] = ((loop_end >> 16) & 0xFF);
aif->data[pos++] = ((loop_end >> 8) & 0xFF);
aif->data[pos++] = (loop_end & 0xFF); // position
aif->data[pos++] = 3; // pascal-style string length
aif->data[pos++] = 'E';
aif->data[pos++] = 'N';
aif->data[pos++] = 'D';
}
// Instrument Chunk ckID
aif->data[pos++] = 'I';
aif->data[pos++] = 'N';
aif->data[pos++] = 'S';
aif->data[pos++] = 'T';
// Instrument Chunk ckSize
aif->data[pos++] = 0;
aif->data[pos++] = 0;
aif->data[pos++] = 0;
aif->data[pos++] = 20;
aif->data[pos++] = base_note; // baseNote
aif->data[pos++] = 0; // detune
aif->data[pos++] = 0; // lowNote
aif->data[pos++] = 127; // highNote
aif->data[pos++] = 1; // lowVelocity
aif->data[pos++] = 127; // highVelocity
aif->data[pos++] = 0; // gain (hi)
aif->data[pos++] = 0; // gain (lo)
// Instrument Chunk sustainLoop
aif->data[pos++] = 0;
aif->data[pos++] = 1; // playMode = ForwardLooping
aif->data[pos++] = 0;
aif->data[pos++] = 1; // beginLoop marker id
aif->data[pos++] = 0;
aif->data[pos++] = 2; // endLoop marker id
// Instrument Chunk releaseLoop
aif->data[pos++] = 0;
aif->data[pos++] = 1; // playMode = ForwardLooping
aif->data[pos++] = 0;
aif->data[pos++] = 1; // beginLoop marker id
aif->data[pos++] = 0;
aif->data[pos++] = 2; // endLoop marker id
// Finally, write the Sound Data Chunk
// Sound Data Chunk ckID
aif->data[pos++] = 'S';
aif->data[pos++] = 'S';
aif->data[pos++] = 'N';
aif->data[pos++] = 'D';
// Sound Data Chunk ckSize
unsigned long sound_data_size = pcm->length + 8;
aif->data[pos++] = ((sound_data_size >> 24) & 0xFF);
aif->data[pos++] = ((sound_data_size >> 16) & 0xFF);
aif->data[pos++] = ((sound_data_size >> 8) & 0xFF);
aif->data[pos++] = (sound_data_size & 0xFF);
// Sound Data Chunk offset
aif->data[pos++] = 0;
aif->data[pos++] = 0;
aif->data[pos++] = 0;
aif->data[pos++] = 0;
// Sound Data Chunk blockSize
aif->data[pos++] = 0;
aif->data[pos++] = 0;
aif->data[pos++] = 0;
aif->data[pos++] = 0;
// Sound Data Chunk soundData
for (unsigned int i = 0; i < aif_data->loop_offset; i++)
{
aif->data[pos++] = aif_data->samples8[i];
}
int j = 0;
for (unsigned int i = aif_data->loop_offset; i < pcm->length; i++)
{
int pcm_index = aif_data->loop_offset + (j++ % (pcm->length - aif_data->loop_offset));
aif->data[pos++] = aif_data->samples8[pcm_index];
}
aif->length = pos;
// Go back and rewrite ckSize
data_size = aif->length - 8;
aif->data[form_size + 0] = ((data_size >> 24) & 0xFF);
aif->data[form_size + 1] = ((data_size >> 16) & 0xFF);
aif->data[form_size + 2] = ((data_size >> 8) & 0xFF);
aif->data[form_size + 3] = (data_size & 0xFF);
write_bytearray(aif_filename, aif);
free(aif->data);
free(aif);
}
void usage(void)
{
fprintf(stderr, "Usage: aif2pcm bin_file [aif_file]\n");
fprintf(stderr, " aif2pcm aif_file [bin_file] [--compress]\n");
}
int main(int argc, char **argv)
{
if (argc < 2)
{
usage();
exit(1);
}
char *input_file = argv[1];
char *extension = get_file_extension(input_file);
char *output_file;
bool compressed = false;
if (argc > 3)
{
for (int i = 3; i < argc; i++)
{
if (strcmp(argv[i], "--compress") == 0)
{
compressed = true;
}
}
}
if (strcmp(extension, "aif") == 0 || strcmp(extension, "aiff") == 0)
{
if (argc >= 3)
{
output_file = argv[2];
aif2pcm(input_file, output_file, compressed);
}
else
{
output_file = new_file_extension(input_file, "bin");
aif2pcm(input_file, output_file, compressed);
free(output_file);
}
}
else if (strcmp(extension, "bin") == 0)
{
if (argc >= 3)
{
output_file = argv[2];
pcm2aif(input_file, output_file, 60);
}
else
{
output_file = new_file_extension(input_file, "aif");
pcm2aif(input_file, output_file, 60);
free(output_file);
}
}
else
{
FATAL_ERROR("Input file must be .aif or .bin: '%s'\n", input_file);
}
return 0;
}
+10
View File
@@ -0,0 +1,10 @@
*.o
*.exe
*.s
*.gba
*.sdf
wav2agb
Debug
Release
.vs
@@ -1,5 +1,6 @@
Copyright (c) 2016 huderlem
Copyright (c) 2005, 2006 by Marco Trillo <marcotrillo@gmail.com>
The MIT License (MIT)
Copyright (c) 2016 ipatix
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -8,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+33
View File
@@ -0,0 +1,33 @@
GIT_VERSION := $(shell git describe --abbrev=7 --dirty --always --tags)
CXX = g++
STRIP = strip
CXXFLAGS = -Wall -Wextra -Wconversion -std=c++17 -O2 -g -DGIT_VERSION=\"$(GIT_VERSION)\"
EXE :=
ifeq ($(OS),Windows_NT)
EXE := .exe
endif
BINARY = wav2agb$(EXE)
SRC_FILES = $(wildcard *.cpp)
OBJ_FILES = $(SRC_FILES:.cpp=.o)
LDFLAGS :=
ifneq (,$(RELEASE))
LDFLAGS += -static
CXXFLAGS += -flto
endif
.PHONY: clean clean
all: $(BINARY)
clean:
rm -f $(OBJ_FILES) $(BINARY)
$(BINARY): $(OBJ_FILES)
$(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $^
if [ $(RELEASE)x != x ]; then strip -s $@; fi
%.o: %.cpp
$(CXX) -c -o $@ $< $(CXXFLAGS)
+29
View File
@@ -0,0 +1,29 @@
# wav2agb
"wav2agb" is a tool to convert standard .wav files to GBA compatible .s or .bin files. Intended to convert .wav files for the use with the mp2k/m4a sound driver.
This copy has been slightly modified from [ipatix's original implementation](https://github.com/ipatix/wav2agb) in the following ways:
1. Support outputting `.bin` files with a command line option `-b, --binary`. (The original only supports outputting `.s` assembly files.)
2. Support reading an override "pitch" value from a custom `agbp` RIFF chunk.
- This is needed to properly match some samples, due to float-point rounding errors when attempting to infer the pitch/sample rate from the .wav file's sample rate.
- If the custom `agbp` chunk isn't present, it will simply use the .wav's sample rate to calculate this "pitch" value.
3. Optionally omits trailing padding from compressed output.
Usage:
```
Usage: wav2agb [options] <input.wav> [<output>]
Options:
-s, --symbol <sym> | symbol name for wave header (default: file name)
-l, --lookahead <amount> | DPCM compression lookahead 1..8 (default: 3)
-c, --compress | compress output with DPCM
-f, --fast-compress | compress output with DPCM fast
--no-pad | omit trailing padding in compressed output
-b, --binary | output raw binary instead of assembly
--loop-start <pos> | override loop start (integer)
--loop-end <pos> | override loop end (integer)
--tune <cents> | override tuning (float)
--key <key> | override midi key (int)
--rate <rate> | override base samplerate (int)
```
Flag -c enables compression (only supported by Pokemon Games)
+462
View File
@@ -0,0 +1,462 @@
#include "converter.h"
#include <stdexcept>
#include <fstream>
#include <algorithm>
#include <vector>
#include <cmath>
#include <cstdarg>
#include <cassert>
#include <cstring>
#include <chrono>
#include "wav_file.h"
static void agb_out(std::ofstream& ofs, const char *msg, ...) {
char buf[256];
va_list args;
va_start(args, msg);
vsnprintf(buf, sizeof(buf), msg, args);
va_end(args);
ofs << buf;
}
static void data_write(std::ofstream& ofs, uint32_t& block_pos, int data, bool hex) {
if (block_pos++ == 0) {
if (hex)
agb_out(ofs, "\n .byte 0x%02X", data);
else
agb_out(ofs, "\n .byte %4d", data);
} else {
if (hex)
agb_out(ofs, ", 0x%02X", data);
else
agb_out(ofs, ", %4d", data);
}
block_pos %= 16;
}
static void bin_write_u8(std::vector<uint8_t>& data, uint8_t value) {
data.push_back(value);
}
static void bin_write_u32_le(std::vector<uint8_t>& data, uint32_t value) {
data.push_back(static_cast<uint8_t>(value & 0xFF));
data.push_back(static_cast<uint8_t>((value >> 8) & 0xFF));
data.push_back(static_cast<uint8_t>((value >> 16) & 0xFF));
data.push_back(static_cast<uint8_t>((value >> 24) & 0xFF));
}
template<typename T>
const T& clamp(const T& v, const T& lo, const T& hi) {
return (v < lo) ? lo : (hi < v) ? hi : v;
}
static void convert_uncompressed(wav_file& wf, std::ofstream& ofs)
{
int loop_sample = 0;
uint32_t block_pos = 0;
for (size_t i = 0; i < wf.loopEnd; i++) {
double ds;
wf.readData(i, &ds, 1);
// TODO apply dither noise
int s = clamp(static_cast<int>(floor(ds * 128.0)), -128, 127);
if (wf.loopEnabled && i == wf.loopStart)
loop_sample = s;
data_write(ofs, block_pos, s, false);
}
data_write(ofs, block_pos, loop_sample, false);
}
static void convert_uncompressed_bin(wav_file& wf, std::vector<uint8_t>& data)
{
for (size_t i = 0; i < wf.loopEnd; i++) {
double ds;
wf.readData(i, &ds, 1);
// TODO apply dither noise
int s = clamp(static_cast<int>(floor(ds * 128.0)), -128, 127);
bin_write_u8(data, static_cast<uint8_t>(s));
}
// Align to 4 bytes.
while (data.size() % 4 != 0) {
bin_write_u8(data, 0);
}
}
static uint32_t wav_loop_start;
static bool wav_loop_start_override = false;
static uint32_t wav_loop_end;
static bool wav_loop_end_override = false;
static double wav_tune;
static bool wav_tune_override = false;
static uint8_t wav_key;
static bool wav_key_override = false;
static uint32_t wav_rate;
static bool wav_rate_override = false;
static bool dpcm_verbose = false;
static bool dpcm_lookahead_fast = false;
static bool dpcm_include_padding = true;
static size_t dpcm_enc_lookahead = 3;
static const size_t DPCM_BLK_SIZE = 0x40;
static const std::vector<int8_t> dpcmLookupTable = {
0, 1, 4, 9, 16, 25, 36, 49, -64, -49, -36, -25, -16, -9, -4, -1
};
static const std::vector<size_t> dpcmIndexTable = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
};
static const std::vector<std::vector<size_t>> dpcmFastLookupTable = {
{8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8},
{8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8},
{8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8},
{8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8},
{8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8},
{8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8},
{8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8},
{8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8},
{8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8},
{8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8},
{8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8},
{8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8}, {8},
{8, 9}, {8, 9}, {8, 9}, {8, 9}, {8, 9}, {8, 9}, {8, 9}, {8, 9}, {8, 9}, {8, 9}, {8, 9}, {8, 9}, {8, 9}, {8, 9}, {9, 10}, {9, 10},
{9, 10}, {9, 10}, {9, 10}, {9, 10}, {9, 10}, {9, 10}, {9, 10}, {9, 10}, {9, 10}, {9, 10}, {9, 10}, {10, 11}, {10, 11}, {10, 11}, {10, 11}, {10, 11},
{10, 11}, {10, 11}, {10, 11}, {10, 11}, {10, 11}, {10, 11}, {11, 12}, {11, 12}, {11, 12}, {11, 12}, {11, 12}, {11, 12}, {11, 12}, {11, 12}, {11, 12}, {12, 13},
{12, 13}, {12, 13}, {12, 13}, {12, 13}, {12, 13}, {12, 13}, {13, 14}, {13, 14}, {13, 14}, {13, 14}, {13, 14}, {14, 15}, {14, 15}, {14, 15}, {0, 15}, {0, 1, 15},
{1, 0}, {1, 2}, {1, 2}, {2, 1}, {2, 3}, {2, 3}, {2, 3}, {2, 3}, {3, 2}, {3, 4}, {3, 4}, {3, 4}, {3, 4}, {3, 4}, {3, 4}, {4, 3},
{4, 5}, {4, 5}, {4, 5}, {4, 5}, {4, 5}, {4, 5}, {4, 5}, {4, 5}, {5, 4}, {5, 6}, {5, 6}, {5, 6}, {5, 6}, {5, 6}, {5, 6}, {5, 6},
{5, 6}, {5, 6}, {5, 6}, {6, 5}, {6, 7}, {6, 7}, {6, 7}, {6, 7}, {6, 7}, {6, 7}, {6, 7}, {6, 7}, {6, 7}, {6, 7}, {6, 7}, {6, 7},
{7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7},
{7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7},
{7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7},
{7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7},
{7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7},
{7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7},
{7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7},
{7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7},
{7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7},
{7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7},
{7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7},
{7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7},
{7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}, {7}
};
static int squared(int x) { return x * x; }
static void dpcm_lookahead(
int& minimumError, size_t& minimumErrorIndex,
const double *sampleBuf, const size_t lookahead, const int prevLevel)
{
if (lookahead == 0) {
minimumError = 0;
return;
}
minimumError = std::numeric_limits<int>::max();
minimumErrorIndex = dpcmLookupTable.size();
const int s = clamp(static_cast<int>(floor(sampleBuf[0] * 128.0)), -128, 127);
const std::vector<size_t> indexCandicateSet = dpcm_lookahead_fast? dpcmFastLookupTable[s - prevLevel + 255]: dpcmIndexTable;
for (auto i : indexCandicateSet) {
int newLevel = prevLevel + dpcmLookupTable[i];
int recMinimumError;
size_t recMinimumErrorIndex;
// TODO apply dither noise
int errorEstimation = squared(s - newLevel);
if (errorEstimation >= minimumError)
continue;
dpcm_lookahead(recMinimumError, recMinimumErrorIndex,
sampleBuf + 1, lookahead - 1, newLevel);
// TODO weigh the error squared
int error = squared(s - newLevel) + recMinimumError;
if (error < minimumError) {
if (newLevel <= 127 && newLevel >= -128) {
minimumError = error;
minimumErrorIndex = i;
}
}
}
}
static double calculate_snr(const std::vector<double>& uncompressedData, const std::vector<int>& decompressedData)
{
int sum_son = 0;
int sum_mum = 0;
assert(uncompressedData.size() == decompressedData.size());
for (size_t i = 0; i < uncompressedData.size(); i++) {
const int s = clamp(static_cast<int>(floor(uncompressedData[i] * 128.0)), -128, 127) + 128;
sum_son += s * s;
const int sub = decompressedData[i] + 128 - s;
sum_mum += sub * sub;
}
if (sum_mum == 0) {
return 100;
}
return 10 * std::log10((double)sum_son / sum_mum);
}
template<typename InitialSampleWriter, typename CompressedDataWriter>
static void convert_dpcm_impl(wav_file& wf, InitialSampleWriter writeInitialSample, CompressedDataWriter writeCompressedData)
{
int minimumError;
size_t minimumErrorIndex;
std::vector<double> uncompressedData;
std::vector<int> decompressedData;
const auto startTime = std::chrono::high_resolution_clock::now();
for (size_t i = 0; i < wf.loopEnd; i += DPCM_BLK_SIZE) {
double ds[DPCM_BLK_SIZE];
size_t samples_in_block = std::min(DPCM_BLK_SIZE, wf.loopEnd - i);
wf.readData(i, ds, samples_in_block);
// Pad remaining samples in block with zeros if needed
for (size_t j = samples_in_block; j < DPCM_BLK_SIZE; j++) {
ds[j] = 0.0;
}
if (dpcm_verbose) {
uncompressedData.insert(uncompressedData.end(), std::begin(ds), std::end(ds));
}
// TODO apply dither noise
int s = clamp(static_cast<int>(floor(ds[0] * 128.0)), -128, 127);
writeInitialSample(s);
if (dpcm_verbose) {
decompressedData.push_back(s);
}
size_t innerLoopCount = 1;
size_t samples_to_process = dpcm_include_padding ? DPCM_BLK_SIZE : samples_in_block;
uint8_t outData = 0;
size_t sampleBufReadLen;
goto initial_loop_enter;
do {
if (innerLoopCount >= samples_to_process)
break;
sampleBufReadLen = std::min(dpcm_enc_lookahead, DPCM_BLK_SIZE - innerLoopCount);
dpcm_lookahead(
minimumError, minimumErrorIndex,
&ds[innerLoopCount], sampleBufReadLen, s);
outData = static_cast<uint8_t>((minimumErrorIndex & 0xF) << 4);
s += dpcmLookupTable[minimumErrorIndex];
if (dpcm_verbose) {
decompressedData.push_back(s);
}
innerLoopCount += 1;
initial_loop_enter:
if (innerLoopCount >= samples_to_process)
break;
sampleBufReadLen = std::min(dpcm_enc_lookahead, DPCM_BLK_SIZE - innerLoopCount);
dpcm_lookahead(
minimumError, minimumErrorIndex,
&ds[innerLoopCount], sampleBufReadLen, s);
outData |= static_cast<uint8_t>(minimumErrorIndex & 0xF);
s += dpcmLookupTable[minimumErrorIndex];
innerLoopCount += 1;
if (dpcm_verbose) {
decompressedData.push_back(s);
}
writeCompressedData(outData);
} while (innerLoopCount < DPCM_BLK_SIZE);
}
const auto endTime = std::chrono::high_resolution_clock::now();
if (dpcm_verbose) {
const auto dur = std::chrono::duration_cast<std::chrono::nanoseconds>(endTime - startTime);
const double durSecs = static_cast<double>(dur.count()) / 1000000000.0;
printf("SNR: %.2fdB, run time: %.2fs\n", calculate_snr(uncompressedData, decompressedData), durSecs);
}
}
static void convert_dpcm(wav_file& wf, std::ofstream& ofs)
{
uint32_t block_pos = 0;
convert_dpcm_impl(wf,
[&](int s) { data_write(ofs, block_pos, s, false); },
[&](uint8_t outData) { data_write(ofs, block_pos, outData, true); });
}
static void convert_dpcm_bin(wav_file& wf, std::vector<uint8_t>& data)
{
convert_dpcm_impl(wf,
[&](int s) { bin_write_u8(data, static_cast<uint8_t>(s)); },
[&](uint8_t outData) { bin_write_u8(data, outData); });
}
void enable_dpcm_verbose()
{
dpcm_verbose = true;
}
void enable_dpcm_lookahead_fast()
{
dpcm_lookahead_fast = true;
}
void disable_dpcm_padding()
{
dpcm_include_padding = false;
}
void set_dpcm_lookahead(size_t lookahead)
{
dpcm_enc_lookahead = clamp<size_t>(lookahead, 1, 8);
}
void set_wav_loop_start(uint32_t start)
{
wav_loop_start = start;
wav_loop_start_override = true;
}
void set_wav_loop_end(uint32_t end)
{
wav_loop_end = end;
wav_loop_end_override = true;
}
void set_wav_tune(double tune)
{
wav_tune = tune;
wav_tune_override = true;
}
void set_wav_key(uint8_t key)
{
wav_key = key;
wav_key_override = true;
}
void set_wav_rate(uint32_t rate)
{
wav_rate = rate;
wav_rate_override = true;
}
void convert(const std::string& wav_file_str, const std::string& out_file_str,
const std::string& sym, cmp_type ct, out_type ot)
{
wav_file wf(wav_file_str);
// check command line overrides
if (wav_loop_start_override) {
wf.loopStart = std::min(wav_loop_start, wf.loopEnd);
wf.loopEnabled = true;
}
if (wav_loop_end_override) {
wf.loopEnd = std::min(wav_loop_end, wf.loopEnd);
}
if (wav_tune_override) {
wf.tuning = wav_tune;
}
if (wav_key_override) {
wf.midiKey = wav_key;
}
if (wav_rate_override) {
wf.sampleRate = wav_rate;
}
uint8_t fmt;
if (ct == cmp_type::none)
fmt = 0;
else if (ct == cmp_type::dpcm)
fmt = 1;
else
throw std::runtime_error("convert: invalid compression type");
double pitch;
if (wf.midiKey == 60 && wf.tuning == 0.0) {
pitch = wf.sampleRate;
} else {
pitch = wf.sampleRate * pow(2.0, (60.0 - wf.midiKey) / 12.0 + wf.tuning / 1200.0);
}
uint32_t pitch_value;
if (wf.agbPitch != 0) {
pitch_value = wf.agbPitch;
} else {
pitch_value = static_cast<uint32_t>(pitch * 1024.0);
}
if (ot == out_type::binary) {
// Binary output mode
std::vector<uint8_t> bin_data;
// Write header (16 bytes)
// Bytes 0-3: flags (format in bit 0, loop in bit 30)
uint32_t flags = fmt;
if (wf.loopEnabled)
flags |= 0x40000000;
bin_write_u32_le(bin_data, flags);
// Bytes 4-7: pitch
bin_write_u32_le(bin_data, pitch_value);
// Bytes 8-11: loop start
bin_write_u32_le(bin_data, wf.loopStart);
// Bytes 12-15: loop end
// wf.loopEnd is the exclusive end position; binary format expects (end - 1)
bin_write_u32_le(bin_data, wf.loopEnd > 0 ? wf.loopEnd - 1 : 0);
// Write sample data
if (ct == cmp_type::none)
convert_uncompressed_bin(wf, bin_data);
else if (ct == cmp_type::dpcm)
convert_dpcm_bin(wf, bin_data);
else
throw std::runtime_error("convert: invalid compression type");
// Write binary file
std::ofstream fout(out_file_str, std::ios::out | std::ios::binary);
if (!fout.is_open()) {
perror("ofstream");
throw std::runtime_error("unable to open output file");
}
fout.write(reinterpret_cast<const char*>(bin_data.data()), bin_data.size());
fout.close();
} else {
// Assembly output mode
std::ofstream fout(out_file_str, std::ios::out);
if (!fout.is_open()) {
perror("ofstream");
throw std::runtime_error("unable to open output file");
}
agb_out(fout, " .section .rodata\n");
agb_out(fout, " .global %s\n", sym.c_str());
agb_out(fout, " .align 2\n\n%s:\n\n", sym.c_str());
agb_out(fout, " .byte 0x%X, 0x0, 0x0, 0x%X\n", fmt, wf.loopEnabled ? 0x40 : 0x0);
agb_out(fout, " .word 0x%08X @ Mid-C ~%f\n", pitch_value, pitch);
agb_out(fout, " .word %u, %u\n", wf.loopStart, wf.loopEnd);
if (ct == cmp_type::none)
convert_uncompressed(wf, fout);
else if (ct == cmp_type::dpcm)
convert_dpcm(wf, fout);
else
throw std::runtime_error("convert: invalid compression type");
agb_out(fout, "\n\n .end\n");
fout.close();
}
}
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#include <string>
#include <algorithm>
#include <cstdint>
enum class cmp_type {
none, dpcm
};
enum class out_type {
assembly, binary
};
void enable_dpcm_verbose();
void enable_dpcm_lookahead_fast();
void disable_dpcm_padding();
void set_dpcm_lookahead(size_t lookahead);
void set_wav_loop_start(uint32_t start);
void set_wav_loop_end(uint32_t end);
void set_wav_tune(double tune);
void set_wav_key(uint8_t key);
void set_wav_rate(uint32_t rate);
void convert(const std::string&, const std::string&,
const std::string& sym, cmp_type ct, out_type ot);
+9
View File
@@ -0,0 +1,9 @@
#!/bin/sh
for l in $(seq 1 8)
do
echo lookahead="$l":
wav2agb "$1" -c -l "$l" --verbose
echo lookahead="$l" fast:
wav2agb "$1" -f -l "$l" --verbose
done
+213
View File
@@ -0,0 +1,213 @@
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cstdarg>
#include <cassert>
#include <string>
#include "converter.h"
static void usage() {
fprintf(stderr, "wav2agb\n");
fprintf(stderr, "\n");
fprintf(stderr, "Usage: wav2agb [options] <input.wav> [<output>]\n");
fprintf(stderr, "\n");
fprintf(stderr, "Options:\n");
fprintf(stderr, "-s, --symbol <sym> | symbol name for wave header (default: file name)\n");
fprintf(stderr, "-l, --lookahead <amount> | DPCM compression lookahead 1..8 (default: 3)\n");
fprintf(stderr, "-c, --compress | compress output with DPCM\n");
fprintf(stderr, "-f, --fast-compress | compress output with DPCM fast\n");
fprintf(stderr, "--no-pad | omit trailing padding in compressed output\n");
fprintf(stderr, "-b, --binary | output raw binary instead of assembly\n");
fprintf(stderr, "--loop-start <pos> | override loop start (integer)\n");
fprintf(stderr, "--loop-end <pos> | override loop end (integer)\n");
fprintf(stderr, "--tune <cents> | override tuning (float)\n");
fprintf(stderr, "--key <key> | override midi key (int)\n");
fprintf(stderr, "--rate <rate> | override base samplerate (int)\n");
exit(1);
}
static void version() {
printf("wav2agb v1.1 (c) 2019 ipatix\n");
exit(0);
}
static void die(const char *msg, ...) {
va_list args;
va_start(args, msg);
vfprintf(stderr, msg, args);
va_end(args);
exit(1);
}
static void fix_str(std::string& str) {
// replaces all characters that are not alphanumerical
for (size_t i = 0; i < str.size(); i++) {
if (str[i] >= 'a' && str[i] <= 'z')
continue;
if (str[i] >= 'A' && str[i] <= 'Z')
continue;
if (str[i] >= '0' && str[i] <= '9' && i > 0)
continue;
str[i] = '_';
}
}
static char path_seperators[] = {
'/',
#ifdef _WIN32
'\\',
#endif
'\0'
};
static std::string filename_without_ext(const std::string& str) {
size_t last_path_seperator = 0;
char *sep = path_seperators;
while (*sep) {
size_t pos = str.find_last_of(*sep);
if (pos != std::string::npos)
last_path_seperator = std::max(pos, last_path_seperator);
sep += 1;
}
size_t file_ext_dot_pos = str.find_last_of('.');
if (file_ext_dot_pos == std::string::npos)
return std::string(str);
assert(file_ext_dot_pos != last_path_seperator);
if (file_ext_dot_pos > last_path_seperator)
return str.substr(0, file_ext_dot_pos);
return std::string(str);
}
static std::string filename_without_dir(const std::string& str) {
size_t last_path_seperator = 0;
bool path_seperator_found = false;
char *sep = path_seperators;
while (*sep) {
size_t pos = str.find_last_of(*sep);
if (pos != std::string::npos) {
last_path_seperator = std::max(pos, last_path_seperator);
path_seperator_found = true;
}
sep += 1;
}
if (str.size() > 0 && path_seperator_found) {
return str.substr(last_path_seperator + 1);
} else {
return std::string(str);
}
}
static cmp_type arg_compress = cmp_type::none;
static out_type arg_output_type = out_type::assembly;
static std::string arg_sym;
static bool arg_input_file_read = false;
static bool arg_output_file_read = false;
static std::string arg_input_file;
static std::string arg_output_file;
int main(int argc, char *argv[]) {
try {
if (argc == 1)
usage();
for (int i = 1; i < argc; i++) {
std::string st(argv[i]);
if (st == "-s" || st == "--symbol") {
if (++i >= argc)
die("-s: missing symbol name\n");
arg_sym = argv[i];
fix_str(arg_sym);
} else if (st == "-c" || st == "--compress") {
arg_compress = cmp_type::dpcm;
} else if (st == "-f" || st == "--compress-fast") {
arg_compress = cmp_type::dpcm;
enable_dpcm_lookahead_fast();
} else if (st == "--no-pad") {
disable_dpcm_padding();
} else if (st == "-b" || st == "--binary") {
arg_output_type = out_type::binary;
} else if (st == "--verbose") {
enable_dpcm_verbose();
} else if (st == "-l" || st == "--lookahead") {
if (++i >= argc)
die("-l: missing parameter");
set_dpcm_lookahead(std::stoul(argv[i], nullptr, 10));
} else if (st == "--version") {
version();
} else if (st == "--loop-start") {
if (++i >= argc)
die("--loop-start: missing parameter");
uint32_t start = static_cast<uint32_t>(std::stoul(argv[i], nullptr, 10));
set_wav_loop_start(start);
} else if (st == "--loop-end") {
if (++i >= argc)
die("--loop-end: missing parameter");
uint32_t end = static_cast<uint32_t>(std::stoul(argv[i], nullptr, 10));
set_wav_loop_end(end);
} else if (st == "--tune") {
if (++i >= argc)
die("--tune: missing parameter");
double tune = std::stod(argv[i], nullptr);
set_wav_tune(tune);
} else if (st == "--key") {
if (++i >= argc)
die("--key: missing parameter");
int key = std::stoi(argv[i], nullptr, 10);
if (key < 0) key = 0;
if (key > 127) key = 127;
set_wav_key(static_cast<uint8_t>(key));
} else if (st == "--rate") {
if (++i >= argc)
die("--rate: missing parameter");
uint32_t rate = static_cast<uint32_t>(std::stoul(argv[i], nullptr, 10));
set_wav_rate(rate);
} else {
if (st == "--") {
if (++i >= argc)
die("--: missing file name\n");
}
if (!arg_input_file_read) {
arg_input_file = argv[i];
if (arg_input_file.size() < 1)
die("empty input file name\n");
arg_input_file_read = true;
} else if (!arg_output_file_read) {
arg_output_file = argv[i];
if (arg_output_file.size() < 1)
die("empty output file name\n");
arg_output_file_read = true;
} else {
die("Too many files specified\n");
}
}
}
// check arguments
if (!arg_input_file_read) {
die("No input file specified\n");
}
if (!arg_output_file_read) {
// create output file name if none is provided
if (arg_output_type == out_type::binary) {
arg_output_file = filename_without_ext(arg_input_file) + ".bin";
} else {
arg_output_file = filename_without_ext(arg_input_file) + ".s";
}
arg_output_file_read = true;
}
if (arg_sym.size() == 0) {
arg_sym = filename_without_dir(filename_without_ext(arg_output_file));
fix_str(arg_sym);
}
convert(arg_input_file, arg_output_file, arg_sym, arg_compress, arg_output_type);
return 0;
} catch (const std::exception& e) {
fprintf(stderr, "std lib error:\n%s\n", e.what());
}
return 1;
}
+287
View File
@@ -0,0 +1,287 @@
#include "wav_file.h"
#include <stdexcept>
#include <cerrno>
#include <cstring>
#include <algorithm>
static uint32_t read_u32(std::ifstream& ifs)
{
uint8_t lenBytes[4];
ifs.read(reinterpret_cast<char *>(lenBytes), sizeof(lenBytes));
uint32_t retval = lenBytes[0] | (lenBytes[1] << 8) | (lenBytes[2] << 16) | (lenBytes[3] << 24);
return retval;
}
//static uint16_t read_u16(std::ifstream& ifs)
//{
// uint8_t lenBytes[2];
// ifs.read(reinterpret_cast<char *>(lenBytes), sizeof(lenBytes));
// uint16_t retval = uint16_t(lenBytes[0] | (lenBytes[1] << 8));
// return retval;
//}
static std::string read_str(std::ifstream& ifs, size_t len)
{
std::vector<char> buf(len);
ifs.read(buf.data(), buf.size());
return std::string(buf.data(), buf.size());
}
static std::vector<uint8_t> read_arr(std::ifstream& ifs, size_t len)
{
std::vector<uint8_t> buf(len);
ifs.read(reinterpret_cast<char *>(buf.data()), buf.size());
return buf;
}
static uint16_t arr_u16(const std::vector<uint8_t>& arr, size_t pos)
{
uint16_t val = uint16_t(arr.at(pos) | (arr.at(pos + 1) << 8));
return val;
}
static uint32_t arr_u32(const std::vector<uint8_t>& arr, size_t pos)
{
uint32_t val = uint32_t(arr.at(pos) | (arr.at(pos + 1) << 8) |
(arr.at(pos + 2) << 16) | (arr.at(pos + 3) << 24));
return val;
}
static const size_t loadChunkSize = 2048;
uint32_t wav_file::fmt_size() const
{
if (fmt == format_type::u8)
return 1;
else if (fmt == format_type::s16)
return 2;
else if (fmt == format_type::s24)
return 3;
else if (fmt == format_type::s32)
return 4;
else if (fmt == format_type::f32)
return 4;
else if (fmt == format_type::f64)
return 8;
else
throw std::runtime_error("INTERNAL ERROR: invalid format type");
}
wav_file::wav_file(const std::string& path) : loadBuffer(loadChunkSize)
{
ifs.exceptions(std::ios::badbit | std::ios::eofbit);
ifs.open(path, std::ios::binary);
if (!ifs.good())
throw std::runtime_error("failed to open file: " + path + ", reason: " + strerror(errno));
if (!ifs.is_open())
throw std::runtime_error("failed to open file: " + path + ", reason: " + strerror(errno));
ifs.seekg(0, ifs.end);
std::streampos len = ifs.tellg();
ifs.seekg(0, ifs.beg);
std::string chunkId = read_str(ifs, 4);
if (chunkId != "RIFF")
throw std::runtime_error("RIFF ID invalid");
uint32_t mainChunkLen = read_u32(ifs);
if (mainChunkLen + 8 != len)
throw std::runtime_error("RIFF chunk len (=" +
std::to_string(mainChunkLen) +
") doesn't match file len (=" +
std::to_string(len) +
")");
std::string riffType = read_str(ifs, 4);
if (riffType != "WAVE")
throw std::runtime_error("WAVE ID invalid");
bool dataChunkFound = false;
bool fmtChunkFound = false;
// search all chunks
std::streampos curPos;
while ((curPos = ifs.tellg()) + std::streampos(8) <= len) {
chunkId = read_str(ifs, 4);
uint32_t chunkLen = read_u32(ifs);
if (curPos + std::streampos(8) + std::streampos(chunkLen) > len)
throw std::runtime_error("ERROR: chunk goes beyond end of file: offset=" + std::to_string(curPos));
if (chunkId == "fmt ") {
fmtChunkFound = true;
std::vector<uint8_t> fmtChunk = read_arr(ifs, chunkLen);
uint16_t fmtTag = arr_u16(fmtChunk, 0);
uint16_t numChannels = arr_u16(fmtChunk, 2);
if (numChannels != 1)
throw std::runtime_error("ERROR: input file is NOT mono");
this->sampleRate = arr_u32(fmtChunk, 4);
uint16_t block_align = arr_u16(fmtChunk, 12);
uint16_t bits_per_sample = arr_u16(fmtChunk, 14);
if (fmtTag == 1) {
// integer
if (block_align == 1 && bits_per_sample == 8)
this->fmt = format_type::u8;
else if (block_align == 2 && bits_per_sample == 16)
this->fmt = format_type::s16;
else if (block_align == 3 && bits_per_sample == 24)
this->fmt = format_type::s24;
else if (block_align == 4 && bits_per_sample == 32)
this->fmt = format_type::s32;
else
throw std::runtime_error("ERROR: unsupported integer format combination");
} else if (fmtTag == 3) {
// float
if (block_align == 4 && bits_per_sample == 32)
this->fmt = format_type::f32;
else if (block_align == 8 && bits_per_sample == 64)
this->fmt = format_type::f64;
else
throw std::runtime_error("ERROR: unsupported float format combination");
} else {
throw std::runtime_error("ERROR: unsupported format code: " + std::to_string(fmtTag));
}
} else if (chunkId == "data") {
dataChunkFound = true;
dataChunkPos = ifs.tellg();
dataChunkEndPos = dataChunkPos + std::streampos(chunkLen);
ifs.seekg(chunkLen, ifs.cur);
} else if (chunkId == "smpl") {
std::vector<uint8_t> smplChunk = read_arr(ifs, chunkLen);
uint32_t midiUnityNote = arr_u32(smplChunk, 12);
this->midiKey = static_cast<uint8_t>(std::min(midiUnityNote, 127u));
uint32_t midiPitchFraction = arr_u32(smplChunk, 16);
// the values below convert the uint32_t range to 0.0 to 100.0 range
this->tuning = static_cast<double>(midiPitchFraction) / (4294967296.0 * 100.0);
uint32_t numLoops = arr_u32(smplChunk, 28);
if (numLoops > 1)
throw std::runtime_error("ERROR: too many loops in smpl chunk");
if (numLoops == 1) {
uint32_t loopType = arr_u32(smplChunk, 36 + 4);
if (loopType != 0)
throw std::runtime_error("ERROR: loop type not supported: " + std::to_string(loopType));
this->loopStart = arr_u32(smplChunk, 36 + 8);
// sampler chunks tell the last sample to be played (so including rather than excluding), thus +1
this->loopEnd = arr_u32(smplChunk, 36 + 12) + 1;
this->loopEnabled = true;
}
} else if (chunkId == "agbp") {
// Custom chunk: exact GBA pitch value (sample_rate * 1024)
// This allows perfect round-trip conversion without period-based precision loss
std::vector<uint8_t> agbpChunk = read_arr(ifs, chunkLen);
if (chunkLen >= 4) {
this->agbPitch = arr_u32(agbpChunk, 0);
}
} else {
//fprintf(stderr, "WARNING: ignoring unknown chunk type: <%s>\n", chunkId.c_str());
ifs.seekg(chunkLen, ifs.cur);
}
/* https://en.wikipedia.org/wiki/Resource_Interchange_File_Format#Explanation
* If chunk size is odd, skip the pad byte */
if ((chunkLen % 2) == 1)
ifs.seekg(1, ifs.cur);
}
if (!fmtChunkFound)
throw std::runtime_error("ERROR: fmt chunk not found");
if (!dataChunkFound)
throw std::runtime_error("ERROR: data chunk not found");
uint32_t numSamples = static_cast<uint32_t>(dataChunkEndPos - dataChunkPos) / fmt_size();
this->loopEnd = std::min(this->loopEnd, numSamples);
}
wav_file::~wav_file()
{
ifs.close();
}
void wav_file::readData(size_t location, double *data, size_t len)
{
while (len-- > 0) {
if (loadedChunk != location - (location % loadChunkSize)) {
loadedChunk = location - (location % loadChunkSize);
std::streampos blockpos = this->dataChunkPos + std::streampos(loadedChunk * fmt_size());
std::streampos endblockpos = this->dataChunkEndPos;
size_t actualChunkSize = std::min(loadChunkSize, static_cast<size_t>(endblockpos - blockpos) / fmt_size());
if (actualChunkSize == 0) {
std::fill(loadBuffer.begin(), loadBuffer.end(), 0.0);
goto load_sample;
}
ifs.seekg(blockpos, ifs.beg);
std::vector<uint8_t> ld(actualChunkSize * fmt_size());
ifs.read(reinterpret_cast<char *>(ld.data()), ld.size());
if (fmt == format_type::u8) {
for (size_t i = 0; i < actualChunkSize; i++) {
loadBuffer[i] = (double(ld[i]) - 128.0) / 128.0;
}
} else if (fmt == format_type::s16) {
for (size_t i = 0; i < actualChunkSize; i++) {
int32_t s =
(ld[i * fmt_size() + 0] << 0) |
(ld[i * fmt_size() + 1] << 8);
s <<= 16;
s >>= 16;
loadBuffer[i] = double(s) / 32768.0;
}
} else if (fmt == format_type::s24) {
for (size_t i = 0; i < actualChunkSize; i++) {
int32_t s =
(ld[i * fmt_size() + 0] << 0) |
(ld[i * fmt_size() + 1] << 8) |
(ld[i * fmt_size() + 2] << 16);
s <<= 8;
s >>= 8;
loadBuffer[i] = double(s) / 8388608.0;
}
} else if (fmt == format_type::s32) {
for (size_t i = 0; i < actualChunkSize; i++) {
int32_t s =
(ld[i * fmt_size() + 0] << 0) |
(ld[i * fmt_size() + 1] << 8) |
(ld[i * fmt_size() + 2] << 16) |
(ld[i * fmt_size() + 3] << 24);
loadBuffer[i] = double(s) / 2147483648.0;
}
} else if (fmt == format_type::f32) {
for (size_t i = 0; i < actualChunkSize; i++) {
union {
uint32_t s;
float f;
} u;
u.s =
(ld[i * fmt_size() + 0] << 0) |
(ld[i * fmt_size() + 1] << 8) |
(ld[i * fmt_size() + 2] << 16) |
(ld[i * fmt_size() + 3] << 24);
loadBuffer[i] = u.f;
}
} else if (fmt == format_type::f64) {
for (size_t i = 0; i < actualChunkSize; i++) {
union {
uint64_t s;
double d;
} u;
u.s =
(uint64_t(ld[i * fmt_size() + 0]) << 0) |
(uint64_t(ld[i * fmt_size() + 1]) << 8) |
(uint64_t(ld[i * fmt_size() + 2]) << 16) |
(uint64_t(ld[i * fmt_size() + 3]) << 24) |
(uint64_t(ld[i * fmt_size() + 4]) << 32) |
(uint64_t(ld[i * fmt_size() + 5]) << 40) |
(uint64_t(ld[i * fmt_size() + 6]) << 48) |
(uint64_t(ld[i * fmt_size() + 7]) << 56);
loadBuffer[i] = u.d;
}
}
for (size_t i = actualChunkSize; i < loadChunkSize; i++) {
loadBuffer[i] = 0.0;
}
}
load_sample:
*data++ = loadBuffer[location % loadChunkSize];
location++;
}
}
+38
View File
@@ -0,0 +1,38 @@
#pragma once
#include <string>
#include <fstream>
#include <vector>
#include <limits>
#include <cstdint>
#define WAV_INVALID_VAL 0xFFFFFFFFu
class wav_file {
public:
wav_file(const std::string& path);
~wav_file();
void readData(size_t location, double *data, size_t len);
private:
std::ifstream ifs;
std::streampos dataChunkPos;
std::streampos dataChunkEndPos;
size_t loadedChunk = WAV_INVALID_VAL;
std::vector<double> loadBuffer;
size_t loadBufferBlock = std::numeric_limits<size_t>::max();
enum class format_type {
u8, s16, s24, s32,
f32, f64,
} fmt;
uint32_t fmt_size() const;
public:
uint32_t loopStart = 0; // samples
uint32_t loopEnd = std::numeric_limits<uint32_t>::max(); // samples
bool loopEnabled = false;
double tuning = 0.0; // cents
uint8_t midiKey = 60;
uint32_t sampleRate;
uint32_t agbPitch = 0; // optional: exact GBA pitch value from 'agbp' chunk (0 = not present)
};