summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorMarcus Huderle <huderlem@gmail.com>2016-09-08 14:05:20 -0700
committerMarcus Huderle <huderlem@gmail.com>2016-09-08 14:06:35 -0700
commitccdc106333cb669ffa09779a94cc0646ccaea08e (patch)
tree2f1daf00eaab1d06293e303b185b117ec58bbae6 /tools
parent949bc718359b40553c5bbf7e04974dc43283a883 (diff)
Convert .aif files to .pcm samples, and update Makefile to use aif2pcm tool.
Diffstat (limited to 'tools')
-rw-r--r--tools/aif2pcm/LICENSE19
-rw-r--r--tools/aif2pcm/Makefile15
-rw-r--r--tools/aif2pcm/extended.c171
-rw-r--r--tools/aif2pcm/main.c554
4 files changed, 759 insertions, 0 deletions
diff --git a/tools/aif2pcm/LICENSE b/tools/aif2pcm/LICENSE
new file mode 100644
index 000000000..60c9e63c4
--- /dev/null
+++ b/tools/aif2pcm/LICENSE
@@ -0,0 +1,19 @@
+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.
diff --git a/tools/aif2pcm/Makefile b/tools/aif2pcm/Makefile
new file mode 100644
index 000000000..ec6343b12
--- /dev/null
+++ b/tools/aif2pcm/Makefile
@@ -0,0 +1,15 @@
+CC = gcc
+
+CFLAGS = -Wall -Wextra -Wno-switch -std=c11 -O2
+
+LIBS = -lm
+
+SRCS = main.c extended.c
+
+.PHONY: clean
+
+aif2pcm: $(SRCS)
+ $(CC) $(CFLAGS) $(SRCS) -o $@ $(LIBS)
+
+clean:
+ $(RM) aif2pcm aif2pcm.exe
diff --git a/tools/aif2pcm/extended.c b/tools/aif2pcm/extended.c
new file mode 100644
index 000000000..067019661
--- /dev/null
+++ b/tools/aif2pcm/extended.c
@@ -0,0 +1,171 @@
+/* $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>
+
+/*
+ * 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, unsigned char* 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] = ((unsigned char) sgn) << 7;
+ out[0] |= (unsigned char) lexp;
+ out[1] = (unsigned char) hexp;
+ out[2] = (unsigned char) (low >> 24);
+ out[3] = (unsigned char) ((low >> 16) & 0xFF);
+ out[4] = (unsigned char) ((low >> 8) & 0xFF);
+ out[5] = (unsigned char) (low & 0xFF);
+ out[6] = (unsigned char) (high >> 24);
+ out[7] = (unsigned char) ((high >> 16) & 0xFF);
+ out[8] = (unsigned char) ((high >> 8) & 0xFF);
+ out[9] = (unsigned char) (high & 0xFF);
+
+ return;
+}
+
+
+/*
+ * Read IEEE Extended Precision Numbers
+ */
+double
+ieee754_read_extended(unsigned char* 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);
+}
diff --git a/tools/aif2pcm/main.c b/tools/aif2pcm/main.c
new file mode 100644
index 000000000..31842ff8e
--- /dev/null
+++ b/tools/aif2pcm/main.c
@@ -0,0 +1,554 @@
+// 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>
+
+/* extended.c */
+void ieee754_write_extended (double, unsigned char*);
+double ieee754_read_extended (unsigned char*);
+
+#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;
+ char *samples;
+ unsigned char midi_note;
+ unsigned long loop_offset;
+ double sample_rate;
+} AifData;
+
+
+char * get_file_extension(char *filename)
+{
+ char *dot = strrchr(filename, '.');
+ if (!dot || dot == filename)
+ {
+ FATAL_ERROR("Input file has no file extension.\n");
+ }
+
+ return dot + 1;
+}
+
+void change_file_extension(char *filename, const char *new_extension)
+{
+ char *dot = strrchr(filename, '.');
+ if (!dot || dot == filename)
+ {
+ FATAL_ERROR("Input file has no file extension.\n");
+ }
+
+ memcpy(dot + 1, new_extension, 3);
+}
+
+AifData *read_aif(char * aif_file_data, unsigned long aif_file_data_size)
+{
+ AifData *aif_data = (AifData *)malloc(sizeof(AifData));
+
+ 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_file_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_file_data[pos++] << 24;
+ whole_chunk_size |= (aif_file_data[pos++] << 16);
+ whole_chunk_size |= (aif_file_data[pos++] << 8);
+ whole_chunk_size |= (unsigned char)aif_file_data[pos++];
+
+ unsigned long expected_whole_chunk_size = aif_file_data_size - 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_file_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 < aif_file_data_size)
+ {
+ // Read Chunk id
+ memcpy(chunk_name, aif_file_data + pos, 4);
+ pos += 4;
+
+ unsigned long chunk_size = (aif_file_data[pos++] << 24);
+ chunk_size |= (aif_file_data[pos++] << 16);
+ chunk_size |= (aif_file_data[pos++] << 8);
+ chunk_size |= aif_file_data[pos++];
+
+ if (strcmp(chunk_name, "COMM") == 0)
+ {
+ short num_channels = (aif_file_data[pos++] << 8);
+ num_channels |= (unsigned char)aif_file_data[pos++];
+ if (num_channels != 1)
+ {
+ FATAL_ERROR("numChannels (%d) in the COMM Chunk must be 1!\n", num_channels);
+ }
+
+ num_sample_frames = (aif_file_data[pos++] << 24);
+ num_sample_frames |= (aif_file_data[pos++] << 16);
+ num_sample_frames |= (aif_file_data[pos++] << 8);
+ num_sample_frames |= (unsigned char)aif_file_data[pos++];
+
+ short sample_size = (aif_file_data[pos++] << 8);
+ sample_size |= (unsigned char)aif_file_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((unsigned char*)(aif_file_data + pos));
+ pos += 10;
+
+ aif_data->num_samples = num_sample_frames;
+ aif_data->sample_rate = sample_rate;
+ }
+ else if (strcmp(chunk_name, "MARK") == 0)
+ {
+ unsigned short num_markers = (aif_file_data[pos++] << 8);
+ num_markers |= (unsigned char)aif_file_data[pos++];
+
+ unsigned long loop_start = 0;
+
+ // Read each marker and look for the "START" marker.
+ for (int i = 0; i < num_markers; i++)
+ {
+ unsigned short marker_id = (aif_file_data[pos++] << 8);
+ marker_id |= (unsigned char)aif_file_data[pos++];
+
+ unsigned long marker_position = (aif_file_data[pos++] << 24);
+ marker_position |= (aif_file_data[pos++] << 16);
+ marker_position |= (aif_file_data[pos++] << 8);
+ marker_position |= (unsigned char)aif_file_data[pos++];
+
+ // Marker id is a pascal-style string.
+ unsigned char marker_name_size = aif_file_data[pos++];
+ char *marker_name = (char *)malloc((marker_name_size + 1) * sizeof(char));
+ memcpy(marker_name, aif_file_data + pos, marker_name_size);
+ marker_name[marker_name_size] = '\0';
+ pos += marker_name_size;
+
+ if (strcmp(marker_name, "START") == 0)
+ {
+ loop_start = marker_position;
+ }
+
+ free(marker_name);
+ }
+
+ aif_data->loop_offset = loop_start;
+ }
+ else if (strcmp(chunk_name, "INST") == 0)
+ {
+ unsigned char midi_note = (unsigned char)aif_file_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;
+
+ char *sample_data = (char *)malloc(num_sample_frames * sizeof(char));
+ memcpy(sample_data, aif_file_data + pos, num_sample_frames);
+
+ aif_data->samples = sample_data;
+ pos += num_sample_frames;
+ }
+ else
+ {
+ // Skip over stuff we unsupported chunks.
+ pos += chunk_size;
+ }
+ }
+
+ return aif_data;
+}
+
+// Reads an .aif file and produces a .pcm file containing an array of 8-bit samples.
+void aif2pcm(const char *aif_filename)
+{
+ // Get .pcm filename.
+ char pcm_filename[strlen(aif_filename)];
+ strcpy(pcm_filename, aif_filename);
+ change_file_extension(pcm_filename, "pcm");
+
+ // Open the given .aif file so we can read its contents.
+ FILE *aif_file;
+ aif_file = fopen(aif_filename, "rb");
+ if (!aif_file)
+ {
+ FATAL_ERROR("Failed to open '%s' for reading!\n", aif_filename);
+ }
+
+ // Get file length.
+ fseek(aif_file, 0, SEEK_END);
+ unsigned long aif_file_length = ftell(aif_file);
+ fseek(aif_file, 0, SEEK_SET);
+
+ // Create buffer for samples.
+ char *aif_file_data = (char *)malloc(aif_file_length * sizeof(char));
+ if (!aif_file_data)
+ {
+ FATAL_ERROR("Failed to allocate buffer for aif file data!\n");
+ }
+
+ // Populate buffer from file.
+ unsigned long read = fread(aif_file_data, aif_file_length, 1, aif_file);
+ fclose(aif_file);
+ if (read <= 0)
+ {
+ FATAL_ERROR("Failed to read data from '%s'!\n", aif_filename);
+ }
+
+ AifData *aif_data = read_aif(aif_file_data, aif_file_length);
+
+ // Write the output .pcm file
+ FILE *pcm_file;
+ pcm_file = fopen(pcm_filename, "wb");
+ fwrite(aif_data->samples, aif_data->num_samples, 1, pcm_file);
+ fclose(pcm_file);
+
+ free(aif_data->samples);
+ free(aif_data);
+ free(aif_file_data);
+}
+
+// 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, char base_note, long pitch_adjust, long loop_start)
+{
+ // Get .aif filename.
+ char aif_filename[strlen(pcm_filename)];
+ strcpy(aif_filename, pcm_filename);
+ change_file_extension(aif_filename, "aif");
+
+ // Open the given .pcm file so we can read its 8-bit samples.
+ FILE *pcm_file;
+ pcm_file = fopen(pcm_filename, "rb");
+ if (!pcm_file)
+ {
+ FATAL_ERROR("Failed to open '%s' for reading!\n", pcm_filename);
+ }
+
+ // Get file length.
+ fseek(pcm_file, 0, SEEK_END);
+ unsigned long num_samples = ftell(pcm_file);
+ fseek(pcm_file, 0, SEEK_SET);
+
+ unsigned long num_samples_extended = num_samples * 1;
+
+ // Create buffer for samples.
+ signed char *pcm_samples = (signed char *)malloc(num_samples_extended * sizeof(signed char));
+ if (!pcm_samples)
+ {
+ FATAL_ERROR("Failed to allocate buffer for pcm samples!\n");
+ }
+
+ // Populate buffer from file.
+ unsigned long read = fread(pcm_samples, num_samples, 1, pcm_file);
+ fclose(pcm_file);
+ if (read <= 0)
+ {
+ FATAL_ERROR("Failed to read data from '%s'!\n", pcm_filename);
+ }
+
+ // Allocate buffer for output .aif file.
+ unsigned long aif_file_size = 54 + 60 + num_samples_extended;
+ char *aif_buffer = (char *)malloc(aif_file_size * sizeof(char));
+ if (!aif_buffer)
+ {
+ FATAL_ERROR("Failed to allocate buffer for aif file!\n");
+ }
+
+ long pos = 0;
+
+ // First, write the FORM header chunk.
+ // FORM Chunk ckID
+ aif_buffer[pos++] = 'F';
+ aif_buffer[pos++] = 'O';
+ aif_buffer[pos++] = 'R';
+ aif_buffer[pos++] = 'M';
+
+ // FORM Chunk ckSize
+ unsigned long data_size = aif_file_size - 8;
+ aif_buffer[pos++] = ((data_size >> 24) & 0xFF);
+ aif_buffer[pos++] = ((data_size >> 16) & 0xFF);
+ aif_buffer[pos++] = ((data_size >> 8) & 0xFF);
+ aif_buffer[pos++] = (data_size & 0xFF);
+
+ // FORM Chunk formType
+ aif_buffer[pos++] = 'A';
+ aif_buffer[pos++] = 'I';
+ aif_buffer[pos++] = 'F';
+ aif_buffer[pos++] = 'F';
+
+ // Next, write the Common Chunk
+ // Common Chunk ckID
+ aif_buffer[pos++] = 'C';
+ aif_buffer[pos++] = 'O';
+ aif_buffer[pos++] = 'M';
+ aif_buffer[pos++] = 'M';
+
+ // Common Chunk ckSize
+ aif_buffer[pos++] = 0;
+ aif_buffer[pos++] = 0;
+ aif_buffer[pos++] = 0;
+ aif_buffer[pos++] = 18;
+
+ // Common Chunk numChannels
+ aif_buffer[pos++] = 0;
+ aif_buffer[pos++] = 1; // 1 channel
+
+ // Common Chunk numSampleFrames
+ aif_buffer[pos++] = ((num_samples_extended >> 24) & 0xFF);
+ aif_buffer[pos++] = ((num_samples_extended >> 16) & 0xFF);
+ aif_buffer[pos++] = ((num_samples_extended >> 8) & 0xFF);
+ aif_buffer[pos++] = (num_samples_extended & 0xFF);
+
+ // Common Chunk sampleSize
+ aif_buffer[pos++] = 0;
+ aif_buffer[pos++] = 8; // 8 bits per sample
+
+ // Common Chunk sampleRate
+ double sample_rate = pitch_adjust / 1024.0;
+ unsigned char sample_rate_buffer[10];
+ ieee754_write_extended(sample_rate, sample_rate_buffer);
+ for (int i = 0; i < 10; i++)
+ {
+ aif_buffer[pos++] = sample_rate_buffer[i];
+ }
+
+ // Marker Chunk ckID
+ aif_buffer[pos++] = 'M';
+ aif_buffer[pos++] = 'A';
+ aif_buffer[pos++] = 'R';
+ aif_buffer[pos++] = 'K';
+
+ // Marker Chunk ckSize
+ aif_buffer[pos++] = 0;
+ aif_buffer[pos++] = 0;
+ aif_buffer[pos++] = 0;
+ aif_buffer[pos++] = 24;
+
+ // Marker Chunk numMarkers
+ aif_buffer[pos++] = 0;
+ aif_buffer[pos++] = 2;
+
+ // Marker loop start
+ aif_buffer[pos++] = 0;
+ aif_buffer[pos++] = 1; // id = 1
+
+ aif_buffer[pos++] = ((loop_start >> 24) & 0xFF);
+ aif_buffer[pos++] = ((loop_start >> 16) & 0xFF);
+ aif_buffer[pos++] = ((loop_start >> 8) & 0xFF);
+ aif_buffer[pos++] = (loop_start & 0xFF); // position
+
+ aif_buffer[pos++] = 5; // pascal-style string length
+ aif_buffer[pos++] = 'S';
+ aif_buffer[pos++] = 'T';
+ aif_buffer[pos++] = 'A';
+ aif_buffer[pos++] = 'R';
+ aif_buffer[pos++] = 'T'; // markerName
+
+ // Marker loop end
+ aif_buffer[pos++] = 0;
+ aif_buffer[pos++] = 2; // id = 2
+
+ long loop_end = num_samples;
+ aif_buffer[pos++] = ((loop_end >> 24) & 0xFF);
+ aif_buffer[pos++] = ((loop_end >> 16) & 0xFF);
+ aif_buffer[pos++] = ((loop_end >> 8) & 0xFF);
+ aif_buffer[pos++] = (loop_end & 0xFF); // position
+
+ aif_buffer[pos++] = 3; // pascal-style string length
+ aif_buffer[pos++] = 'E';
+ aif_buffer[pos++] = 'N';
+ aif_buffer[pos++] = 'D';
+
+
+ // Instrument Chunk ckID
+ aif_buffer[pos++] = 'I';
+ aif_buffer[pos++] = 'N';
+ aif_buffer[pos++] = 'S';
+ aif_buffer[pos++] = 'T';
+
+ // Instrument Chunk ckSize
+ aif_buffer[pos++] = 0;
+ aif_buffer[pos++] = 0;
+ aif_buffer[pos++] = 0;
+ aif_buffer[pos++] = 20;
+
+ aif_buffer[pos++] = base_note; // baseNote
+ aif_buffer[pos++] = 0; // detune
+ aif_buffer[pos++] = 0; // lowNote
+ aif_buffer[pos++] = 127; // highNote
+ aif_buffer[pos++] = 1; // lowVelocity
+ aif_buffer[pos++] = 127; // highVelocity
+ aif_buffer[pos++] = 0; // gain (hi)
+ aif_buffer[pos++] = 0; // gain (lo)
+
+ // Instrument Chunk sustainLoop
+ aif_buffer[pos++] = 0;
+ aif_buffer[pos++] = 1; // playMode = ForwardLooping
+
+ aif_buffer[pos++] = 0;
+ aif_buffer[pos++] = 1; // beginLoop marker id
+
+ aif_buffer[pos++] = 0;
+ aif_buffer[pos++] = 2; // endLoop marker id
+
+ // Instrument Chunk releaseLoop
+ aif_buffer[pos++] = 0;
+ aif_buffer[pos++] = 1; // playMode = ForwardLooping
+
+ aif_buffer[pos++] = 0;
+ aif_buffer[pos++] = 1; // beginLoop marker id
+
+ aif_buffer[pos++] = 0;
+ aif_buffer[pos++] = 2; // endLoop marker id
+
+ // Finally, write the Sound Data Chunk
+ // Sound Data Chunk ckID
+ aif_buffer[pos++] = 'S';
+ aif_buffer[pos++] = 'S';
+ aif_buffer[pos++] = 'N';
+ aif_buffer[pos++] = 'D';
+
+ // Sound Data Chunk ckSize
+ unsigned long sound_data_size = num_samples_extended + 8;
+ aif_buffer[pos++] = ((sound_data_size >> 24) & 0xFF);
+ aif_buffer[pos++] = ((sound_data_size >> 16) & 0xFF);
+ aif_buffer[pos++] = ((sound_data_size >> 8) & 0xFF);
+ aif_buffer[pos++] = (sound_data_size & 0xFF);
+
+ // Sound Data Chunk offset
+ aif_buffer[pos++] = 0;
+ aif_buffer[pos++] = 0;
+ aif_buffer[pos++] = 0;
+ aif_buffer[pos++] = 0;
+
+ // Sound Data Chunk blockSize
+ aif_buffer[pos++] = 0;
+ aif_buffer[pos++] = 0;
+ aif_buffer[pos++] = 0;
+ aif_buffer[pos++] = 0;
+
+ // Sound Data Chunk soundData
+ for (unsigned int i = 0; i < loop_start; i++)
+ {
+ aif_buffer[pos++] = pcm_samples[i];
+ }
+
+ int j = 0;
+ for (unsigned int i = loop_start; i < num_samples_extended; i++)
+ {
+ int pcm_index = loop_start + (j++ % (num_samples - loop_start - 1));
+ aif_buffer[pos++] = pcm_samples[pcm_index];
+ }
+
+ // Write the .aif file contents.
+ FILE *aif_file;
+ aif_file = fopen(aif_filename, "wb");
+ if (!aif_file)
+ {
+ FATAL_ERROR("Failed to open '%s' for writing!\n", aif_filename);
+ }
+
+ fwrite(aif_buffer, 1, aif_file_size, aif_file);
+ fclose(aif_file);
+
+ free(aif_buffer);
+ free(pcm_samples);
+
+ printf("Wrote %s\n", aif_filename);
+}
+
+int main(int argc, char **argv)
+{
+ if (argc < 2)
+ {
+ FATAL_ERROR("Usage: aif2pcm <aif_file>\n");
+ }
+
+ char *input_filename = argv[1];
+ char *extension = get_file_extension(input_filename);
+
+ if (strcmp(extension, "aif") == 0)
+ {
+ aif2pcm(input_filename);
+ }
+ else if (strcmp(extension, "pcm") == 0)
+ {
+ if (argc < 5)
+ {
+ FATAL_ERROR("Usage: aif2pcm <pcm_file> <midi_note> <pitch_adjust> <loop_start>\n");
+ }
+
+ char base_note = atoi(argv[2]);
+ long pitch_adjust = atol(argv[3]);
+ long loop_start = atol(argv[4]);
+ pcm2aif(input_filename, base_note, pitch_adjust, loop_start);
+ }
+ else
+ {
+ FATAL_ERROR("Input file must be .aif or .pcm: '%s'\n", input_filename);
+ }
+
+ return 0;
+}