/*---------------------------------------------------------------------------- * * 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(valueEAS_CHORUS_RATE_MAX) return EAS_ERROR_INVALID_PARAMETER; p->m_nRate = (EAS_I16) value; break; case EAS_PARAM_CHORUS_DEPTH: if(valueEAS_CHORUS_DEPTH_MAX) return EAS_ERROR_INVALID_PARAMETER; p->m_nDepth = (EAS_I16) value; break; case EAS_PARAM_CHORUS_LEVEL: if(valueEAS_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 */