Files
BT411/engine/MUNGA/AUDLOC.cpp
T

563 lines
15 KiB
C++

#include <cstdlib> // (BT411) getenv -- the 28Hz doppler-operand clock (cadence census item 4)
#include <map> // (BT411) per-instance sample-and-hold registry
#include "munga.h"
#pragma hdrstop
#include "audloc.h"
#include "objstrm.h"
#include "namelist.h"
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ AudioLocation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//#############################################################################
//#############################################################################
//
AudioLocation::AudioLocation(
PlugStream *stream,
Entity *entity
):
AudioComponent(stream),
vectorToSource(0.0f, 0.0f, 0.0f),
locationOffset(0.0f, 0.0f, 0.0f)
{
AudioFrameCount next_update_delay;
Scalar clipping_scale;
MemoryStream_Read(stream, &locationOffset);
MemoryStream_Read(stream, &next_update_delay);
MemoryStream_Read(stream, &clipping_scale);
AudioLocationX(entity, next_update_delay, clipping_scale);
}
//
//#############################################################################
//#############################################################################
//
AudioLocation::~AudioLocation()
{
}
//
//#############################################################################
//#############################################################################
//
void
AudioLocation::AudioLocationX(
Entity *entity,
AudioFrameCount next_update_delay,
Scalar clipping_scale
)
{
Check(entity);
linkedEntity = entity;
entity->AddAudioComponent(this);
clippingScale = clipping_scale;
isHeadSource = False;
distanceToSource = 1.0f;
dopplerCents = 0;
#if 0
angleOffOrientation = 0.0f;
#endif
azimuthOfSource = 0.0f;
distanceVolumeScale = 1.0f;
highFreqCutoffScale = 1.0f;
#if 0
reverbVolumeScale = 0.0f;
#endif
nextUpdateDelay = next_update_delay;
nextUpdateFrame = NullAudioFrameCount;
}
//
//#############################################################################
//#############################################################################
//
Logical
AudioLocation::TestInstance() const
{
Component::TestInstance();
return True;
}
//
//#############################################################################
//#############################################################################
//
void
AudioLocation::BuildFromPage(
PlugStream *stream,
NameList *name_list,
ClassID class_ID,
ObjectID object_ID
)
{
AudioComponent::BuildFromPage(stream, name_list, class_ID, object_ID);
MEM_STRM_WRITE_ENTRY(*stream, name_list, Point3D, location_offset);
//
// Read update delay
//
CString update_delay_string("update_delay");
AudioFrameCount update_delay = DefaultAudioFrameDelay;
if (name_list->FindData(update_delay_string) != NULL)
{
Check_Pointer(name_list->FindData(update_delay_string));
Convert_From_Ascii(
(const char *)name_list->FindData(update_delay_string),
&update_delay
);
}
MemoryStream_Write(stream, &update_delay);
//
// Read culling scale
//
CString clipping_scale_string("clipping_scale");
Scalar clipping_scale = 1.0f;
if (name_list->FindData(clipping_scale_string) != NULL)
{
Check_Pointer(name_list->FindData(clipping_scale_string));
Convert_From_Ascii(
(const char *)name_list->FindData(clipping_scale_string),
&clipping_scale
);
}
MemoryStream_Write(stream, &clipping_scale);
}
//
//#############################################################################
//#############################################################################
//
void
AudioLocation::ReceiveControl(
AudioControlID,
AudioControlValue
)
{
}
//
//#############################################################################
//#############################################################################
//
Logical
AudioLocation::IsAudioLocationClipped(AudioHead *audio_head)
{
Check(this);
Check(audio_head);
//
// HACK - This is a rough approximation in that it does not
// take into account the offset of the location from the
// center of the entity. But, this is more efficient and is OK
// as long as the clipping sphere does not exclude the location
// unnaturally
//
Entity
*head_entity;
Vector3D
difference;
Scalar
scaled_radius;
head_entity = audio_head->GetHeadEntity();
Check(head_entity);
Check(linkedEntity);
difference.Subtract(
head_entity->localOrigin.linearPosition,
linkedEntity->localOrigin.linearPosition
);
scaled_radius = audio_head->GetClippingRadius() * clippingScale;
return difference.LengthSquared() > scaled_radius * scaled_radius;
}
//
//#############################################################################
//#############################################################################
//
void
AudioLocation::UpdateSpatialModelImplementation(AudioHead *audio_head)
{
Check(this);
Check(audio_head);
//
//--------------------------------------------------------------------------
// Get head and source entity
//--------------------------------------------------------------------------
//
Entity *source_entity = GetLinkedEntity();
Entity *head_entity = audio_head->GetHeadEntity();
Check(source_entity);
Check(head_entity);
//
//--------------------------------------------------------------------------
// Catch case of head == source at origin
//--------------------------------------------------------------------------
//
if (
head_entity == source_entity &&
locationOffset == Vector3D::Identity
)
{
isHeadSource = True;
vectorToSource = Vector3D::Identity;
distanceToSource = 0.0f;
dopplerCents = 0;
#if 0
angleOffOrientation = 0.0f;
#endif
azimuthOfSource = 0.0f;
distanceVolumeScale = 1.0f;
highFreqCutoffScale = 1.0f;
#if 0
reverbVolumeScale = audio_head->GetReverbToDryRatio();
#endif
return;
}
isHeadSource = False;
//
//--------------------------------------------------------------------------
// Get the ear to world linear matrix
//--------------------------------------------------------------------------
//
LinearMatrix ear_to_world = audio_head->GetEarToWorld();
//
//--------------------------------------------------------------------------
// Calculate vector to source with respect to local coordinate system
// of the ears
//--------------------------------------------------------------------------
//
{
//
// Get the source location offset from the entity position in
// world coordinates
//
Vector3D world_location_offset;
Vector3D world_source_location;
world_location_offset.Multiply(
locationOffset,
source_entity->localToWorld
);
Check(&world_location_offset);
world_source_location.Add(
source_entity->localOrigin.linearPosition,
world_location_offset
);
Check(&world_source_location);
//
// Get the vector from the ear to the source, note that the vector
// is not calculated by the ears offset but from the entities location
//
Vector3D world_ear_to_source_vector;
world_ear_to_source_vector.Subtract(
world_source_location,
head_entity->localOrigin.linearPosition
);
Check(&world_ear_to_source_vector);
//
// Transform the vector into ear coordinates
//
vectorToSource.MultiplyByInverse(
world_ear_to_source_vector,
ear_to_world
);
Check(&vectorToSource);
}
//
//--------------------------------------------------------------------------
// Calculate distance to source
//--------------------------------------------------------------------------
//
distanceToSource = vectorToSource.Length();
//
//--------------------------------------------------------------------------
// Calculate normal to source
//--------------------------------------------------------------------------
//
Vector3D normal_to_source(0.0f, 0.0f, -1.0f);
if (!Small_Enough(distanceToSource))
{
normal_to_source.Normalize(vectorToSource);
Check(&normal_to_source);
}
#if 0
//
//--------------------------------------------------------------------------
// Calculate angle between the orientation of the head and source
//--------------------------------------------------------------------------
//
if (Small_Enough(distanceToSource))
{
angleOffOrientation = 0.0f;
}
else
{
UnitVector direction;
Scalar cosine;
ear_to_world.GetToAxis(Z_Axis, &direction);
Check(&direction);
Check(&normal_to_source);
cosine = normal_to_source * direction;
Clamp(cosine, -1.0f, 1.0f);
angleOffOrientation = Arccos(cosine);
angleOffOrientation.Normalize();
}
Verify(
angleOffOrientation <= 180.0f*RAD_PER_DEG &&
angleOffOrientation >= -180.0f*RAD_PER_DEG
);
#endif
//
//--------------------------------------------------------------------------
// Calculate azimuth
//--------------------------------------------------------------------------
//
if (Small_Enough(vectorToSource.x) && Small_Enough(vectorToSource.z))
{
azimuthOfSource = 0.0f;
}
else
{
Verify(!(Small_Enough(vectorToSource.x) && Small_Enough(vectorToSource.z)));
azimuthOfSource = Arctan(vectorToSource.x, vectorToSource.z);
}
Verify(
azimuthOfSource <= 180.0f*RAD_PER_DEG &&
azimuthOfSource >= -180.0f*RAD_PER_DEG
);
//
//--------------------------------------------------------------------------
// Calculate distance volume scaling
// Apply clipping scale to rolloff distance scale, thereby giving this
// audio location a specifc rolloff characteristic
//--------------------------------------------------------------------------
//
if (distanceToSource > audio_head->GetAmplitudeRollOffKnee())
{
//
// y = 1 / 1 + (K(x - knee))^exp
//
Verify(!Small_Enough(clippingScale));
const Scalar temp =
(audio_head->GetAmplitudeRollOffDistanceScale() / clippingScale) *
(distanceToSource - audio_head->GetAmplitudeRollOffKnee());
const Scalar temp2 =
1.0f + pow(temp, audio_head->GetAmplitudeRollOffExponent());
Verify(!Small_Enough(temp2));
distanceVolumeScale = 1.0f / temp2;
}
else
{
distanceVolumeScale = 1.0f;
}
Verify(distanceVolumeScale >= 0.0f && distanceVolumeScale <= 1.0f);
//
//--------------------------------------------------------------------------
// Calculate high frequency cutoff scaling
// Apply clipping scale to high frequency rolloff distance scale, thereby
// giving this audio location a specifc rolloff characteristic
//--------------------------------------------------------------------------
//
if (distanceToSource > audio_head->GetHighFrequencyRollOffKnee())
{
//
// y = 1 / 1 + (K(x - knee))^exp
//
Verify(!Small_Enough(clippingScale));
const Scalar temp =
(audio_head->GetHighFrequencyRollOffDistanceScale() / clippingScale) *
(distanceToSource - audio_head->GetHighFrequencyRollOffKnee());
const Scalar temp2 =
1.0f + pow(temp, audio_head->GetHighFrequencyRollOffExponent());
Verify(!Small_Enough(temp2));
highFreqCutoffScale = 1.0f / temp2;
}
else
{
highFreqCutoffScale = 1.0f;
}
Verify(highFreqCutoffScale >= 0.0f && highFreqCutoffScale <= 1.0f);
#if 0
//
//--------------------------------------------------------------------------
// Calculate reverb volume scaling
//--------------------------------------------------------------------------
//
if (distanceToSource > audio_head->GetAmplitudeRollOffKnee())
{
const Scalar temp =
distanceToSource - audio_head->GetAmplitudeRollOffKnee() + 1.0f;
reverbVolumeScale =
audio_head->GetReverbToDryRatio() *
pow(temp, audio_head->GetAmplitudeRollOffExponent());
if (reverbVolumeScale > 1.0f)
reverbVolumeScale = 1.0f;
}
else
{
reverbVolumeScale = audio_head->GetReverbToDryRatio();
}
Verify(reverbVolumeScale >= 0.0f && reverbVolumeScale <= 1.0f);
#endif
//
//--------------------------------------------------------------------------
// Calculate relative velocity of source
//--------------------------------------------------------------------------
//
Vector3D relative_velocity_temp;
Vector3D relative_velocity;
relative_velocity_temp.Subtract(
source_entity->GetWorldLinearVelocity(),
head_entity->GetWorldLinearVelocity()
);
Check(&relative_velocity_temp);
//
// (BT411 cadence census item 4, gotcha 32) 28 Hz SAMPLE-AND-HOLD on the
// doppler velocity operand: worldLinearVelocity is a raw per-render-frame
// position derivative (the mech4 producer feeds other consumers and is
// NOT touched); at ~59 fps its gait-aliasing noise reaches this divisor
// directly and warbles dopplerCents per stride. The pod computed this
// once per 28 Hz frame. Hold the RELATIVE WORLD velocity per instance
// on a 28 Hz wall clock; the head-frame transform below stays live per
// call (geometry is smooth -- only the noisy operand is clocked).
// BT_AUD_DOPPLER_HZ=<hz> overrides (=0 restores per-call sampling).
//
int dopplerFresh = -1; // receipt: -1 legacy / 1 sampled / 0 held
{
static Scalar s_dopHz = -2.0f;
if (s_dopHz < -1.0f)
{
const char *hz = getenv("BT_AUD_DOPPLER_HZ");
s_dopHz = (hz != 0 && *hz != '\0') ? (Scalar)atof(hz) : 28.0f;
}
if (s_dopHz > 0.0f)
{
struct DopplerHold { DWORD nextMs; Vector3D held; };
static std::map<const void *, DopplerHold> s_dopHold;
DopplerHold &h = s_dopHold[this];
const DWORD now = GetTickCount();
if (h.nextMs == 0 || (long)(now - h.nextMs) >= 0)
{
h.held = relative_velocity_temp;
h.nextMs = now + (DWORD)(1000.0f / s_dopHz);
dopplerFresh = 1;
}
else
{
relative_velocity_temp = h.held;
dopplerFresh = 0;
}
}
}
relative_velocity.MultiplyByInverse(
relative_velocity_temp,
head_entity->localToWorld
);
Check(&relative_velocity);
//
//--------------------------------------------------------------------------
// Calculate doppler shift in cents
// cents = (c / (c - v)) / ScaleRatio
//--------------------------------------------------------------------------
//
if (!Small_Enough(distanceToSource))
{
AudioPitchCents
cents;
Scalar speed_of_source =
normal_to_source * relative_velocity;
const Scalar ear_radius =
audio_head->GetDistanceBetweenEars() * 0.5f;
if (distanceToSource < ear_radius)
{
Verify(!Small_Enough(ear_radius));
speed_of_source *= (distanceToSource / ear_radius);
}
if (CalculateDoppler(audio_head, speed_of_source, &cents))
{
dopplerCents = cents;
}
// (BT411) doppler receipt -- jitter A/B for the 28Hz operand clock.
// hm2 = |held relative WORLD velocity|^2 (the clocked operand itself,
// geometry-free); h = 1 fresh sample / 0 held / -1 legacy per-call.
if (getenv("BT_AUD_DOPPLER_LOG")) { static int s_dl = 0; if (s_dl++ < 6000)
DEBUG_STREAM << "[doppler] t=" << (GetTickCount() % 1000000)
<< " this=" << (void *)this
<< " cents=" << dopplerCents
<< " v=" << speed_of_source
<< " d=" << distanceToSource
<< " h=" << dopplerFresh
<< " hm2=" << relative_velocity_temp.LengthSquared()
<< "\n" << std::flush; }
}
else
{
dopplerCents = 0;
}
}
//
//#############################################################################
//#############################################################################
//
Logical
AudioLocation::CalculateDoppler(
AudioHead *audio_head,
Scalar speed,
AudioPitchCents *result
)
{
Check(this);
Check(audio_head);
Check_Pointer(result);
Scalar speed_of_sound = audio_head->GetAudioSoundSpeed();
Clamp(speed, -speed_of_sound, speed_of_sound);
if (!Small_Enough(speed_of_sound - speed))
{
*result = audio_head->GetAudioDopplerConstant() *
( 1 - speed_of_sound / (speed_of_sound - speed) );
return True;
}
return False;
}