/*----------------------------------------------------------------------------
*
* File:
* eas_dlssynth.c
*
* Contents and purpose:
* Implements the Mobile DLS synthesizer.
*
* Copyright Sonic Network Inc. 2006
* 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_data.h"
#include "eas_report.h"
#include "eas_host.h"
#include "eas_math.h"
#include "eas_synth_protos.h"
#include "eas_wtsynth.h"
#include "eas_pan.h"
#include "eas_mdls.h"
#include "eas_dlssynth.h"
#ifdef _METRICS_ENABLED
#include "eas_perf.h"
#endif
static void DLS_UpdateEnvelope (S_SYNTH_VOICE *pVoice, S_SYNTH_CHANNEL *pChannel, const S_DLS_ENVELOPE *pEnvParams, EAS_I16 *pValue, EAS_I16 *pIncrement, EAS_U8 *pState);
/*----------------------------------------------------------------------------
* DLS_MuteVoice()
*----------------------------------------------------------------------------
* Mute the voice using shutdown time from the DLS articulation data
*----------------------------------------------------------------------------
*/
void DLS_MuteVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum)
{
S_WT_VOICE *pWTVoice;
const S_DLS_ARTICULATION *pDLSArt;
pWTVoice = &pVoiceMgr->wtVoices[voiceNum];
pDLSArt = &pSynth->pDLS->pDLSArticulations[pWTVoice->artIndex];
/* clear deferred action flags */
pVoice->voiceFlags &=
~(VOICE_FLAG_DEFER_MIDI_NOTE_OFF |
VOICE_FLAG_SUSTAIN_PEDAL_DEFER_NOTE_OFF |
VOICE_FLAG_DEFER_MUTE);
/* set the envelope state */
pVoiceMgr->wtVoices[voiceNum].eg1State = eEnvelopeStateRelease;
pWTVoice->eg1Increment = pDLSArt->eg1ShutdownTime;
pVoiceMgr->wtVoices[voiceNum].eg2State = eEnvelopeStateRelease;
pWTVoice->eg2Increment = pDLSArt->eg2.releaseTime;
}
/*----------------------------------------------------------------------------
* DLS_ReleaseVoice()
*----------------------------------------------------------------------------
* Release the selected voice.
*----------------------------------------------------------------------------
*/
/*lint -esym(715, pVoice) standard API, pVoice may be used by other synthesizers */
void DLS_ReleaseVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum)
{
S_WT_VOICE *pWTVoice;
const S_DLS_ARTICULATION *pDLSArt;
pWTVoice = &pVoiceMgr->wtVoices[voiceNum];
pDLSArt = &pSynth->pDLS->pDLSArticulations[pWTVoice->artIndex];
/* if still in attack phase, convert units to log */
/*lint -e{732} eg1Value is never negative */
/*lint -e{703} use shift for performance */
if (pWTVoice->eg1State == eEnvelopeStateAttack)
pWTVoice->eg1Value = (EAS_I16) ((EAS_flog2(pWTVoice->eg1Value) << 1) + 2048);
/* release EG1 */
pWTVoice->eg1State = eEnvelopeStateRelease;
pWTVoice->eg1Increment = pDLSArt->eg1.releaseTime;
/* release EG2 */
pWTVoice->eg2State = eEnvelopeStateRelease;
pWTVoice->eg2Increment = pDLSArt->eg2.releaseTime;
}
/*----------------------------------------------------------------------------
* DLS_SustainPedal()
*----------------------------------------------------------------------------
* The sustain pedal was just depressed. If the voice is still
* above the sustain level, catch the voice and continue holding.
*----------------------------------------------------------------------------
*/
/*lint -esym(715, pChannel) pChannel reserved for future use */
void DLS_SustainPedal (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, S_SYNTH_CHANNEL *pChannel, EAS_I32 voiceNum)
{
S_WT_VOICE *pWTVoice;
const S_DLS_ARTICULATION *pDLSArt;
pWTVoice = &pVoiceMgr->wtVoices[voiceNum];
pDLSArt = &pSynth->pDLS->pDLSArticulations[pWTVoice->artIndex];
/* don't catch the voice if below the sustain level */
if (pWTVoice->eg1Value < pDLSArt->eg1.sustainLevel)
return;
/* defer releasing this note until the damper pedal is off */
pWTVoice->eg1State = eEnvelopeStateDecay;
pVoice->voiceState = eVoiceStatePlay;
pVoice->voiceFlags |= VOICE_FLAG_SUSTAIN_PEDAL_DEFER_NOTE_OFF;
#ifdef _DEBUG_SYNTH
{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "DLS_SustainPedal: defer note off because sustain pedal is on\n"); */ }
#endif
}
/*----------------------------------------------------------------------------
* DLS_UpdatePhaseInc()
*----------------------------------------------------------------------------
* Calculate the oscillator phase increment for the next frame
*----------------------------------------------------------------------------
*/
static EAS_I32 DLS_UpdatePhaseInc (S_WT_VOICE *pWTVoice, const S_DLS_ARTICULATION *pDLSArt, S_SYNTH_CHANNEL *pChannel, EAS_I32 pitchCents)
{
EAS_I32 temp;
/* start with base mod LFO modulation */
temp = pDLSArt->modLFOToPitch;
/* add mod wheel effect */
/*lint -e{702} use shift for performance */
temp += ((pDLSArt->modLFOCC1ToPitch * pChannel->modWheel) >> 7);
/* add channel pressure effect */
/*lint -e{702} use shift for performance */
temp += ((pDLSArt->modLFOChanPressToPitch * pChannel->channelPressure) >> 7);
/* add total mod LFO effect */
pitchCents += FMUL_15x15(temp, pWTVoice->modLFO.lfoValue);
/* start with base vib LFO modulation */
temp = pDLSArt->vibLFOToPitch;
/* add mod wheel effect */
/*lint -e{702} use shift for performance */
temp += ((pDLSArt->vibLFOCC1ToPitch * pChannel->modWheel) >> 7);
/* add channel pressure effect */
/*lint -e{702} use shift for performance */
temp += ((pDLSArt->vibLFOChanPressToPitch * pChannel->channelPressure) >> 7);
/* add total vibrato LFO effect */
pitchCents += FMUL_15x15(temp, pWTVoice->vibLFO.lfoValue);
/* add EG2 effect */
pitchCents += FMUL_15x15(pDLSArt->eg2ToPitch, pWTVoice->eg2Value);
/* convert from cents to linear phase increment */
return EAS_Calculate2toX(pitchCents);
}
/*----------------------------------------------------------------------------
* DLS_UpdateGain()
*----------------------------------------------------------------------------
* Calculate the gain for the next frame
*----------------------------------------------------------------------------
*/
static EAS_I32 DLS_UpdateGain (S_WT_VOICE *pWTVoice, const S_DLS_ARTICULATION *pDLSArt, S_SYNTH_CHANNEL *pChannel, EAS_I32 gain, EAS_U8 velocity)
{
EAS_I32 temp;
/* start with base mod LFO modulation */
temp = pDLSArt->modLFOToGain;
/* add mod wheel effect */
/*lint -e{702} use shift for performance */
temp += ((pDLSArt->modLFOCC1ToGain * pChannel->modWheel) >> 7);
/* add channel pressure effect */
/*lint -e{702} use shift for performance */
temp += ((pDLSArt->modLFOChanPressToGain * pChannel->channelPressure) >> 7);
/* add total mod LFO effect */
gain += FMUL_15x15(temp, pWTVoice->modLFO.lfoValue);
if (gain > 0)
gain = 0;
/* convert to linear gain including EG1 */
if (pWTVoice->eg1State != eEnvelopeStateAttack)
{
gain = (DLS_GAIN_FACTOR * gain) >> DLS_GAIN_SHIFT;
/*lint -e{702} use shift for performance */
#if 1
gain += (pWTVoice->eg1Value - 32767) >> 1;
gain = EAS_LogToLinear16(gain);
#else
gain = EAS_LogToLinear16(gain);
temp = EAS_LogToLinear16((pWTVoice->eg1Value - 32767) >> 1);
gain = FMUL_15x15(gain, temp);
#endif
}
else
{
gain = (DLS_GAIN_FACTOR * gain) >> DLS_GAIN_SHIFT;
gain = EAS_LogToLinear16(gain);
gain = FMUL_15x15(gain, pWTVoice->eg1Value);
}
/* include MIDI channel gain */
gain = FMUL_15x15(gain, pChannel->staticGain);
/* include velocity */
if (pDLSArt->filterQandFlags & FLAG_DLS_VELOCITY_SENSITIVE)
{
temp = velocity << 8;
temp = FMUL_15x15(temp, temp);
gain = FMUL_15x15(gain, temp);
}
/* return gain */
return gain;
}
/*----------------------------------------------------------------------------
* DLS_UpdateFilter()
*----------------------------------------------------------------------------
* Update the Filter parameters
*----------------------------------------------------------------------------
*/
static void DLS_UpdateFilter (S_SYNTH_VOICE *pVoice, S_WT_VOICE *pWTVoice, S_WT_INT_FRAME *pIntFrame, S_SYNTH_CHANNEL *pChannel, const S_DLS_ARTICULATION *pDLSArt)
{
EAS_I32 cutoff;
EAS_I32 temp;
/* no need to calculate filter coefficients if it is bypassed */
if (pDLSArt->filterCutoff == DEFAULT_DLS_FILTER_CUTOFF_FREQUENCY)
{
pIntFrame->frame.k = 0;
return;
}
/* start with base cutoff frequency */
cutoff = pDLSArt->filterCutoff;
/* get base mod LFO modulation */
temp = pDLSArt->modLFOToFc;
/* add mod wheel effect */
/*lint -e{702} use shift for performance */
temp += ((pDLSArt->modLFOCC1ToFc * pChannel->modWheel) >> 7);
/* add channel pressure effect */
/*lint -e{702} use shift for performance */
temp += ((pDLSArt->modLFOChanPressToFc* pChannel->channelPressure) >> 7);
/* add total mod LFO effect */
cutoff += FMUL_15x15(temp, pWTVoice->modLFO.lfoValue);
/* add EG2 effect */
cutoff += FMUL_15x15(pWTVoice->eg2Value, pDLSArt->eg2ToFc);
/* add velocity effect */
/*lint -e{702} use shift for performance */
cutoff += (pVoice->velocity * pDLSArt->velToFc) >> 7;
/* add velocity effect */
/*lint -e{702} use shift for performance */
cutoff += (pVoice->note * pDLSArt->keyNumToFc) >> 7;
/* subtract the A5 offset and the sampling frequency */
cutoff -= FILTER_CUTOFF_FREQ_ADJUST + A5_PITCH_OFFSET_IN_CENTS;
/* limit the cutoff frequency */
if (cutoff > FILTER_CUTOFF_MAX_PITCH_CENTS)
cutoff = FILTER_CUTOFF_MAX_PITCH_CENTS;
else if (cutoff < FILTER_CUTOFF_MIN_PITCH_CENTS)
cutoff = FILTER_CUTOFF_MIN_PITCH_CENTS;
WT_SetFilterCoeffs(pIntFrame, cutoff, pDLSArt->filterQandFlags & FILTER_Q_MASK);
}
/*----------------------------------------------------------------------------
* DLS_StartVoice()
*----------------------------------------------------------------------------
* Start up a DLS voice
*----------------------------------------------------------------------------
*/
EAS_RESULT DLS_StartVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum, EAS_U16 regionIndex)
{
S_WT_VOICE *pWTVoice;
const S_DLS_REGION *pDLSRegion;
const S_DLS_ARTICULATION *pDLSArt;
S_SYNTH_CHANNEL *pChannel;
#ifdef _DEBUG_SYNTH
{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "DLS_StartVoice: Voice %ld; Region %d\n", (EAS_I32) (pVoice - pVoiceMgr->voices), regionIndex); */ }
#endif
pWTVoice = &pVoiceMgr->wtVoices[voiceNum];
pChannel = &pSynth->channels[pVoice->channel & 15];
pDLSRegion = &pSynth->pDLS->pDLSRegions[regionIndex & REGION_INDEX_MASK];
pWTVoice->artIndex = pDLSRegion->wtRegion.artIndex;
pDLSArt = &pSynth->pDLS->pDLSArticulations[pWTVoice->artIndex];
/* initialize the envelopes */
pWTVoice->eg1State = eEnvelopeStateInit;
DLS_UpdateEnvelope(pVoice, pChannel, &pDLSArt->eg1, &pWTVoice->eg1Value, &pWTVoice->eg1Increment, &pWTVoice->eg1State);
pWTVoice->eg2State = eEnvelopeStateInit;
DLS_UpdateEnvelope(pVoice, pChannel, &pDLSArt->eg2, &pWTVoice->eg2Value, &pWTVoice->eg2Increment, &pWTVoice->eg2State);
/* initialize the LFOs */
pWTVoice->modLFO.lfoValue = 0;
pWTVoice->modLFO.lfoPhase = pDLSArt->modLFO.lfoDelay;
pWTVoice->vibLFO.lfoValue = 0;
pWTVoice->vibLFO.lfoPhase = pDLSArt->vibLFO.lfoDelay;
/* initalize the envelopes and calculate initial gain */
DLS_UpdateEnvelope(pVoice, pChannel, &pDLSArt->eg1, &pWTVoice->eg1Value, &pWTVoice->eg1Increment, &pWTVoice->eg1State);
DLS_UpdateEnvelope(pVoice, pChannel, &pDLSArt->eg2, &pWTVoice->eg2Value, &pWTVoice->eg2Increment, &pWTVoice->eg2State);
pVoice->gain = (EAS_I16) DLS_UpdateGain(pWTVoice, pDLSArt, pChannel, pDLSRegion->wtRegion.gain, pVoice->velocity);
#if (NUM_OUTPUT_CHANNELS == 2)
EAS_CalcPanControl((EAS_INT) pChannel->pan - 64 + (EAS_INT) pDLSArt->pan, &pWTVoice->gainLeft, &pWTVoice->gainRight);
#endif
/* initialize the filter states */
pWTVoice->filter.z1 = 0;
pWTVoice->filter.z2 = 0;
/* initialize the oscillator */
pWTVoice->phaseAccum = (EAS_U32) pSynth->pDLS->pDLSSamples + pSynth->pDLS->pDLSSampleOffsets[pDLSRegion->wtRegion.waveIndex];
if (pDLSRegion->wtRegion.region.keyGroupAndFlags & REGION_FLAG_IS_LOOPED)
{
pWTVoice->loopStart = pWTVoice->phaseAccum + pDLSRegion->wtRegion.loopStart;
pWTVoice->loopEnd = pWTVoice->phaseAccum + pDLSRegion->wtRegion.loopEnd - 1;
}
else
pWTVoice->loopStart = pWTVoice->loopEnd = pWTVoice->phaseAccum + pSynth->pDLS->pDLSSampleLen[pDLSRegion->wtRegion.waveIndex] - 1;
return EAS_SUCCESS;
}
/*----------------------------------------------------------------------------
* DLS_UpdateVoice()
*----------------------------------------------------------------------------
* Purpose:
* Synthesize a block of samples for the given voice.
* Use linear interpolation.
*
* Inputs:
* pEASData - pointer to overall EAS data structure
*
* Outputs:
* number of samples actually written to buffer
*
* Side Effects:
* - samples are added to the presently free buffer
*
*----------------------------------------------------------------------------
*/
EAS_BOOL DLS_UpdateVoice (S_VOICE_MGR *pVoiceMgr, S_SYNTH *pSynth, S_SYNTH_VOICE *pVoice, EAS_I32 voiceNum, EAS_I32 *pMixBuffer, EAS_I32 numSamples)
{
S_WT_VOICE *pWTVoice;
S_SYNTH_CHANNEL *pChannel;
const S_DLS_REGION *pDLSRegion;
const S_DLS_ARTICULATION *pDLSArt;
S_WT_INT_FRAME intFrame;
EAS_I32 temp;
EAS_BOOL done = EAS_FALSE;
/* establish pointers to critical data */
pWTVoice = &pVoiceMgr->wtVoices[voiceNum];
pDLSRegion = &pSynth->pDLS->pDLSRegions[pVoice->regionIndex & REGION_INDEX_MASK];
pChannel = &pSynth->channels[pVoice->channel & 15];
pDLSArt = &pSynth->pDLS->pDLSArticulations[pWTVoice->artIndex];
/* update the envelopes */
DLS_UpdateEnvelope(pVoice, pChannel, &pDLSArt->eg1, &pWTVoice->eg1Value, &pWTVoice->eg1Increment, &pWTVoice->eg1State);
DLS_UpdateEnvelope(pVoice, pChannel, &pDLSArt->eg2, &pWTVoice->eg2Value, &pWTVoice->eg2Increment, &pWTVoice->eg2State);
/* update the LFOs using the EAS synth function */
WT_UpdateLFO(&pWTVoice->modLFO, pDLSArt->modLFO.lfoFreq);
WT_UpdateLFO(&pWTVoice->vibLFO, pDLSArt->vibLFO.lfoFreq);
/* calculate base frequency */
temp = pDLSArt->tuning + pChannel->staticPitch + pDLSRegion->wtRegion.tuning +
(((EAS_I32) pVoice->note * (EAS_I32) pDLSArt->keyNumToPitch) >> 7);
/* don't transpose rhythm channel */
if ((pChannel ->channelFlags & CHANNEL_FLAG_RHYTHM_CHANNEL) == 0)
temp += pSynth->globalTranspose * 100;
/* calculate phase increment including modulation effects */
intFrame.frame.phaseIncrement = DLS_UpdatePhaseInc(pWTVoice, pDLSArt, pChannel, temp);
/* calculate gain including modulation effects */
intFrame.frame.gainTarget = DLS_UpdateGain(pWTVoice, pDLSArt, pChannel, pDLSRegion->wtRegion.gain, pVoice->velocity);
intFrame.prevGain = pVoice->gain;
DLS_UpdateFilter(pVoice, pWTVoice, &intFrame, pChannel, pDLSArt);
/* call into engine to generate samples */
intFrame.pAudioBuffer = pVoiceMgr->voiceBuffer;
intFrame.pMixBuffer = pMixBuffer;
intFrame.numSamples = numSamples;
if (numSamples < 0)
return EAS_FALSE;
/* check for end of sample */
if ((pWTVoice->loopStart != WT_NOISE_GENERATOR) && (pWTVoice->loopStart == pWTVoice->loopEnd))
done = WT_CheckSampleEnd(pWTVoice, &intFrame, EAS_FALSE);
WT_ProcessVoice(pWTVoice, &intFrame);
/* clear flag */
pVoice->voiceFlags &= ~VOICE_FLAG_NO_SAMPLES_SYNTHESIZED_YET;
/* if the update interval has elapsed, then force the current gain to the next
* gain since we never actually reach the next gain when ramping -- we just get
* very close to the target gain.
*/
pVoice->gain = (EAS_I16) intFrame.frame.gainTarget;
/* if voice has finished, set flag for voice manager */
if ((pVoice->voiceState != eVoiceStateStolen) && (pWTVoice->eg1State == eEnvelopeStateMuted))
done = EAS_TRUE;
return done;
}
/*----------------------------------------------------------------------------
* DLS_UpdateEnvelope()
*----------------------------------------------------------------------------
* Purpose:
* Synthesize a block of samples for the given voice.
* Use linear interpolation.
*
* Inputs:
* pEASData - pointer to overall EAS data structure
*
* Outputs:
* number of samples actually written to buffer
*
* Side Effects:
* - samples are added to the presently free buffer
*
*----------------------------------------------------------------------------
*/
/*lint -esym(715, pChannel) pChannel not used in this instance */
static void DLS_UpdateEnvelope (S_SYNTH_VOICE *pVoice, S_SYNTH_CHANNEL *pChannel, const S_DLS_ENVELOPE *pEnvParams, EAS_I16 *pValue, EAS_I16 *pIncrement, EAS_U8 *pState)
{
EAS_I32 temp;
switch (*pState)
{
/* initial state */
case eEnvelopeStateInit:
*pState = eEnvelopeStateDelay;
*pValue = 0;
*pIncrement = pEnvParams->delayTime;
if (*pIncrement != 0)
return;
/*lint -e{825} falls through to next case */
case eEnvelopeStateDelay:
if (*pIncrement)
{
*pIncrement = *pIncrement - 1;
return;
}
/* calculate attack rate */
*pState = eEnvelopeStateAttack;
if (pEnvParams->attackTime != ZERO_TIME_IN_CENTS)
{
/*lint -e{702} use shift for performance */
temp = pEnvParams->attackTime + ((pEnvParams->velToAttack * pVoice->velocity) >> 7);
*pIncrement = ConvertRate(temp);
return;
}
*pValue = SYNTH_FULL_SCALE_EG1_GAIN;
/*lint -e{825} falls through to next case */
case eEnvelopeStateAttack:
if (*pValue < SYNTH_FULL_SCALE_EG1_GAIN)
{
temp = *pValue + *pIncrement;
*pValue = (EAS_I16) (temp < SYNTH_FULL_SCALE_EG1_GAIN ? temp : SYNTH_FULL_SCALE_EG1_GAIN);
return;
}
/* calculate hold time */
*pState = eEnvelopeStateHold;
if (pEnvParams->holdTime != ZERO_TIME_IN_CENTS)
{
/*lint -e{702} use shift for performance */
temp = pEnvParams->holdTime + ((pEnvParams->keyNumToHold * pVoice->note) >> 7);
*pIncrement = ConvertDelay(temp);
return;
}
else
*pIncrement = 0;
/*lint -e{825} falls through to next case */
case eEnvelopeStateHold:
if (*pIncrement)
{
*pIncrement = *pIncrement - 1;
return;
}
/* calculate decay rate */
*pState = eEnvelopeStateDecay;
if (pEnvParams->decayTime != ZERO_TIME_IN_CENTS)
{
/*lint -e{702} use shift for performance */
temp = pEnvParams->decayTime + ((pEnvParams->keyNumToDecay * pVoice->note) >> 7);
*pIncrement = ConvertRate(temp);
return;
}
// *pValue = pEnvParams->sustainLevel;
/*lint -e{825} falls through to next case */
case eEnvelopeStateDecay:
if (*pValue > pEnvParams->sustainLevel)
{
temp = *pValue - *pIncrement;
*pValue = (EAS_I16) (temp > pEnvParams->sustainLevel ? temp : pEnvParams->sustainLevel);
return;
}
*pState = eEnvelopeStateSustain;
*pValue = pEnvParams->sustainLevel;
/*lint -e{825} falls through to next case */
case eEnvelopeStateSustain:
return;
case eEnvelopeStateRelease:
temp = *pValue - *pIncrement;
if (temp <= 0)
{
*pState = eEnvelopeStateMuted;
*pValue = 0;
}
else
*pValue = (EAS_I16) temp;
break;
case eEnvelopeStateMuted:
*pValue = 0;
return;
default:
{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "Envelope in invalid state %d\n", *pState); */ }
break;
}
}