/*----------------------------------------------------------------------------
*
* File:
* eas_pcm.c
*
* Contents and purpose:
* Implements the PCM engine including ADPCM decode for SMAF and CMX audio playback.
*
* Copyright Sonic Network Inc. 2005
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*----------------------------------------------------------------------------
* Revision Control:
* $Revision: 849 $
* $Date: 2007-08-28 08:59:11 -0700 (Tue, 28 Aug 2007) $
*----------------------------------------------------------------------------
*/
#include "eas_data.h"
#include "eas_report.h"
#include "eas_host.h"
#include "eas_config.h"
#include "eas_parser.h"
#include "eas_pcm.h"
#include "eas_math.h"
#include "eas_mixer.h"
#define PCM_MIXER_GUARD_BITS (NUM_MIXER_GUARD_BITS + 1)
/*----------------------------------------------------------------------------
* Decoder interfaces
*----------------------------------------------------------------------------
*/
static EAS_RESULT LinearPCMDecode (EAS_DATA_HANDLE pEASData, S_PCM_STATE *pState);
static EAS_RESULT LinearPCMLocate (EAS_DATA_HANDLE pEASData, S_PCM_STATE *pState, EAS_I32 time);
static const S_DECODER_INTERFACE PCMDecoder =
{
NULL,
LinearPCMDecode,
LinearPCMLocate,
};
/* SMAF ADPCM decoder */
#ifdef _SMAF_PARSER
extern S_DECODER_INTERFACE SmafDecoder;
#define SMAF_DECODER &SmafDecoder
extern S_DECODER_INTERFACE Smaf7BitDecoder;
#define SMAF_7BIT_DECODER &Smaf7BitDecoder
#else
#define SMAF_DECODER NULL
#define SMAF_7BIT_DECODER NULL
#endif
/* IMA ADPCM decoder */
#ifdef _IMA_DECODER
extern S_DECODER_INTERFACE IMADecoder;
#define IMA_DECODER &IMADecoder
#else
#define IMA_DECODER NULL
#endif
static const S_DECODER_INTERFACE * const decoders[] =
{
&PCMDecoder,
SMAF_DECODER,
IMA_DECODER,
SMAF_7BIT_DECODER
};
/*----------------------------------------------------------------------------
* Sample rate conversion
*----------------------------------------------------------------------------
*/
#define SRC_RATE_MULTIPLER (0x40000000 / _OUTPUT_SAMPLE_RATE)
#ifdef _LOOKUP_SAMPLE_RATE
static const EAS_U32 srcConvRate[][2] =
{
4000L, (4000L << 15) / _OUTPUT_SAMPLE_RATE,
8000L, (8000L << 15) / _OUTPUT_SAMPLE_RATE,
11025L, (11025L << 15) / _OUTPUT_SAMPLE_RATE,
12000L, (12000L << 15) / _OUTPUT_SAMPLE_RATE,
16000L, (16000L << 15) / _OUTPUT_SAMPLE_RATE,
22050L, (22050L << 15) / _OUTPUT_SAMPLE_RATE,
24000L, (24000L << 15) / _OUTPUT_SAMPLE_RATE,
32000L, (32000L << 15) / _OUTPUT_SAMPLE_RATE
};
static EAS_U32 CalcBaseFreq (EAS_U32 sampleRate);
#define SRC_CONV_RATE_ENTRIES (sizeof(srcConvRate)/sizeof(EAS_U32)/2)
#endif
/* interface prototypes */
static EAS_RESULT RenderPCMStream (S_EAS_DATA *pEASData, S_PCM_STATE *pState, EAS_I32 numSamples);
/* local prototypes */
static S_PCM_STATE *FindSlot (S_EAS_DATA *pEASData, EAS_FILE_HANDLE fileHandle, EAS_PCM_CALLBACK pCallbackFunc, EAS_VOID_PTR cbInstData);
static EAS_RESULT InitPCMStream (S_EAS_DATA *pEASData, S_PCM_STATE *pState);
/*----------------------------------------------------------------------------
* EAS_PEInit()
*----------------------------------------------------------------------------
* Purpose:
* Initializes the PCM engine
*
* Inputs:
*
*
* Outputs:
*
*
* Side Effects:
*
*----------------------------------------------------------------------------
*/
EAS_RESULT EAS_PEInit (S_EAS_DATA *pEASData)
{
S_PCM_STATE *pState;
EAS_INT i;
/* check for static memory allocation */
if (pEASData->staticMemoryModel)
pEASData->pPCMStreams = EAS_CMEnumData(EAS_CM_PCM_DATA);
/* allocate dynamic memory */
else
pEASData->pPCMStreams = EAS_HWMalloc(pEASData->hwInstData, sizeof(S_PCM_STATE) * MAX_PCM_STREAMS);
if (!pEASData->pPCMStreams)
{
{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_FATAL, "Failed to allocate memory for PCM streams\n"); */ }
return EAS_ERROR_MALLOC_FAILED;
}
//zero the memory to insure complete initialization
EAS_HWMemSet((void *)(pEASData->pPCMStreams),0, sizeof(S_PCM_STATE) * MAX_PCM_STREAMS);
/* initialize the state data */
for (i = 0, pState = pEASData->pPCMStreams; i < MAX_PCM_STREAMS; i++, pState++)
pState->fileHandle = NULL;
return EAS_SUCCESS;
}
/*----------------------------------------------------------------------------
* EAS_PEShutdown()
*----------------------------------------------------------------------------
* Purpose:
* Shuts down the PCM engine
*
* Inputs:
*
*
* Outputs:
*
*
* Side Effects:
*
*----------------------------------------------------------------------------
*/
EAS_RESULT EAS_PEShutdown (S_EAS_DATA *pEASData)
{
/* free any dynamic memory */
if (!pEASData->staticMemoryModel)
{
if (pEASData->pPCMStreams)
{
EAS_HWFree(pEASData->hwInstData, pEASData->pPCMStreams);
pEASData->pPCMStreams = NULL;
}
}
return EAS_SUCCESS;
}
/*----------------------------------------------------------------------------
* EAS_PERender()
*----------------------------------------------------------------------------
* Purpose:
* Render a buffer of PCM audio
*
* Inputs:
*
*
* Outputs:
*
*
* Side Effects:
*
*----------------------------------------------------------------------------
*/
EAS_RESULT EAS_PERender (S_EAS_DATA* pEASData, EAS_I32 numSamples)
{
S_PCM_STATE *pState;
EAS_RESULT result;
EAS_INT i;
/* render all the active streams */
for (i = 0, pState = pEASData->pPCMStreams; i < MAX_PCM_STREAMS; i++, pState++)
{
if ((pState->fileHandle) && (pState->state != EAS_STATE_STOPPED) && (pState->state != EAS_STATE_PAUSED))
if ((result = RenderPCMStream(pEASData, pState, numSamples)) != EAS_SUCCESS)
return result;
}
return EAS_SUCCESS;
}
/*----------------------------------------------------------------------------
* EAS_PEState()
*----------------------------------------------------------------------------
* Purpose:
* Returns the current state of the stream
*
* Inputs:
* pEASData - pointer to overall EAS data structure
* handle - pointer to file handle
* pState - pointer to variable to store state
*
* Outputs:
*
*
* Side Effects:
*
* Notes:
* This interface is also exposed in the internal library for use by the other modules.
*----------------------------------------------------------------------------
*/
/*lint -esym(715, pEASData) reserved for future use */
EAS_RESULT EAS_PEState (S_EAS_DATA *pEASData, EAS_PCM_HANDLE pInstData, EAS_STATE *pState)
{
/* return current state */
*pState = pInstData->state;
return EAS_SUCCESS;
}
/*----------------------------------------------------------------------------
* EAS_PEClose()
*----------------------------------------------------------------------------
* Purpose:
* Close the file and clean up
*
* Inputs:
* pEASData - pointer to overall EAS data structure
* handle - pointer to file handle
*
* Outputs:
*
*
* Side Effects:
*
*----------------------------------------------------------------------------
*/
EAS_RESULT EAS_PEClose (S_EAS_DATA *pEASData, EAS_PCM_HANDLE pState)
{
EAS_RESULT result;
if ((result = EAS_HWCloseFile(pEASData->hwInstData, pState->fileHandle)) != EAS_SUCCESS)
return result;
pState->fileHandle = NULL;
return EAS_SUCCESS;
}
/*----------------------------------------------------------------------------
* PCM_Reset()
*----------------------------------------------------------------------------
* Purpose:
* Reset the sequencer. Used for locating backwards in the file.
*
* Inputs:
* pEASData - pointer to overall EAS data structure
* handle - pointer to file handle
*
* Outputs:
*
*
* Side Effects:
*
*----------------------------------------------------------------------------
*/
EAS_RESULT EAS_PEReset (S_EAS_DATA *pEASData, EAS_PCM_HANDLE pState)
{
EAS_RESULT result;
/* reset file position to first byte of data in the stream */
if ((result = EAS_HWFileSeek(pEASData->hwInstData, pState->fileHandle, pState->startPos)) != EAS_SUCCESS)
{
{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "Error %d seeking to start of PCM file\n", result); */ }
return result;
}
/* re-initialize stream */
return InitPCMStream(pEASData, pState);
}
/*----------------------------------------------------------------------------
* EAS_PEOpenStream()
*----------------------------------------------------------------------------
* Purpose:
* Starts up a PCM playback
*
* Inputs:
*
*
* Outputs:
*
*
* Side Effects:
*
*----------------------------------------------------------------------------
*/
EAS_RESULT EAS_PEOpenStream (S_EAS_DATA *pEASData, S_PCM_OPEN_PARAMS *pParams, EAS_PCM_HANDLE *pHandle)
{
EAS_RESULT result;
S_PCM_STATE *pState;
EAS_I32 filePos;
/* make sure we support this decoder */
if (pParams->decoder >= NUM_DECODER_MODULES)
{
{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "Decoder selector out of range\n"); */ }
return EAS_ERROR_PARAMETER_RANGE;
}
if (decoders[pParams->decoder] == NULL)
{
{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "Decoder module not available\n"); */ }
return EAS_ERROR_FEATURE_NOT_AVAILABLE;
}
/* find a slot for the new stream */
if ((pState = FindSlot(pEASData, pParams->fileHandle, pParams->pCallbackFunc, pParams->cbInstData)) == NULL)
{
{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "Unable to open ADPCM stream, too many streams open\n"); */ }
return EAS_ERROR_MAX_PCM_STREAMS;
}
/* get the current file position */
if ((result = EAS_HWFilePos(pEASData->hwInstData, pState->fileHandle, &filePos)) != EAS_SUCCESS)
{
{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "EAS_HWFilePos returned %ld\n",result); */ }
pState->fileHandle = NULL;
return result;
}
pState->pDecoder = decoders[pParams->decoder];
pState->startPos = filePos;
pState->bytesLeftLoop = pState->byteCount = pParams->size;
pState->loopStart = pParams->loopStart;
pState->samplesTilLoop = (EAS_I32) pState->loopStart;
pState->loopSamples = pParams->loopSamples;
pState->samplesInLoop = 0;
pState->blockSize = (EAS_U16) pParams->blockSize;
pState->flags = pParams->flags;
pState->envData = pParams->envData;
pState->volume = pParams->volume;
pState->sampleRate = (EAS_U16) pParams->sampleRate;
/* set the base frequency */
pState->basefreq = (SRC_RATE_MULTIPLER * (EAS_U32) pParams->sampleRate) >> 15;
/* calculate shift for frequencies > 1.0 */
pState->rateShift = 0;
while (pState->basefreq > 32767)
{
pState->basefreq = pState->basefreq >> 1;
pState->rateShift++;
}
/* initialize */
if ((result = InitPCMStream(pEASData, pState)) != EAS_SUCCESS)
return result;
*pHandle = pState;
{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL, "EAS_PEOpenStream: StartPos=%d, byteCount = %d, loopSamples=%d\n",
pState->startPos, pState->byteCount, pState->loopSamples); */ }
return EAS_SUCCESS;
}
/*----------------------------------------------------------------------------
* EAS_PEContinueStream()
*----------------------------------------------------------------------------
* Purpose:
* Continues a PCM stream
*
* Inputs:
*
*
* Outputs:
*
*
* Side Effects:
*
*----------------------------------------------------------------------------
*/
/*lint -e{715} reserved for future use */
EAS_RESULT EAS_PEContinueStream (S_EAS_DATA *pEASData, EAS_PCM_HANDLE pState, EAS_I32 size)
{
/* add new samples to count */
pState->bytesLeft += size;
if (pState->bytesLeft > 0)
pState->flags &= ~PCM_FLAGS_EMPTY;
return EAS_SUCCESS;
}
/*----------------------------------------------------------------------------
* EAS_PEGetFileHandle()
*----------------------------------------------------------------------------
* Purpose:
* Returns the file handle of a stream
*
* Inputs:
*
*
* Outputs:
*
*
* Side Effects:
*
*----------------------------------------------------------------------------
*/
/*lint -esym(715, pEASData) reserved for future use */
EAS_RESULT EAS_PEGetFileHandle (S_EAS_DATA *pEASData, EAS_PCM_HANDLE pState, EAS_FILE_HANDLE *pFileHandle)
{
*pFileHandle = pState->fileHandle;
return EAS_SUCCESS;
}
/*----------------------------------------------------------------------------
* EAS_PEUpdateParams()
*----------------------------------------------------------------------------
* Purpose:
* Update the pitch and volume parameters for a PCM stream
*
* Inputs:
* pEASData - pointer to EAS library instance data
* handle - pointer to S_PCM_STATE for this stream
* gainLeft - linear gain multipler in 1.15 fraction format
* gainRight - linear gain multipler in 1.15 fraction format
* pitch - pitch shift in cents
* initial - initial settings, set current gain
*
* Outputs:
*
*
* Side Effects:
*
* Notes
* In mono mode, leftGain controls the output gain and rightGain is ignored
*----------------------------------------------------------------------------
*/
/*lint -esym(715, pEASData) reserved for future use */
/*lint -esym(715, gainRight) used only in 2-channel version */
EAS_RESULT EAS_PEUpdateParams (S_EAS_DATA* pEASData, EAS_PCM_HANDLE pState, EAS_I16 pitch, EAS_I16 gainLeft, EAS_I16 gainRight)
{
pState->gainLeft = gainLeft;
#if (NUM_OUTPUT_CHANNELS == 2)
pState->gainRight = gainRight;
#endif
pState->pitch = pitch;
return EAS_SUCCESS;
}
/*----------------------------------------------------------------------------
* EAS_PELocate()
*----------------------------------------------------------------------------
* Purpose:
* This function seeks to the requested place in the file. Accuracy
* is dependent on the sample rate and block size.
*
* Inputs:
* pEASData - pointer to overall EAS data structure
* pState - stream handle
* time - media time in milliseconds
*----------------------------------------------------------------------------
*/
EAS_RESULT EAS_PELocate (S_EAS_DATA *pEASData, EAS_PCM_HANDLE pState, EAS_I32 time)
{
if (pState->pDecoder->pfLocate == NULL)
return EAS_ERROR_FEATURE_NOT_AVAILABLE;
return pState->pDecoder->pfLocate(pEASData, pState, time);
}
/*----------------------------------------------------------------------------
* EAS_PEUpdateVolume()
*----------------------------------------------------------------------------
* Purpose:
* Update the volume parameters for a PCM stream
*
* Inputs:
* pEASData - pointer to EAS library instance data
* handle - pointer to S_PCM_STATE for this stream
* gainLeft - linear gain multipler in 1.15 fraction format
* gainRight - linear gain multipler in 1.15 fraction format
* initial - initial settings, set current gain
*
* Outputs:
*
*
* Side Effects:
*
* Notes
* In mono mode, leftGain controls the output gain and rightGain is ignored
*----------------------------------------------------------------------------
*/
/*lint -esym(715, pEASData) reserved for future use */
EAS_RESULT EAS_PEUpdateVolume (S_EAS_DATA* pEASData, EAS_PCM_HANDLE pState, EAS_I16 volume)
{
pState->volume = volume;
return EAS_SUCCESS;
}
/*----------------------------------------------------------------------------
* EAS_PEUpdatePitch()
*----------------------------------------------------------------------------
* Purpose:
* Update the pitch parameter for a PCM stream
*
* Inputs:
* pEASData - pointer to EAS library instance data
* pState - pointer to S_PCM_STATE for this stream
* pitch - new pitch value in pitch cents
*----------------------------------------------------------------------------
*/
/*lint -esym(715, pEASData) reserved for future use */
EAS_RESULT EAS_PEUpdatePitch (S_EAS_DATA* pEASData, EAS_PCM_HANDLE pState, EAS_I16 pitch)
{
pState->pitch = pitch;
return EAS_SUCCESS;
}
/*----------------------------------------------------------------------------
* EAS_PEPause()
*----------------------------------------------------------------------------
* Purpose:
* Mute and stop rendering a PCM stream. Sets the gain target to zero and stops the playback
* at the end of the next audio frame.
*
* Inputs:
* pEASData - pointer to EAS library instance data
* handle - pointer to S_PCM_STATE for this stream
*
* Outputs:
*
*
* Side Effects:
*
*----------------------------------------------------------------------------
*/
/*lint -esym(715, pEASData) reserved for future use */
EAS_RESULT EAS_PEPause (S_EAS_DATA *pEASData, EAS_PCM_HANDLE pState)
{
/* set state to stopping */
pState->state = EAS_STATE_PAUSING;
return EAS_SUCCESS;
}
/*----------------------------------------------------------------------------
* EAS_PEResume()
*----------------------------------------------------------------------------
* Purpose:
* Resume rendering a PCM stream. Sets the gain target back to its
* previous setting and restarts playback at the end of the next audio
* frame.
*
* Inputs:
* pEASData - pointer to EAS library instance data
* handle - pointer to S_PCM_STATE for this stream
*
* Outputs:
*
*
* Side Effects:
*
*----------------------------------------------------------------------------
*/
/*lint -esym(715, pEASData) reserved for future use */
EAS_RESULT EAS_PEResume (S_EAS_DATA *pEASData, EAS_PCM_HANDLE pState)
{
/* set state to stopping */
pState->state = EAS_STATE_PLAY;
return EAS_SUCCESS;
}
EAS_U32 getDecayScale(EAS_U32 index)
{
EAS_U32 utemp;
//envelope decay segment
switch (index)
{
case 0: //no decay
utemp = 512;//32768;
break;
case 1: //.0156 dB per update
utemp = 511;//32709;
break;
case 2: //.03125
utemp = 510;//32649;
break;
case 3: //.0625
utemp = 508;//32532;
break;
case 4: //.125
utemp = 505;//32298;
break;
case 5: //.25
utemp = 497;//31835;
break;
case 6: //.5
utemp = 483;//30929;
break;
case 7: //1.0
utemp = 456;//29193;
break;
case 8: //2.0
utemp = 406;//26008;
break;
case 9: //4.0
utemp = 323;//20642;
break;
case 10: //8.0
utemp = 203;//13004;
break;
case 11: //16.0
utemp = 81;//5160;
break;
case 12: //32.0
utemp = 13;//813;
break;
case 13: //64.0
utemp = 0;//20;
break;
case 14: //128.0
utemp = 0;
break;
case 15: //256.0
default:
utemp = 0;
break;
}
//printf("getdecayscale returned %d\n",utemp);
return utemp;
}
EAS_U32 getAttackIncrement(EAS_U32 index)
{
EAS_U32 utemp;
//envelope decay segment
switch (index)
{
case 0:
utemp = 32;
break;
case 1:
utemp = 64;
break;
case 2:
utemp = 128;
break;
case 3:
utemp = 256;
break;
case 4:
utemp = 512;
break;
case 5:
utemp = 1024;
break;
case 6:
utemp = 2048;
break;
case 7:
utemp = 4096;
break;
case 8:
utemp = 8192;
break;
case 9:
utemp = 16384;
break;
case 10:
utemp = 32768;
break;
case 11:
utemp = 65536;
break;
case 12:
utemp = 65536;
break;
case 13:
utemp = 65536;
break;
case 14:
utemp = 65535;
break;
case 15:
default:
utemp = 0;
break;
}
//printf("getattackincrement returned %d\n",utemp);
return utemp;
}
/*----------------------------------------------------------------------------
* EAS_PERelease()
*----------------------------------------------------------------------------
* Purpose:
* Put the PCM stream envelope into release.
*
* Inputs:
* pEASData - pointer to EAS library instance data
* handle - pointer to S_PCM_STATE for this stream
*
* Outputs:
*
*
* Side Effects:
*
*----------------------------------------------------------------------------
*/
/*lint -esym(715, pEASData) reserved for future use */
EAS_RESULT EAS_PERelease (S_EAS_DATA *pEASData, EAS_PCM_HANDLE pState)
{
EAS_U32 utemp;
//printf("handling note-off part of envelope\n");
/*if the note is not ignore release or sustained*/
if (((pState->envData >> 24) & 0x0F)==0)
{
/* set envelope state to release */
pState->envState = PCM_ENV_RELEASE;
utemp = ((pState->envData >> 20) & 0x0F);
pState->envScale = getDecayScale(utemp); //getReleaseScale(utemp);
}
else
{
/*else change envelope state to sustain */
pState->envState = PCM_ENV_SUSTAIN;
utemp = ((pState->envData >> 28) & 0x0F);
pState->envScale = getDecayScale(utemp); //getSustainScale(utemp);
}
//since we are in release, don't let anything hang around too long
//printf("checking env scale, val = %d\n",((S_PCM_STATE*) handle)->envScale);
if (pState->envScale > 505)
pState->envScale = 505;
return EAS_SUCCESS;
}
/*----------------------------------------------------------------------------
* FindSlot()
*----------------------------------------------------------------------------
* Purpose:
* Locates an empty stream slot and assigns the file handle
*
* Inputs:
* pEASData - pointer to EAS library instance data
* fileHandle - file handle
* pCallbackFunc - function to be called back upon EAS_STATE_STOPPED
*
* Outputs:
* returns handle to slot or NULL if all slots are used
*
* Side Effects:
*
*----------------------------------------------------------------------------
*/
static S_PCM_STATE *FindSlot (S_EAS_DATA *pEASData, EAS_FILE_HANDLE fileHandle, EAS_PCM_CALLBACK pCallbackFunc, EAS_VOID_PTR cbInstData)
{
EAS_INT i;
S_PCM_STATE *pState;
#ifndef NO_PCM_STEAL
S_PCM_STATE *foundState = NULL;
EAS_INT count = 0;
EAS_U32 startOrder = 0xFFFFFFFF;
S_PCM_STATE *stealState = NULL;
EAS_U32 youngest = 0;
/* find an empty slot, count total in use, and find oldest in use (lowest start order) */
for (i = 0, pState = pEASData->pPCMStreams; i < MAX_PCM_STREAMS; i++, pState++)
{
/* if this one is available */
if (pState->fileHandle == NULL)
{
foundState = pState;
}
/* else this one is in use, so see if it is the oldest, and count total in use */
/* also find youngest */
else
{
/*one more voice in use*/
count++;
/* is this the oldest? (lowest start order) */
if ((pState->state != EAS_STATE_STOPPING) && (pState->startOrder < startOrder))
{
/* remember this one */
stealState = pState;
/* remember the oldest so far */
startOrder = pState->startOrder;
}
/* is this the youngest? (highest start order) */
if (pState->startOrder >= youngest)
{
youngest = pState->startOrder;
}
}
}
/* if there are too many voices active, stop the oldest one */
if (count > PCM_STREAM_THRESHOLD)
{
//printf("stealing!!!\n");
/* make sure we got one, although we should always have one at this point */
if (stealState != NULL)
{
//flag this as stopping, so it will get shut off
stealState->state = EAS_STATE_STOPPING;
}
}
/* if there are no available open streams (we won't likely see this, due to stealing) */
if (foundState == NULL)
return NULL;
/* save info */
foundState->startOrder = youngest + 1;
foundState->fileHandle = fileHandle;
foundState->pCallback = pCallbackFunc;
foundState->cbInstData = cbInstData;
return foundState;
#else
/* find an empty slot*/
for (i = 0; i < MAX_PCM_STREAMS; i++)
{
pState = &pEASData->pPCMStreams[i];
if (pState->fileHandle != NULL)
continue;
pState->fileHandle = fileHandle;
pState->pCallback = pCallbackFunc;
pState->cbInstData = cbInstData;
return pState;
}
return NULL;
#endif
}
#ifdef _LOOKUP_SAMPLE_RATE
/*----------------------------------------------------------------------------
* CalcBaseFreq()
*----------------------------------------------------------------------------
* Purpose:
* Calculates the fractional phase increment for the sample rate converter
*
* Inputs:
* sampleRate - sample rate in samples/sec
*
* Outputs:
* Returns fractional sample rate with a 15-bit fraction
*
* Side Effects:
*
*----------------------------------------------------------------------------
*/
static EAS_U32 CalcBaseFreq (EAS_U32 sampleRate)
{
EAS_INT i;
/* look up the conversion rate */
for (i = 0; i < (EAS_INT)(SRC_CONV_RATE_ENTRIES); i ++)
{
if (srcConvRate[i][0] == sampleRate)
return srcConvRate[i][1];
}
/* if not found in table, do it the long way */
{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_WARNING, "Sample rate %u not in table, calculating by division\n", sampleRate); */ }
return (SRC_RATE_MULTIPLER * (EAS_U32) sampleRate) >> 15;
}
#endif
/*----------------------------------------------------------------------------
* InitPCMStream()
*----------------------------------------------------------------------------
* Purpose:
* Start an ADPCM stream playback. Decodes the header, preps the engine.
*
* Inputs:
*
*
* Outputs:
*
*
* Side Effects:
*
*----------------------------------------------------------------------------
*/
static EAS_RESULT InitPCMStream (S_EAS_DATA *pEASData, S_PCM_STATE *pState)
{
/* initialize the data structure */
pState->bytesLeft = pState->byteCount;
pState->phase = 0;
pState->srcByte = 0;
pState->decoderL.acc = 0;
pState->decoderL.output = 0;
pState->decoderL.x0 = pState->decoderL.x1 = 0;
pState->decoderL.step = 0;
pState->decoderR.acc = 0;
pState->decoderR.output = 0;
pState->decoderR.x0 = pState->decoderR.x1 = 0;
pState->decoderR.step = 0;
pState->hiNibble = EAS_FALSE;
pState->pitch = 0;
pState->blockCount = 0;
pState->gainLeft = PCM_DEFAULT_GAIN_SETTING;
// pState->currentGainLeft = PCM_DEFAULT_GAIN_SETTING;
pState->envValue = 0;
pState->envState = PCM_ENV_START;
#if (NUM_OUTPUT_CHANNELS == 2)
pState->gainRight = PCM_DEFAULT_GAIN_SETTING;
// pState->currentGainRight = PCM_DEFAULT_GAIN_SETTING;
#endif
pState->state = EAS_STATE_READY;
/* initialize the decoder */
if (pState->pDecoder->pfInit)
return (*pState->pDecoder->pfInit)(pEASData, pState);
return EAS_SUCCESS;
}
/*----------------------------------------------------------------------------
* RenderPCMStream()
*----------------------------------------------------------------------------
* Purpose:
* Decodes a buffer of ADPCM data.
*
* Inputs:
*
*
* Outputs:
*
*
* Side Effects:
*
*----------------------------------------------------------------------------
*/
static EAS_RESULT RenderPCMStream (S_EAS_DATA *pEASData, S_PCM_STATE *pState, EAS_I32 numSamples)
{
EAS_RESULT result;
EAS_U32 phaseInc;
EAS_I32 gainLeft, gainIncLeft;
EAS_I32 *pOut;
EAS_I32 temp;
EAS_U32 utemp;
#if (NUM_OUTPUT_CHANNELS == 2)
EAS_I32 gainRight, gainIncRight;
#endif
#if 0
printf("env data: AR = %d, DR = %d, SL = %d, SR = %d, RR = %d\n",
((pState->envData >> 12) & 0x0F),
((pState->envData >> 16) & 0x0F),
((pState->envData >> 8) & 0x0F),
((pState->envData >> 28) & 0x0F),
((pState->envData >> 20) & 0x0F));
#endif
if (pState->envState == PCM_ENV_START)
{
//printf("env start\n");
utemp = ((pState->envData >> 12) & 0x0F);
//if fastest rate, attack is already completed
//do the same for slowest rate, since that allows zero to be passed for default envelope
if (utemp == 0x0F || utemp == 0x00)
{
//start envelope at full
pState->envValue = (32768<<7);
//jump right into decay
utemp = ((pState->envData >> 16) & 0x0F);
pState->envScale = getDecayScale(utemp);
pState->envState = PCM_ENV_DECAY;
pState->currentGainLeft = (EAS_I16) FMUL_15x15(pState->gainLeft, pState->volume);
pState->currentGainRight = (EAS_I16) FMUL_15x15(pState->gainRight, pState->volume);
}
//else attack has a ramp
else
{
//start the envelope very low
pState->envValue = (2<<7);
pState->currentGainLeft = 0;
pState->currentGainRight = 0;
//get envelope attack scaling value
pState->envScale = getAttackIncrement(utemp);
//go to attack state
pState->envState = PCM_ENV_ATTACK;
}
}
if (pState->envState == PCM_ENV_ATTACK)
{
//printf("env attack, env value = %d, env scale = %d\n",pState->envValue>>7,pState->envScale);
//update envelope value
pState->envValue = pState->envValue + (pState->envScale << 7);
//check envelope level and update state if needed
if (pState->envValue >= (32768<<7))
{
pState->envValue = (32768<<7);
utemp = ((pState->envData >> 16) & 0x0F);
pState->envScale = getDecayScale(utemp);
pState->envState = PCM_ENV_DECAY;
}
}
else if (pState->envState == PCM_ENV_DECAY)
{
//printf("env decay, env value = %d, env scale = %d\n",pState->envValue>>7,pState->envScale);
//update envelope value
pState->envValue = (pState->envValue * pState->envScale)>>9;
//check envelope level against sustain level and update state if needed
utemp = ((pState->envData >> 8) & 0x0F);
if (utemp == (EAS_U32)0x0F)
utemp = (2<<7);
else
{
utemp = ((32769<<7) >> (utemp>>1));
}
if (pState->envValue <= utemp)
{
utemp = ((pState->envData >> 28) & 0x0F);
pState->envScale = getDecayScale(utemp); //getSustainScale(utemp);
pState->envState = PCM_ENV_SUSTAIN;
}
}
else if (pState->envState == PCM_ENV_SUSTAIN)
{
//printf("env sustain, env value = %d, env scale = %d\n",pState->envValue>>7,pState->envScale);
//update envelope value
pState->envValue = (pState->envValue * pState->envScale)>>9;
//check envelope level against bottom level and update state if needed
if (pState->envValue <= (2<<7))
{
//no more decay
pState->envScale = 512;
pState->envState = PCM_ENV_END;
}
}
else if (pState->envState == PCM_ENV_RELEASE)
{
//printf("env release, env value = %d, env scale = %d\n",pState->envValue>>7,pState->envScale);
//update envelope value
pState->envValue = (pState->envValue * pState->envScale)>>9;
//check envelope level against bottom level and update state if needed
if (pState->envValue <= (2<<7))
{
//no more decay
pState->envScale = 512;
pState->envState = PCM_ENV_END;
}
}
else if (pState->envState == PCM_ENV_END)
{
//printf("env end\n");
/* set state to stopping, already ramped down */
pState->state = EAS_STATE_STOPPING;
}
//pState->gainLeft = (EAS_U16)((pState->gainLeft * (pState->envValue>>7))>>15);
//pState->gainRight = (EAS_U16)((pState->gainRight * (pState->envValue>>7))>>15);
/* gain to 32-bits to increase resolution on anti-zipper filter */
/*lint -e{703} use shift for performance */
gainLeft = (EAS_I32) pState->currentGainLeft << SYNTH_UPDATE_PERIOD_IN_BITS;
#if (NUM_OUTPUT_CHANNELS == 2)
/*lint -e{703} use shift for performance */
gainRight = (EAS_I32) pState->currentGainRight << SYNTH_UPDATE_PERIOD_IN_BITS;
#endif
/* calculate a new gain increment, gain target is zero if pausing */
if ((pState->state == EAS_STATE_PAUSING) || (pState->state == EAS_STATE_PAUSED))
{
gainIncLeft = -pState->currentGainLeft;
#if (NUM_OUTPUT_CHANNELS == 2)
gainIncRight= -pState->currentGainRight;
#endif
}
else
{
EAS_I32 gain = FMUL_15x15(pState->envValue >> 7, pState->volume);
gainIncLeft = FMUL_15x15(pState->gainLeft, gain) - pState->currentGainLeft;
#if (NUM_OUTPUT_CHANNELS == 2)
gainIncRight = FMUL_15x15(pState->gainRight, gain) - pState->currentGainRight;
#endif
}
/* calculate phase increment */
phaseInc = pState->basefreq;
/* convert pitch cents to linear multiplier */
if (pState->pitch)
{
temp = EAS_Calculate2toX(pState->pitch);
phaseInc = FMUL_15x15(phaseInc, temp);
}
phaseInc = phaseInc << pState->rateShift;
/* pointer to mix buffer */
pOut = pEASData->pMixBuffer;
/* render a buffer of samples */
while (numSamples--)
{
/* interpolate an output sample */
pState->decoderL.output = pState->decoderL.x0 + FMUL_15x15((pState->decoderL.x1 - pState->decoderL.x0), pState->phase & PHASE_FRAC_MASK);
/* stereo output */
#if (NUM_OUTPUT_CHANNELS == 2)
/* stereo stream? */
if (pState->flags & PCM_FLAGS_STEREO)
pState->decoderR.output = pState->decoderR.x0 + FMUL_15x15((pState->decoderR.x1 - pState->decoderR.x0), pState->phase & PHASE_FRAC_MASK);
/* gain scale and mix */
/*lint -e{704} use shift instead of division */
*pOut++ += (pState->decoderL.output * (gainLeft >> SYNTH_UPDATE_PERIOD_IN_BITS)) >> PCM_MIXER_GUARD_BITS;
gainLeft += gainIncLeft;
/*lint -e{704} use shift instead of division */
if (pState->flags & PCM_FLAGS_STEREO)
*pOut++ += (pState->decoderR.output * (gainRight >> SYNTH_UPDATE_PERIOD_IN_BITS)) >> PCM_MIXER_GUARD_BITS;
else
*pOut++ += (pState->decoderL.output * (gainRight >> SYNTH_UPDATE_PERIOD_IN_BITS)) >> PCM_MIXER_GUARD_BITS;
gainRight += gainIncRight;
/* mono output */
#else
/* if stereo stream, decode right channel and mix to mono */
if (pState->flags & PCM_FLAGS_STEREO)
{
pState->decoderR.output= pState->decoderR.x0 + FMUL_15x15((pState->decoderR.x1 - pState->decoderR.x0), pState->phase & PHASE_FRAC_MASK);
/* for mono, sum stereo ADPCM to mono */
/*lint -e{704} use shift instead of division */
*pOut++ += ((pState->decoderL.output + pState->decoderR.output) * (gainLeft >> SYNTH_UPDATE_PERIOD_IN_BITS)) >> PCM_MIXER_GUARD_BITS;
}
else
/*lint -e{704} use shift instead of division */
*pOut++ += (pState->decoderL.output * (gainLeft >> SYNTH_UPDATE_PERIOD_IN_BITS)) >> PCM_MIXER_GUARD_BITS;
gainLeft += gainIncLeft;
#endif
/* advance phase accumulator */
pState->phase += phaseInc;
/* if integer part of phase accumulator is non-zero, advance to next sample */
while (pState->phase & ~PHASE_FRAC_MASK)
{
pState->decoderL.x0 = pState->decoderL.x1;
pState->decoderR.x0 = pState->decoderR.x1;
/* give the source a chance to continue the stream */
if (!pState->bytesLeft && pState->pCallback && ((pState->flags & PCM_FLAGS_EMPTY) == 0))
{
pState->flags |= PCM_FLAGS_EMPTY;
(*pState->pCallback)(pEASData, pState->cbInstData, pState, EAS_STATE_EMPTY);
{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL, "RenderPCMStream: After empty callback, bytesLeft = %d\n", pState->bytesLeft); */ }
}
/* decode the next sample */
if ((result = (*pState->pDecoder->pfDecodeSample)(pEASData, pState)) != EAS_SUCCESS)
return result;
/* adjust phase by one sample */
pState->phase -= (1L << NUM_PHASE_FRAC_BITS);
}
}
/* save new gain */
/*lint -e{704} use shift instead of division */
pState->currentGainLeft = (EAS_I16) (gainLeft >> SYNTH_UPDATE_PERIOD_IN_BITS);
#if (NUM_OUTPUT_CHANNELS == 2)
/*lint -e{704} use shift instead of division */
pState->currentGainRight = (EAS_I16) (gainRight >> SYNTH_UPDATE_PERIOD_IN_BITS);
#endif
/* if pausing, set new state and notify */
if (pState->state == EAS_STATE_PAUSING)
{
pState->state = EAS_STATE_PAUSED;
if (pState->pCallback)
(*pState->pCallback)(pEASData, pState->cbInstData, pState, pState->state);
}
/* if out of data, set stopped state and notify */
if (pState->bytesLeft == 0 || pState->state == EAS_STATE_STOPPING)
{
pState->state = EAS_STATE_STOPPED;
/* do callback unless the file has already been closed */
if (pState->pCallback && pState->fileHandle)
(*pState->pCallback)(pEASData, pState->cbInstData, pState, pState->state);
}
if (pState->state == EAS_STATE_READY)
pState->state = EAS_STATE_PLAY;
return EAS_SUCCESS;
}
/*----------------------------------------------------------------------------
* LinearPCMDecode()
*----------------------------------------------------------------------------
* Purpose:
* Decodes a PCM sample
*
* Inputs:
*
*
* Outputs:
*
*
* Side Effects:
*
*----------------------------------------------------------------------------
*/
static EAS_RESULT LinearPCMDecode (EAS_DATA_HANDLE pEASData, S_PCM_STATE *pState)
{
EAS_RESULT result;
EAS_HW_DATA_HANDLE hwInstData;
hwInstData = ((S_EAS_DATA*) pEASData)->hwInstData;
/* if out of data, check for loop */
if ((pState->bytesLeft == 0) && (pState->loopSamples != 0))
{
if ((result = EAS_HWFileSeek(pEASData->hwInstData, pState->fileHandle, (EAS_I32) (pState->startPos + pState->loopLocation))) != EAS_SUCCESS)
return result;
pState->bytesLeft = pState->byteCount = (EAS_I32) pState->bytesLeftLoop;
pState->flags &= ~PCM_FLAGS_EMPTY;
{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL, "LinearPCMDecode: Rewind file to %d, bytesLeft = %d\n", pState->startPos, pState->bytesLeft); */ }
}
if (pState->bytesLeft)
{
/* check format byte for 8-bit samples */
if (pState->flags & PCM_FLAGS_8_BIT)
{
/* fetch left or mono sample */
if ((result = EAS_HWGetByte(hwInstData, pState->fileHandle, &pState->srcByte)) != EAS_SUCCESS)
return result;
/* if unsigned */
if (pState->flags & PCM_FLAGS_UNSIGNED)
{
/*lint -e{734} converting unsigned 8-bit to signed 16-bit */
pState->decoderL.x1 = (EAS_PCM)(((EAS_PCM) pState->srcByte << 8) ^ 0x8000);
}
else
{
/*lint -e{734} converting signed 8-bit to signed 16-bit */
pState->decoderL.x1 = (EAS_PCM)((EAS_PCM) pState->srcByte << 8);
}
pState->bytesLeft--;
/* fetch right sample */
if(pState->flags & PCM_FLAGS_STEREO)
{
if ((result = EAS_HWGetByte(hwInstData, pState->fileHandle, &pState->srcByte)) != EAS_SUCCESS)
return result;
/* if unsigned */
if (pState->flags & PCM_FLAGS_UNSIGNED)
{
/*lint -e{734} converting unsigned 8-bit to signed 16-bit */
pState->decoderR.x1 = (EAS_PCM)(((EAS_PCM) pState->srcByte << 8) ^ 0x8000);
}
else
{
/*lint -e{734} converting signed 8-bit to signed 16-bit */
pState->decoderR.x1 = (EAS_PCM)((EAS_PCM) pState->srcByte << 8);
}
pState->bytesLeft--;
}
}
/* must be 16-bit samples */
else
{
//unsigned 16 bit currently not supported
if (pState->flags & PCM_FLAGS_UNSIGNED)
{
return EAS_ERROR_INVALID_PCM_TYPE;
}
/* fetch left or mono sample */
if ((result = EAS_HWGetWord(hwInstData, pState->fileHandle, &pState->decoderL.x1, EAS_FALSE)) != EAS_SUCCESS)
return result;
pState->bytesLeft -= 2;
/* fetch right sample */
if(pState->flags & PCM_FLAGS_STEREO)
{
if ((result = EAS_HWGetWord(hwInstData, pState->fileHandle, &pState->decoderR.x1, EAS_FALSE)) != EAS_SUCCESS)
return result;
pState->bytesLeft -= 2;
}
}
}
/* no more data, force zero samples */
else
pState->decoderL.x1 = pState->decoderR.x1 = 0;
return EAS_SUCCESS;
}
/*----------------------------------------------------------------------------
* LinearPCMLocate()
*----------------------------------------------------------------------------
* Purpose:
* Locate in a linear PCM stream
*----------------------------------------------------------------------------
*/
static EAS_RESULT LinearPCMLocate (EAS_DATA_HANDLE pEASData, S_PCM_STATE *pState, EAS_I32 time)
{
EAS_RESULT result;
EAS_I32 temp;
EAS_I32 secs, msecs;
EAS_INT shift;
/* calculate size of sample frame */
if (pState->flags & PCM_FLAGS_8_BIT)
shift = 0;
else
shift = 1;
if (pState->flags & PCM_FLAGS_STEREO)
shift++;
/* break down into secs and msecs */
secs = time / 1000;
msecs = time - (secs * 1000);
/* calculate sample number fraction from msecs */
temp = (msecs * pState->sampleRate);
temp = (temp >> 10) + ((temp * 49) >> 21);
/* add integer sample count */
temp += secs * pState->sampleRate;
/* calculate the position based on sample frame size */
/*lint -e{703} use shift for performance */
temp <<= shift;
/* past end of sample? */
if (temp > (EAS_I32) pState->loopStart)
{
/* if not looped, flag error */
if (pState->loopSamples == 0)
{
pState->bytesLeft = 0;
pState->flags |= PCM_FLAGS_EMPTY;
return EAS_ERROR_LOCATE_BEYOND_END;
}
/* looped sample - calculate position in loop */
while (temp > (EAS_I32) pState->loopStart)
temp -= (EAS_I32) pState->loopStart;
}
/* seek to new position */
if ((result = EAS_PESeek(pEASData, pState, &temp)) != EAS_SUCCESS)
return result;
/* reset state */
if ((pState->state != EAS_STATE_PAUSING) && (pState->state != EAS_STATE_PAUSED))
pState->state = EAS_STATE_READY;
return EAS_SUCCESS;
}
/*----------------------------------------------------------------------------
* EAS_PESeek
*----------------------------------------------------------------------------
* Purpose:
* Locate to a particular byte in a PCM stream
*----------------------------------------------------------------------------
* This bit is tricky because the chunks may not be contiguous,
* so we have to rely on the parser to position in the file. We
* do this by seeking to the end of each chunk and simulating an
* empty buffer condition until we get to where we want to go.
*
* A better solution would be a parser API for re-positioning,
* but there isn't time at the moment to re-factor all the
* parsers to support a new API.
*----------------------------------------------------------------------------
*/
EAS_RESULT EAS_PESeek (S_EAS_DATA *pEASData, S_PCM_STATE *pState, EAS_I32 *pLocation)
{
EAS_RESULT result;
/* seek to start of audio */
if ((result = EAS_HWFileSeek(pEASData->hwInstData, pState->fileHandle, pState->startPos)) != EAS_SUCCESS)
{
pState->state = EAS_STATE_ERROR;
return result;
}
pState->bytesLeft = pState->bytesLeftLoop;
/* skip through chunks until we find the right chunk */
while (*pLocation > (EAS_I32) pState->bytesLeft)
{
/* seek to end of audio chunk */
{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL, "EAS_PESeek: Seek to offset = %d\n", pState->bytesLeft); */ }
if ((result = EAS_HWFileSeekOfs(pEASData->hwInstData, pState->fileHandle, pState->bytesLeft)) != EAS_SUCCESS)
{
pState->state = EAS_STATE_ERROR;
return result;
}
*pLocation -= pState->bytesLeft;
pState->bytesLeft = 0;
pState->flags |= PCM_FLAGS_EMPTY;
/* retrieve more data */
if (pState->pCallback)
(*pState->pCallback)(pEASData, pState->cbInstData, pState, EAS_STATE_EMPTY);
{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL, "EAS_PESeek: bytesLeft=%d, byte location = %d\n", pState->bytesLeft, *pLocation); */ }
/* no more samples */
if (pState->bytesLeft == 0)
return EAS_ERROR_LOCATE_BEYOND_END;
}
/* seek to new offset in current chunk */
if (*pLocation > 0)
{
{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL, "EAS_PESeek: Seek to offset = %d\n", *pLocation); */ }
if ((result = EAS_HWFileSeekOfs(pEASData->hwInstData, pState->fileHandle, *pLocation)) != EAS_SUCCESS)
{
pState->state = EAS_STATE_ERROR;
return result;
}
/* if not streamed, calculate number of bytes left */
if (pState->flags & PCM_FLAGS_STREAMING)
pState->bytesLeft = 0x7fffffff;
else
pState->bytesLeft -= *pLocation;
}
return EAS_SUCCESS;
}