Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build-antora-doc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
- name: "Install npm"
uses: actions/setup-node@v4
with:
node-version: 'latest'
node-version: 'lts/*'
cache: 'npm'
cache-dependency-path: './antora-ui-khronos/package-lock.json'
- name: "Install dependencies"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/simple_engine_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ jobs:
distribution: 'temurin'

- name: Setup Android SDK
uses: android-actions/setup-android@v3
uses: android-actions/setup-android@v4

- name: Cache Gradle packages
uses: actions/cache@v4
Expand Down
35 changes: 35 additions & 0 deletions attachments/advanced_gltf/audio_system.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,38 @@
#include "engine.h"
#include "renderer.h"

// Rotate a world-space direction into the listener's frame (+X right, +Y up, +Z ahead).
// orientation is {forward, up}; right = forward x up. This is the frame the HRTF table is laid out in.
// Leaves the direction unchanged if the orientation is degenerate.
static void WorldToListenerSpace(float direction[3], const float orientation[6]) {
const float* fwd = &orientation[0];
const float* up = &orientation[3];
auto normalize = [](float v[3]) {
float len = std::sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
if (len <= 0.0001f) return false;
v[0] /= len; v[1] /= len; v[2] /= len;
return true;
};
auto cross = [](const float a[3], const float b[3], float out[3]) {
out[0] = a[1] * b[2] - a[2] * b[1];
out[1] = a[2] * b[0] - a[0] * b[2];
out[2] = a[0] * b[1] - a[1] * b[0];
};
auto dot = [](const float a[3], const float b[3]) { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; };

float f[3] = {fwd[0], fwd[1], fwd[2]};
float r[3];
cross(f, up, r);
if (!normalize(f) || !normalize(r)) return;
float u[3];
cross(r, f, u);

const float local[3] = {dot(direction, r), dot(direction, u), dot(direction, f)};
direction[0] = local[0];
direction[1] = local[1];
direction[2] = local[2];
}

