/*
===========================================================================
ARX FATALIS GPL Source Code
Copyright (C) 1999-2010 Arkane Studios SA, a ZeniMax Media company.
This file is part of the Arx Fatalis GPL Source Code ('Arx Fatalis Source Code').
Arx Fatalis Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
Arx Fatalis Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with Arx Fatalis Source Code. If not, see
.
In addition, the Arx Fatalis Source Code is also subject to certain additional terms. You should have received a copy of these
additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Arx
Fatalis Source Code. If not, please request a copy in writing from Arkane Studios at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing Arkane Studios, c/o
ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#include
#include
#include
#include "Athena_Codec_ADPCM.h"
#include "Athena_FileIO.h"
#define _CRTDBG_MAP_ALLOC
#include
namespace ATHENA
{
// Fixed point delta adaption table //
static const short gai_p4[] =
{
230, 230, 230, 230, 307, 409, 512, 614,
768, 614, 512, 409, 307, 230, 230, 230
};
///////////////////////////////////////////////////////////////////////////////
// //
// Constructor and destructor //
// //
///////////////////////////////////////////////////////////////////////////////
CodecADPCM::CodecADPCM() :
header(NULL),
stream(NULL),
padding(0),
shift(0),
sample_i(0xffffffff),
predictor(NULL),
delta(NULL),
samp1(NULL), samp2(NULL),
coef1(NULL), coef2(NULL),
nybble_c(0), nybble_i(0), nybble_l(NULL),
nybble(0),
odd(0),
cache_c(0), cache_i(0), cache_l(NULL),
cursor(0)
{
}
CodecADPCM::~CodecADPCM()
{
free(predictor);
free(delta);
free(samp1);
free(samp2);
free(coef1);
free(coef2);
free(cache_l);
free(nybble_l);
}
///////////////////////////////////////////////////////////////////////////////
// //
// Setup //
// //
///////////////////////////////////////////////////////////////////////////////
aalError CodecADPCM::SetHeader(aalVoid * _header)
{
header = (ADPCMWAVEFORMAT *)_header;
if (header->wfx.nChannels != 1 && header->wfx.nChannels != 2)
return AAL_ERROR_FORMAT;
shift = header->wfx.nChannels - 1;
padding = 0;
sample_i = 0xffffffff;
aalVoid * ptr;
ptr = realloc(predictor, sizeof(char) << shift);
if (!ptr) return AAL_ERROR_MEMORY;
predictor = (char *)ptr;
ptr = realloc(delta, sizeof(aalSWord) << shift);
if (!ptr) return AAL_ERROR_MEMORY;
delta = (aalSWord *)ptr;
ptr = realloc(coef1, sizeof(aalSWord) << shift);
if (!ptr) return AAL_ERROR_MEMORY;
coef1 = (aalSWord *)ptr;
ptr = realloc(coef2, sizeof(aalSWord) << shift);
if (!ptr) return AAL_ERROR_MEMORY;
coef2 = (aalSWord *)ptr;
ptr = realloc(samp1, sizeof(aalSWord) << shift);
if (!ptr) return AAL_ERROR_MEMORY;
samp1 = (aalSWord *)ptr;
ptr = realloc(samp2, sizeof(aalSWord) << shift);
if (!ptr) return AAL_ERROR_MEMORY;
samp2 = (aalSWord *)ptr;
ptr = realloc(cache_l, cache_c = cache_i = (aalUByte)(sizeof(aalSWord) << shift));
if (!ptr) return AAL_ERROR_MEMORY;
cache_l = ptr;
nybble_c = header->wSamplesPerBlock - 2;
if (!shift) nybble_c >>= 1;
ptr = realloc(nybble_l, nybble_c);
if (!ptr) return AAL_ERROR_MEMORY;
nybble_l = (aalSByte *)ptr;
padding = ((header->wfx.nBlockAlign - (7 << shift)) << 3) -
(header->wSamplesPerBlock - 2) * (header->wfx.wBitsPerSample << shift);
aalError error(GetNextBlock());
if (error) return error;
sample_i++;
return AAL_OK;
}
aalError CodecADPCM::SetStream(FILE * _stream)
{
stream = _stream;
return AAL_OK;
}
// The stream cursor must be at the begining of waveform data //
aalError CodecADPCM::SetPosition(const aalULong & _position)
{
aalError error;
aalULong i = (_position >> shift) / header->wSamplesPerBlock;
if (FileSeek(stream, i * header->wfx.nBlockAlign, SEEK_CUR))
return AAL_ERROR_FILEIO;
i = _position - i * (header->wSamplesPerBlock << shift);
// Get header from current block
error = GetNextBlock();
if (error) return error;
sample_i++;
cache_i = 0;
char buffer[256];
aalULong to_read, read;
while (i)
{
to_read = i >= 256 ? 256 : i;
error = Read(buffer, to_read, read);
if (error) return error;
i -= read;
}
cursor = _position;
return AAL_OK;
}
///////////////////////////////////////////////////////////////////////////////
// //
// Status //
// //
///////////////////////////////////////////////////////////////////////////////
aalError CodecADPCM::GetHeader(aalVoid *&_header)
{
_header = header;
return AAL_OK;
}
aalError CodecADPCM::GetStream(FILE *&_stream)
{
_stream = stream;
return AAL_OK;
}
aalError CodecADPCM::GetPosition(aalULong & _position)
{
_position = cursor;
return AAL_OK;
}
///////////////////////////////////////////////////////////////////////////////
// //
// Macros! //
// //
///////////////////////////////////////////////////////////////////////////////
__forceinline aalVoid CodecADPCM::GetSample(const aalULong & i, aalSByte adpcm_sample)
{
aalSLong predict, pcm_sample, old_delta;
// Update delta
old_delta = delta[i];
delta[i] = aalSWord((gai_p4[adpcm_sample] * old_delta) >> 8);
if (delta[i] < 16) delta[i] = 16;
// Sign-extend adpcm_sample
if (adpcm_sample & 0x08) adpcm_sample -= 16;
// Predict next sample
predict = ((aalSLong)samp1[i] * coef1[i] + (aalSLong)samp2[i] * coef2[i]) >> 8;
// Reconstruct original PCM
pcm_sample = adpcm_sample * old_delta + predict;
// Clip value to signed 16 bits limits
if (pcm_sample > 32767) pcm_sample = 32767;
else if (pcm_sample < -32768) pcm_sample = -32768;
// Update samples
samp2[i] = samp1[i];
samp1[i] = (aalSWord)pcm_sample;
}
///////////////////////////////////////////////////////////////////////////////
// //
// File I/O //
// //
///////////////////////////////////////////////////////////////////////////////
aalError CodecADPCM::Read(aalVoid * buffer, const aalULong & to_read, aalULong & read)
{
read = 0;
while (read < to_read)
{
// If prefetched bytes are remaining, put the next one into the buffer
if (cache_i < cache_c)
{
((aalSByte *)buffer)[read++] = ((aalSByte *)cache_l)[cache_i++];
continue;
}
// Load next block header if there is no more sample in current one
if (sample_i >= header->wSamplesPerBlock)
{
aalError error;
if (padding) FileSeek(stream, padding, SEEK_CUR);
error = GetNextBlock();
if (error) return error;
}
else if (sample_i == 1)
{
for (aalULong i(0); i < header->wfx.nChannels; i++)
((aalSWord *)cache_l)[i] = samp1[i];
}
else
{
// Get new sample for each channel
for (aalULong i(0); i < header->wfx.nChannels; i++)
{
if (odd)
{
GetSample(i, (aalSByte)(nybble & 0x0f));
odd = AAL_UFALSE;
}
else
{
nybble = nybble_l[nybble_i++];
GetSample(i, aalSByte((nybble >> 4) & 0x0f));
odd = AAL_UTRUE;
}
((aalSWord *)cache_l)[i] = samp1[i];
}
}
sample_i++;
cache_i = 0;
}
return AAL_OK;
}
aalError CodecADPCM::Write(aalVoid *, const aalULong &, aalULong & write)
{
write = 0;
return AAL_ERROR;
}
aalError CodecADPCM::GetNextBlock()
{
// Load and check block header
if (!FileRead(predictor, sizeof(aalUByte) << shift, 1, stream)) return AAL_ERROR_FILEIO;
if (!FileRead(delta, sizeof(aalSWord) << shift, 1, stream)) return AAL_ERROR_FILEIO;
if (!FileRead(samp1, sizeof(aalSWord) << shift, 1, stream)) return AAL_ERROR_FILEIO;
if (!FileRead(samp2, sizeof(aalSWord) << shift, 1, stream)) return AAL_ERROR_FILEIO;
odd = AAL_UFALSE;
sample_i = 0;
nybble_i = 0;
for (aalULong i(0); i < header->wfx.nChannels; i++)
{
if (predictor[i] >= header->wNumCoef) return AAL_ERROR_FORMAT;
coef1[i] = header->aCoef[predictor[i]].iCoef1;
coef2[i] = header->aCoef[predictor[i]].iCoef2;
((aalSWord *)cache_l)[i] = samp2[i];
}
if (!FileRead(nybble_l, nybble_c, 1, stream)) return AAL_ERROR_FILEIO;
return AAL_OK;
}
}//ATHENA::