/*----------------------------------------------------------------------------
*
* File:
* eas_chorus.c
*
* Contents and purpose:
* Contains the implementation of the Chorus effect.
*
*
* Copyright Sonic Network Inc. 2006
* 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: 499 $
* $Date: 2006-12-11 16:07:20 -0800 (Mon, 11 Dec 2006) $
*----------------------------------------------------------------------------
*/
#include "eas_data.h"
#include "eas_effects.h"
#include "eas_math.h"
#include "eas_chorusdata.h"
#include "eas_chorus.h"
#include "eas_config.h"
#include "eas_host.h"
#include "eas_report.h"
/* prototypes for effects interface */
static EAS_RESULT ChorusInit (EAS_DATA_HANDLE pEASData, EAS_VOID_PTR *pInstData);
static void ChorusProcess (EAS_VOID_PTR pInstData, EAS_PCM *pSrc, EAS_PCM *pDst, EAS_I32 numSamples);
static EAS_RESULT ChorusShutdown (EAS_DATA_HANDLE pEASData, EAS_VOID_PTR pInstData);
static EAS_RESULT ChorusGetParam (EAS_VOID_PTR pInstData, EAS_I32 param, EAS_I32 *pValue);
static EAS_RESULT ChorusSetParam (EAS_VOID_PTR pInstData, EAS_I32 param, EAS_I32 value);
/* common effects interface for configuration module */
const S_EFFECTS_INTERFACE EAS_Chorus =
{
ChorusInit,
ChorusProcess,
ChorusShutdown,
ChorusGetParam,
ChorusSetParam
};
//LFO shape table used by the chorus, larger table would sound better
//this is a sine wave, where 32767 = 1.0
static const EAS_I16 EAS_chorusShape[CHORUS_SHAPE_SIZE] = {
0, 1608, 3212, 4808, 6393, 7962, 9512, 11309, 12539, 14010, 15446, 16846, 18204, 19519, 20787, 22005, 23170,
24279, 25329, 26319, 27245, 28105, 28898, 29621, 30273, 30852, 31356, 31785, 32137, 32412, 32609, 32728,
32767, 32728, 32609, 32412, 32137, 31785, 31356, 30852, 30273, 29621, 28898, 28105, 27245, 26319, 25329,
24279, 23170, 22005, 20787, 19519, 18204, 16846, 15446, 14010, 12539, 11039, 9512, 7962, 6393, 4808, 3212,
1608, 0, -1608, -3212, -4808, -6393, -7962, -9512, -11309, -12539, -14010, -15446, -16846, -18204, -19519,
-20787, -22005, -23170, -24279, -25329, -26319, -27245, -28105, -28898, -29621, -30273, -30852, -31356, -31785,
-32137, -32412, -32609, -32728, -32767, -32728, -32609, -32412, -32137, -31785, -31356, -30852, -30273, -29621,
-28898, -28105, -27245, -26319, -25329, -24279, -23170, -22005, -20787, -19519, -18204, -16846, -15446, -14010,
-12539, -11039, -9512, -7962, -6393, -4808, -3212, -1608
};
/*----------------------------------------------------------------------------
* InitializeChorus()
*----------------------------------------------------------------------------
* Purpose: Initializes chorus parameters
*
*
* Inputs:
*
* Outputs:
*
*----------------------------------------------------------------------------
*/
static EAS_RESULT ChorusInit (EAS_DATA_HANDLE pEASData, EAS_VOID_PTR *pInstData)
{
S_CHORUS_OBJECT *pChorusData;
S_CHORUS_PRESET *pPreset;
EAS_I32 index;
/* check Configuration Module for data allocation */
if (pEASData->staticMemoryModel)
pChorusData = EAS_CMEnumFXData(EAS_MODULE_CHORUS);
/* allocate dynamic memory */
else
pChorusData = EAS_HWMalloc(pEASData->hwInstData, sizeof(S_CHORUS_OBJECT));
if (pChorusData == NULL)
{
{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_FATAL, "Failed to allocate Chorus memory\n"); */ }
return EAS_ERROR_MALLOC_FAILED;
}
/* clear the structure */
EAS_HWMemSet(pChorusData, 0, sizeof(S_CHORUS_OBJECT));
ChorusReadInPresets(pChorusData);
/* set some default values */
pChorusData->bypass = EAS_CHORUS_BYPASS_DEFAULT;
pChorusData->preset = EAS_CHORUS_PRESET_DEFAULT;
pChorusData->m_nLevel = EAS_CHORUS_LEVEL_DEFAULT;
pChorusData->m_nRate = EAS_CHORUS_RATE_DEFAULT;
pChorusData->m_nDepth = EAS_CHORUS_DEPTH_DEFAULT;
//chorus rate and depth need some massaging from preset value (which is sample rate independent)
//convert rate from steps of .05 Hz to value which can be used as phase increment,
//with current CHORUS_SHAPE_SIZE and rate limits, this fits into 16 bits
//want to compute ((shapeSize * 65536) * (storedRate/20))/sampleRate;
//computing it as below allows rate steps to be evenly spaced
//uses 32 bit divide, but only once when new value is selected
pChorusData->m_nRate = (EAS_I16)
((((EAS_I32)CHORUS_SHAPE_SIZE<<16)/(20*(EAS_I32)_OUTPUT_SAMPLE_RATE)) * pChorusData->m_nRate);
//convert depth from steps of .05 ms, to samples, with 16 bit whole part, discard fraction
//want to compute ((depth * sampleRate)/20000)
//use the following approximation since 105/32 is roughly 65536/20000
/*lint -e{704} use shift for performance */
pChorusData->m_nDepth = (EAS_I16)
(((((EAS_I32)pChorusData->m_nDepth * _OUTPUT_SAMPLE_RATE)>>5) * 105) >> 16);
pChorusData->m_nLevel = pChorusData->m_nLevel;
//zero delay memory for chorus
for (index = CHORUS_L_SIZE - 1; index >= 0; index--)
{
pChorusData->chorusDelayL[index] = 0;
}
for (index = CHORUS_R_SIZE - 1; index >= 0; index--)
{
pChorusData->chorusDelayR[index] = 0;
}
//init delay line index, these are used to implement circular delay buffer
pChorusData->chorusIndexL = 0;
pChorusData->chorusIndexR = 0;
//init LFO phase
//16 bit whole part, 16 bit fraction
pChorusData->lfoLPhase = 0;
pChorusData->lfoRPhase = (CHORUS_SHAPE_SIZE << 16) >> 2; // 1/4 of total, i.e. 90 degrees out of phase;
//init chorus delay position
//right now chorus delay is a compile-time value, as is sample rate
pChorusData->chorusTapPosition = (EAS_I16)((CHORUS_DELAY_MS * _OUTPUT_SAMPLE_RATE)/1000);
//now copy from the new preset into Chorus
pPreset = &pChorusData->m_sPreset.m_sPreset[pChorusData->m_nNextChorus];
pChorusData->m_nLevel = pPreset->m_nLevel;
pChorusData->m_nRate = pPreset->m_nRate;
pChorusData->m_nDepth = pPreset->m_nDepth;
pChorusData->m_nRate = (EAS_I16)
((((EAS_I32)CHORUS_SHAPE_SIZE<<16)/(20*(EAS_I32)_OUTPUT_SAMPLE_RATE)) * pChorusData->m_nRate);
/*lint -e{704} use shift for performance */
pChorusData->m_nDepth = (EAS_I16)
(((((EAS_I32)pChorusData->m_nDepth * _OUTPUT_SAMPLE_RATE)>>5) * 105) >> 16);
*pInstData = pChorusData;
return EAS_SUCCESS;
} /* end ChorusInit */
/*----------------------------------------------------------------------------
* WeightedTap()
*----------------------------------------------------------------------------
* Purpose: Does fractional array look-up using linear interpolation
*
* first convert indexDesired to actual desired index by taking into account indexReference
* then do linear interpolation between two actual samples using fractional part
*
* Inputs:
* array: pointer to array of signed 16 bit values, typically either PCM data or control data
* indexReference: the circular buffer relative offset
* indexDesired: the fractional index we are looking up (16 bits index + 16 bits fraction)
* indexLimit: the total size of the array, used to compute buffer wrap
*
* Outputs:
* Value from the input array, linearly interpolated between two actual data values
*
*----------------------------------------------------------------------------
*/
static EAS_I16 WeightedTap(const EAS_I16 *array, EAS_I16 indexReference, EAS_I32 indexDesired, EAS_I16 indexLimit)
{
EAS_I16 index;
EAS_I16 fraction;
EAS_I16 val1;
EAS_I16 val2;
//separate indexDesired into whole and fractional parts
/*lint -e{704} use shift for performance */
index = (EAS_I16)(indexDesired >> 16);
/*lint -e{704} use shift for performance */
fraction = (EAS_I16)((indexDesired>>1) & 0x07FFF); //just use 15 bits of fractional part
//adjust whole part by indexReference
index = indexReference - index;
//make sure we stay within array bounds, this implements circular buffer
while (index < 0)
{
index += indexLimit;
}
//get two adjacent values from the array
val1 = array[index];
//handle special case when index == 0, else typical case
if (index == 0)
{
val2 = array[indexLimit-1]; //get last value from array
}
else
{
val2 = array[index-1]; //get previous value from array
}
//compute linear interpolation as (val1 + ((val2-val1)*fraction))
return(val1 + (EAS_I16)MULT_EG1_EG1(val2-val1,fraction));
}
/*----------------------------------------------------------------------------
* ChorusProcess()
*----------------------------------------------------------------------------
* Purpose: compute the chorus on the input buffer, and mix into output buffer
*
*
* Inputs:
* src: pointer to input buffer of PCM values to be processed
* dst: pointer to output buffer of PCM values we are to sume the result with
* bufSize: the number of sample frames (i.e. stereo samples) in the buffer
*
* Outputs:
* None
*
*----------------------------------------------------------------------------
*/
//compute the chorus, and mix into output buffer
static void ChorusProcess (EAS_VOID_PTR pInstData, EAS_PCM *pSrc, EAS_PCM *pDst, EAS_I32 numSamples)
{
EAS_I32 ix;
EAS_I32 nChannelNumber;
EAS_I16 lfoValueLeft;
EAS_I16 lfoValueRight;
EAS_I32 positionOffsetL;
EAS_I32 positionOffsetR;
EAS_PCM tapL;
EAS_PCM tapR;
EAS_I32 tempValue;
EAS_PCM nInputSample;
EAS_I32 nOutputSample;
EAS_PCM *pIn;
EAS_PCM *pOut;
S_CHORUS_OBJECT *pChorusData;
pChorusData = (S_CHORUS_OBJECT*) pInstData;
//if the chorus is disabled or turned all the way down
if (pChorusData->bypass == EAS_TRUE || pChorusData->m_nLevel == 0)
{
if (pSrc != pDst)
EAS_HWMemCpy(pSrc, pDst, numSamples * NUM_OUTPUT_CHANNELS * (EAS_I32) sizeof(EAS_PCM));
return;
}
if (pChorusData->m_nNextChorus != pChorusData->m_nCurrentChorus)
{
ChorusUpdate(pChorusData);
}
for (nChannelNumber = 0; nChannelNumber < NUM_OUTPUT_CHANNELS; nChannelNumber++)
{
pIn = pSrc + nChannelNumber;
pOut = pDst + nChannelNumber;
if(nChannelNumber==0)
{
for (ix = 0; ix < numSamples; ix++)
{
nInputSample = *pIn;
pIn += NUM_OUTPUT_CHANNELS;
//feed input into chorus delay line
pChorusData->chorusDelayL[pChorusData->chorusIndexL] = nInputSample;
//compute chorus lfo value using phase as fractional index into chorus shape table
//resulting value is between -1.0 and 1.0, expressed as signed 16 bit number
lfoValueLeft = WeightedTap(EAS_chorusShape, 0, pChorusData->lfoLPhase, CHORUS_SHAPE_SIZE);
//scale chorus depth by lfo value to get relative fractional sample index
//index is expressed as 32 bit number with 16 bit fractional part
/*lint -e{703} use shift for performance */
positionOffsetL = pChorusData->m_nDepth * (((EAS_I32)lfoValueLeft) << 1);
//add fixed chorus delay to get actual fractional sample index
positionOffsetL += ((EAS_I32)pChorusData->chorusTapPosition) << 16;
//get tap value from chorus delay using fractional sample index
tapL = WeightedTap(pChorusData->chorusDelayL, pChorusData->chorusIndexL, positionOffsetL, CHORUS_L_SIZE);
//scale by chorus level, then sum with input buffer contents and saturate
tempValue = MULT_EG1_EG1(tapL, pChorusData->m_nLevel);
nOutputSample = SATURATE(tempValue + nInputSample);
*pOut = (EAS_I16)SATURATE(nOutputSample);
pOut += NUM_OUTPUT_CHANNELS;
//increment chorus delay index and make it wrap as needed
//this implements circular buffer
if ((pChorusData->chorusIndexL+=1) >= CHORUS_L_SIZE)
pChorusData->chorusIndexL = 0;
//increment fractional lfo phase, and make it wrap as needed
pChorusData->lfoLPhase += pChorusData->m_nRate;
while (pChorusData->lfoLPhase >= (CHORUS_SHAPE_SIZE<<16))
{
pChorusData->lfoLPhase -= (CHORUS_SHAPE_SIZE<<16);
}
}
}
else
{
for (ix = 0; ix < numSamples; ix++)
{
nInputSample = *pIn;
pIn += NUM_OUTPUT_CHANNELS;
//feed input into chorus delay line
pChorusData->chorusDelayR[pChorusData->chorusIndexR] = nInputSample;
//compute chorus lfo value using phase as fractional index into chorus shape table
//resulting value is between -1.0 and 1.0, expressed as signed 16 bit number
lfoValueRight = WeightedTap(EAS_chorusShape, 0, pChorusData->lfoRPhase, CHORUS_SHAPE_SIZE);
//scale chorus depth by lfo value to get relative fractional sample index
//index is expressed as 32 bit number with 16 bit fractional part
/*lint -e{703} use shift for performance */
positionOffsetR = pChorusData->m_nDepth * (((EAS_I32)lfoValueRight) << 1);
//add fixed chorus delay to get actual fractional sample index
positionOffsetR += ((EAS_I32)pChorusData->chorusTapPosition) << 16;
//get tap value from chorus delay using fractional sample index
tapR = WeightedTap(pChorusData->chorusDelayR, pChorusData->chorusIndexR, positionOffsetR, CHORUS_R_SIZE);
//scale by chorus level, then sum with output buffer contents and saturate
tempValue = MULT_EG1_EG1(tapR, pChorusData->m_nLevel);
nOutputSample = SATURATE(tempValue + nInputSample);
*pOut = (EAS_I16)SATURATE(nOutputSample);
pOut += NUM_OUTPUT_CHANNELS;
//increment chorus delay index and make it wrap as needed
//this implements circular buffer
if ((pChorusData->chorusIndexR+=1) >= CHORUS_R_SIZE)
pChorusData->chorusIndexR = 0;
//increment fractional lfo phase, and make it wrap as needed
pChorusData->lfoRPhase += pChorusData->m_nRate;
while (pChorusData->lfoRPhase >= (CHORUS_SHAPE_SIZE<<16))
{
pChorusData->lfoRPhase -= (CHORUS_SHAPE_SIZE<<16);
}
}
}
}
} /* end ChorusProcess */
/*----------------------------------------------------------------------------
* ChorusShutdown()
*----------------------------------------------------------------------------
* Purpose:
* Initializes the Chorus effect.
*
* Inputs:
* pInstData - handle to instance data
*
* Outputs:
*
*
* Side Effects:
*
*----------------------------------------------------------------------------
*/
static EAS_RESULT ChorusShutdown (EAS_DATA_HANDLE pEASData, EAS_VOID_PTR pInstData)
{
/* check Configuration Module for static memory allocation */
if (!pEASData->staticMemoryModel)
EAS_HWFree(pEASData->hwInstData, pInstData);
return EAS_SUCCESS;
} /* end ChorusShutdown */
/*----------------------------------------------------------------------------
* ChorusGetParam()
*----------------------------------------------------------------------------
* Purpose:
* Get a Chorus parameter
*
* Inputs:
* pInstData - handle to instance data
* param - parameter index
* *pValue - pointer to variable to hold retrieved value
*
* Outputs:
*
*
* Side Effects:
*
*----------------------------------------------------------------------------
*/
static EAS_RESULT ChorusGetParam (EAS_VOID_PTR pInstData, EAS_I32 param, EAS_I32 *pValue)
{
S_CHORUS_OBJECT *p;
p = (S_CHORUS_OBJECT*) pInstData;
switch (param)
{
case EAS_PARAM_CHORUS_BYPASS:
*pValue = (EAS_I32) p->bypass;
break;
case EAS_PARAM_CHORUS_PRESET:
*pValue = (EAS_I8) p->m_nCurrentChorus;
break;
case EAS_PARAM_CHORUS_RATE:
*pValue = (EAS_I32) p->m_nRate;
break;
case EAS_PARAM_CHORUS_DEPTH:
*pValue = (EAS_I32) p->m_nDepth;
break;
case EAS_PARAM_CHORUS_LEVEL:
*pValue = (EAS_I32) p->m_nLevel;
break;
default:
return EAS_ERROR_INVALID_PARAMETER;
}
return EAS_SUCCESS;
} /* end ChorusGetParam */
/*----------------------------------------------------------------------------
* ChorusSetParam()
*----------------------------------------------------------------------------
* Purpose:
* Set a Chorus parameter
*
* Inputs:
* pInstData - handle to instance data
* param - parameter index
* *pValue - new paramter value
*
* Outputs:
*
*
* Side Effects:
*
*----------------------------------------------------------------------------
*/
static EAS_RESULT ChorusSetParam (EAS_VOID_PTR pInstData, EAS_I32 param, EAS_I32 value)
{
S_CHORUS_OBJECT *p;
p = (S_CHORUS_OBJECT*) pInstData;
switch (param)
{
case EAS_PARAM_CHORUS_BYPASS:
p->bypass = (EAS_BOOL) value;
break;
case EAS_PARAM_CHORUS_PRESET:
if(value!=EAS_PARAM_CHORUS_PRESET1 && value!=EAS_PARAM_CHORUS_PRESET2 &&
value!=EAS_PARAM_CHORUS_PRESET3 && value!=EAS_PARAM_CHORUS_PRESET4)
return EAS_ERROR_INVALID_PARAMETER;
p->m_nNextChorus = (EAS_I8)value;
break;
case EAS_PARAM_CHORUS_RATE:
if(value<EAS_CHORUS_RATE_MIN || value>EAS_CHORUS_RATE_MAX)
return EAS_ERROR_INVALID_PARAMETER;
p->m_nRate = (EAS_I16) value;
break;
case EAS_PARAM_CHORUS_DEPTH:
if(value<EAS_CHORUS_DEPTH_MIN || value>EAS_CHORUS_DEPTH_MAX)
return EAS_ERROR_INVALID_PARAMETER;
p->m_nDepth = (EAS_I16) value;
break;
case EAS_PARAM_CHORUS_LEVEL:
if(value<EAS_CHORUS_LEVEL_MIN || value>EAS_CHORUS_LEVEL_MAX)
return EAS_ERROR_INVALID_PARAMETER;
p->m_nLevel = (EAS_I16) value;
break;
default:
return EAS_ERROR_INVALID_PARAMETER;
}
return EAS_SUCCESS;
} /* end ChorusSetParam */
/*----------------------------------------------------------------------------
* ChorusReadInPresets()
*----------------------------------------------------------------------------
* Purpose: sets global Chorus preset bank to defaults
*
* Inputs:
*
* Outputs:
*
*----------------------------------------------------------------------------
*/
static EAS_RESULT ChorusReadInPresets(S_CHORUS_OBJECT *pChorusData)
{
int preset = 0;
int defaultPreset = 0;
//now init any remaining presets to defaults
for (defaultPreset = preset; defaultPreset < CHORUS_MAX_TYPE; defaultPreset++)
{
S_CHORUS_PRESET *pPreset = &pChorusData->m_sPreset.m_sPreset[defaultPreset];
if (defaultPreset == 0 || defaultPreset > CHORUS_MAX_TYPE-1)
{
pPreset->m_nDepth = 39;
pPreset->m_nRate = 30;
pPreset->m_nLevel = 32767;
}
else if (defaultPreset == 1)
{
pPreset->m_nDepth = 21;
pPreset->m_nRate = 45;
pPreset->m_nLevel = 25000;
}
else if (defaultPreset == 2)
{
pPreset->m_nDepth = 53;
pPreset->m_nRate = 25;
pPreset->m_nLevel = 32000;
}
else if (defaultPreset == 3)
{
pPreset->m_nDepth = 32;
pPreset->m_nRate = 37;
pPreset->m_nLevel = 29000;
}
}
return EAS_SUCCESS;
}
/*----------------------------------------------------------------------------
* ChorusUpdate
*----------------------------------------------------------------------------
* Purpose:
* Update the Chorus preset parameters as required
*
* Inputs:
*
* Outputs:
*
*
* Side Effects:
* - chorus paramters will be changed
* - m_nCurrentRoom := m_nNextRoom
*----------------------------------------------------------------------------
*/
static EAS_RESULT ChorusUpdate(S_CHORUS_OBJECT *pChorusData)
{
S_CHORUS_PRESET *pPreset = &pChorusData->m_sPreset.m_sPreset[pChorusData->m_nNextChorus];
pChorusData->m_nLevel = pPreset->m_nLevel;
pChorusData->m_nRate = pPreset->m_nRate;
pChorusData->m_nDepth = pPreset->m_nDepth;
pChorusData->m_nRate = (EAS_I16)
((((EAS_I32)CHORUS_SHAPE_SIZE<<16)/(20*(EAS_I32)_OUTPUT_SAMPLE_RATE)) * pChorusData->m_nRate);
/*lint -e{704} use shift for performance */
pChorusData->m_nDepth = (EAS_I16)
(((((EAS_I32)pChorusData->m_nDepth * _OUTPUT_SAMPLE_RATE)>>5) * 105) >> 16);
pChorusData->m_nCurrentChorus = pChorusData->m_nNextChorus;
return EAS_SUCCESS;
} /* end ChorusUpdate */