#if !defined(PLATFORM_ANDROID)
// OpenAL error checking utility
static void CheckOpenALError(const std::string& operation) {
Expand Down Expand Up @@ -1344,6 +1376,9 @@ bool AudioSystem::ProcessHRTF(const float* inputBuffer, float* outputBuffer, uin
direction[2] = -1.0f; // Default to front
}

// Rotate into the listener's frame so head turns change the result
WorldToListenerSpace(direction, listenerOrientation);

// Calculate azimuth and elevation
float azimuth = std::atan2(direction[0], direction[2]);
float elevation = std::asin(std::max(-1.0f, std::min(1.0f, direction[1])));
Expand Down
24 changes: 15 additions & 9 deletions attachments/openxr_engine/shaders/hrtf.slang
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ uint FindClosestHRTF(float azimuth, float elevation) {
// In a real implementation, this would find the closest HRTF in the dataset
// based on the azimuth and elevation angles

// Normalize azimuth to [0, 360) degrees
azimuth = fmod(azimuth + 360.0, 360.0);
// Azimuth arrives as 0 = ahead, positive = right. The table starts at -180 degrees (directly behind),
// so shift by 180 and wrap into [0, 360) to get the table's angular offset.
azimuth = fmod(azimuth + 180.0 + 360.0, 360.0);

// Clamp elevation to [-90, 90] degrees
elevation = clamp(elevation, -90.0, 90.0);
Expand All @@ -64,13 +65,18 @@ uint FindClosestHRTF(float azimuth, float elevation) {

// Helper function to calculate azimuth and elevation angles
void CalculateAngles(float3 sourceDir, float3 listenerForward, float3 listenerUp, out float azimuth, out float elevation) {
// Simplified angle calculation - directly use source direction
// Calculate azimuth (horizontal angle) - angle around Y axis
azimuth = atan2(sourceDir.x, -sourceDir.z) * 57.2957795; // Convert to degrees, negate z for correct orientation

// Calculate elevation (vertical angle) - angle from horizontal plane
float horizontalLength = sqrt(sourceDir.x * sourceDir.x + sourceDir.z * sourceDir.z);
elevation = atan2(sourceDir.y, horizontalLength) * 57.2957795; // Convert to degrees
// Express the source direction in the listener's frame: +Z ahead, +X right, +Y up
float3 f = normalize(listenerForward);
float3 r = normalize(cross(f, listenerUp));
float3 u = cross(r, f);
float3 local = float3(dot(sourceDir, r), dot(sourceDir, u), dot(sourceDir, f));

// Azimuth (horizontal angle): 0 = ahead, positive = right
azimuth = atan2(local.x, local.z) * 57.2957795; // Convert to degrees

// Elevation (vertical angle) - angle from horizontal plane
float horizontalLength = sqrt(local.x * local.x + local.z * local.z);
elevation = atan2(local.y, horizontalLength) * 57.2957795; // Convert to degrees
}

// Main compute shader function
Expand Down
35 changes: 35 additions & 0 deletions attachments/simple_engine/audio_system.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,38 @@
#include "engine.h"
#include "renderer.h"

// Rotate a world-space direction into the listener's frame (+X right, +Y up, +Z ahead).
// orientation is {forward, up}; right = forward x up. This is the frame the HRTF table is laid out in.
// Leaves the direction unchanged if the orientation is degenerate.
static void WorldToListenerSpace(float direction[3], const float orientation[6]) {
const float* fwd = &orientation[0];
const float* up = &orientation[3];
auto normalize = [](float v[3]) {
float len = std::sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
if (len <= 0.0001f) return false;
v[0] /= len; v[1] /= len; v[2] /= len;
return true;
};
auto cross = [](const float a[3], const float b[3], float out[3]) {
out[0] = a[1] * b[2] - a[2] * b[1];
out[1] = a[2] * b[0] - a[0] * b[2];
out[2] = a[0] * b[1] - a[1] * b[0];
};
auto dot = [](const float a[3], const float b[3]) { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; };

float f[3] = {fwd[0], fwd[1], fwd[2]};
float r[3];
cross(f, up, r);
if (!normalize(f) || !normalize(r)) return;
float u[3];
cross(r, f, u);

const float local[3] = {dot(direction, r), dot(direction, u), dot(direction, f)};
direction[0] = local[0];
direction[1] = local[1];
direction[2] = local[2];
}

#if !defined(PLATFORM_ANDROID)
// OpenAL error checking utility
static void CheckOpenALError(const std::string& operation) {
Expand Down Expand Up @@ -1344,6 +1376,9 @@ bool AudioSystem::ProcessHRTF(const float* inputBuffer, float* outputBuffer, uin
direction[2] = -1.0f; // Default to front
}

// Rotate into the listener's frame so head turns change the result
WorldToListenerSpace(direction, listenerOrientation);

// Calculate azimuth and elevation
float azimuth = std::atan2(direction[0], direction[2]);
float elevation = std::asin(std::max(-1.0f, std::min(1.0f, direction[1])));
Expand Down
24 changes: 15 additions & 9 deletions attachments/simple_engine/shaders/hrtf.slang
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ uint FindClosestHRTF(float azimuth, float elevation) {
// In a real implementation, this would find the closest HRTF in the dataset
// based on the azimuth and elevation angles

// Normalize azimuth to [0, 360) degrees
azimuth = fmod(azimuth + 360.0, 360.0);
// Azimuth arrives as 0 = ahead, positive = right. The table starts at -180 degrees (directly behind),
// so shift by 180 and wrap into [0, 360) to get the table's angular offset.
azimuth = fmod(azimuth + 180.0 + 360.0, 360.0);

// Clamp elevation to [-90, 90] degrees
elevation = clamp(elevation, -90.0, 90.0);
Expand All @@ -64,13 +65,18 @@ uint FindClosestHRTF(float azimuth, float elevation) {

// Helper function to calculate azimuth and elevation angles
void CalculateAngles(float3 sourceDir, float3 listenerForward, float3 listenerUp, out float azimuth, out float elevation) {
// Simplified angle calculation - directly use source direction
// Calculate azimuth (horizontal angle) - angle around Y axis
azimuth = atan2(sourceDir.x, -sourceDir.z) * 57.2957795; // Convert to degrees, negate z for correct orientation

// Calculate elevation (vertical angle) - angle from horizontal plane
float horizontalLength = sqrt(sourceDir.x * sourceDir.x + sourceDir.z * sourceDir.z);
elevation = atan2(sourceDir.y, horizontalLength) * 57.2957795; // Convert to degrees
// Express the source direction in the listener's frame: +Z ahead, +X right, +Y up
float3 f = normalize(listenerForward);
float3 r = normalize(cross(f, listenerUp));
float3 u = cross(r, f);
float3 local = float3(dot(sourceDir, r), dot(sourceDir, u), dot(sourceDir, f));

// Azimuth (horizontal angle): 0 = ahead, positive = right
azimuth = atan2(local.x, local.z) * 57.2957795; // Convert to degrees

// Elevation (vertical angle) - angle from horizontal plane
float horizontalLength = sqrt(local.x * local.x + local.z * local.z);
elevation = atan2(local.y, horizontalLength) * 57.2957795; // Convert to degrees
}

// Main compute shader function
Expand Down
35 changes: 35 additions & 0 deletions attachments/sync2_engine/audio_system.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,38 @@
#include "engine.h"
#include "renderer.h"

// Rotate a world-space direction into the listener's frame (+X right, +Y up, +Z ahead).
// orientation is {forward, up}; right = forward x up. This is the frame the HRTF table is laid out in.
// Leaves the direction unchanged if the orientation is degenerate.
static void WorldToListenerSpace(float direction[3], const float orientation[6]) {
const float* fwd = &orientation[0];
const float* up = &orientation[3];
auto normalize = [](float v[3]) {
float len = std::sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
if (len <= 0.0001f) return false;
v[0] /= len; v[1] /= len; v[2] /= len;
return true;
};
auto cross = [](const float a[3], const float b[3], float out[3]) {
out[0] = a[1] * b[2] - a[2] * b[1];
out[1] = a[2] * b[0] - a[0] * b[2];
out[2] = a[0] * b[1] - a[1] * b[0];
};
auto dot = [](const float a[3], const float b[3]) { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; };

float f[3] = {fwd[0], fwd[1], fwd[2]};
float r[3];
cross(f, up, r);
if (!normalize(f) || !normalize(r)) return;
float u[3];
cross(r, f, u);

const float local[3] = {dot(direction, r), dot(direction, u), dot(direction, f)};
direction[0] = local[0];
direction[1] = local[1];
direction[2] = local[2];
}

#if !defined(PLATFORM_ANDROID)
// OpenAL error checking utility
static void CheckOpenALError(const std::string& operation) {
Expand Down Expand Up @@ -1346,6 +1378,9 @@ bool AudioSystem::ProcessHRTF(const float* inputBuffer, float* outputBuffer, uin
direction[2] = -1.0f; // Default to front
}

// Rotate into the listener's frame so head turns change the result
WorldToListenerSpace(direction, listenerOrientation);

// Calculate azimuth and elevation
float azimuth = std::atan2(direction[0], direction[2]);
float elevation = std::asin(std::max(-1.0f, std::min(1.0f, direction[1])));
Expand Down
30 changes: 16 additions & 14 deletions en/Building_a_Simple_Engine/Subsystems/03_vulkan_audio.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ private:
void CleanupVulkanResources();

// Process audio with HRTF using Vulkan
void ProcessAudioWithVulkan(float* inputBuffer, float* outputBuffer, size_t frameCount);
void ProcessAudioWithVulkan(float* inputBuffer, float* outputBuffer, size_t frameCount,
float azimuth, float elevation);
};

} // namespace Audio
Expand Down Expand Up @@ -215,7 +216,8 @@ void AudioSystem::InitializeVulkanResources() {
m_VulkanResources.commandBuffer = std::move(commandBuffers[0]);
}

void AudioSystem::ProcessAudioWithVulkan(float* inputBuffer, float* outputBuffer, size_t frameCount) {
void AudioSystem::ProcessAudioWithVulkan(float* inputBuffer, float* outputBuffer, size_t frameCount,
float azimuth, float elevation) {
if (!m_HRTFEnabled || !m_HRTFDatabase) {
// If HRTF is disabled, just copy input to output (or do simple stereo panning)
memcpy(outputBuffer, inputBuffer, frameCount * sizeof(float));
Expand All @@ -231,10 +233,9 @@ void AudioSystem::ProcessAudioWithVulkan(float* inputBuffer, float* outputBuffer
memcpy(data, inputBuffer, frameCount * sizeof(float));
vkUnmapMemory(device, *m_VulkanResources.inputBufferMemory);

// Update HRTF data based on source positions
// In a real implementation, you would update this for each sound source
// For simplicity, we're just using a single HRTF filter here
const auto& hrtfData = m_HRTFDatabase->GetHRTFData(0.0f, 0.0f);
// Select the HRTF filter for this source's direction relative to the listener.
// Each source is processed separately, so the filter is updated per call.
const auto& hrtfData = m_HRTFDatabase->GetHRTFData(azimuth, elevation);
vkMapMemory(device, *m_VulkanResources.hrtfBufferMemory, 0, sizeof(HRTFData), 0, &data);
memcpy(data, &hrtfData, sizeof(HRTFData));
vkUnmapMemory(device, *m_VulkanResources.hrtfBufferMemory);
Expand Down Expand Up @@ -276,13 +277,13 @@ void AudioSystem::Update(float deltaTime) {
// Calculate spatial position relative to listener
glm::vec3 relativePosition = source->GetPosition() - m_Listener.GetPosition();

// Rotate relative position based on listener orientation
glm::mat3 listenerOrientation(
glm::cross(m_Listener.GetForward(), m_Listener.GetUp()),
m_Listener.GetUp(),
-m_Listener.GetForward()
);
relativePosition = listenerOrientation * relativePosition;
// Rotate relative position into the listener's frame (+X right, +Y up, +Z ahead).
// The columns form the listener-to-world basis, so its transpose maps world to listener space.
glm::vec3 forward = glm::normalize(m_Listener.GetForward());
glm::vec3 right = glm::normalize(glm::cross(forward, m_Listener.GetUp()));
glm::vec3 up = glm::cross(right, forward);
glm::mat3 listenerToWorld(right, up, forward);
relativePosition = glm::transpose(listenerToWorld) * relativePosition;

// Calculate azimuth and elevation
float distance = glm::length(relativePosition);
Expand All @@ -296,7 +297,8 @@ void AudioSystem::Update(float deltaTime) {

// Process audio with HRTF using Vulkan
float processedAudio[2048]; // Stereo output (2 channels)
ProcessAudioWithVulkan(const_cast<float*>(audioData), processedAudio, framesToProcess);
ProcessAudioWithVulkan(const_cast<float*>(audioData), processedAudio, framesToProcess,
azimuth, elevation);

// Send processed audio to the audio backend
// ...
Expand Down
Loading