Skip to content

Commit

Permalink
feat(hrtf): Nearest neighbor sampling of HRIR data.
Browse files Browse the repository at this point in the history
Signed-off-by: Axel Nana <[email protected]>
  • Loading branch information
na2axl committed Oct 3, 2024
1 parent 8d177ae commit 924eeee
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 26 deletions.
18 changes: 14 additions & 4 deletions include/SparkyStudios/Audio/Amplitude/HRTF/HRIRSphere.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,14 +150,24 @@ namespace SparkyStudios::Audio::Amplitude

/**
* @brief Samples the HRIR sphere for the given direction using bilinear interpolation.
* See more info here http://www02.smt.ufrj.br/~diniz/conf/confi117.pdf.
*
* @param direction The sound to listener direction.
* @param leftHRIR The left HRIR data.
* @param rightHRIR The right HRIR data.
* See more info about bilinear sampling [here](http://www02.smt.ufrj.br/~diniz/conf/confi117.pdf).
*
* @param[in] direction The sound to listener direction.
* @param[out] leftHRIR The left HRIR data.
* @param[out] rightHRIR The right HRIR data.
*/
virtual void SampleBilinear(const AmVec3& direction, AmReal32* leftHRIR, AmReal32* rightHRIR) const = 0;

/**
* @brief Samples the HRIR sphere for the given direction using nearest neighbor interpolation.
*
* @param[in] direction The sound to listener direction.
* @param[out] leftHRIR The left HRIR data.
* @param[out] rightHRIR The right HRIR data.
*/
virtual void SampleNearestNeighbor(const AmVec3& direction, AmReal32* leftHRIR, AmReal32* rightHRIR) const = 0;

virtual void Transform(const AmMat4& matrix) = 0;

[[nodiscard]] virtual bool IsLoaded() const = 0;
Expand Down
108 changes: 86 additions & 22 deletions src/HRTF/HRIRSphere.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,40 +149,71 @@ namespace SparkyStudios::Audio::Amplitude
if (face == nullptr)
return;

// If we are very close to any vertex, just return the HRIR of that vertex
{
const auto* vertex = GetClosestVertex(direction, face);

if (vertex != nullptr)
{
const AmSize length = vertex->m_LeftIR.size();
std::memcpy(leftHRIR, vertex->m_LeftIR.data(), length * sizeof(AmReal32));
std::memcpy(rightHRIR, vertex->m_RightIR.data(), length * sizeof(AmReal32));
return;
}
}

const auto& vertexA = _vertices[face->m_A];
const auto& vertexB = _vertices[face->m_B];
const auto& vertexC = _vertices[face->m_C];

// If we are close to any vertex, just return the HRIR of that vertex
// Otherwise, perform bilinear interpolation
{
constexpr AmReal32 k2 = kEpsilon * kEpsilon;

if (AM_LenSqr(vertexA.m_Position - direction) < k2)
BarycentricCoordinates barycenter;
if (!BarycentricCoordinates::RayTriangleIntersection(
AM_V3(0.0f, 0.0f, 0.0f), dir, { vertexA.m_Position, vertexB.m_Position, vertexC.m_Position }, barycenter))
{
const AmSize length = vertexA.m_LeftIR.size();
std::memcpy(leftHRIR, vertexA.m_LeftIR.data(), length * sizeof(AmReal32));
std::memcpy(rightHRIR, vertexA.m_RightIR.data(), length * sizeof(AmReal32));
return;
}

if (AM_LenSqr(vertexB.m_Position - direction) < k2)
const AmSize length = vertexA.m_LeftIR.size();

for (AmSize i = 0; i < length; ++i)
{
const AmSize length = vertexB.m_LeftIR.size();
std::memcpy(leftHRIR, vertexB.m_LeftIR.data(), length * sizeof(AmReal32));
std::memcpy(rightHRIR, vertexB.m_RightIR.data(), length * sizeof(AmReal32));
return;
leftHRIR[i] =
vertexA.m_LeftIR[i] * barycenter.m_U + vertexB.m_LeftIR[i] * barycenter.m_V + vertexC.m_LeftIR[i] * barycenter.m_W;

rightHRIR[i] =
vertexA.m_RightIR[i] * barycenter.m_U + vertexB.m_RightIR[i] * barycenter.m_V + vertexC.m_RightIR[i] * barycenter.m_W;
}
}
}

