mirror of
https://github.com/id-Software/DOOM-IOS2.git
synced 2026-03-20 08:59:35 +01:00
911 lines
30 KiB
C
Executable File
911 lines
30 KiB
C
Executable File
/*----------------------------------------------------------------------------
|
|
*
|
|
* 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} <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;
|
|
}
|
|
|