C++程序  |  605行  |  19.93 KB

/*----------------------------------------------------------------------------
 *
 * 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 */