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 */
86 extern const EAS_I16 fmControlTable[128];
87 extern const EAS_U16 fmRateTable[256];
88 extern const EAS_U16 fmAttackTable[16];
89 extern const EAS_U8 fmDecayTable[16];
90 extern const EAS_U8 fmReleaseTable[16];
91 extern const EAS_U8 fmScaleTable[16];
92
93 /* local prototypes */
94 /*lint -esym(715, pVoiceMgr) standard synthesizer interface - pVoiceMgr not used */
FM_Initialize(S_VOICE_MGR * pVoiceMgr)95 static EAS_RESULT FM_Initialize (S_VOICE_MGR *pVoiceMgr) { return EAS_SUCCESS; }
96 static EAS_RESULT FM_StartVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum, EAS_U16 regionIndex);
97 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);
98 static void FM_ReleaseVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum);
99 static void FM_MuteVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum);
100 static void FM_SustainPedal (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, S_SYNTH_CHANNEL *pChannel, EAS_I32 voiceNum);
101 static void FM_UpdateChannel (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, EAS_U8 channel);
102
103
104 /*----------------------------------------------------------------------------
105 * Synthesizer interface
106 *----------------------------------------------------------------------------
107 */
108 const 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
120 const S_FRAME_INTERFACE fmFrameInterface =
121 {
122 FM_StartFrame,
123 FM_EndFrame
124 };
125 #endif
126
127 /*----------------------------------------------------------------------------
128 * inline functions
129 *----------------------------------------------------------------------------
130 */
GetFMVoicePtr(S_VOICE_MGR * pVoiceMgr,EAS_INT voiceNum)131 EAS_INLINE S_FM_VOICE *GetFMVoicePtr (S_VOICE_MGR *pVoiceMgr, EAS_INT voiceNum)
132 {
133 return &pVoiceMgr->fmVoices[voiceNum];
134 }
GetChannelPtr(S_SYNTH * pSynth,S_SYNTH_VOICE * pVoice)135 EAS_INLINE S_SYNTH_CHANNEL *GetChannelPtr (S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice)
136 {
137 return &pSynth->channels[pVoice->channel & 15];
138 }
GetFMRegionPtr(S_SYNTH * pSynth,S_SYNTH_VOICE * pVoice)139 EAS_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 */
FM_SynthIsOutputOperator(const S_FM_REGION * pRegion,EAS_INT operIndex)160 static 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
FM_CalcEGRate(EAS_U8 nKeyNumber,EAS_U8 nLogRate,EAS_U8 nEGScale)223 static 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 */
FM_ReleaseVoice(S_VOICE_MGR * pVoiceMgr,S_SYNTH * pSynth,S_SYNTH_VOICE * pVoice,EAS_I32 voiceNum)254 static 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 */
FM_MuteVoice(S_VOICE_MGR * pVoiceMgr,S_SYNTH * pSynth,S_SYNTH_VOICE * pVoice,EAS_I32 voiceNum)293 static 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 */
FM_SustainPedal(S_VOICE_MGR * pVoiceMgr,S_SYNTH * pSynth,S_SYNTH_VOICE * pVoice,S_SYNTH_CHANNEL * pChannel,EAS_I32 voiceNum)325 static 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 */
FM_StartVoice(S_VOICE_MGR * pVoiceMgr,S_SYNTH * pSynth,S_SYNTH_VOICE * pVoice,EAS_I32 voiceNum,EAS_U16 regionIndex)396 static 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 */
FM_UpdateChannel(S_VOICE_MGR * pVoiceMgr,S_SYNTH * pSynth,EAS_U8 channel)515 static 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 */
FM_UpdateLFO(S_FM_VOICE * pFMVoice,const S_FM_REGION * pRegion)578 static 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 */
FM_UpdateEG(S_SYNTH_VOICE * pVoice,S_OPERATOR * pOper,const S_FM_OPER * pOperData,EAS_BOOL oneShot)625 static 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 */
FM_UpdateDynamic(S_SYNTH_VOICE * pVoice,S_FM_VOICE * pFMVoice,const S_FM_REGION * pRegion,S_SYNTH_CHANNEL * pChannel)728 static 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 */
FM_UpdateVoice(S_VOICE_MGR * pVoiceMgr,S_SYNTH * pSynth,S_SYNTH_VOICE * pVoice,EAS_I32 voiceNum,EAS_I32 * pMixBuffer,EAS_I32 numSamples)799 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)
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