/*---------------------------------------------------------------------------- * * 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. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *---------------------------------------------------------------------------- * Revision Control: * $Revision: 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; }