ref: 60d213d2a215dc1c764507b51a065d81b6a2a8d7
dir: /src/smploaders/ft2_load_aiff.c/
/* Apple AIFF sample loader
**
** Note: Vol/loop sanitation is done in the last stage
** of sample loading, so you don't need to do that here.
** Do NOT close the file handle!
*/
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <math.h>
#include "../ft2_header.h"
#include "../ft2_audio.h"
#include "../ft2_sample_ed.h"
#include "../ft2_sysreqs.h"
#include "../ft2_sample_loader.h"
static double getAIFFSampleRate(uint8_t *in);
static bool aiffIsStereo(FILE *f); // only ran on files that are confirmed to be AIFFs
bool loadAIFF(FILE *f, uint32_t filesize)
{
char compType[4];
int8_t *audioDataS8;
uint8_t sampleRateBytes[10], *audioDataU8;
int16_t *audioDataS16, smp16;
uint16_t numChannels, bitDepth;
int32_t *audioDataS32;
uint32_t i, blockName, blockSize;
uint32_t offset, len32;
sample_t *s = &tmpSmp;
fseek(f, 8, SEEK_SET);
fread(compType, 1, 4, f);
rewind(f);
if (filesize < 12)
{
loaderMsgBox("Error loading sample: The sample is not supported or is invalid!");
return false;
}
uint32_t commPtr = 0, commLen = 0;
uint32_t ssndPtr = 0, ssndLen = 0;
fseek(f, 12, SEEK_SET);
while (!feof(f) && (uint32_t)ftell(f) < filesize-12)
{
fread(&blockName, 4, 1, f); if (feof(f)) break;
fread(&blockSize, 4, 1, f); if (feof(f)) break;
blockName = SWAP32(blockName);
blockSize = SWAP32(blockSize);
switch (blockName)
{
case 0x434F4D4D: // "COMM"
{
commPtr = ftell(f);
commLen = blockSize;
}
break;
case 0x53534E44: // "SSND"
{
ssndPtr = ftell(f);
ssndLen = blockSize;
}
break;
default: break;
}
fseek(f, blockSize + (blockSize & 1), SEEK_CUR);
}
if (commPtr == 0 || commLen < 18 || ssndPtr == 0)
{
loaderMsgBox("Error loading sample: The sample is not supported or is invalid!");
return false;
}
// kludge for strange AIFFs
if (ssndLen == 0)
ssndLen = filesize - ssndPtr;
if (ssndPtr+ssndLen > (uint32_t)filesize)
ssndLen = filesize - ssndPtr;
fseek(f, commPtr, SEEK_SET);
fread(&numChannels, 2, 1, f); numChannels = SWAP16(numChannels);
fseek(f, 4, SEEK_CUR);
fread(&bitDepth, 2, 1, f); bitDepth = SWAP16(bitDepth);
fread(sampleRateBytes, 1, 10, f);
if (numChannels != 1 && numChannels != 2)
{
loaderMsgBox("Error loading sample: Unsupported amounts of channels!");
return false;
}
// read compression type (if present)
bool signedSample = true;
bool floatSample = false;
if (commLen > 18)
{
fread(&compType, 1, 4, f);
if (!memcmp(compType, "raw ", 4))
{
signedSample = false;
}
else if (!memcmp(compType, "FL32", 4) || !memcmp(compType, "fl32", 4) || !memcmp(compType, "FL64", 4) || !memcmp(compType, "fl64", 4))
{
floatSample = true;
}
else
{
loaderMsgBox("Error loading sample: Unsupported AIFF type!");
return false;
}
}
if (bitDepth != 8 && bitDepth != 16 && bitDepth != 24 && bitDepth != 32 &&
!(floatSample && bitDepth == 64) && !(floatSample && bitDepth == 32))
{
loaderMsgBox("Error loading sample: Unsupported AIFF type!");
return false;
}
double dSampleRate = getAIFFSampleRate(sampleRateBytes);
// sample data chunk
fseek(f, ssndPtr, SEEK_SET);
fread(&offset, 4, 1, f);
if (offset > 0)
{
loaderMsgBox("Error loading sample: The sample is not supported or is invalid!");
return false;
}
fseek(f, 4, SEEK_CUR);
ssndLen -= 8; // don't include offset and blockSize datas
uint32_t sampleLength = ssndLen;
int16_t stereoSampleLoadMode = -1;
if (aiffIsStereo(f))
stereoSampleLoadMode = loaderSysReq(4, "System request", "This is a stereo sample...", NULL);
// read sample data
if (!floatSample && bitDepth == 8) // 8-BIT INTEGER SAMPLE
{
if (!allocateSmpData(s, sampleLength, false))
{
loaderMsgBox("Not enough memory!");
return false;
}
if (fread(s->dataPtr, sampleLength, 1, f) != 1)
{
loaderMsgBox("General I/O error during loading! Is the file in use?");
return false;
}
audioDataS8 = (int8_t *)s->dataPtr;
if (!signedSample)
{
for (i = 0; i < sampleLength; i++)
audioDataS8[i] ^= 0x80;
}
// stereo conversion
if (numChannels == 2)
{
sampleLength /= 2;
switch (stereoSampleLoadMode)
{
case STEREO_SAMPLE_READ_LEFT:
{
for (i = 1; i < sampleLength; i++)
audioDataS8[i] = audioDataS8[(i * 2) + 0];
}
break;
case STEREO_SAMPLE_READ_RIGHT:
{
len32 = sampleLength - 1;
for (i = 0; i < len32; i++)
audioDataS8[i] = audioDataS8[(i * 2) + 1];
audioDataS8[i] = 0;
}
break;
default:
case STEREO_SAMPLE_CONVERT:
{
len32 = sampleLength - 1;
for (i = 0; i < len32; i++)
{
smp16 = (audioDataS8[(i * 2) + 0] + audioDataS8[(i * 2) + 1]) >> 1;
audioDataS8[i] = (int8_t)smp16;
}
audioDataS8[i] = 0;
}
break;
}
}
}
else if (!floatSample && bitDepth == 16) // 16-BIT INTEGER SAMPLE
{
sampleLength /= sizeof (int16_t);
if (!allocateSmpData(s, sampleLength * sizeof (int16_t), false))
{
loaderMsgBox("Not enough memory!");
return false;
}
if (fread(s->dataPtr, sampleLength, sizeof (int16_t), f) != sizeof (int16_t))
{
loaderMsgBox("General I/O error during loading! Is the file in use?");
return false;
}
// change endianness
audioDataS16 = (int16_t *)s->dataPtr;
for (i = 0; i < sampleLength; i++)
audioDataS16[i] = SWAP16(audioDataS16[i]);
// stereo conversion
if (numChannels == 2)
{
sampleLength /= 2;
switch (stereoSampleLoadMode)
{
case STEREO_SAMPLE_READ_LEFT:
{
for (i = 1; i < sampleLength; i++)
audioDataS16[i] = audioDataS16[(i * 2) + 0];
}
break;
case STEREO_SAMPLE_READ_RIGHT:
{
len32 = sampleLength - 1;
for (i = 0; i < len32; i++)
audioDataS16[i] = audioDataS16[(i * 2) + 1];
audioDataS16[i] = 0;
}
break;
default:
case STEREO_SAMPLE_CONVERT:
{
len32 = sampleLength - 1;
for (i = 0; i < len32; i++)
{
int32_t smp32 = (audioDataS16[(i * 2) + 0] + audioDataS16[(i * 2) + 1]) >> 1;
audioDataS16[i] = (int16_t)smp32;
}
audioDataS16[i] = 0;
}
break;
}
}
s->flags |= SAMPLE_16BIT;
}
else if (!floatSample && bitDepth == 24) // 24-BIT INTEGER SAMPLE
{
sampleLength /= 3;
if (!allocateSmpData(s, sampleLength * sizeof (int32_t), false))
{
loaderMsgBox("Not enough memory!");
return false;
}
if (fread(&s->dataPtr[sampleLength], sampleLength, 3, f) != 3)
{
loaderMsgBox("General I/O error during loading! Is the file in use?");
return false;
}
audioDataS32 = (int32_t *)s->dataPtr;
// convert to 32-bit
audioDataU8 = (uint8_t *)s->dataPtr + sampleLength;
for (i = 0; i < sampleLength; i++)
{
audioDataS32[i] = (audioDataU8[0] << 24) | (audioDataU8[1] << 16) | (audioDataU8[2] << 8);
audioDataU8 += 3;
}
// stereo conversion
if (numChannels == 2)
{
sampleLength /= 2;
switch (stereoSampleLoadMode)
{
case STEREO_SAMPLE_READ_LEFT:
{
for (i = 1; i < sampleLength; i++)
audioDataS32[i] = audioDataS32[(i * 2) + 0];
}
break;
case STEREO_SAMPLE_READ_RIGHT:
{
len32 = sampleLength - 1;
for (i = 0; i < len32; i++)
audioDataS32[i] = audioDataS32[(i * 2) + 1];
audioDataS32[i] = 0;
}
break;
default:
case STEREO_SAMPLE_CONVERT:
{
len32 = sampleLength - 1;
for (i = 0; i < len32; i++)
{
int64_t smp64 = audioDataS32[(i * 2) + 0];
smp64 += audioDataS32[(i * 2) + 1];
smp64 >>= 1;
audioDataS32[i] = (int32_t)smp64;
}
audioDataS32[i] = 0;
}
break;
}
}
normalizeSigned32Bit(audioDataS32, sampleLength);
audioDataS16 = (int16_t *)s->dataPtr;
for (i = 0; i < sampleLength; i++)
audioDataS16[i] = audioDataS32[i] >> 16;
s->flags |= SAMPLE_16BIT;
}
else if (!floatSample && bitDepth == 32) // 32-BIT INTEGER SAMPLE
{
sampleLength /= sizeof (int32_t);
if (!allocateSmpData(s, sampleLength * sizeof (int32_t), false))
{
loaderMsgBox("Not enough memory!");
return false;
}
if (fread(s->dataPtr, sampleLength, sizeof (int32_t), f) != sizeof (int32_t))
{
loaderMsgBox("General I/O error during loading! Is the file in use?");
return false;
}
// change endianness
audioDataS32 = (int32_t *)s->dataPtr;
for (i = 0; i < sampleLength; i++)
audioDataS32[i] = SWAP32(audioDataS32[i]);
// stereo conversion
if (numChannels == 2)
{
sampleLength /= 2;
switch (stereoSampleLoadMode)
{
case STEREO_SAMPLE_READ_LEFT:
{
for (i = 1; i < sampleLength; i++)
audioDataS32[i] = audioDataS32[(i * 2) + 0];
}
break;
case STEREO_SAMPLE_READ_RIGHT:
{
len32 = sampleLength - 1;
for (i = 0; i < len32; i++)
audioDataS32[i] = audioDataS32[(i * 2) + 1];
audioDataS32[i] = 0;
}
break;
default:
case STEREO_SAMPLE_CONVERT:
{
len32 = sampleLength - 1;
for (i = 0; i < len32; i++)
{
int64_t smp64 = audioDataS32[(i * 2) + 0];
smp64 += audioDataS32[(i * 2) + 1];
smp64 >>= 1;
audioDataS32[i] = (int32_t)smp64;
}
audioDataS32[i] = 0;
}
break;
}
}
normalizeSigned32Bit(audioDataS32, sampleLength);
audioDataS16 = (int16_t *)s->dataPtr;
for (i = 0; i < sampleLength; i++)
audioDataS16[i] = audioDataS32[i] >> 16;
s->flags |= SAMPLE_16BIT;
}
else if (floatSample && bitDepth == 32) // 32-BIT FLOAT SAMPLE
{
sampleLength /= sizeof (float);
if (!allocateSmpData(s, sampleLength * sizeof (float), false))
{
loaderMsgBox("Not enough memory!");
return false;
}
if (fread(s->dataPtr, sampleLength, sizeof (float), f) != sizeof (float))
{
loaderMsgBox("General I/O error during loading! Is the file in use?");
return false;
}
// change endianness
audioDataS32 = (int32_t *)s->dataPtr;
for (i = 0; i < sampleLength; i++)
audioDataS32[i] = SWAP32(audioDataS32[i]);
float *fAudioDataFloat = (float *)s->dataPtr;
// stereo conversion
if (numChannels == 2)
{
sampleLength /= 2;
switch (stereoSampleLoadMode)
{
case STEREO_SAMPLE_READ_LEFT:
{
// remove right channel data
for (i = 1; i < sampleLength; i++)
fAudioDataFloat[i] = fAudioDataFloat[(i * 2) + 0];
}
break;
case STEREO_SAMPLE_READ_RIGHT:
{
// remove left channel data
len32 = sampleLength - 1;
for (i = 0; i < len32; i++)
fAudioDataFloat[i] = fAudioDataFloat[(i * 2) + 1];
fAudioDataFloat[i] = 0.0f;
}
break;
default:
case STEREO_SAMPLE_CONVERT:
{
// mix stereo to mono
len32 = sampleLength - 1;
for (i = 0; i < len32; i++)
fAudioDataFloat[i] = (fAudioDataFloat[(i * 2) + 0] + fAudioDataFloat[(i * 2) + 1]) * 0.5f;
fAudioDataFloat[i] = 0.0f;
}
break;
}
}
normalize32BitFloatToSigned16Bit(fAudioDataFloat, sampleLength);
int16_t *ptr16 = (int16_t *)s->dataPtr;
for (i = 0; i < sampleLength; i++)
{
const int32_t smp32 = (const int32_t)fAudioDataFloat[i];
ptr16[i] = (int16_t)smp32;
}
s->flags |= SAMPLE_16BIT;
}
else if (floatSample && bitDepth == 64) // 64-BIT FLOAT SAMPLE
{
sampleLength /= sizeof (double);
if (!allocateSmpData(s, sampleLength * sizeof (double), false))
{
loaderMsgBox("Not enough memory!");
return false;
}
if (fread(s->dataPtr, sampleLength, sizeof (double), f) != sizeof (double))
{
loaderMsgBox("General I/O error during loading! Is the file in use?");
return false;
}
// change endianness
int64_t *audioDataS64 = (int64_t *)s->dataPtr;
for (i = 0; i < sampleLength; i++)
audioDataS64[i] = SWAP64(audioDataS64[i]);
double *dAudioDataDouble = (double *)s->dataPtr;
// stereo conversion
if (numChannels == 2)
{
sampleLength /= 2;
switch (stereoSampleLoadMode)
{
case STEREO_SAMPLE_READ_LEFT:
{
// remove right channel data
for (i = 1; i < sampleLength; i++)
dAudioDataDouble[i] = dAudioDataDouble[(i * 2) + 0];
}
break;
case STEREO_SAMPLE_READ_RIGHT:
{
// remove left channel data
len32 = sampleLength - 1;
for (i = 0; i < len32; i++)
dAudioDataDouble[i] = dAudioDataDouble[(i * 2) + 1];
dAudioDataDouble[i] = 0.0;
}
break;
default:
case STEREO_SAMPLE_CONVERT:
{
// mix stereo to mono
len32 = sampleLength - 1;
for (i = 0; i < len32; i++)
dAudioDataDouble[i] = (dAudioDataDouble[(i * 2) + 0] + dAudioDataDouble[(i * 2) + 1]) * 0.5;
dAudioDataDouble[i] = 0.0;
}
break;
}
}
normalize64BitFloatToSigned16Bit(dAudioDataDouble, sampleLength);
int16_t *ptr16 = (int16_t *)s->dataPtr;
for (i = 0; i < sampleLength; i++)
{
const int32_t smp32 = (const int32_t)dAudioDataDouble[i];
ptr16[i] = (int16_t)smp32;
}
s->flags |= SAMPLE_16BIT;
}
if (sampleLength > MAX_SAMPLE_LEN)
sampleLength = MAX_SAMPLE_LEN;
bool sample16Bit = !!(s->flags & SAMPLE_16BIT);
reallocateSmpData(s, sampleLength, sample16Bit); // readjust memory needed
s->length = sampleLength;
s->volume = 64;
s->panning = 128;
setSampleC4Hz(s, dSampleRate);
return true;
}
static double getAIFFSampleRate(uint8_t *in)
{
/* 80-bit IEEE-754 to unsigned double-precision float.
** Sign bit is ignored.
*/
#define EXP_BIAS 16383
const uint16_t exp15 = ((in[0] & 0x7F) << 8) | in[1];
const uint64_t mantissaBits = *(uint64_t *)&in[2];
const uint64_t mantissa63 = SWAP64(mantissaBits) & INT64_MAX;
double dExp = exp15 - EXP_BIAS;
double dMantissa = mantissa63 / (INT64_MAX+1.0);
return (1.0 + dMantissa) * exp2(dExp);
}
static bool aiffIsStereo(FILE *f) // only ran on files that are confirmed to be AIFFs
{
uint16_t numChannels;
uint32_t chunkID, chunkSize;
uint32_t oldPos = ftell(f);
fseek(f, 0, SEEK_END);
int32_t filesize = ftell(f);
if (filesize < 12)
{
fseek(f, oldPos, SEEK_SET);
return false;
}
fseek(f, 12, SEEK_SET);
uint32_t commPtr = 0;
uint32_t commLen = 0;
int32_t bytesRead = 0;
while (!feof(f) && bytesRead < filesize-12)
{
fread(&chunkID, 4, 1, f); chunkID = SWAP32(chunkID); if (feof(f)) break;
fread(&chunkSize, 4, 1, f); chunkSize = SWAP32(chunkSize); if (feof(f)) break;
int32_t endOfChunk = (ftell(f) + chunkSize) + (chunkSize & 1);
switch (chunkID)
{
case 0x434F4D4D: // "COMM"
{
commPtr = ftell(f);
commLen = chunkSize;
}
break;
default: break;
}
bytesRead += (chunkSize + (chunkSize & 1));
fseek(f, endOfChunk, SEEK_SET);
}
if (commPtr == 0 || commLen < 2)
{
fseek(f, oldPos, SEEK_SET);
return false;
}
fseek(f, commPtr, SEEK_SET);
fread(&numChannels, 2, 1, f); numChannels = SWAP16(numChannels);
fseek(f, oldPos, SEEK_SET);
return (numChannels == 2);
}