Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016bw71WVsccjwW7uKRg4aWD
563 lines
15 KiB
C++
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, ¢s))
|
|
{
|
|
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;
|
|
}
|