void HRIRSphereImpl::SampleNearestNeighbor(const AmVec3& direction, AmReal32* leftHRIR, AmReal32* rightHRIR) const
{
const auto& dir = AM_Mul(direction, 10.0f);
const auto* face = _tree.Query(dir);

if (AM_LenSqr(vertexC.m_Position - direction) < k2)
if (face == nullptr)
return;

// If we are very close to any vertex, just return the HRIR of that vertex
{
const auto* vertex = GetClosestVertex(direction, face);

if (vertex != nullptr)
{
const AmSize length = vertexC.m_LeftIR.size();
std::memcpy(leftHRIR, vertexC.m_LeftIR.data(), length * sizeof(AmReal32));
std::memcpy(rightHRIR, vertexC.m_RightIR.data(), length * sizeof(AmReal32));
const AmSize length = vertex->m_LeftIR.size();
std::memcpy(leftHRIR, vertex->m_LeftIR.data(), length * sizeof(AmReal32));
std::memcpy(rightHRIR, vertex->m_RightIR.data(), length * sizeof(AmReal32));
return;
}
}

// Otherwise, perform bilinear interpolation
const auto& vertexA = _vertices[face->m_A];
const auto& vertexB = _vertices[face->m_B];
const auto& vertexC = _vertices[face->m_C];

// Otherwise, perform nearest neighbor interpolation
{
BarycentricCoordinates barycenter;
if (!BarycentricCoordinates::RayTriangleIntersection(
Expand All @@ -192,14 +223,27 @@ namespace SparkyStudios::Audio::Amplitude
}

const AmSize length = vertexA.m_LeftIR.size();
const AmReal32 min = std::min({ barycenter.m_U, barycenter.m_V, barycenter.m_W });

for (AmSize i = 0; i < length; ++i)
if (min == barycenter.m_U)
{
leftHRIR[i] =
vertexA.m_LeftIR[i] * barycenter.m_U + vertexB.m_LeftIR[i] * barycenter.m_V + vertexC.m_LeftIR[i] * barycenter.m_W;
std::memcpy(leftHRIR, vertexA.m_LeftIR.data(), length * sizeof(AmReal32));
std::memcpy(rightHRIR, vertexA.m_RightIR.data(), length * sizeof(AmReal32));
return;
}

rightHRIR[i] =
vertexA.m_RightIR[i] * barycenter.m_U + vertexB.m_RightIR[i] * barycenter.m_V + vertexC.m_RightIR[i] * barycenter.m_W;
if (min == barycenter.m_V)
{
std::memcpy(leftHRIR, vertexB.m_LeftIR.data(), length * sizeof(AmReal32));
std::memcpy(rightHRIR, vertexB.m_RightIR.data(), length * sizeof(AmReal32));
return;
}

if (min == barycenter.m_W)
{
std::memcpy(leftHRIR, vertexC.m_LeftIR.data(), length * sizeof(AmReal32));
std::memcpy(rightHRIR, vertexC.m_RightIR.data(), length * sizeof(AmReal32));
return;
}
}
}
Expand All @@ -214,4 +258,24 @@ namespace SparkyStudios::Audio::Amplitude
{
return _loaded;
}

const HRIRSphereVertex* HRIRSphereImpl::GetClosestVertex(const AmVec3& position, const Face* face) const
{
const auto& vertexA = _vertices[face->m_A];
const auto& vertexB = _vertices[face->m_B];
const auto& vertexC = _vertices[face->m_C];

constexpr AmReal32 k2 = kEpsilon * kEpsilon;

if (AM_LenSqr(vertexA.m_Position - position) < k2)
return &vertexA;

if (AM_LenSqr(vertexB.m_Position - position) < k2)
return &vertexB;

if (AM_LenSqr(vertexC.m_Position - position) < k2)
return &vertexC;

return nullptr;
}
} // namespace SparkyStudios::Audio::Amplitude
3 changes: 3 additions & 0 deletions src/HRTF/HRIRSphere.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,13 @@ namespace SparkyStudios::Audio::Amplitude
[[nodiscard]] AmUInt32 GetSampleRate() const override;
[[nodiscard]] AmUInt32 GetIRLength() const override;
void SampleBilinear(const AmVec3& direction, AmReal32* leftHRIR, AmReal32* rightHRIR) const override;
void SampleNearestNeighbor(const AmVec3& direction, AmReal32* leftHRIR, AmReal32* rightHRIR) const override;
void Transform(const AmMat4& matrix) override;
[[nodiscard]] bool IsLoaded() const override;

private:
const HRIRSphereVertex* GetClosestVertex(const AmVec3& position, const Face* face) const;

HRIRSphereFileHeaderDescription _header;
std::vector<HRIRSphereVertex> _vertices;
std::vector<Face> _faces;
Expand Down

0 comments on commit 924eeee

Please sign in to comment.