Convert .aif files to .wav (#711)
* Migrate .aif files to .wav * cleanup
This commit is contained in:
@@ -1,2 +0,0 @@
|
||||
aif2pcm
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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++;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
};
|
||||
Reference in New Issue
Block a user