mirror of https://github.com/pret/pokeemerald.git
831 lines
20 KiB
C
831 lines
20 KiB
C
// 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;
|
|
uint8_t *samples;
|
|
uint8_t midi_note;
|
|
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 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);
|
|
}
|
|
|
|
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++];
|
|
|
|
short sample_size = (aif->data[pos++] << 8);
|
|
sample_size |= (uint8_t)aif->data[pos++];
|
|
if (sample_size != 8)
|
|
{
|
|
FATAL_ERROR("sampleSize (%d) in the COMM Chunk must be 8!\n", 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)
|
|
{
|
|
unsigned short num_markers = (aif->data[pos++] << 8);
|
|
num_markers |= (uint8_t)aif->data[pos++];
|
|
|
|
// Read each marker and look for the "START" 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 id is a pascal-style string.
|
|
uint8_t marker_name_size = aif->data[pos++];
|
|
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;
|
|
|
|
if (strcmp(marker_name, "START") == 0)
|
|
{
|
|
aif_data->loop_offset = marker_position;
|
|
aif_data->has_loop = true;
|
|
}
|
|
else if (strcmp(marker_name, "END") == 0)
|
|
{
|
|
if (!aif_data->has_loop) {
|
|
aif_data->loop_offset = marker_position;
|
|
aif_data->has_loop = true;
|
|
}
|
|
aif_data->num_samples = marker_position;
|
|
}
|
|
|
|
free(marker_name);
|
|
}
|
|
}
|
|
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 += 19;
|
|
}
|
|
else if (strcmp(chunk_name, "SSND") == 0)
|
|
{
|
|
// SKip offset and blockSize
|
|
pos += 8;
|
|
|
|
unsigned long num_samples = chunk_size - 8;
|
|
uint8_t *sample_data = (uint8_t *)malloc(num_samples * sizeof(uint8_t));
|
|
memcpy(sample_data, &aif->data[pos], num_samples);
|
|
|
|
aif_data->samples = sample_data;
|
|
aif_data->real_num_samples = num_samples;
|
|
pos += chunk_size - 8;
|
|
}
|
|
else
|
|
{
|
|
// Skip over unsupported chunks.
|
|
pos += chunk_size;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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,
|
|
};
|
|
|
|
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;
|
|
}
|
|
|
|
int get_delta_index(uint8_t sample, uint8_t prev_sample)
|
|
{
|
|
int best_error = INT_MAX;
|
|
int best_index = -1;
|
|
|
|
for (int i = 0; i < 16; i++)
|
|
{
|
|
uint8_t new_sample = prev_sample + gDeltaEncodingTable[i];
|
|
int error = sample > new_sample ? sample - new_sample : new_sample - sample;
|
|
|
|
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);
|
|
|
|
int header_size = 0x10;
|
|
struct Bytes *pcm;
|
|
struct Bytes output = {0};
|
|
|
|
if (compress)
|
|
{
|
|
struct Bytes *input = malloc(sizeof(struct Bytes));
|
|
input->data = aif_data.samples;
|
|
input->length = aif_data.real_num_samples;
|
|
pcm = delta_compress(input);
|
|
free(input);
|
|
}
|
|
else
|
|
{
|
|
pcm = malloc(sizeof(struct Bytes));
|
|
pcm->data = aif_data.samples;
|
|
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.samples);
|
|
}
|
|
|
|
// 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->samples = malloc(pcm->length);
|
|
memcpy(aif_data->samples, 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->samples[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->samples[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;
|
|
}
|