From 56c99cd2c2c1e6ab038dac5fced5b92ccf11ff6c Mon Sep 17 00:00:00 2001 From: Dave Sparks Date: Mon, 24 Aug 2009 17:35:45 -0700 Subject: Sonivox whitespace cleanup --- arm-hybrid-22k/lib_src/eas_fmsynth.c | 1796 +++++++++++++++++----------------- 1 file changed, 898 insertions(+), 898 deletions(-) (limited to 'arm-hybrid-22k/lib_src/eas_fmsynth.c') diff --git a/arm-hybrid-22k/lib_src/eas_fmsynth.c b/arm-hybrid-22k/lib_src/eas_fmsynth.c index 83f0087..629506a 100644 --- a/arm-hybrid-22k/lib_src/eas_fmsynth.c +++ b/arm-hybrid-22k/lib_src/eas_fmsynth.c @@ -1,12 +1,12 @@ -/*---------------------------------------------------------------------------- - * - * File: - * fmsynth.c - * - * Contents and purpose: - * Implements the high-level FM synthesizer functions. - * - * Copyright Sonic Network Inc. 2004 +/*---------------------------------------------------------------------------- + * + * File: + * fmsynth.c + * + * Contents and purpose: + * Implements the high-level FM synthesizer functions. + * + * Copyright Sonic Network Inc. 2004 * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,892 +19,892 @@ * 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: 795 $ - * $Date: 2007-08-01 00:14:45 -0700 (Wed, 01 Aug 2007) $ - *---------------------------------------------------------------------------- -*/ - -// includes -#include "eas_host.h" -#include "eas_report.h" - -#include "eas_data.h" -#include "eas_synth_protos.h" -#include "eas_audioconst.h" -#include "eas_fmengine.h" -#include "eas_math.h" - -/* option sanity check */ -#ifdef _REVERB -#error "No reverb for FM synthesizer" -#endif -#ifdef _CHORUS -#error "No chorus for FM synthesizer" -#endif - -/* - * WARNING: These macros can cause unwanted side effects. Use them - * with care. For example, min(x++,y++) will cause either x or y to be - * incremented twice. - */ -#define min(a,b) ((a) < (b) ? (a) : (b)) -#define max(a,b) ((a) > (b) ? (a) : (b)) - -/* pivot point for keyboard scalars */ -#define EG_SCALE_PIVOT_POINT 64 -#define KEY_SCALE_PIVOT_POINT 36 - -/* This number is the negative of the frequency of the note (in cents) of - * the sine wave played at unity. The number can be calculated as follows: - * - * MAGIC_NUMBER = 1200 * log(base2) (SINE_TABLE_SIZE * 8.175798916 / SAMPLE_RATE) - * - * 8.17578 is a reference to the frequency of MIDI note 0 - */ -#if defined (_SAMPLE_RATE_8000) -#define MAGIC_NUMBER 1279 -#elif defined (_SAMPLE_RATE_16000) -#define MAGIC_NUMBER 79 -#elif defined (_SAMPLE_RATE_20000) -#define MAGIC_NUMBER -308 -#elif defined (_SAMPLE_RATE_22050) -#define MAGIC_NUMBER -477 -#elif defined (_SAMPLE_RATE_24000) -#define MAGIC_NUMBER -623 -#elif defined (_SAMPLE_RATE_32000) -#define MAGIC_NUMBER -1121 -#elif defined (_SAMPLE_RATE_44100) -#define MAGIC_NUMBER -1677 -#elif defined (_SAMPLE_RATE_48000) -#define MAGIC_NUMBER -1823 -#endif - -/* externs */ -extern const EAS_I16 fmControlTable[128]; -extern const EAS_U16 fmRateTable[256]; -extern const EAS_U16 fmAttackTable[16]; -extern const EAS_U8 fmDecayTable[16]; -extern const EAS_U8 fmReleaseTable[16]; -extern const EAS_U8 fmScaleTable[16]; - -/* local prototypes */ -/*lint -esym(715, pVoiceMgr) standard synthesizer interface - pVoiceMgr not used */ -static EAS_RESULT FM_Initialize (S_VOICE_MGR *pVoiceMgr) { return EAS_SUCCESS; } -static EAS_RESULT FM_StartVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum, EAS_U16 regionIndex); -static EAS_BOOL FM_UpdateVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum, EAS_I32 *pMixBuffer, EAS_I32 numSamples); -static void FM_ReleaseVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum); -static void FM_MuteVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum); -static void FM_SustainPedal (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, S_SYNTH_CHANNEL *pChannel, EAS_I32 voiceNum); -static void FM_UpdateChannel (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, EAS_U8 channel); - - -/*---------------------------------------------------------------------------- - * Synthesizer interface - *---------------------------------------------------------------------------- -*/ -const S_SYNTH_INTERFACE fmSynth = -{ - FM_Initialize, - FM_StartVoice, - FM_UpdateVoice, - FM_ReleaseVoice, - FM_MuteVoice, - FM_SustainPedal, - FM_UpdateChannel -}; - -#ifdef FM_OFFBOARD -const S_FRAME_INTERFACE fmFrameInterface = -{ - FM_StartFrame, - FM_EndFrame -}; -#endif - -/*---------------------------------------------------------------------------- - * inline functions - *---------------------------------------------------------------------------- - */ -EAS_INLINE S_FM_VOICE *GetFMVoicePtr (S_VOICE_MGR *pVoiceMgr, EAS_INT voiceNum) -{ - return &pVoiceMgr->fmVoices[voiceNum]; -} -EAS_INLINE S_SYNTH_CHANNEL *GetChannelPtr (S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice) -{ - return &pSynth->channels[pVoice->channel & 15]; -} -EAS_INLINE const S_FM_REGION *GetFMRegionPtr (S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice) -{ -#ifdef _SECONDARY_SYNTH - return &pSynth->pEAS->pFMRegions[pVoice->regionIndex & REGION_INDEX_MASK]; -#else - return &pSynth->pEAS->pFMRegions[pVoice->regionIndex]; -#endif -} - -/*---------------------------------------------------------------------------- - * FM_SynthIsOutputOperator - *---------------------------------------------------------------------------- - * Purpose: - * Returns true if the operator is a direct output and not muted - * - * Inputs: - * - * Outputs: - * Returns boolean - *---------------------------------------------------------------------------- -*/ -static EAS_BOOL FM_SynthIsOutputOperator (const S_FM_REGION *pRegion, EAS_INT operIndex) -{ - - /* see if voice is muted */ - if ((pRegion->oper[operIndex].gain & 0xfc) == 0) - return 0; - - /* check based on mode */ - switch (pRegion->region.keyGroupAndFlags & 7) - { - - /* mode 0 - all operators are external */ - case 0: - return EAS_TRUE; - - /* mode 1 - operators 1-3 are external */ - case 1: - if (operIndex != 0) - return EAS_TRUE; - break; - - /* mode 2 - operators 1 & 3 are external */ - case 2: - if ((operIndex == 1) || (operIndex == 3)) - return EAS_TRUE; - break; - - /* mode 2 - operators 1 & 2 are external */ - case 3: - if ((operIndex == 1) || (operIndex == 2)) - return EAS_TRUE; - break; - - /* modes 4 & 5 - operator 1 is external */ - case 4: - case 5: - if (operIndex == 1) - return EAS_TRUE; - break; - - default: - { /* dpp: EAS_ReportEx(_EAS_SEVERITY_FATAL,"Invalid voice mode: %d", - pRegion->region.keyGroupAndFlags & 7); */ } - } - - return EAS_FALSE; -} - -/*---------------------------------------------------------------------------- - * FM_CalcEGRate() - *---------------------------------------------------------------------------- - * Purpose: - * - * Inputs: - * nKeyNumber - MIDI note - * nLogRate - logarithmic scale rate from patch data - * nKeyScale - key scaling factor for this EG - * - * Outputs: - * 16-bit linear multiplier - *---------------------------------------------------------------------------- -*/ - -static EAS_U16 FM_CalcEGRate (EAS_U8 nKeyNumber, EAS_U8 nLogRate, EAS_U8 nEGScale) -{ - EAS_I32 temp; - - /* incorporate key scaling on release rate */ - temp = (EAS_I32) nLogRate << 7; - temp += ((EAS_I32) nKeyNumber - EG_SCALE_PIVOT_POINT) * (EAS_I32) nEGScale; - - /* saturate */ - temp = max(temp, 0); - temp = min(temp, 32767); - - /* look up in rate table */ - /*lint -e{704} use shift for performance */ - return fmRateTable[temp >> 8]; -} - -/*---------------------------------------------------------------------------- - * FM_ReleaseVoice() - *---------------------------------------------------------------------------- - * Purpose: - * The selected voice is being released. - * - * Inputs: - * psEASData - pointer to S_EAS_DATA - * pVoice - pointer to voice to release - * - * Outputs: - * None - *---------------------------------------------------------------------------- -*/ -static void FM_ReleaseVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum) -{ - EAS_INT operIndex; - const S_FM_REGION *pRegion; - S_FM_VOICE *pFMVoice; - - /* check to see if voice responds to NOTE-OFF's */ - pRegion = GetFMRegionPtr(pSynth, pVoice); - if (pRegion->region.keyGroupAndFlags & REGION_FLAG_ONE_SHOT) - return; - - /* set all envelopes to release state */ - pFMVoice = GetFMVoicePtr(pVoiceMgr, voiceNum); - for (operIndex = 0; operIndex < 4; operIndex++) - { - pFMVoice->oper[operIndex].envState = eFMEnvelopeStateRelease; - - /* incorporate key scaling on release rate */ - pFMVoice->oper[operIndex].envRate = FM_CalcEGRate( - pVoice->note, - fmReleaseTable[pRegion->oper[operIndex].velocityRelease & 0x0f], - fmScaleTable[pRegion->oper[operIndex].egKeyScale >> 4]); - } /* end for (operIndex = 0; operIndex < 4; operIndex++) */ -} - -/*---------------------------------------------------------------------------- - * FM_MuteVoice() - *---------------------------------------------------------------------------- - * Purpose: - * The selected voice is being muted. - * - * Inputs: - * pVoice - pointer to voice to release - * - * Outputs: - * None - *---------------------------------------------------------------------------- -*/ -/*lint -esym(715, pSynth) standard interface, pVoiceMgr not used */ -static void FM_MuteVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum) -{ - S_FM_VOICE *pFMVoice; - - /* clear deferred action flags */ - pVoice->voiceFlags &= - ~(VOICE_FLAG_DEFER_MIDI_NOTE_OFF | - VOICE_FLAG_SUSTAIN_PEDAL_DEFER_NOTE_OFF | - VOICE_FLAG_DEFER_MUTE); - - /* set all envelopes to muted state */ - pFMVoice = GetFMVoicePtr(pVoiceMgr, voiceNum); - pFMVoice->oper[0].envState = eFMEnvelopeStateMuted; - pFMVoice->oper[1].envState = eFMEnvelopeStateMuted; - pFMVoice->oper[2].envState = eFMEnvelopeStateMuted; - pFMVoice->oper[3].envState = eFMEnvelopeStateMuted; -} - -/*---------------------------------------------------------------------------- - * FM_SustainPedal() - *---------------------------------------------------------------------------- - * Purpose: - * The selected voice is held due to sustain pedal - * - * Inputs: - * pVoice - pointer to voice to sustain - * - * Outputs: - * None - *---------------------------------------------------------------------------- -*/ -/*lint -esym(715, pChannel) standard interface, pVoiceMgr not used */ -static void FM_SustainPedal (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, S_SYNTH_CHANNEL *pChannel, EAS_I32 voiceNum) -{ - const S_FM_REGION *pRegion; - S_FM_VOICE *pFMVoice; - EAS_INT operIndex; - - pRegion = GetFMRegionPtr(pSynth, pVoice); - pFMVoice = GetFMVoicePtr(pVoiceMgr, voiceNum); - - /* check to see if any envelopes are above the sustain level */ - for (operIndex = 0; operIndex < 4; operIndex++) - { - - /* if level control or envelope gain is zero, skip this envelope */ - if (((pRegion->oper[operIndex].gain & 0xfc) == 0) || - (pFMVoice->oper[operIndex].envGain == 0)) - { - continue; - } - - /* if the envelope gain is above the sustain level, we need to catch this voice */ - if (pFMVoice->oper[operIndex].envGain >= ((EAS_U16) (pRegion->oper[operIndex].sustain & 0xfc) << 7)) - { - - /* reset envelope to decay state */ - pFMVoice->oper[operIndex].envState = eFMEnvelopeStateDecay; - - pFMVoice->oper[operIndex].envRate = FM_CalcEGRate( - pVoice->note, - fmDecayTable[pRegion->oper[operIndex].attackDecay & 0x0f], - fmScaleTable[pRegion->oper[operIndex].egKeyScale >> 4]); - - /* set voice to decay state */ - pVoice->voiceState = eVoiceStatePlay; - - /* set sustain flag */ - pVoice->voiceFlags |= VOICE_FLAG_SUSTAIN_PEDAL_DEFER_NOTE_OFF; - } - } /* end for (operIndex = 0; operIndex < 4; operIndex++) */ -} - -/*---------------------------------------------------------------------------- - * FM_StartVoice() - *---------------------------------------------------------------------------- - * Purpose: - * Assign the region for the given instrument using the midi key number - * and the RPN2 (coarse tuning) value. By using RPN2 as part of the - * region selection process, we reduce the amount a given sample has - * to be transposed by selecting the closest recorded root instead. - * - * This routine is the second half of SynthAssignRegion(). - * If the region was successfully found by SynthFindRegionIndex(), - * then assign the region's parameters to the voice. - * - * Setup and initialize the following voice parameters: - * m_nRegionIndex - * - * Inputs: - * pVoice - ptr to the voice we have assigned for this channel - * nRegionIndex - index of the region - * psEASData - pointer to overall EAS data structure - * - * Outputs: - * success - could find and assign the region for this voice's note otherwise - * failure - could not find nor assign the region for this voice's note - * - * Side Effects: - * psSynthObject->m_sVoice[].m_nRegionIndex is assigned - * psSynthObject->m_sVoice[] parameters are assigned - *---------------------------------------------------------------------------- -*/ -static EAS_RESULT FM_StartVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum, EAS_U16 regionIndex) -{ - S_FM_VOICE *pFMVoice; - S_SYNTH_CHANNEL *pChannel; - const S_FM_REGION *pRegion; - EAS_I32 temp; - EAS_INT operIndex; - - /* establish pointers to data */ - pVoice->regionIndex = regionIndex; - pFMVoice = GetFMVoicePtr(pVoiceMgr, voiceNum); - pChannel = GetChannelPtr(pSynth, pVoice); - pRegion = GetFMRegionPtr(pSynth, pVoice); - - /* update static channel parameters */ - if (pChannel->channelFlags & CHANNEL_FLAG_UPDATE_CHANNEL_PARAMETERS) - FM_UpdateChannel(pVoiceMgr, pSynth, pVoice->channel & 15); - - /* init the LFO */ - pFMVoice->lfoValue = 0; - pFMVoice->lfoPhase = 0; - pFMVoice->lfoDelay = (EAS_U16) (fmScaleTable[pRegion->lfoFreqDelay & 0x0f] >> 1); - -#if (NUM_OUTPUT_CHANNELS == 2) - /* calculate pan gain values only if stereo output */ - /* set up panning only at note start */ - temp = (EAS_I32) pChannel->pan - 64; - temp += (EAS_I32) pRegion->pan; - if (temp < -64) - temp = -64; - if (temp > 64) - temp = 64; - pFMVoice->pan = (EAS_I8) temp; -#endif /* #if (NUM_OUTPUT_CHANNELS == 2) */ - - /* no samples have been synthesized for this note yet */ - pVoice->voiceFlags = VOICE_FLAG_NO_SAMPLES_SYNTHESIZED_YET; - - /* initialize gain value for anti-zipper filter */ - pFMVoice->voiceGain = (EAS_I16) EAS_LogToLinear16(pChannel->staticGain); - pFMVoice->voiceGain = (EAS_I16) FMUL_15x15(pFMVoice->voiceGain, pSynth->masterVolume); - - /* initialize the operators */ - for (operIndex = 0; operIndex < 4; operIndex++) - { - - /* establish operator output gain level */ - /*lint -e{701} */ - pFMVoice->oper[operIndex].outputGain = EAS_LogToLinear16(((EAS_I16) (pRegion->oper[operIndex].gain & 0xfc) - 0xfc) << 7); - - /* check for linear velocity flag */ - /*lint -e{703} */ - if (pRegion->oper[operIndex].flags & FM_OPER_FLAG_LINEAR_VELOCITY) - temp = (EAS_I32) (pVoice->velocity - 127) << 5; - else - temp = (EAS_I32) fmControlTable[pVoice->velocity]; - - /* scale velocity */ - /*lint -e{704} use shift for performance */ - temp = (temp * (EAS_I32)(pRegion->oper[operIndex].velocityRelease & 0xf0)) >> 7; - - /* include key scalar */ - temp -= ((EAS_I32) pVoice->note - KEY_SCALE_PIVOT_POINT) * (EAS_I32) fmScaleTable[pRegion->oper[operIndex].egKeyScale & 0x0f]; - - /* saturate */ - temp = min(temp, 0); - temp = max(temp, -32768); - - /* save static gain parameters */ - pFMVoice->oper[operIndex].baseGain = (EAS_I16) EAS_LogToLinear16(temp); - - /* incorporate key scaling on decay rate */ - pFMVoice->oper[operIndex].envRate = FM_CalcEGRate( - pVoice->note, - fmDecayTable[pRegion->oper[operIndex].attackDecay & 0x0f], - fmScaleTable[pRegion->oper[operIndex].egKeyScale >> 4]); - - /* if zero attack time, max out envelope and jump to decay state */ - if ((pRegion->oper[operIndex].attackDecay & 0xf0) == 0xf0) - { - - /* start out envelope at max */ - pFMVoice->oper[operIndex].envGain = 0x7fff; - - /* set envelope to decay state */ - pFMVoice->oper[operIndex].envState = eFMEnvelopeStateDecay; - } - - /* start envelope at zero and start in attack state */ - else - { - pFMVoice->oper[operIndex].envGain = 0; - pFMVoice->oper[operIndex].envState = eFMEnvelopeStateAttack; - } - } - - return EAS_SUCCESS; -} - -/*---------------------------------------------------------------------------- - * FM_UpdateChannel() - *---------------------------------------------------------------------------- - * Purpose: - * Calculate and assign static channel parameters - * These values only need to be updated if one of the controller values - * for this channel changes. - * Called when CHANNEL_FLAG_UPDATE_CHANNEL_PARAMETERS flag is set. - * - * Inputs: - * nChannel - channel to update - * psEASData - pointer to overall EAS data structure - * - * Outputs: - * - * Side Effects: - * - the given channel's static gain and static pitch are updated - *---------------------------------------------------------------------------- -*/ -/*lint -esym(715, pVoiceMgr) standard interface, pVoiceMgr not used */ -static void FM_UpdateChannel (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, EAS_U8 channel) -{ - S_SYNTH_CHANNEL *pChannel; - EAS_I32 temp; - - pChannel = &pSynth->channels[channel]; - - /* convert CC7 volume controller to log scale */ - temp = fmControlTable[pChannel->volume]; - - /* incorporate CC11 expression controller */ - temp += fmControlTable[pChannel->expression]; - - /* saturate */ - pChannel->staticGain = (EAS_I16) max(temp,-32768); - - /* calculate pitch bend */ - /*lint -e{703} */ - temp = (((EAS_I32)(pChannel->pitchBend) << 2) - 32768); - - temp = FMUL_15x15(temp, pChannel->pitchBendSensitivity); - - /* include "magic number" compensation for sample rate and lookup table size */ - temp += MAGIC_NUMBER; - - /* if this is not a drum channel, then add in the per-channel tuning */ - if (!(pChannel->channelFlags & CHANNEL_FLAG_RHYTHM_CHANNEL)) - temp += (pChannel->finePitch + (pChannel->coarsePitch * 100)); - - /* save static pitch */ - pChannel->staticPitch = temp; - - /* Calculate LFO modulation depth */ - /* mod wheel to LFO depth */ - temp = FMUL_15x15(DEFAULT_LFO_MOD_WHEEL_TO_PITCH_CENTS, - pChannel->modWheel << (NUM_EG1_FRAC_BITS -7)); - - /* channel pressure to LFO depth */ - pChannel->lfoAmt = (EAS_I16) (temp + - FMUL_15x15(DEFAULT_LFO_CHANNEL_PRESSURE_TO_PITCH_CENTS, - pChannel->channelPressure << (NUM_EG1_FRAC_BITS -7))); - - /* clear update flag */ - pChannel->channelFlags &= ~CHANNEL_FLAG_UPDATE_CHANNEL_PARAMETERS; - return; -} - -/*---------------------------------------------------------------------------- - * FM_UpdateLFO() - *---------------------------------------------------------------------------- - * Purpose: - * Calculate the LFO for the given voice - * - * Inputs: - * pVoice - ptr to the voice whose LFO we want to update - * psEASData - pointer to overall EAS data structure - used for debug only - * - * Outputs: - * - * Side Effects: - * - updates LFO values for the given voice - *---------------------------------------------------------------------------- -*/ -static void FM_UpdateLFO (S_FM_VOICE *pFMVoice, const S_FM_REGION *pRegion) -{ - - /* increment the LFO phase if the delay time has elapsed */ - if (!pFMVoice->lfoDelay) - { - /*lint -e{701} */ - pFMVoice->lfoPhase = pFMVoice->lfoPhase + (EAS_U16) (-fmControlTable[((15 - (pRegion->lfoFreqDelay >> 4)) << 3) + 4]); - - /* square wave LFO? */ - if (pRegion->region.keyGroupAndFlags & REGION_FLAG_SQUARE_WAVE) - pFMVoice->lfoValue = (EAS_I16)(pFMVoice->lfoPhase & 0x8000 ? -32767 : 32767); - - /* trick to get a triangle wave out of a sawtooth */ - else - { - pFMVoice->lfoValue = (EAS_I16) (pFMVoice->lfoPhase << 1); - /*lint -e{502} */ - if ((pFMVoice->lfoPhase > 0x3fff) && (pFMVoice->lfoPhase < 0xC000)) - pFMVoice->lfoValue = ~pFMVoice->lfoValue; - } - } - - /* still in delay */ - else - pFMVoice->lfoDelay--; - - return; -} - -/*---------------------------------------------------------------------------- - * FM_UpdateEG() - *---------------------------------------------------------------------------- - * Purpose: - * Calculate the synthesis parameters for an operator to be used during - * the next buffer - * - * Inputs: - * pVoice - pointer to the voice being updated - * psEASData - pointer to overall EAS data structure - * - * Outputs: - * - * Side Effects: - * - *---------------------------------------------------------------------------- -*/ -static EAS_BOOL FM_UpdateEG (S_SYNTH_VOICE *pVoice, S_OPERATOR *pOper, const S_FM_OPER *pOperData, EAS_BOOL oneShot) -{ - EAS_U32 temp; - EAS_BOOL done; - - /* set flag assuming the envelope is not done */ - done = EAS_FALSE; - - /* take appropriate action based on state */ - switch (pOper->envState) - { - - case eFMEnvelopeStateAttack: - - /* the envelope is linear during the attack, so add the value */ - temp = pOper->envGain + fmAttackTable[pOperData->attackDecay >> 4]; - - /* check for end of attack */ - if (temp >= 0x7fff) - { - pOper->envGain = 0x7fff; - pOper->envState = eFMEnvelopeStateDecay; - } - else - pOper->envGain = (EAS_U16) temp; - break; - - case eFMEnvelopeStateDecay: - - /* decay is exponential, multiply by decay rate */ - pOper->envGain = (EAS_U16) FMUL_15x15(pOper->envGain, pOper->envRate); - - /* check for sustain level reached */ - temp = (EAS_U32) (pOperData->sustain & 0xfc) << 7; - if (pOper->envGain <= (EAS_U16) temp) - { - /* if this is a one-shot patch, go directly to release phase */ - if (oneShot) - { - pOper->envRate = FM_CalcEGRate( - pVoice->note, - fmReleaseTable[pOperData->velocityRelease & 0x0f], - fmScaleTable[pOperData->egKeyScale >> 4]); - pOper->envState = eFMEnvelopeStateRelease; - } - - /* normal sustaining type */ - else - { - pOper->envGain = (EAS_U16) temp; - pOper->envState = eFMEnvelopeStateSustain; - } - } - break; - - case eFMEnvelopeStateSustain: - pOper->envGain = (EAS_U16)((EAS_U16)(pOperData->sustain & 0xfc) << 7); - break; - - case eFMEnvelopeStateRelease: - - /* release is exponential, multiply by release rate */ - pOper->envGain = (EAS_U16) FMUL_15x15(pOper->envGain, pOper->envRate); - - /* fully released */ - if (pOper->envGain == 0) - { - pOper->envGain = 0; - pOper->envState = eFMEnvelopeStateMuted; - done = EAS_TRUE; - } - break; - - case eFMEnvelopeStateMuted: - pOper->envGain = 0; - done = EAS_TRUE; - break; - default: - { /* dpp: EAS_ReportEx(_EAS_SEVERITY_FATAL,"Invalid operator state: %d", pOper->envState); */ } - } /* end switch (pOper->m_eState) */ - - return done; -} - -/*---------------------------------------------------------------------------- - * FM_UpdateDynamic() - *---------------------------------------------------------------------------- - * Purpose: - * Calculate the synthesis parameters for this voice that will be used to - * synthesize the next buffer - * - * Inputs: - * pVoice - pointer to the voice being updated - * psEASData - pointer to overall EAS data structure - * - * Outputs: - * Returns EAS_TRUE if voice will be fully ramped to zero at the end of - * the next synthesized buffer. - * - * Side Effects: - * - *---------------------------------------------------------------------------- -*/ -static EAS_BOOL FM_UpdateDynamic (S_SYNTH_VOICE *pVoice, S_FM_VOICE *pFMVoice, const S_FM_REGION *pRegion, S_SYNTH_CHANNEL *pChannel) -{ - EAS_I32 temp; - EAS_I32 pitch; - EAS_I32 lfoPitch; - EAS_INT operIndex; - EAS_BOOL done; - - /* increment LFO phase */ - FM_UpdateLFO(pFMVoice, pRegion); - - /* base pitch in cents */ - pitch = pVoice->note * 100; - - /* LFO amount includes LFO depth from programming + channel dynamics */ - temp = (fmScaleTable[pRegion->vibTrem >> 4] >> 1) + pChannel->lfoAmt; - - /* multiply by LFO output to get final pitch modulation */ - lfoPitch = FMUL_15x15(pFMVoice->lfoValue, temp); - - /* flag to indicate this voice is done */ - done = EAS_TRUE; - - /* iterate through operators to establish parameters */ - for (operIndex = 0; operIndex < 4; operIndex++) - { - EAS_BOOL bTemp; - - /* set base phase increment for each operator */ - temp = pRegion->oper[operIndex].tuning + - pChannel->staticPitch; - - /* add vibrato effect unless it is disabled for this operator */ - if ((pRegion->oper[operIndex].flags & FM_OPER_FLAG_NO_VIBRATO) == 0) - temp += lfoPitch; - - /* if note is monotonic, bias to MIDI note 60 */ - if (pRegion->oper[operIndex].flags & FM_OPER_FLAG_MONOTONE) - temp += 6000; - else - temp += pitch; - pFMVoice->oper[operIndex].pitch = (EAS_I16) temp; - - /* calculate envelope, returns true if done */ - bTemp = FM_UpdateEG(pVoice, &pFMVoice->oper[operIndex], &pRegion->oper[operIndex], pRegion->region.keyGroupAndFlags & REGION_FLAG_ONE_SHOT); - - /* check if all output envelopes have completed */ - if (FM_SynthIsOutputOperator(pRegion, operIndex)) - done = done && bTemp; - } - - return done; -} - -/*---------------------------------------------------------------------------- - * FM_UpdateVoice() - *---------------------------------------------------------------------------- - * Purpose: - * Synthesize a block of samples for the given voice. - * - * Inputs: - * psEASData - pointer to overall EAS data structure - * - * Outputs: - * number of samples actually written to buffer - * - * Side Effects: - * - samples are added to the presently free buffer - * - *---------------------------------------------------------------------------- -*/ -static EAS_BOOL FM_UpdateVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum, EAS_I32 *pMixBuffer, EAS_I32 numSamples) -{ - S_SYNTH_CHANNEL *pChannel; - const S_FM_REGION *pRegion; - S_FM_VOICE *pFMVoice; - S_FM_VOICE_CONFIG vCfg; - S_FM_VOICE_FRAME vFrame; - EAS_I32 temp; - EAS_INT oper; - EAS_U16 voiceGainTarget; - EAS_BOOL done; - - /* setup some pointers */ - pChannel = GetChannelPtr(pSynth, pVoice); - pRegion = GetFMRegionPtr(pSynth, pVoice); - pFMVoice = GetFMVoicePtr(pVoiceMgr, voiceNum); - - /* if the voice is just starting, get the voice configuration data */ - if (pVoice->voiceFlags & VOICE_FLAG_NO_SAMPLES_SYNTHESIZED_YET) - { - - /* split architecture may limit the number of voice starts */ -#ifdef MAX_VOICE_STARTS - if (pVoiceMgr->numVoiceStarts == MAX_VOICE_STARTS) - return EAS_FALSE; - pVoiceMgr->numVoiceStarts++; -#endif - - /* get initial parameters */ - vCfg.feedback = pRegion->feedback; - vCfg.voiceGain = (EAS_U16) pFMVoice->voiceGain; - -#if (NUM_OUTPUT_CHANNELS == 2) - vCfg.pan = pFMVoice->pan; -#endif - - /* get voice mode */ - vCfg.flags = pRegion->region.keyGroupAndFlags & 7; - - /* get operator parameters */ - for (oper = 0; oper < 4; oper++) - { - /* calculate initial gain */ - vCfg.gain[oper] = (EAS_U16) FMUL_15x15(pFMVoice->oper[oper].baseGain, pFMVoice->oper[oper].envGain); - vCfg.outputGain[oper] = pFMVoice->oper[oper].outputGain; - - /* copy noise waveform flag */ - if (pRegion->oper[oper].flags & FM_OPER_FLAG_NOISE) - vCfg.flags |= (EAS_U8) (FLAG_FM_ENG_VOICE_OP1_NOISE << oper); - } - -#ifdef FM_OFFBOARD - FM_ConfigVoice(voiceNum, &vCfg, pVoiceMgr->pFrameBuffer); -#else - FM_ConfigVoice(voiceNum, &vCfg, NULL); -#endif - - /* clear startup flag */ - pVoice->voiceFlags &= ~VOICE_FLAG_NO_SAMPLES_SYNTHESIZED_YET; - } - - /* calculate new synthesis parameters */ - done = FM_UpdateDynamic(pVoice, pFMVoice, pRegion, pChannel); - - /* calculate LFO gain modulation */ - /*lint -e{702} */ - temp = ((fmScaleTable[pRegion->vibTrem & 0x0f] >> 1) * pFMVoice->lfoValue) >> FM_LFO_GAIN_SHIFT; - - /* include channel gain */ - temp += pChannel->staticGain; - - /* -32768 or lower is infinite attenuation */ - if (temp < -32767) - voiceGainTarget = 0; - - /* translate to linear gain multiplier */ - else - voiceGainTarget = EAS_LogToLinear16(temp); - - /* include synth master volume */ - voiceGainTarget = (EAS_U16) FMUL_15x15(voiceGainTarget, pSynth->masterVolume); - - /* save target values for this frame */ - vFrame.voiceGain = voiceGainTarget; - - /* assume voice output is zero */ - pVoice->gain = 0; - - /* save operator targets for this frame */ - for (oper = 0; oper < 4; oper++) - { - vFrame.gain[oper] = (EAS_U16) FMUL_15x15(pFMVoice->oper[oper].baseGain, pFMVoice->oper[oper].envGain); - vFrame.pitch[oper] = pFMVoice->oper[oper].pitch; - - /* use the highest output envelope level as the gain for the voice stealing algorithm */ - if (FM_SynthIsOutputOperator(pRegion, oper)) - pVoice->gain = max(pVoice->gain, (EAS_I16) vFrame.gain[oper]); - } - - /* consider voice gain multiplier in calculating gain for stealing algorithm */ - pVoice->gain = (EAS_I16) FMUL_15x15(voiceGainTarget, pVoice->gain); - - /* synthesize samples */ -#ifdef FM_OFFBOARD - FM_ProcessVoice(voiceNum, &vFrame, numSamples, pVoiceMgr->operMixBuffer, pVoiceMgr->voiceBuffer, pMixBuffer, pVoiceMgr->pFrameBuffer); -#else - FM_ProcessVoice(voiceNum, &vFrame, numSamples, pVoiceMgr->operMixBuffer, pVoiceMgr->voiceBuffer, pMixBuffer, NULL); -#endif - - return done; -} - + * + *---------------------------------------------------------------------------- + * Revision Control: + * $Revision: 795 $ + * $Date: 2007-08-01 00:14:45 -0700 (Wed, 01 Aug 2007) $ + *---------------------------------------------------------------------------- +*/ + +// includes +#include "eas_host.h" +#include "eas_report.h" + +#include "eas_data.h" +#include "eas_synth_protos.h" +#include "eas_audioconst.h" +#include "eas_fmengine.h" +#include "eas_math.h" + +/* option sanity check */ +#ifdef _REVERB +#error "No reverb for FM synthesizer" +#endif +#ifdef _CHORUS +#error "No chorus for FM synthesizer" +#endif + +/* + * WARNING: These macros can cause unwanted side effects. Use them + * with care. For example, min(x++,y++) will cause either x or y to be + * incremented twice. + */ +#define min(a,b) ((a) < (b) ? (a) : (b)) +#define max(a,b) ((a) > (b) ? (a) : (b)) + +/* pivot point for keyboard scalars */ +#define EG_SCALE_PIVOT_POINT 64 +#define KEY_SCALE_PIVOT_POINT 36 + +/* This number is the negative of the frequency of the note (in cents) of + * the sine wave played at unity. The number can be calculated as follows: + * + * MAGIC_NUMBER = 1200 * log(base2) (SINE_TABLE_SIZE * 8.175798916 / SAMPLE_RATE) + * + * 8.17578 is a reference to the frequency of MIDI note 0 + */ +#if defined (_SAMPLE_RATE_8000) +#define MAGIC_NUMBER 1279 +#elif defined (_SAMPLE_RATE_16000) +#define MAGIC_NUMBER 79 +#elif defined (_SAMPLE_RATE_20000) +#define MAGIC_NUMBER -308 +#elif defined (_SAMPLE_RATE_22050) +#define MAGIC_NUMBER -477 +#elif defined (_SAMPLE_RATE_24000) +#define MAGIC_NUMBER -623 +#elif defined (_SAMPLE_RATE_32000) +#define MAGIC_NUMBER -1121 +#elif defined (_SAMPLE_RATE_44100) +#define MAGIC_NUMBER -1677 +#elif defined (_SAMPLE_RATE_48000) +#define MAGIC_NUMBER -1823 +#endif + +/* externs */ +extern const EAS_I16 fmControlTable[128]; +extern const EAS_U16 fmRateTable[256]; +extern const EAS_U16 fmAttackTable[16]; +extern const EAS_U8 fmDecayTable[16]; +extern const EAS_U8 fmReleaseTable[16]; +extern const EAS_U8 fmScaleTable[16]; + +/* local prototypes */ +/*lint -esym(715, pVoiceMgr) standard synthesizer interface - pVoiceMgr not used */ +static EAS_RESULT FM_Initialize (S_VOICE_MGR *pVoiceMgr) { return EAS_SUCCESS; } +static EAS_RESULT FM_StartVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum, EAS_U16 regionIndex); +static EAS_BOOL FM_UpdateVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum, EAS_I32 *pMixBuffer, EAS_I32 numSamples); +static void FM_ReleaseVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum); +static void FM_MuteVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum); +static void FM_SustainPedal (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, S_SYNTH_CHANNEL *pChannel, EAS_I32 voiceNum); +static void FM_UpdateChannel (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, EAS_U8 channel); + + +/*---------------------------------------------------------------------------- + * Synthesizer interface + *---------------------------------------------------------------------------- +*/ +const S_SYNTH_INTERFACE fmSynth = +{ + FM_Initialize, + FM_StartVoice, + FM_UpdateVoice, + FM_ReleaseVoice, + FM_MuteVoice, + FM_SustainPedal, + FM_UpdateChannel +}; + +#ifdef FM_OFFBOARD +const S_FRAME_INTERFACE fmFrameInterface = +{ + FM_StartFrame, + FM_EndFrame +}; +#endif + +/*---------------------------------------------------------------------------- + * inline functions + *---------------------------------------------------------------------------- + */ +EAS_INLINE S_FM_VOICE *GetFMVoicePtr (S_VOICE_MGR *pVoiceMgr, EAS_INT voiceNum) +{ + return &pVoiceMgr->fmVoices[voiceNum]; +} +EAS_INLINE S_SYNTH_CHANNEL *GetChannelPtr (S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice) +{ + return &pSynth->channels[pVoice->channel & 15]; +} +EAS_INLINE const S_FM_REGION *GetFMRegionPtr (S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice) +{ +#ifdef _SECONDARY_SYNTH + return &pSynth->pEAS->pFMRegions[pVoice->regionIndex & REGION_INDEX_MASK]; +#else + return &pSynth->pEAS->pFMRegions[pVoice->regionIndex]; +#endif +} + +/*---------------------------------------------------------------------------- + * FM_SynthIsOutputOperator + *---------------------------------------------------------------------------- + * Purpose: + * Returns true if the operator is a direct output and not muted + * + * Inputs: + * + * Outputs: + * Returns boolean + *---------------------------------------------------------------------------- +*/ +static EAS_BOOL FM_SynthIsOutputOperator (const S_FM_REGION *pRegion, EAS_INT operIndex) +{ + + /* see if voice is muted */ + if ((pRegion->oper[operIndex].gain & 0xfc) == 0) + return 0; + + /* check based on mode */ + switch (pRegion->region.keyGroupAndFlags & 7) + { + + /* mode 0 - all operators are external */ + case 0: + return EAS_TRUE; + + /* mode 1 - operators 1-3 are external */ + case 1: + if (operIndex != 0) + return EAS_TRUE; + break; + + /* mode 2 - operators 1 & 3 are external */ + case 2: + if ((operIndex == 1) || (operIndex == 3)) + return EAS_TRUE; + break; + + /* mode 2 - operators 1 & 2 are external */ + case 3: + if ((operIndex == 1) || (operIndex == 2)) + return EAS_TRUE; + break; + + /* modes 4 & 5 - operator 1 is external */ + case 4: + case 5: + if (operIndex == 1) + return EAS_TRUE; + break; + + default: + { /* dpp: EAS_ReportEx(_EAS_SEVERITY_FATAL,"Invalid voice mode: %d", + pRegion->region.keyGroupAndFlags & 7); */ } + } + + return EAS_FALSE; +} + +/*---------------------------------------------------------------------------- + * FM_CalcEGRate() + *---------------------------------------------------------------------------- + * Purpose: + * + * Inputs: + * nKeyNumber - MIDI note + * nLogRate - logarithmic scale rate from patch data + * nKeyScale - key scaling factor for this EG + * + * Outputs: + * 16-bit linear multiplier + *---------------------------------------------------------------------------- +*/ + +static EAS_U16 FM_CalcEGRate (EAS_U8 nKeyNumber, EAS_U8 nLogRate, EAS_U8 nEGScale) +{ + EAS_I32 temp; + + /* incorporate key scaling on release rate */ + temp = (EAS_I32) nLogRate << 7; + temp += ((EAS_I32) nKeyNumber - EG_SCALE_PIVOT_POINT) * (EAS_I32) nEGScale; + + /* saturate */ + temp = max(temp, 0); + temp = min(temp, 32767); + + /* look up in rate table */ + /*lint -e{704} use shift for performance */ + return fmRateTable[temp >> 8]; +} + +/*---------------------------------------------------------------------------- + * FM_ReleaseVoice() + *---------------------------------------------------------------------------- + * Purpose: + * The selected voice is being released. + * + * Inputs: + * psEASData - pointer to S_EAS_DATA + * pVoice - pointer to voice to release + * + * Outputs: + * None + *---------------------------------------------------------------------------- +*/ +static void FM_ReleaseVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum) +{ + EAS_INT operIndex; + const S_FM_REGION *pRegion; + S_FM_VOICE *pFMVoice; + + /* check to see if voice responds to NOTE-OFF's */ + pRegion = GetFMRegionPtr(pSynth, pVoice); + if (pRegion->region.keyGroupAndFlags & REGION_FLAG_ONE_SHOT) + return; + + /* set all envelopes to release state */ + pFMVoice = GetFMVoicePtr(pVoiceMgr, voiceNum); + for (operIndex = 0; operIndex < 4; operIndex++) + { + pFMVoice->oper[operIndex].envState = eFMEnvelopeStateRelease; + + /* incorporate key scaling on release rate */ + pFMVoice->oper[operIndex].envRate = FM_CalcEGRate( + pVoice->note, + fmReleaseTable[pRegion->oper[operIndex].velocityRelease & 0x0f], + fmScaleTable[pRegion->oper[operIndex].egKeyScale >> 4]); + } /* end for (operIndex = 0; operIndex < 4; operIndex++) */ +} + +/*---------------------------------------------------------------------------- + * FM_MuteVoice() + *---------------------------------------------------------------------------- + * Purpose: + * The selected voice is being muted. + * + * Inputs: + * pVoice - pointer to voice to release + * + * Outputs: + * None + *---------------------------------------------------------------------------- +*/ +/*lint -esym(715, pSynth) standard interface, pVoiceMgr not used */ +static void FM_MuteVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum) +{ + S_FM_VOICE *pFMVoice; + + /* clear deferred action flags */ + pVoice->voiceFlags &= + ~(VOICE_FLAG_DEFER_MIDI_NOTE_OFF | + VOICE_FLAG_SUSTAIN_PEDAL_DEFER_NOTE_OFF | + VOICE_FLAG_DEFER_MUTE); + + /* set all envelopes to muted state */ + pFMVoice = GetFMVoicePtr(pVoiceMgr, voiceNum); + pFMVoice->oper[0].envState = eFMEnvelopeStateMuted; + pFMVoice->oper[1].envState = eFMEnvelopeStateMuted; + pFMVoice->oper[2].envState = eFMEnvelopeStateMuted; + pFMVoice->oper[3].envState = eFMEnvelopeStateMuted; +} + +/*---------------------------------------------------------------------------- + * FM_SustainPedal() + *---------------------------------------------------------------------------- + * Purpose: + * The selected voice is held due to sustain pedal + * + * Inputs: + * pVoice - pointer to voice to sustain + * + * Outputs: + * None + *---------------------------------------------------------------------------- +*/ +/*lint -esym(715, pChannel) standard interface, pVoiceMgr not used */ +static void FM_SustainPedal (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, S_SYNTH_CHANNEL *pChannel, EAS_I32 voiceNum) +{ + const S_FM_REGION *pRegion; + S_FM_VOICE *pFMVoice; + EAS_INT operIndex; + + pRegion = GetFMRegionPtr(pSynth, pVoice); + pFMVoice = GetFMVoicePtr(pVoiceMgr, voiceNum); + + /* check to see if any envelopes are above the sustain level */ + for (operIndex = 0; operIndex < 4; operIndex++) + { + + /* if level control or envelope gain is zero, skip this envelope */ + if (((pRegion->oper[operIndex].gain & 0xfc) == 0) || + (pFMVoice->oper[operIndex].envGain == 0)) + { + continue; + } + + /* if the envelope gain is above the sustain level, we need to catch this voice */ + if (pFMVoice->oper[operIndex].envGain >= ((EAS_U16) (pRegion->oper[operIndex].sustain & 0xfc) << 7)) + { + + /* reset envelope to decay state */ + pFMVoice->oper[operIndex].envState = eFMEnvelopeStateDecay; + + pFMVoice->oper[operIndex].envRate = FM_CalcEGRate( + pVoice->note, + fmDecayTable[pRegion->oper[operIndex].attackDecay & 0x0f], + fmScaleTable[pRegion->oper[operIndex].egKeyScale >> 4]); + + /* set voice to decay state */ + pVoice->voiceState = eVoiceStatePlay; + + /* set sustain flag */ + pVoice->voiceFlags |= VOICE_FLAG_SUSTAIN_PEDAL_DEFER_NOTE_OFF; + } + } /* end for (operIndex = 0; operIndex < 4; operIndex++) */ +} + +/*---------------------------------------------------------------------------- + * FM_StartVoice() + *---------------------------------------------------------------------------- + * Purpose: + * Assign the region for the given instrument using the midi key number + * and the RPN2 (coarse tuning) value. By using RPN2 as part of the + * region selection process, we reduce the amount a given sample has + * to be transposed by selecting the closest recorded root instead. + * + * This routine is the second half of SynthAssignRegion(). + * If the region was successfully found by SynthFindRegionIndex(), + * then assign the region's parameters to the voice. + * + * Setup and initialize the following voice parameters: + * m_nRegionIndex + * + * Inputs: + * pVoice - ptr to the voice we have assigned for this channel + * nRegionIndex - index of the region + * psEASData - pointer to overall EAS data structure + * + * Outputs: + * success - could find and assign the region for this voice's note otherwise + * failure - could not find nor assign the region for this voice's note + * + * Side Effects: + * psSynthObject->m_sVoice[].m_nRegionIndex is assigned + * psSynthObject->m_sVoice[] parameters are assigned + *---------------------------------------------------------------------------- +*/ +static EAS_RESULT FM_StartVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum, EAS_U16 regionIndex) +{ + S_FM_VOICE *pFMVoice; + S_SYNTH_CHANNEL *pChannel; + const S_FM_REGION *pRegion; + EAS_I32 temp; + EAS_INT operIndex; + + /* establish pointers to data */ + pVoice->regionIndex = regionIndex; + pFMVoice = GetFMVoicePtr(pVoiceMgr, voiceNum); + pChannel = GetChannelPtr(pSynth, pVoice); + pRegion = GetFMRegionPtr(pSynth, pVoice); + + /* update static channel parameters */ + if (pChannel->channelFlags & CHANNEL_FLAG_UPDATE_CHANNEL_PARAMETERS) + FM_UpdateChannel(pVoiceMgr, pSynth, pVoice->channel & 15); + + /* init the LFO */ + pFMVoice->lfoValue = 0; + pFMVoice->lfoPhase = 0; + pFMVoice->lfoDelay = (EAS_U16) (fmScaleTable[pRegion->lfoFreqDelay & 0x0f] >> 1); + +#if (NUM_OUTPUT_CHANNELS == 2) + /* calculate pan gain values only if stereo output */ + /* set up panning only at note start */ + temp = (EAS_I32) pChannel->pan - 64; + temp += (EAS_I32) pRegion->pan; + if (temp < -64) + temp = -64; + if (temp > 64) + temp = 64; + pFMVoice->pan = (EAS_I8) temp; +#endif /* #if (NUM_OUTPUT_CHANNELS == 2) */ + + /* no samples have been synthesized for this note yet */ + pVoice->voiceFlags = VOICE_FLAG_NO_SAMPLES_SYNTHESIZED_YET; + + /* initialize gain value for anti-zipper filter */ + pFMVoice->voiceGain = (EAS_I16) EAS_LogToLinear16(pChannel->staticGain); + pFMVoice->voiceGain = (EAS_I16) FMUL_15x15(pFMVoice->voiceGain, pSynth->masterVolume); + + /* initialize the operators */ + for (operIndex = 0; operIndex < 4; operIndex++) + { + + /* establish operator output gain level */ + /*lint -e{701} */ + pFMVoice->oper[operIndex].outputGain = EAS_LogToLinear16(((EAS_I16) (pRegion->oper[operIndex].gain & 0xfc) - 0xfc) << 7); + + /* check for linear velocity flag */ + /*lint -e{703} */ + if (pRegion->oper[operIndex].flags & FM_OPER_FLAG_LINEAR_VELOCITY) + temp = (EAS_I32) (pVoice->velocity - 127) << 5; + else + temp = (EAS_I32) fmControlTable[pVoice->velocity]; + + /* scale velocity */ + /*lint -e{704} use shift for performance */ + temp = (temp * (EAS_I32)(pRegion->oper[operIndex].velocityRelease & 0xf0)) >> 7; + + /* include key scalar */ + temp -= ((EAS_I32) pVoice->note - KEY_SCALE_PIVOT_POINT) * (EAS_I32) fmScaleTable[pRegion->oper[operIndex].egKeyScale & 0x0f]; + + /* saturate */ + temp = min(temp, 0); + temp = max(temp, -32768); + + /* save static gain parameters */ + pFMVoice->oper[operIndex].baseGain = (EAS_I16) EAS_LogToLinear16(temp); + + /* incorporate key scaling on decay rate */ + pFMVoice->oper[operIndex].envRate = FM_CalcEGRate( + pVoice->note, + fmDecayTable[pRegion->oper[operIndex].attackDecay & 0x0f], + fmScaleTable[pRegion->oper[operIndex].egKeyScale >> 4]); + + /* if zero attack time, max out envelope and jump to decay state */ + if ((pRegion->oper[operIndex].attackDecay & 0xf0) == 0xf0) + { + + /* start out envelope at max */ + pFMVoice->oper[operIndex].envGain = 0x7fff; + + /* set envelope to decay state */ + pFMVoice->oper[operIndex].envState = eFMEnvelopeStateDecay; + } + + /* start envelope at zero and start in attack state */ + else + { + pFMVoice->oper[operIndex].envGain = 0; + pFMVoice->oper[operIndex].envState = eFMEnvelopeStateAttack; + } + } + + return EAS_SUCCESS; +} + +/*---------------------------------------------------------------------------- + * FM_UpdateChannel() + *---------------------------------------------------------------------------- + * Purpose: + * Calculate and assign static channel parameters + * These values only need to be updated if one of the controller values + * for this channel changes. + * Called when CHANNEL_FLAG_UPDATE_CHANNEL_PARAMETERS flag is set. + * + * Inputs: + * nChannel - channel to update + * psEASData - pointer to overall EAS data structure + * + * Outputs: + * + * Side Effects: + * - the given channel's static gain and static pitch are updated + *---------------------------------------------------------------------------- +*/ +/*lint -esym(715, pVoiceMgr) standard interface, pVoiceMgr not used */ +static void FM_UpdateChannel (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, EAS_U8 channel) +{ + S_SYNTH_CHANNEL *pChannel; + EAS_I32 temp; + + pChannel = &pSynth->channels[channel]; + + /* convert CC7 volume controller to log scale */ + temp = fmControlTable[pChannel->volume]; + + /* incorporate CC11 expression controller */ + temp += fmControlTable[pChannel->expression]; + + /* saturate */ + pChannel->staticGain = (EAS_I16) max(temp,-32768); + + /* calculate pitch bend */ + /*lint -e{703} */ + temp = (((EAS_I32)(pChannel->pitchBend) << 2) - 32768); + + temp = FMUL_15x15(temp, pChannel->pitchBendSensitivity); + + /* include "magic number" compensation for sample rate and lookup table size */ + temp += MAGIC_NUMBER; + + /* if this is not a drum channel, then add in the per-channel tuning */ + if (!(pChannel->channelFlags & CHANNEL_FLAG_RHYTHM_CHANNEL)) + temp += (pChannel->finePitch + (pChannel->coarsePitch * 100)); + + /* save static pitch */ + pChannel->staticPitch = temp; + + /* Calculate LFO modulation depth */ + /* mod wheel to LFO depth */ + temp = FMUL_15x15(DEFAULT_LFO_MOD_WHEEL_TO_PITCH_CENTS, + pChannel->modWheel << (NUM_EG1_FRAC_BITS -7)); + + /* channel pressure to LFO depth */ + pChannel->lfoAmt = (EAS_I16) (temp + + FMUL_15x15(DEFAULT_LFO_CHANNEL_PRESSURE_TO_PITCH_CENTS, + pChannel->channelPressure << (NUM_EG1_FRAC_BITS -7))); + + /* clear update flag */ + pChannel->channelFlags &= ~CHANNEL_FLAG_UPDATE_CHANNEL_PARAMETERS; + return; +} + +/*---------------------------------------------------------------------------- + * FM_UpdateLFO() + *---------------------------------------------------------------------------- + * Purpose: + * Calculate the LFO for the given voice + * + * Inputs: + * pVoice - ptr to the voice whose LFO we want to update + * psEASData - pointer to overall EAS data structure - used for debug only + * + * Outputs: + * + * Side Effects: + * - updates LFO values for the given voice + *---------------------------------------------------------------------------- +*/ +static void FM_UpdateLFO (S_FM_VOICE *pFMVoice, const S_FM_REGION *pRegion) +{ + + /* increment the LFO phase if the delay time has elapsed */ + if (!pFMVoice->lfoDelay) + { + /*lint -e{701} */ + pFMVoice->lfoPhase = pFMVoice->lfoPhase + (EAS_U16) (-fmControlTable[((15 - (pRegion->lfoFreqDelay >> 4)) << 3) + 4]); + + /* square wave LFO? */ + if (pRegion->region.keyGroupAndFlags & REGION_FLAG_SQUARE_WAVE) + pFMVoice->lfoValue = (EAS_I16)(pFMVoice->lfoPhase & 0x8000 ? -32767 : 32767); + + /* trick to get a triangle wave out of a sawtooth */ + else + { + pFMVoice->lfoValue = (EAS_I16) (pFMVoice->lfoPhase << 1); + /*lint -e{502} */ + if ((pFMVoice->lfoPhase > 0x3fff) && (pFMVoice->lfoPhase < 0xC000)) + pFMVoice->lfoValue = ~pFMVoice->lfoValue; + } + } + + /* still in delay */ + else + pFMVoice->lfoDelay--; + + return; +} + +/*---------------------------------------------------------------------------- + * FM_UpdateEG() + *---------------------------------------------------------------------------- + * Purpose: + * Calculate the synthesis parameters for an operator to be used during + * the next buffer + * + * Inputs: + * pVoice - pointer to the voice being updated + * psEASData - pointer to overall EAS data structure + * + * Outputs: + * + * Side Effects: + * + *---------------------------------------------------------------------------- +*/ +static EAS_BOOL FM_UpdateEG (S_SYNTH_VOICE *pVoice, S_OPERATOR *pOper, const S_FM_OPER *pOperData, EAS_BOOL oneShot) +{ + EAS_U32 temp; + EAS_BOOL done; + + /* set flag assuming the envelope is not done */ + done = EAS_FALSE; + + /* take appropriate action based on state */ + switch (pOper->envState) + { + + case eFMEnvelopeStateAttack: + + /* the envelope is linear during the attack, so add the value */ + temp = pOper->envGain + fmAttackTable[pOperData->attackDecay >> 4]; + + /* check for end of attack */ + if (temp >= 0x7fff) + { + pOper->envGain = 0x7fff; + pOper->envState = eFMEnvelopeStateDecay; + } + else + pOper->envGain = (EAS_U16) temp; + break; + + case eFMEnvelopeStateDecay: + + /* decay is exponential, multiply by decay rate */ + pOper->envGain = (EAS_U16) FMUL_15x15(pOper->envGain, pOper->envRate); + + /* check for sustain level reached */ + temp = (EAS_U32) (pOperData->sustain & 0xfc) << 7; + if (pOper->envGain <= (EAS_U16) temp) + { + /* if this is a one-shot patch, go directly to release phase */ + if (oneShot) + { + pOper->envRate = FM_CalcEGRate( + pVoice->note, + fmReleaseTable[pOperData->velocityRelease & 0x0f], + fmScaleTable[pOperData->egKeyScale >> 4]); + pOper->envState = eFMEnvelopeStateRelease; + } + + /* normal sustaining type */ + else + { + pOper->envGain = (EAS_U16) temp; + pOper->envState = eFMEnvelopeStateSustain; + } + } + break; + + case eFMEnvelopeStateSustain: + pOper->envGain = (EAS_U16)((EAS_U16)(pOperData->sustain & 0xfc) << 7); + break; + + case eFMEnvelopeStateRelease: + + /* release is exponential, multiply by release rate */ + pOper->envGain = (EAS_U16) FMUL_15x15(pOper->envGain, pOper->envRate); + + /* fully released */ + if (pOper->envGain == 0) + { + pOper->envGain = 0; + pOper->envState = eFMEnvelopeStateMuted; + done = EAS_TRUE; + } + break; + + case eFMEnvelopeStateMuted: + pOper->envGain = 0; + done = EAS_TRUE; + break; + default: + { /* dpp: EAS_ReportEx(_EAS_SEVERITY_FATAL,"Invalid operator state: %d", pOper->envState); */ } + } /* end switch (pOper->m_eState) */ + + return done; +} + +/*---------------------------------------------------------------------------- + * FM_UpdateDynamic() + *---------------------------------------------------------------------------- + * Purpose: + * Calculate the synthesis parameters for this voice that will be used to + * synthesize the next buffer + * + * Inputs: + * pVoice - pointer to the voice being updated + * psEASData - pointer to overall EAS data structure + * + * Outputs: + * Returns EAS_TRUE if voice will be fully ramped to zero at the end of + * the next synthesized buffer. + * + * Side Effects: + * + *---------------------------------------------------------------------------- +*/ +static EAS_BOOL FM_UpdateDynamic (S_SYNTH_VOICE *pVoice, S_FM_VOICE *pFMVoice, const S_FM_REGION *pRegion, S_SYNTH_CHANNEL *pChannel) +{ + EAS_I32 temp; + EAS_I32 pitch; + EAS_I32 lfoPitch; + EAS_INT operIndex; + EAS_BOOL done; + + /* increment LFO phase */ + FM_UpdateLFO(pFMVoice, pRegion); + + /* base pitch in cents */ + pitch = pVoice->note * 100; + + /* LFO amount includes LFO depth from programming + channel dynamics */ + temp = (fmScaleTable[pRegion->vibTrem >> 4] >> 1) + pChannel->lfoAmt; + + /* multiply by LFO output to get final pitch modulation */ + lfoPitch = FMUL_15x15(pFMVoice->lfoValue, temp); + + /* flag to indicate this voice is done */ + done = EAS_TRUE; + + /* iterate through operators to establish parameters */ + for (operIndex = 0; operIndex < 4; operIndex++) + { + EAS_BOOL bTemp; + + /* set base phase increment for each operator */ + temp = pRegion->oper[operIndex].tuning + + pChannel->staticPitch; + + /* add vibrato effect unless it is disabled for this operator */ + if ((pRegion->oper[operIndex].flags & FM_OPER_FLAG_NO_VIBRATO) == 0) + temp += lfoPitch; + + /* if note is monotonic, bias to MIDI note 60 */ + if (pRegion->oper[operIndex].flags & FM_OPER_FLAG_MONOTONE) + temp += 6000; + else + temp += pitch; + pFMVoice->oper[operIndex].pitch = (EAS_I16) temp; + + /* calculate envelope, returns true if done */ + bTemp = FM_UpdateEG(pVoice, &pFMVoice->oper[operIndex], &pRegion->oper[operIndex], pRegion->region.keyGroupAndFlags & REGION_FLAG_ONE_SHOT); + + /* check if all output envelopes have completed */ + if (FM_SynthIsOutputOperator(pRegion, operIndex)) + done = done && bTemp; + } + + return done; +} + +/*---------------------------------------------------------------------------- + * FM_UpdateVoice() + *---------------------------------------------------------------------------- + * Purpose: + * Synthesize a block of samples for the given voice. + * + * Inputs: + * psEASData - pointer to overall EAS data structure + * + * Outputs: + * number of samples actually written to buffer + * + * Side Effects: + * - samples are added to the presently free buffer + * + *---------------------------------------------------------------------------- +*/ +static EAS_BOOL FM_UpdateVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum, EAS_I32 *pMixBuffer, EAS_I32 numSamples) +{ + S_SYNTH_CHANNEL *pChannel; + const S_FM_REGION *pRegion; + S_FM_VOICE *pFMVoice; + S_FM_VOICE_CONFIG vCfg; + S_FM_VOICE_FRAME vFrame; + EAS_I32 temp; + EAS_INT oper; + EAS_U16 voiceGainTarget; + EAS_BOOL done; + + /* setup some pointers */ + pChannel = GetChannelPtr(pSynth, pVoice); + pRegion = GetFMRegionPtr(pSynth, pVoice); + pFMVoice = GetFMVoicePtr(pVoiceMgr, voiceNum); + + /* if the voice is just starting, get the voice configuration data */ + if (pVoice->voiceFlags & VOICE_FLAG_NO_SAMPLES_SYNTHESIZED_YET) + { + + /* split architecture may limit the number of voice starts */ +#ifdef MAX_VOICE_STARTS + if (pVoiceMgr->numVoiceStarts == MAX_VOICE_STARTS) + return EAS_FALSE; + pVoiceMgr->numVoiceStarts++; +#endif + + /* get initial parameters */ + vCfg.feedback = pRegion->feedback; + vCfg.voiceGain = (EAS_U16) pFMVoice->voiceGain; + +#if (NUM_OUTPUT_CHANNELS == 2) + vCfg.pan = pFMVoice->pan; +#endif + + /* get voice mode */ + vCfg.flags = pRegion->region.keyGroupAndFlags & 7; + + /* get operator parameters */ + for (oper = 0; oper < 4; oper++) + { + /* calculate initial gain */ + vCfg.gain[oper] = (EAS_U16) FMUL_15x15(pFMVoice->oper[oper].baseGain, pFMVoice->oper[oper].envGain); + vCfg.outputGain[oper] = pFMVoice->oper[oper].outputGain; + + /* copy noise waveform flag */ + if (pRegion->oper[oper].flags & FM_OPER_FLAG_NOISE) + vCfg.flags |= (EAS_U8) (FLAG_FM_ENG_VOICE_OP1_NOISE << oper); + } + +#ifdef FM_OFFBOARD + FM_ConfigVoice(voiceNum, &vCfg, pVoiceMgr->pFrameBuffer); +#else + FM_ConfigVoice(voiceNum, &vCfg, NULL); +#endif + + /* clear startup flag */ + pVoice->voiceFlags &= ~VOICE_FLAG_NO_SAMPLES_SYNTHESIZED_YET; + } + + /* calculate new synthesis parameters */ + done = FM_UpdateDynamic(pVoice, pFMVoice, pRegion, pChannel); + + /* calculate LFO gain modulation */ + /*lint -e{702} */ + temp = ((fmScaleTable[pRegion->vibTrem & 0x0f] >> 1) * pFMVoice->lfoValue) >> FM_LFO_GAIN_SHIFT; + + /* include channel gain */ + temp += pChannel->staticGain; + + /* -32768 or lower is infinite attenuation */ + if (temp < -32767) + voiceGainTarget = 0; + + /* translate to linear gain multiplier */ + else + voiceGainTarget = EAS_LogToLinear16(temp); + + /* include synth master volume */ + voiceGainTarget = (EAS_U16) FMUL_15x15(voiceGainTarget, pSynth->masterVolume); + + /* save target values for this frame */ + vFrame.voiceGain = voiceGainTarget; + + /* assume voice output is zero */ + pVoice->gain = 0; + + /* save operator targets for this frame */ + for (oper = 0; oper < 4; oper++) + { + vFrame.gain[oper] = (EAS_U16) FMUL_15x15(pFMVoice->oper[oper].baseGain, pFMVoice->oper[oper].envGain); + vFrame.pitch[oper] = pFMVoice->oper[oper].pitch; + + /* use the highest output envelope level as the gain for the voice stealing algorithm */ + if (FM_SynthIsOutputOperator(pRegion, oper)) + pVoice->gain = max(pVoice->gain, (EAS_I16) vFrame.gain[oper]); + } + + /* consider voice gain multiplier in calculating gain for stealing algorithm */ + pVoice->gain = (EAS_I16) FMUL_15x15(voiceGainTarget, pVoice->gain); + + /* synthesize samples */ +#ifdef FM_OFFBOARD + FM_ProcessVoice(voiceNum, &vFrame, numSamples, pVoiceMgr->operMixBuffer, pVoiceMgr->voiceBuffer, pMixBuffer, pVoiceMgr->pFrameBuffer); +#else + FM_ProcessVoice(voiceNum, &vFrame, numSamples, pVoiceMgr->operMixBuffer, pVoiceMgr->voiceBuffer, pMixBuffer, NULL); +#endif + + return done; +} + -- cgit v1.2.3