1/*----------------------------------------------------------------------------
2 *
3 * File:
4 * fmsynth.c
5 *
6 * Contents and purpose:
7 * Implements the high-level FM synthesizer functions.
8 *
9 * Copyright Sonic Network Inc. 2004
10
11 * Licensed under the Apache License, Version 2.0 (the "License");
12 * you may not use this file except in compliance with the License.
13 * You may obtain a copy of the License at
14 *
15 *      http://www.apache.org/licenses/LICENSE-2.0
16 *
17 * Unless required by applicable law or agreed to in writing, software
18 * distributed under the License is distributed on an "AS IS" BASIS,
19 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 * See the License for the specific language governing permissions and
21 * limitations under the License.
22 *
23 *----------------------------------------------------------------------------
24 * Revision Control:
25 *   $Revision: 795 $
26 *   $Date: 2007-08-01 00:14:45 -0700 (Wed, 01 Aug 2007) $
27 *----------------------------------------------------------------------------
28*/
29
30// includes
31#include "eas_host.h"
32#include "eas_report.h"
33
34#include "eas_data.h"
35#include "eas_synth_protos.h"
36#include "eas_audioconst.h"
37#include "eas_fmengine.h"
38#include "eas_math.h"
39
40/* option sanity check */
41#ifdef _REVERB
42#error "No reverb for FM synthesizer"
43#endif
44#ifdef _CHORUS
45#error "No chorus for FM synthesizer"
46#endif
47
48/*
49 * WARNING: These macros can cause unwanted side effects. Use them
50 * with care. For example, min(x++,y++) will cause either x or y to be
51 * incremented twice.
52 */
53#define min(a,b) ((a) < (b) ? (a) : (b))
54#define max(a,b) ((a) > (b) ? (a) : (b))
55
56/* pivot point for keyboard scalars */
57#define EG_SCALE_PIVOT_POINT 64
58#define KEY_SCALE_PIVOT_POINT 36
59
60/* This number is the negative of the frequency of the note (in cents) of
61 * the sine wave played at unity. The number can be calculated as follows:
62 *
63 * MAGIC_NUMBER = 1200 * log(base2) (SINE_TABLE_SIZE * 8.175798916 / SAMPLE_RATE)
64 *
65 * 8.17578 is a reference to the frequency of MIDI note 0
66 */
67#if defined (_SAMPLE_RATE_8000)
68#define MAGIC_NUMBER 1279
69#elif   defined (_SAMPLE_RATE_16000)
70#define MAGIC_NUMBER 79
71#elif   defined (_SAMPLE_RATE_20000)
72#define MAGIC_NUMBER -308
73#elif   defined (_SAMPLE_RATE_22050)
74#define MAGIC_NUMBER -477
75#elif   defined (_SAMPLE_RATE_24000)
76#define MAGIC_NUMBER -623
77#elif defined (_SAMPLE_RATE_32000)
78#define MAGIC_NUMBER -1121
79#elif defined (_SAMPLE_RATE_44100)
80#define MAGIC_NUMBER -1677
81#elif defined (_SAMPLE_RATE_48000)
82#define MAGIC_NUMBER -1823
83#endif
84
85/* externs */
86extern const EAS_I16 fmControlTable[128];
87extern const EAS_U16 fmRateTable[256];
88extern const EAS_U16 fmAttackTable[16];
89extern const EAS_U8 fmDecayTable[16];
90extern const EAS_U8 fmReleaseTable[16];
91extern const EAS_U8 fmScaleTable[16];
92
93/* local prototypes */
94/*lint -esym(715, pVoiceMgr) standard synthesizer interface - pVoiceMgr not used */
95static EAS_RESULT FM_Initialize (S_VOICE_MGR *pVoiceMgr) { return EAS_SUCCESS; }
96static EAS_RESULT FM_StartVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum, EAS_U16 regionIndex);
97static EAS_BOOL FM_UpdateVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum, EAS_I32 *pMixBuffer, EAS_I32 numSamples);
98static void FM_ReleaseVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum);
99static void FM_MuteVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum);
100static void FM_SustainPedal (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, S_SYNTH_CHANNEL *pChannel, EAS_I32 voiceNum);
101static void FM_UpdateChannel (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, EAS_U8 channel);
102
103
104/*----------------------------------------------------------------------------
105 * Synthesizer interface
106 *----------------------------------------------------------------------------
107*/
108const S_SYNTH_INTERFACE fmSynth =
109{
110    FM_Initialize,
111    FM_StartVoice,
112    FM_UpdateVoice,
113    FM_ReleaseVoice,
114    FM_MuteVoice,
115    FM_SustainPedal,
116    FM_UpdateChannel
117};
118
119#ifdef FM_OFFBOARD
120const S_FRAME_INTERFACE fmFrameInterface =
121{
122    FM_StartFrame,
123    FM_EndFrame
124};
125#endif
126
127/*----------------------------------------------------------------------------
128 * inline functions
129 *----------------------------------------------------------------------------
130 */
131EAS_INLINE S_FM_VOICE *GetFMVoicePtr (S_VOICE_MGR *pVoiceMgr, EAS_INT voiceNum)
132{
133    return &pVoiceMgr->fmVoices[voiceNum];
134}
135EAS_INLINE S_SYNTH_CHANNEL *GetChannelPtr (S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice)
136{
137    return &pSynth->channels[pVoice->channel & 15];
138}
139EAS_INLINE const S_FM_REGION *GetFMRegionPtr (S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice)
140{
141#ifdef _SECONDARY_SYNTH
142    return &pSynth->pEAS->pFMRegions[pVoice->regionIndex & REGION_INDEX_MASK];
143#else
144    return &pSynth->pEAS->pFMRegions[pVoice->regionIndex];
145#endif
146}
147
148/*----------------------------------------------------------------------------
149 * FM_SynthIsOutputOperator
150 *----------------------------------------------------------------------------
151 * Purpose:
152 * Returns true if the operator is a direct output and not muted
153 *
154 * Inputs:
155 *
156 * Outputs:
157 * Returns boolean
158 *----------------------------------------------------------------------------
159*/
160static EAS_BOOL FM_SynthIsOutputOperator (const S_FM_REGION *pRegion, EAS_INT operIndex)
161{
162
163    /* see if voice is muted */
164    if ((pRegion->oper[operIndex].gain & 0xfc) == 0)
165        return 0;
166
167    /* check based on mode */
168    switch (pRegion->region.keyGroupAndFlags & 7)
169    {
170
171        /* mode 0 - all operators are external */
172        case 0:
173            return EAS_TRUE;
174
175        /* mode 1 - operators 1-3 are external */
176        case 1:
177            if (operIndex != 0)
178                return EAS_TRUE;
179        break;
180
181        /* mode 2 - operators 1 & 3 are external */
182        case 2:
183            if ((operIndex == 1) || (operIndex == 3))
184                return EAS_TRUE;
185            break;
186
187        /* mode 2 - operators 1 & 2 are external */
188        case 3:
189            if ((operIndex == 1) || (operIndex == 2))
190                return EAS_TRUE;
191            break;
192
193        /* modes 4 & 5 - operator 1 is external */
194        case 4:
195        case 5:
196            if (operIndex == 1)
197                return EAS_TRUE;
198            break;
199
200        default:
201            { /* dpp: EAS_ReportEx(_EAS_SEVERITY_FATAL,"Invalid voice mode: %d",
202                pRegion->region.keyGroupAndFlags & 7); */ }
203    }
204
205    return EAS_FALSE;
206}
207
208/*----------------------------------------------------------------------------
209 * FM_CalcEGRate()
210 *----------------------------------------------------------------------------
211 * Purpose:
212 *
213 * Inputs:
214 * nKeyNumber - MIDI note
215 * nLogRate - logarithmic scale rate from patch data
216 * nKeyScale - key scaling factor for this EG
217 *
218 * Outputs:
219 * 16-bit linear multiplier
220 *----------------------------------------------------------------------------
221*/
222
223static EAS_U16 FM_CalcEGRate (EAS_U8 nKeyNumber, EAS_U8 nLogRate, EAS_U8 nEGScale)
224{
225    EAS_I32 temp;
226
227    /* incorporate key scaling on release rate */
228    temp = (EAS_I32) nLogRate << 7;
229    temp += ((EAS_I32) nKeyNumber - EG_SCALE_PIVOT_POINT) * (EAS_I32) nEGScale;
230
231    /* saturate */
232    temp = max(temp, 0);
233    temp = min(temp, 32767);
234
235    /* look up in rate table */
236    /*lint -e{704} use shift for performance */
237    return fmRateTable[temp >> 8];
238}
239
240/*----------------------------------------------------------------------------
241 * FM_ReleaseVoice()
242 *----------------------------------------------------------------------------
243 * Purpose:
244 * The selected voice is being released.
245 *
246 * Inputs:
247 * psEASData - pointer to S_EAS_DATA
248 * pVoice - pointer to voice to release
249 *
250 * Outputs:
251 * None
252 *----------------------------------------------------------------------------
253*/
254static void FM_ReleaseVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum)
255{
256    EAS_INT operIndex;
257    const S_FM_REGION *pRegion;
258    S_FM_VOICE *pFMVoice;
259
260    /* check to see if voice responds to NOTE-OFF's */
261    pRegion = GetFMRegionPtr(pSynth, pVoice);
262    if (pRegion->region.keyGroupAndFlags & REGION_FLAG_ONE_SHOT)
263        return;
264
265    /* set all envelopes to release state */
266    pFMVoice = GetFMVoicePtr(pVoiceMgr, voiceNum);
267    for (operIndex = 0; operIndex < 4; operIndex++)
268    {
269        pFMVoice->oper[operIndex].envState = eFMEnvelopeStateRelease;
270
271        /* incorporate key scaling on release rate */
272        pFMVoice->oper[operIndex].envRate = FM_CalcEGRate(
273                pVoice->note,
274                fmReleaseTable[pRegion->oper[operIndex].velocityRelease & 0x0f],
275                fmScaleTable[pRegion->oper[operIndex].egKeyScale >> 4]);
276    } /* end for (operIndex = 0; operIndex < 4; operIndex++) */
277}
278
279/*----------------------------------------------------------------------------
280 * FM_MuteVoice()
281 *----------------------------------------------------------------------------
282 * Purpose:
283 * The selected voice is being muted.
284 *
285 * Inputs:
286 * pVoice - pointer to voice to release
287 *
288 * Outputs:
289 * None
290 *----------------------------------------------------------------------------
291*/
292/*lint -esym(715, pSynth) standard interface, pVoiceMgr not used */
293static void FM_MuteVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum)
294{
295    S_FM_VOICE *pFMVoice;
296
297    /* clear deferred action flags */
298    pVoice->voiceFlags &=
299        ~(VOICE_FLAG_DEFER_MIDI_NOTE_OFF |
300        VOICE_FLAG_SUSTAIN_PEDAL_DEFER_NOTE_OFF |
301        VOICE_FLAG_DEFER_MUTE);
302
303    /* set all envelopes to muted state */
304    pFMVoice = GetFMVoicePtr(pVoiceMgr, voiceNum);
305    pFMVoice->oper[0].envState = eFMEnvelopeStateMuted;
306    pFMVoice->oper[1].envState = eFMEnvelopeStateMuted;
307    pFMVoice->oper[2].envState = eFMEnvelopeStateMuted;
308    pFMVoice->oper[3].envState = eFMEnvelopeStateMuted;
309}
310
311/*----------------------------------------------------------------------------
312 * FM_SustainPedal()
313 *----------------------------------------------------------------------------
314 * Purpose:
315 * The selected voice is held due to sustain pedal
316 *
317 * Inputs:
318 * pVoice - pointer to voice to sustain
319 *
320 * Outputs:
321 * None
322 *----------------------------------------------------------------------------
323*/
324/*lint -esym(715, pChannel) standard interface, pVoiceMgr not used */
325static void FM_SustainPedal (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, S_SYNTH_CHANNEL *pChannel, EAS_I32 voiceNum)
326{
327    const S_FM_REGION *pRegion;
328    S_FM_VOICE *pFMVoice;
329    EAS_INT operIndex;
330
331    pRegion = GetFMRegionPtr(pSynth, pVoice);
332    pFMVoice = GetFMVoicePtr(pVoiceMgr, voiceNum);
333
334    /* check to see if any envelopes are above the sustain level */
335    for (operIndex = 0; operIndex < 4; operIndex++)
336    {
337
338        /* if level control or envelope gain is zero, skip this envelope */
339        if (((pRegion->oper[operIndex].gain & 0xfc) == 0) ||
340            (pFMVoice->oper[operIndex].envGain == 0))
341        {
342            continue;
343        }
344
345        /* if the envelope gain is above the sustain level, we need to catch this voice */
346        if (pFMVoice->oper[operIndex].envGain >= ((EAS_U16) (pRegion->oper[operIndex].sustain & 0xfc) << 7))
347        {
348
349            /* reset envelope to decay state */
350            pFMVoice->oper[operIndex].envState = eFMEnvelopeStateDecay;
351
352            pFMVoice->oper[operIndex].envRate = FM_CalcEGRate(
353                    pVoice->note,
354                    fmDecayTable[pRegion->oper[operIndex].attackDecay & 0x0f],
355                    fmScaleTable[pRegion->oper[operIndex].egKeyScale >> 4]);
356
357            /* set voice to decay state */
358            pVoice->voiceState = eVoiceStatePlay;
359
360            /* set sustain flag */
361            pVoice->voiceFlags |= VOICE_FLAG_SUSTAIN_PEDAL_DEFER_NOTE_OFF;
362        }
363    } /* end for (operIndex = 0; operIndex < 4; operIndex++) */
364}
365
366/*----------------------------------------------------------------------------
367 * FM_StartVoice()
368 *----------------------------------------------------------------------------
369 * Purpose:
370 * Assign the region for the given instrument using the midi key number
371 * and the RPN2 (coarse tuning) value. By using RPN2 as part of the
372 * region selection process, we reduce the amount a given sample has
373 * to be transposed by selecting the closest recorded root instead.
374 *
375 * This routine is the second half of SynthAssignRegion().
376 * If the region was successfully found by SynthFindRegionIndex(),
377 * then assign the region's parameters to the voice.
378 *
379 * Setup and initialize the following voice parameters:
380 * m_nRegionIndex
381 *
382 * Inputs:
383 * pVoice - ptr to the voice we have assigned for this channel
384 * nRegionIndex - index of the region
385 * psEASData - pointer to overall EAS data structure
386 *
387 * Outputs:
388 * success - could find and assign the region for this voice's note otherwise
389 * failure - could not find nor assign the region for this voice's note
390 *
391 * Side Effects:
392 * psSynthObject->m_sVoice[].m_nRegionIndex is assigned
393 * psSynthObject->m_sVoice[] parameters are assigned
394 *----------------------------------------------------------------------------
395*/
396static EAS_RESULT FM_StartVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum, EAS_U16 regionIndex)
397{
398    S_FM_VOICE *pFMVoice;
399    S_SYNTH_CHANNEL *pChannel;
400    const S_FM_REGION *pRegion;
401    EAS_I32 temp;
402    EAS_INT operIndex;
403
404    /* establish pointers to data */
405    pVoice->regionIndex = regionIndex;
406    pFMVoice = GetFMVoicePtr(pVoiceMgr, voiceNum);
407    pChannel = GetChannelPtr(pSynth, pVoice);
408    pRegion = GetFMRegionPtr(pSynth, pVoice);
409
410    /* update static channel parameters */
411    if (pChannel->channelFlags & CHANNEL_FLAG_UPDATE_CHANNEL_PARAMETERS)
412        FM_UpdateChannel(pVoiceMgr, pSynth, pVoice->channel & 15);
413
414    /* init the LFO */
415    pFMVoice->lfoValue = 0;
416    pFMVoice->lfoPhase = 0;
417    pFMVoice->lfoDelay = (EAS_U16) (fmScaleTable[pRegion->lfoFreqDelay & 0x0f] >> 1);
418
419#if (NUM_OUTPUT_CHANNELS == 2)
420    /* calculate pan gain values only if stereo output */
421    /* set up panning only at note start */
422    temp = (EAS_I32) pChannel->pan - 64;
423    temp += (EAS_I32) pRegion->pan;
424    if (temp < -64)
425        temp = -64;
426    if (temp > 64)
427        temp = 64;
428    pFMVoice->pan = (EAS_I8) temp;
429#endif /* #if (NUM_OUTPUT_CHANNELS == 2) */
430
431    /* no samples have been synthesized for this note yet */
432    pVoice->voiceFlags = VOICE_FLAG_NO_SAMPLES_SYNTHESIZED_YET;
433
434    /* initialize gain value for anti-zipper filter */
435    pFMVoice->voiceGain = (EAS_I16) EAS_LogToLinear16(pChannel->staticGain);
436    pFMVoice->voiceGain = (EAS_I16) FMUL_15x15(pFMVoice->voiceGain, pSynth->masterVolume);
437
438    /* initialize the operators */
439    for (operIndex = 0; operIndex < 4; operIndex++)
440    {
441
442        /* establish operator output gain level */
443        /*lint -e{701} <use shift for performance> */
444        pFMVoice->oper[operIndex].outputGain = EAS_LogToLinear16(((EAS_I16) (pRegion->oper[operIndex].gain & 0xfc) - 0xfc) << 7);
445
446        /* check for linear velocity flag */
447        /*lint -e{703} <use shift for performance> */
448        if (pRegion->oper[operIndex].flags & FM_OPER_FLAG_LINEAR_VELOCITY)
449            temp = (EAS_I32) (pVoice->velocity - 127) << 5;
450        else
451            temp = (EAS_I32) fmControlTable[pVoice->velocity];
452
453        /* scale velocity */
454        /*lint -e{704} use shift for performance */
455        temp = (temp * (EAS_I32)(pRegion->oper[operIndex].velocityRelease & 0xf0)) >> 7;
456
457        /* include key scalar */
458        temp -= ((EAS_I32) pVoice->note - KEY_SCALE_PIVOT_POINT) * (EAS_I32) fmScaleTable[pRegion->oper[operIndex].egKeyScale & 0x0f];
459
460        /* saturate */
461        temp = min(temp, 0);
462        temp = max(temp, -32768);
463
464        /* save static gain parameters */
465        pFMVoice->oper[operIndex].baseGain = (EAS_I16) EAS_LogToLinear16(temp);
466
467        /* incorporate key scaling on decay rate */
468        pFMVoice->oper[operIndex].envRate = FM_CalcEGRate(
469            pVoice->note,
470            fmDecayTable[pRegion->oper[operIndex].attackDecay & 0x0f],
471            fmScaleTable[pRegion->oper[operIndex].egKeyScale >> 4]);
472
473        /* if zero attack time, max out envelope and jump to decay state */
474        if ((pRegion->oper[operIndex].attackDecay & 0xf0) == 0xf0)
475        {
476
477            /* start out envelope at max */
478            pFMVoice->oper[operIndex].envGain = 0x7fff;
479
480            /* set envelope to decay state */
481            pFMVoice->oper[operIndex].envState = eFMEnvelopeStateDecay;
482        }
483
484        /* start envelope at zero and start in attack state */
485        else
486        {
487            pFMVoice->oper[operIndex].envGain = 0;
488            pFMVoice->oper[operIndex].envState = eFMEnvelopeStateAttack;
489        }
490    }
491
492    return EAS_SUCCESS;
493}
494
495/*----------------------------------------------------------------------------
496 * FM_UpdateChannel()
497 *----------------------------------------------------------------------------
498 * Purpose:
499 * Calculate and assign static channel parameters
500 * These values only need to be updated if one of the controller values
501 * for this channel changes.
502 * Called when CHANNEL_FLAG_UPDATE_CHANNEL_PARAMETERS flag is set.
503 *
504 * Inputs:
505 * nChannel - channel to update
506 * psEASData - pointer to overall EAS data structure
507 *
508 * Outputs:
509 *
510 * Side Effects:
511 * - the given channel's static gain and static pitch are updated
512 *----------------------------------------------------------------------------
513*/
514/*lint -esym(715, pVoiceMgr) standard interface, pVoiceMgr not used */
515static void FM_UpdateChannel (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, EAS_U8 channel)
516{
517    S_SYNTH_CHANNEL *pChannel;
518    EAS_I32 temp;
519
520    pChannel = &pSynth->channels[channel];
521
522    /* convert CC7 volume controller to log scale */
523    temp = fmControlTable[pChannel->volume];
524
525    /* incorporate CC11 expression controller */
526    temp += fmControlTable[pChannel->expression];
527
528    /* saturate */
529    pChannel->staticGain = (EAS_I16) max(temp,-32768);
530
531    /* calculate pitch bend */
532    /*lint -e{703} <avoid multiply for performance>*/
533    temp = (((EAS_I32)(pChannel->pitchBend) << 2) - 32768);
534
535    temp = FMUL_15x15(temp, pChannel->pitchBendSensitivity);
536
537    /* include "magic number" compensation for sample rate and lookup table size */
538    temp += MAGIC_NUMBER;
539
540    /* if this is not a drum channel, then add in the per-channel tuning */
541    if (!(pChannel->channelFlags & CHANNEL_FLAG_RHYTHM_CHANNEL))
542        temp += (pChannel->finePitch + (pChannel->coarsePitch * 100));
543
544    /* save static pitch */
545    pChannel->staticPitch = temp;
546
547    /* Calculate LFO modulation depth */
548    /* mod wheel to LFO depth */
549    temp = FMUL_15x15(DEFAULT_LFO_MOD_WHEEL_TO_PITCH_CENTS,
550    pChannel->modWheel << (NUM_EG1_FRAC_BITS -7));
551
552    /* channel pressure to LFO depth */
553    pChannel->lfoAmt = (EAS_I16) (temp +
554    FMUL_15x15(DEFAULT_LFO_CHANNEL_PRESSURE_TO_PITCH_CENTS,
555    pChannel->channelPressure << (NUM_EG1_FRAC_BITS -7)));
556
557    /* clear update flag */
558    pChannel->channelFlags &= ~CHANNEL_FLAG_UPDATE_CHANNEL_PARAMETERS;
559    return;
560}
561
562/*----------------------------------------------------------------------------
563 * FM_UpdateLFO()
564 *----------------------------------------------------------------------------
565 * Purpose:
566 * Calculate the LFO for the given voice
567 *
568 * Inputs:
569 * pVoice - ptr to the voice whose LFO we want to update
570 * psEASData - pointer to overall EAS data structure - used for debug only
571 *
572 * Outputs:
573 *
574 * Side Effects:
575 * - updates LFO values for the given voice
576 *----------------------------------------------------------------------------
577*/
578static void FM_UpdateLFO (S_FM_VOICE *pFMVoice, const S_FM_REGION *pRegion)
579{
580
581    /* increment the LFO phase if the delay time has elapsed */
582    if (!pFMVoice->lfoDelay)
583    {
584        /*lint -e{701} <use shift for performance> */
585        pFMVoice->lfoPhase = pFMVoice->lfoPhase + (EAS_U16) (-fmControlTable[((15 - (pRegion->lfoFreqDelay >> 4)) << 3) + 4]);
586
587        /* square wave LFO? */
588        if (pRegion->region.keyGroupAndFlags & REGION_FLAG_SQUARE_WAVE)
589            pFMVoice->lfoValue = (EAS_I16)(pFMVoice->lfoPhase & 0x8000 ? -32767 : 32767);
590
591        /* trick to get a triangle wave out of a sawtooth */
592        else
593        {
594            pFMVoice->lfoValue = (EAS_I16) (pFMVoice->lfoPhase << 1);
595            /*lint -e{502} <shortcut to turn sawtooth into sine wave> */
596            if ((pFMVoice->lfoPhase > 0x3fff) && (pFMVoice->lfoPhase < 0xC000))
597                pFMVoice->lfoValue = ~pFMVoice->lfoValue;
598        }
599    }
600
601    /* still in delay */
602    else
603        pFMVoice->lfoDelay--;
604
605    return;
606}
607
608/*----------------------------------------------------------------------------
609 * FM_UpdateEG()
610 *----------------------------------------------------------------------------
611 * Purpose:
612 * Calculate the synthesis parameters for an operator to be used during
613 * the next buffer
614 *
615 * Inputs:
616 * pVoice - pointer to the voice being updated
617 * psEASData - pointer to overall EAS data structure
618 *
619 * Outputs:
620 *
621 * Side Effects:
622 *
623 *----------------------------------------------------------------------------
624*/
625static EAS_BOOL FM_UpdateEG (S_SYNTH_VOICE *pVoice, S_OPERATOR *pOper, const S_FM_OPER *pOperData, EAS_BOOL oneShot)
626{
627    EAS_U32 temp;
628    EAS_BOOL done;
629
630    /* set flag assuming the envelope is not done */
631    done = EAS_FALSE;
632
633    /* take appropriate action based on state */
634    switch (pOper->envState)
635    {
636
637        case eFMEnvelopeStateAttack:
638
639            /* the envelope is linear during the attack, so add the value */
640            temp = pOper->envGain + fmAttackTable[pOperData->attackDecay >> 4];
641
642            /* check for end of attack */
643            if (temp >= 0x7fff)
644            {
645                pOper->envGain = 0x7fff;
646                pOper->envState = eFMEnvelopeStateDecay;
647            }
648            else
649                pOper->envGain = (EAS_U16) temp;
650            break;
651
652        case eFMEnvelopeStateDecay:
653
654            /* decay is exponential, multiply by decay rate */
655            pOper->envGain = (EAS_U16) FMUL_15x15(pOper->envGain, pOper->envRate);
656
657            /* check for sustain level reached */
658            temp = (EAS_U32) (pOperData->sustain & 0xfc) << 7;
659            if (pOper->envGain <= (EAS_U16) temp)
660            {
661                /* if this is a one-shot patch, go directly to release phase */
662                if (oneShot)
663                {
664                    pOper->envRate = FM_CalcEGRate(
665                    pVoice->note,
666                    fmReleaseTable[pOperData->velocityRelease & 0x0f],
667                    fmScaleTable[pOperData->egKeyScale >> 4]);
668                    pOper->envState = eFMEnvelopeStateRelease;
669                }
670
671                /* normal sustaining type */
672                else
673                {
674                    pOper->envGain = (EAS_U16) temp;
675                    pOper->envState = eFMEnvelopeStateSustain;
676                }
677            }
678            break;
679
680        case eFMEnvelopeStateSustain:
681            pOper->envGain = (EAS_U16)((EAS_U16)(pOperData->sustain & 0xfc) << 7);
682            break;
683
684        case eFMEnvelopeStateRelease:
685
686            /* release is exponential, multiply by release rate */
687            pOper->envGain = (EAS_U16) FMUL_15x15(pOper->envGain, pOper->envRate);
688
689            /* fully released */
690            if (pOper->envGain == 0)
691            {
692                pOper->envGain = 0;
693                pOper->envState = eFMEnvelopeStateMuted;
694                done = EAS_TRUE;
695            }
696            break;
697
698        case eFMEnvelopeStateMuted:
699            pOper->envGain = 0;
700            done = EAS_TRUE;
701            break;
702        default:
703            { /* dpp: EAS_ReportEx(_EAS_SEVERITY_FATAL,"Invalid operator state: %d", pOper->envState); */ }
704    } /* end switch (pOper->m_eState) */
705
706    return done;
707}
708
709/*----------------------------------------------------------------------------
710 * FM_UpdateDynamic()
711 *----------------------------------------------------------------------------
712 * Purpose:
713 * Calculate the synthesis parameters for this voice that will be used to
714 * synthesize the next buffer
715 *
716 * Inputs:
717 * pVoice - pointer to the voice being updated
718 * psEASData - pointer to overall EAS data structure
719 *
720 * Outputs:
721 * Returns EAS_TRUE if voice will be fully ramped to zero at the end of
722 * the next synthesized buffer.
723 *
724 * Side Effects:
725 *
726 *----------------------------------------------------------------------------
727*/
728static EAS_BOOL FM_UpdateDynamic (S_SYNTH_VOICE *pVoice, S_FM_VOICE *pFMVoice, const S_FM_REGION *pRegion, S_SYNTH_CHANNEL *pChannel)
729{
730    EAS_I32 temp;
731    EAS_I32 pitch;
732    EAS_I32 lfoPitch;
733    EAS_INT operIndex;
734    EAS_BOOL done;
735
736    /* increment LFO phase */
737    FM_UpdateLFO(pFMVoice, pRegion);
738
739    /* base pitch in cents */
740    pitch = pVoice->note * 100;
741
742    /* LFO amount includes LFO depth from programming + channel dynamics */
743    temp = (fmScaleTable[pRegion->vibTrem >> 4] >> 1) + pChannel->lfoAmt;
744
745    /* multiply by LFO output to get final pitch modulation */
746    lfoPitch = FMUL_15x15(pFMVoice->lfoValue, temp);
747
748    /* flag to indicate this voice is done */
749    done = EAS_TRUE;
750
751    /* iterate through operators to establish parameters */
752    for (operIndex = 0; operIndex < 4; operIndex++)
753    {
754        EAS_BOOL bTemp;
755
756        /* set base phase increment for each operator */
757        temp = pRegion->oper[operIndex].tuning +
758        pChannel->staticPitch;
759
760        /* add vibrato effect unless it is disabled for this operator */
761        if ((pRegion->oper[operIndex].flags & FM_OPER_FLAG_NO_VIBRATO) == 0)
762            temp += lfoPitch;
763
764        /* if note is monotonic, bias to MIDI note 60 */
765        if (pRegion->oper[operIndex].flags & FM_OPER_FLAG_MONOTONE)
766            temp += 6000;
767        else
768            temp += pitch;
769        pFMVoice->oper[operIndex].pitch = (EAS_I16) temp;
770
771        /* calculate envelope, returns true if done */
772        bTemp = FM_UpdateEG(pVoice, &pFMVoice->oper[operIndex], &pRegion->oper[operIndex], pRegion->region.keyGroupAndFlags & REGION_FLAG_ONE_SHOT);
773
774        /* check if all output envelopes have completed */
775        if (FM_SynthIsOutputOperator(pRegion, operIndex))
776            done = done && bTemp;
777    }
778
779    return done;
780}
781
782/*----------------------------------------------------------------------------
783 * FM_UpdateVoice()
784 *----------------------------------------------------------------------------
785 * Purpose:
786 * Synthesize a block of samples for the given voice.
787 *
788 * Inputs:
789 * psEASData - pointer to overall EAS data structure
790 *
791 * Outputs:
792 * number of samples actually written to buffer
793 *
794 * Side Effects:
795 * - samples are added to the presently free buffer
796 *
797 *----------------------------------------------------------------------------
798*/
799static EAS_BOOL FM_UpdateVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum, EAS_I32 *pMixBuffer, EAS_I32 numSamples)
800{
801    S_SYNTH_CHANNEL *pChannel;
802    const S_FM_REGION *pRegion;
803    S_FM_VOICE *pFMVoice;
804    S_FM_VOICE_CONFIG vCfg;
805    S_FM_VOICE_FRAME vFrame;
806    EAS_I32 temp;
807    EAS_INT oper;
808    EAS_U16 voiceGainTarget;
809    EAS_BOOL done;
810
811    /* setup some pointers */
812    pChannel = GetChannelPtr(pSynth, pVoice);
813    pRegion = GetFMRegionPtr(pSynth, pVoice);
814    pFMVoice = GetFMVoicePtr(pVoiceMgr, voiceNum);
815
816    /* if the voice is just starting, get the voice configuration data */
817    if (pVoice->voiceFlags & VOICE_FLAG_NO_SAMPLES_SYNTHESIZED_YET)
818    {
819
820        /* split architecture may limit the number of voice starts */
821#ifdef MAX_VOICE_STARTS
822        if (pVoiceMgr->numVoiceStarts == MAX_VOICE_STARTS)
823            return EAS_FALSE;
824        pVoiceMgr->numVoiceStarts++;
825#endif
826
827        /* get initial parameters */
828        vCfg.feedback = pRegion->feedback;
829        vCfg.voiceGain = (EAS_U16) pFMVoice->voiceGain;
830
831#if (NUM_OUTPUT_CHANNELS == 2)
832        vCfg.pan = pFMVoice->pan;
833#endif
834
835        /* get voice mode */
836        vCfg.flags = pRegion->region.keyGroupAndFlags & 7;
837
838        /* get operator parameters */
839        for (oper = 0; oper < 4; oper++)
840        {
841            /* calculate initial gain */
842            vCfg.gain[oper] = (EAS_U16) FMUL_15x15(pFMVoice->oper[oper].baseGain, pFMVoice->oper[oper].envGain);
843            vCfg.outputGain[oper] = pFMVoice->oper[oper].outputGain;
844
845            /* copy noise waveform flag */
846            if (pRegion->oper[oper].flags & FM_OPER_FLAG_NOISE)
847                vCfg.flags |= (EAS_U8) (FLAG_FM_ENG_VOICE_OP1_NOISE << oper);
848        }
849
850#ifdef FM_OFFBOARD
851        FM_ConfigVoice(voiceNum, &vCfg, pVoiceMgr->pFrameBuffer);
852#else
853        FM_ConfigVoice(voiceNum, &vCfg, NULL);
854#endif
855
856        /* clear startup flag */
857        pVoice->voiceFlags &= ~VOICE_FLAG_NO_SAMPLES_SYNTHESIZED_YET;
858    }
859
860    /* calculate new synthesis parameters */
861    done = FM_UpdateDynamic(pVoice, pFMVoice, pRegion, pChannel);
862
863    /* calculate LFO gain modulation */
864    /*lint -e{702} <use shift for performance> */
865    temp = ((fmScaleTable[pRegion->vibTrem & 0x0f] >> 1) * pFMVoice->lfoValue) >> FM_LFO_GAIN_SHIFT;
866
867    /* include channel gain */
868    temp += pChannel->staticGain;
869
870    /* -32768 or lower is infinite attenuation */
871    if (temp < -32767)
872        voiceGainTarget = 0;
873
874    /* translate to linear gain multiplier */
875    else
876        voiceGainTarget = EAS_LogToLinear16(temp);
877
878    /* include synth master volume */
879    voiceGainTarget = (EAS_U16) FMUL_15x15(voiceGainTarget, pSynth->masterVolume);
880
881    /* save target values for this frame */
882    vFrame.voiceGain = voiceGainTarget;
883
884    /* assume voice output is zero */
885    pVoice->gain = 0;
886
887    /* save operator targets for this frame */
888    for (oper = 0; oper < 4; oper++)
889    {
890        vFrame.gain[oper] = (EAS_U16) FMUL_15x15(pFMVoice->oper[oper].baseGain, pFMVoice->oper[oper].envGain);
891        vFrame.pitch[oper] = pFMVoice->oper[oper].pitch;
892
893        /* use the highest output envelope level as the gain for the voice stealing algorithm */
894        if (FM_SynthIsOutputOperator(pRegion, oper))
895            pVoice->gain = max(pVoice->gain, (EAS_I16) vFrame.gain[oper]);
896    }
897
898    /* consider voice gain multiplier in calculating gain for stealing algorithm */
899    pVoice->gain = (EAS_I16) FMUL_15x15(voiceGainTarget, pVoice->gain);
900
901    /* synthesize samples */
902#ifdef FM_OFFBOARD
903    FM_ProcessVoice(voiceNum, &vFrame, numSamples, pVoiceMgr->operMixBuffer, pVoiceMgr->voiceBuffer, pMixBuffer, pVoiceMgr->pFrameBuffer);
904#else
905    FM_ProcessVoice(voiceNum, &vFrame, numSamples, pVoiceMgr->operMixBuffer, pVoiceMgr->voiceBuffer, pMixBuffer, NULL);
906#endif
907
908    return done;
909}
910
911