aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Alc/ALu.c529
1 files changed, 265 insertions, 264 deletions
diff --git a/Alc/ALu.c b/Alc/ALu.c
index 04562b54..26532091 100644
--- a/Alc/ALu.c
+++ b/Alc/ALu.c
@@ -461,315 +461,316 @@ static ALvoid CalcSourceParams(const ALCcontext *ALContext,
OuterGainHF = ALSource->OuterGainHF;
//Only apply 3D calculations for mono buffers
- if(isMono != AL_FALSE)
+ if(isMono == AL_FALSE)
{
- //1. Translate Listener to origin (convert to head relative)
- // Note that Direction and SourceToListener are *not* transformed.
- // SourceToListener is used with the source and listener velocities,
- // which are untransformed, and Direction is used with SourceToListener
- // for the sound cone
- if(ALSource->bHeadRelative==AL_FALSE)
- {
- // Build transform matrix
- aluCrossproduct(ALContext->Listener.Forward, ALContext->Listener.Up, U); // Right-vector
- aluNormalize(U); // Normalized Right-vector
- memcpy(V, ALContext->Listener.Up, sizeof(V)); // Up-vector
- aluNormalize(V); // Normalized Up-vector
- memcpy(N, ALContext->Listener.Forward, sizeof(N)); // At-vector
- aluNormalize(N); // Normalized At-vector
- Matrix[0][0] = U[0]; Matrix[0][1] = V[0]; Matrix[0][2] = -N[0];
- Matrix[1][0] = U[1]; Matrix[1][1] = V[1]; Matrix[1][2] = -N[1];
- Matrix[2][0] = U[2]; Matrix[2][1] = V[2]; Matrix[2][2] = -N[2];
-
- // Translate source position into listener space
- Position[0] -= ALContext->Listener.Position[0];
- Position[1] -= ALContext->Listener.Position[1];
- Position[2] -= ALContext->Listener.Position[2];
-
- SourceToListener[0] = -Position[0];
- SourceToListener[1] = -Position[1];
- SourceToListener[2] = -Position[2];
-
- // Transform source position into listener space
- aluMatrixVector(Position, Matrix);
- }
- else
+ //1. Multi-channel buffers always play "normal"
+ pitch[0] = ALSource->flPitch;
+
+ DryMix = SourceVolume;
+ DryMix = __min(DryMix,MaxVolume);
+ DryMix = __max(DryMix,MinVolume);
+
+ switch(ALSource->DirectFilter.type)
{
- SourceToListener[0] = -Position[0];
- SourceToListener[1] = -Position[1];
- SourceToListener[2] = -Position[2];
+ case AL_FILTER_LOWPASS:
+ DryMix *= ALSource->DirectFilter.Gain;
+ DryGainHF *= ALSource->DirectFilter.GainHF;
+ break;
}
- aluNormalize(SourceToListener);
- aluNormalize(Direction);
- //2. Calculate distance attenuation
- Distance = aluSqrt(aluDotproduct(Position, Position));
+ drysend[FRONT_LEFT] = DryMix * ListenerGain;
+ drysend[FRONT_RIGHT] = DryMix * ListenerGain;
+ drysend[SIDE_LEFT] = DryMix * ListenerGain;
+ drysend[SIDE_RIGHT] = DryMix * ListenerGain;
+ drysend[BACK_LEFT] = DryMix * ListenerGain;
+ drysend[BACK_RIGHT] = DryMix * ListenerGain;
+ drysend[FRONT_CENTER] = DryMix * ListenerGain;
+ drysend[BACK_CENTER] = DryMix * ListenerGain;
+ drysend[LFE] = DryMix * ListenerGain;
+ *drygainhf = DryGainHF;
- flAttenuation = 1.0f;
for(i = 0;i < MAX_SENDS;i++)
{
- RoomAttenuation[i] = 1.0f;
-
- RoomRolloff[i] = ALSource->RoomRolloffFactor;
- if(ALSource->Send[i].Slot &&
- ALSource->Send[i].Slot->effect.type == AL_EFFECT_REVERB)
- RoomRolloff[i] += ALSource->Send[i].Slot->effect.Reverb.RoomRolloffFactor;
+ wetsend[i] = 0.0f;
+ wetgainhf[i] = 1.0f;
}
- switch (ALSource->DistanceModel)
- {
- case AL_INVERSE_DISTANCE_CLAMPED:
- Distance=__max(Distance,MinDist);
- Distance=__min(Distance,MaxDist);
- if (MaxDist < MinDist)
- break;
- //fall-through
- case AL_INVERSE_DISTANCE:
- if (MinDist > 0.0f)
- {
- if ((MinDist + (Rolloff * (Distance - MinDist))) > 0.0f)
- flAttenuation = MinDist / (MinDist + (Rolloff * (Distance - MinDist)));
- for(i = 0;i < NumSends;i++)
- {
- if ((MinDist + (RoomRolloff[i] * (Distance - MinDist))) > 0.0f)
- RoomAttenuation[i] = MinDist / (MinDist + (RoomRolloff[i] * (Distance - MinDist)));
- }
- }
- break;
+ return;
+ }
- case AL_LINEAR_DISTANCE_CLAMPED:
- Distance=__max(Distance,MinDist);
- Distance=__min(Distance,MaxDist);
- if (MaxDist < MinDist)
- break;
- //fall-through
- case AL_LINEAR_DISTANCE:
- Distance=__min(Distance,MaxDist);
- if (MaxDist != MinDist)
- {
- flAttenuation = 1.0f - (Rolloff*(Distance-MinDist)/(MaxDist - MinDist));
- for(i = 0;i < NumSends;i++)
- RoomAttenuation[i] = 1.0f - (RoomRolloff[i]*(Distance-MinDist)/(MaxDist - MinDist));
- }
- break;
+ //1. Translate Listener to origin (convert to head relative)
+ // Note that Direction and SourceToListener are *not* transformed.
+ // SourceToListener is used with the source and listener velocities,
+ // which are untransformed, and Direction is used with SourceToListener
+ // for the sound cone
+ if(ALSource->bHeadRelative==AL_FALSE)
+ {
+ // Build transform matrix
+ aluCrossproduct(ALContext->Listener.Forward, ALContext->Listener.Up, U); // Right-vector
+ aluNormalize(U); // Normalized Right-vector
+ memcpy(V, ALContext->Listener.Up, sizeof(V)); // Up-vector
+ aluNormalize(V); // Normalized Up-vector
+ memcpy(N, ALContext->Listener.Forward, sizeof(N)); // At-vector
+ aluNormalize(N); // Normalized At-vector
+ Matrix[0][0] = U[0]; Matrix[0][1] = V[0]; Matrix[0][2] = -N[0];
+ Matrix[1][0] = U[1]; Matrix[1][1] = V[1]; Matrix[1][2] = -N[1];
+ Matrix[2][0] = U[2]; Matrix[2][1] = V[2]; Matrix[2][2] = -N[2];
+
+ // Translate source position into listener space
+ Position[0] -= ALContext->Listener.Position[0];
+ Position[1] -= ALContext->Listener.Position[1];
+ Position[2] -= ALContext->Listener.Position[2];
+
+ SourceToListener[0] = -Position[0];
+ SourceToListener[1] = -Position[1];
+ SourceToListener[2] = -Position[2];
+
+ // Transform source position into listener space
+ aluMatrixVector(Position, Matrix);
+ }
+ else
+ {
+ SourceToListener[0] = -Position[0];
+ SourceToListener[1] = -Position[1];
+ SourceToListener[2] = -Position[2];
+ }
+ aluNormalize(SourceToListener);
+ aluNormalize(Direction);
- case AL_EXPONENT_DISTANCE_CLAMPED:
- Distance=__max(Distance,MinDist);
- Distance=__min(Distance,MaxDist);
- if (MaxDist < MinDist)
- break;
- //fall-through
- case AL_EXPONENT_DISTANCE:
- if ((Distance > 0.0f) && (MinDist > 0.0f))
+ //2. Calculate distance attenuation
+ Distance = aluSqrt(aluDotproduct(Position, Position));
+
+ flAttenuation = 1.0f;
+ for(i = 0;i < MAX_SENDS;i++)
+ {
+ RoomAttenuation[i] = 1.0f;
+
+ RoomRolloff[i] = ALSource->RoomRolloffFactor;
+ if(ALSource->Send[i].Slot &&
+ ALSource->Send[i].Slot->effect.type == AL_EFFECT_REVERB)
+ RoomRolloff[i] += ALSource->Send[i].Slot->effect.Reverb.RoomRolloffFactor;
+ }
+
+ switch(ALSource->DistanceModel)
+ {
+ case AL_INVERSE_DISTANCE_CLAMPED:
+ Distance=__max(Distance,MinDist);
+ Distance=__min(Distance,MaxDist);
+ if(MaxDist < MinDist)
+ break;
+ //fall-through
+ case AL_INVERSE_DISTANCE:
+ if(MinDist > 0.0f)
+ {
+ if((MinDist + (Rolloff * (Distance - MinDist))) > 0.0f)
+ flAttenuation = MinDist / (MinDist + (Rolloff * (Distance - MinDist)));
+ for(i = 0;i < NumSends;i++)
{
- flAttenuation = (ALfloat)pow(Distance/MinDist, -Rolloff);
- for(i = 0;i < NumSends;i++)
- RoomAttenuation[i] = (ALfloat)pow(Distance/MinDist, -RoomRolloff[i]);
+ if((MinDist + (RoomRolloff[i] * (Distance - MinDist))) > 0.0f)
+ RoomAttenuation[i] = MinDist / (MinDist + (RoomRolloff[i] * (Distance - MinDist)));
}
+ }
+ break;
+
+ case AL_LINEAR_DISTANCE_CLAMPED:
+ Distance=__max(Distance,MinDist);
+ Distance=__min(Distance,MaxDist);
+ if(MaxDist < MinDist)
break;
+ //fall-through
+ case AL_LINEAR_DISTANCE:
+ Distance=__min(Distance,MaxDist);
+ if(MaxDist != MinDist)
+ {
+ flAttenuation = 1.0f - (Rolloff*(Distance-MinDist)/(MaxDist - MinDist));
+ for(i = 0;i < NumSends;i++)
+ RoomAttenuation[i] = 1.0f - (RoomRolloff[i]*(Distance-MinDist)/(MaxDist - MinDist));
+ }
+ break;
- case AL_NONE:
+ case AL_EXPONENT_DISTANCE_CLAMPED:
+ Distance=__max(Distance,MinDist);
+ Distance=__min(Distance,MaxDist);
+ if(MaxDist < MinDist)
break;
- }
+ //fall-through
+ case AL_EXPONENT_DISTANCE:
+ if(Distance > 0.0f && MinDist > 0.0f)
+ {
+ flAttenuation = (ALfloat)pow(Distance/MinDist, -Rolloff);
+ for(i = 0;i < NumSends;i++)
+ RoomAttenuation[i] = (ALfloat)pow(Distance/MinDist, -RoomRolloff[i]);
+ }
+ break;
- // Source Gain + Attenuation and clamp to Min/Max Gain
- DryMix = SourceVolume * flAttenuation;
- DryMix = __min(DryMix,MaxVolume);
- DryMix = __max(DryMix,MinVolume);
+ case AL_NONE:
+ break;
+ }
- for(i = 0;i < NumSends;i++)
- {
- ALfloat WetMix = SourceVolume * RoomAttenuation[i];
- WetMix = __min(WetMix,MaxVolume);
- wetsend[i] = __max(WetMix,MinVolume);
- wetgainhf[i] = 1.0f;
- }
+ // Source Gain + Attenuation and clamp to Min/Max Gain
+ DryMix = SourceVolume * flAttenuation;
+ DryMix = __min(DryMix,MaxVolume);
+ DryMix = __max(DryMix,MinVolume);
- // Distance-based air absorption
- if(ALSource->AirAbsorptionFactor > 0.0f && ALSource->DistanceModel != AL_NONE)
- {
- ALfloat dist = Distance-MinDist;
- ALfloat absorb;
-
- if(dist < 0.0f) dist = 0.0f;
- // Absorption calculation is done in dB
- absorb = (ALSource->AirAbsorptionFactor*AIRABSORBGAINDBHF) *
- (dist*MetersPerUnit);
- // Convert dB to linear gain before applying
- absorb = pow(10.0, absorb/20.0);
- DryGainHF *= absorb;
- for(i = 0;i < MAX_SENDS;i++)
- wetgainhf[i] *= absorb;
- }
+ for(i = 0;i < NumSends;i++)
+ {
+ ALfloat WetMix = SourceVolume * RoomAttenuation[i];
+ WetMix = __min(WetMix,MaxVolume);
+ wetsend[i] = __max(WetMix,MinVolume);
+ wetgainhf[i] = 1.0f;
+ }
- //3. Apply directional soundcones
- Angle = aluAcos(aluDotproduct(Direction,SourceToListener)) * 180.0f/M_PI;
- if(Angle >= InnerAngle && Angle <= OuterAngle)
- {
- ALfloat scale = (Angle-InnerAngle) / (OuterAngle-InnerAngle);
- ConeVolume = (1.0f+(ALSource->flOuterGain-1.0f)*scale);
- ConeHF = (1.0f+(OuterGainHF-1.0f)*scale);
- DryMix *= ConeVolume;
- if(ALSource->DryGainHFAuto)
- DryGainHF *= ConeHF;
- }
- else if(Angle > OuterAngle)
- {
- ConeVolume = (1.0f+(ALSource->flOuterGain-1.0f));
- ConeHF = (1.0f+(OuterGainHF-1.0f));
- DryMix *= ConeVolume;
- if(ALSource->DryGainHFAuto)
- DryGainHF *= ConeHF;
- }
- else
- {
- ConeVolume = 1.0f;
- ConeHF = 1.0f;
- }
+ // Distance-based air absorption
+ if(ALSource->AirAbsorptionFactor > 0.0f && ALSource->DistanceModel != AL_NONE)
+ {
+ ALfloat dist = Distance-MinDist;
+ ALfloat absorb;
+
+ if(dist < 0.0f) dist = 0.0f;
+ // Absorption calculation is done in dB
+ absorb = (ALSource->AirAbsorptionFactor*AIRABSORBGAINDBHF) *
+ (dist*MetersPerUnit);
+ // Convert dB to linear gain before applying
+ absorb = pow(10.0, absorb/20.0);
+ DryGainHF *= absorb;
+ for(i = 0;i < MAX_SENDS;i++)
+ wetgainhf[i] *= absorb;
+ }
- //4. Calculate Velocity
- if(DopplerFactor != 0.0f)
- {
- ALfloat flVSS, flVLS = 0.0f;
+ //3. Apply directional soundcones
+ Angle = aluAcos(aluDotproduct(Direction,SourceToListener)) * 180.0f/M_PI;
+ if(Angle >= InnerAngle && Angle <= OuterAngle)
+ {
+ ALfloat scale = (Angle-InnerAngle) / (OuterAngle-InnerAngle);
+ ConeVolume = (1.0f+(ALSource->flOuterGain-1.0f)*scale);
+ ConeHF = (1.0f+(OuterGainHF-1.0f)*scale);
+ DryMix *= ConeVolume;
+ if(ALSource->DryGainHFAuto)
+ DryGainHF *= ConeHF;
+ }
+ else if(Angle > OuterAngle)
+ {
+ ConeVolume = (1.0f+(ALSource->flOuterGain-1.0f));
+ ConeHF = (1.0f+(OuterGainHF-1.0f));
+ DryMix *= ConeVolume;
+ if(ALSource->DryGainHFAuto)
+ DryGainHF *= ConeHF;
+ }
+ else
+ {
+ ConeVolume = 1.0f;
+ ConeHF = 1.0f;
+ }
- if(ALSource->bHeadRelative==AL_FALSE)
- flVLS = aluDotproduct(ALContext->Listener.Velocity, SourceToListener);
- flVSS = aluDotproduct(ALSource->vVelocity, SourceToListener);
+ //4. Calculate Velocity
+ if(DopplerFactor != 0.0f)
+ {
+ ALfloat flVSS, flVLS = 0.0f;
- flMaxVelocity = (DopplerVelocity * flSpeedOfSound) / DopplerFactor;
+ if(ALSource->bHeadRelative==AL_FALSE)
+ flVLS = aluDotproduct(ALContext->Listener.Velocity, SourceToListener);
+ flVSS = aluDotproduct(ALSource->vVelocity, SourceToListener);
- if (flVSS >= flMaxVelocity)
- flVSS = (flMaxVelocity - 1.0f);
- else if (flVSS <= -flMaxVelocity)
- flVSS = -flMaxVelocity + 1.0f;
+ flMaxVelocity = (DopplerVelocity * flSpeedOfSound) / DopplerFactor;
- if (flVLS >= flMaxVelocity)
- flVLS = (flMaxVelocity - 1.0f);
- else if (flVLS <= -flMaxVelocity)
- flVLS = -flMaxVelocity + 1.0f;
+ if(flVSS >= flMaxVelocity)
+ flVSS = (flMaxVelocity - 1.0f);
+ else if(flVSS <= -flMaxVelocity)
+ flVSS = -flMaxVelocity + 1.0f;
- pitch[0] = ALSource->flPitch *
- ((flSpeedOfSound * DopplerVelocity) - (DopplerFactor * flVLS)) /
- ((flSpeedOfSound * DopplerVelocity) - (DopplerFactor * flVSS));
- }
- else
- pitch[0] = ALSource->flPitch;
+ if(flVLS >= flMaxVelocity)
+ flVLS = (flMaxVelocity - 1.0f);
+ else if(flVLS <= -flMaxVelocity)
+ flVLS = -flMaxVelocity + 1.0f;
- for(i = 0;i < NumSends;i++)
- {
- if(ALSource->Send[i].Slot &&
- ALSource->Send[i].Slot->effect.type != AL_EFFECT_NULL)
- {
- if(ALSource->WetGainAuto)
- wetsend[i] *= ConeVolume;
- if(ALSource->WetGainHFAuto)
- wetgainhf[i] *= ConeHF;
+ pitch[0] = ALSource->flPitch *
+ ((flSpeedOfSound * DopplerVelocity) - (DopplerFactor * flVLS)) /
+ ((flSpeedOfSound * DopplerVelocity) - (DopplerFactor * flVSS));
+ }
+ else
+ pitch[0] = ALSource->flPitch;
- if(ALSource->Send[i].Slot->AuxSendAuto)
- {
- // Apply minimal attenuation in place of missing
- // statistical reverb model.
- wetsend[i] *= pow(DryMix, 1.0f / 2.0f);
- }
- else
- {
- // If the slot's auxilliary send auto is off, the data sent to the
- // effect slot is the same as the dry path, sans filter effects
- wetsend[i] = DryMix;
- wetgainhf[i] = DryGainHF;
- }
+ for(i = 0;i < NumSends;i++)
+ {
+ if(ALSource->Send[i].Slot &&
+ ALSource->Send[i].Slot->effect.type != AL_EFFECT_NULL)
+ {
+ if(ALSource->WetGainAuto)
+ wetsend[i] *= ConeVolume;
+ if(ALSource->WetGainHFAuto)
+ wetgainhf[i] *= ConeHF;
- switch(ALSource->Send[i].WetFilter.type)
- {
- case AL_FILTER_LOWPASS:
- wetsend[i] *= ALSource->Send[i].WetFilter.Gain;
- wetgainhf[i] *= ALSource->Send[i].WetFilter.GainHF;
- break;
- }
- wetsend[i] *= ListenerGain;
+ if(ALSource->Send[i].Slot->AuxSendAuto)
+ {
+ // Apply minimal attenuation in place of missing
+ // statistical reverb model.
+ wetsend[i] *= pow(DryMix, 1.0f / 2.0f);
}
else
{
- wetsend[i] = 0.0f;
- wetgainhf[i] = 1.0f;
+ // If the slot's auxiliary send auto is off, the data sent to
+ // the effect slot is the same as the dry path, sans filter
+ // effects
+ wetsend[i] = DryMix;
+ wetgainhf[i] = DryGainHF;
+ }
+
+ switch(ALSource->Send[i].WetFilter.type)
+ {
+ case AL_FILTER_LOWPASS:
+ wetsend[i] *= ALSource->Send[i].WetFilter.Gain;
+ wetgainhf[i] *= ALSource->Send[i].WetFilter.GainHF;
+ break;
}
+ wetsend[i] *= ListenerGain;
}
- for(i = NumSends;i < MAX_SENDS;i++)
+ else
{
wetsend[i] = 0.0f;
wetgainhf[i] = 1.0f;
}
-
- //5. Apply filter gains and filters
- switch(ALSource->DirectFilter.type)
- {
- case AL_FILTER_LOWPASS:
- DryMix *= ALSource->DirectFilter.Gain;
- DryGainHF *= ALSource->DirectFilter.GainHF;
- break;
- }
- DryMix *= ListenerGain;
-
- // Use energy-preserving panning algorithm for multi-speaker playback
- length = aluSqrt(Position[0]*Position[0] + Position[1]*Position[1] +
- Position[2]*Position[2]);
- length = __max(length, MinDist);
- if(length > 0.0f)
- {
- ALfloat invlen = 1.0f/length;
- Position[0] *= invlen;
- Position[1] *= invlen;
- Position[2] *= invlen;
- }
-
- pos = aluCart2LUTpos(-Position[2], Position[0]);
- SpeakerGain = &ALContext->PanningLUT[OUTPUTCHANNELS * pos];
-
- DirGain = aluSqrt(Position[0]*Position[0] + Position[2]*Position[2]);
- // elevation adjustment for directional gain. this sucks, but
- // has low complexity
- AmbientGain = 1.0/aluSqrt(ALContext->NumChan) * (1.0-DirGain);
- for(s = 0; s < OUTPUTCHANNELS; s++)
- {
- ALfloat gain = SpeakerGain[s]*DirGain + AmbientGain;
- drysend[s] = DryMix * gain;
- }
- *drygainhf = DryGainHF;
}
- else
+ for(i = NumSends;i < MAX_SENDS;i++)
{
- //1. Multi-channel buffers always play "normal"
- pitch[0] = ALSource->flPitch;
+ wetsend[i] = 0.0f;
+ wetgainhf[i] = 1.0f;
+ }
- DryMix = SourceVolume;
- DryMix = __min(DryMix,MaxVolume);
- DryMix = __max(DryMix,MinVolume);
+ //5. Apply filter gains and filters
+ switch(ALSource->DirectFilter.type)
+ {
+ case AL_FILTER_LOWPASS:
+ DryMix *= ALSource->DirectFilter.Gain;
+ DryGainHF *= ALSource->DirectFilter.GainHF;
+ break;
+ }
+ DryMix *= ListenerGain;
- switch(ALSource->DirectFilter.type)
- {
- case AL_FILTER_LOWPASS:
- DryMix *= ALSource->DirectFilter.Gain;
- DryGainHF *= ALSource->DirectFilter.GainHF;
- break;
- }
+ // Use energy-preserving panning algorithm for multi-speaker playback
+ length = aluSqrt(Position[0]*Position[0] + Position[1]*Position[1] +
+ Position[2]*Position[2]);
+ length = __max(length, MinDist);
+ if(length > 0.0f)
+ {
+ ALfloat invlen = 1.0f/length;
+ Position[0] *= invlen;
+ Position[1] *= invlen;
+ Position[2] *= invlen;
+ }
- drysend[FRONT_LEFT] = DryMix * ListenerGain;
- drysend[FRONT_RIGHT] = DryMix * ListenerGain;
- drysend[SIDE_LEFT] = DryMix * ListenerGain;
- drysend[SIDE_RIGHT] = DryMix * ListenerGain;
- drysend[BACK_LEFT] = DryMix * ListenerGain;
- drysend[BACK_RIGHT] = DryMix * ListenerGain;
- drysend[FRONT_CENTER] = DryMix * ListenerGain;
- drysend[BACK_CENTER] = DryMix * ListenerGain;
- drysend[LFE] = DryMix * ListenerGain;
- *drygainhf = DryGainHF;
+ pos = aluCart2LUTpos(-Position[2], Position[0]);
+ SpeakerGain = &ALContext->PanningLUT[OUTPUTCHANNELS * pos];
- for(i = 0;i < MAX_SENDS;i++)
- {
- wetsend[i] = 0.0f;
- wetgainhf[i] = 1.0f;
- }
+ DirGain = aluSqrt(Position[0]*Position[0] + Position[2]*Position[2]);
+ // elevation adjustment for directional gain. this sucks, but
+ // has low complexity
+ AmbientGain = 1.0/aluSqrt(ALContext->NumChan) * (1.0-DirGain);
+ for(s = 0; s < OUTPUTCHANNELS; s++)
+ {
+ ALfloat gain = SpeakerGain[s]*DirGain + AmbientGain;
+ drysend[s] = DryMix * gain;
}
+ *drygainhf = DryGainHF;
}
static __inline ALshort lerp(ALshort val1, ALshort val2, ALint frac)