summaryrefslogtreecommitdiffstats
path: root/arm-hybrid-22k/lib_src/eas_fmsynth.c
diff options
context:
space:
mode:
Diffstat (limited to 'arm-hybrid-22k/lib_src/eas_fmsynth.c')
-rw-r--r--arm-hybrid-22k/lib_src/eas_fmsynth.c1796
1 files changed, 898 insertions, 898 deletions
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} <use shift for performance> */
- pFMVoice->oper[operIndex].outputGain = EAS_LogToLinear16(((EAS_I16) (pRegion->oper[operIndex].gain & 0xfc) - 0xfc) << 7);
-
- /* check for linear velocity flag */
- /*lint -e{703} <use shift for performance> */
- 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} <avoid multiply for performance>*/
- 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} <use shift for performance> */
- 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} <shortcut to turn sawtooth into sine wave> */
- 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} <use shift for performance> */
- 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} <use shift for performance> */
+ pFMVoice->oper[operIndex].outputGain = EAS_LogToLinear16(((EAS_I16) (pRegion->oper[operIndex].gain & 0xfc) - 0xfc) << 7);
+
+ /* check for linear velocity flag */
+ /*lint -e{703} <use shift for performance> */
+ 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} <avoid multiply for performance>*/
+ 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} <use shift for performance> */
+ 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} <shortcut to turn sawtooth into sine wave> */
+ 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} <use shift for performance> */
+ 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;
+}
+