diff options
Diffstat (limited to 'tools/aif2pcm/main.c')
-rw-r--r-- | tools/aif2pcm/main.c | 830 |
1 files changed, 830 insertions, 0 deletions
diff --git a/tools/aif2pcm/main.c b/tools/aif2pcm/main.c new file mode 100644 index 0000000..51dbf1b --- /dev/null +++ b/tools/aif2pcm/main.c @@ -0,0 +1,830 @@ +// 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,0,0,0,0,0,0}; + read_aif(aif, &aif_data); + + 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.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; +} |