diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a22204d..c4817dc6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -173,6 +173,7 @@ set(SA_SOURCE include/SparkyStudios/Audio/Amplitude/Math/Curve.h include/SparkyStudios/Audio/Amplitude/Math/FFT.h include/SparkyStudios/Audio/Amplitude/Math/HandmadeMath.h + include/SparkyStudios/Audio/Amplitude/Math/PolarPosition.h include/SparkyStudios/Audio/Amplitude/Math/Shape.h include/SparkyStudios/Audio/Amplitude/Math/SplitComplex.h include/SparkyStudios/Audio/Amplitude/Math/Utils.h @@ -254,6 +255,7 @@ set(SA_SOURCE src/Math/Curve.cpp src/Math/FFT.cpp + src/Math/PolarPosition.cpp src/Math/Shape.cpp src/Math/SplitComplex.cpp diff --git a/include/SparkyStudios/Audio/Amplitude/Amplitude.h b/include/SparkyStudios/Audio/Amplitude/Amplitude.h index 6484fe0c..9cb14c8b 100644 --- a/include/SparkyStudios/Audio/Amplitude/Amplitude.h +++ b/include/SparkyStudios/Audio/Amplitude/Amplitude.h @@ -51,6 +51,7 @@ #include #include #include +#include #include #include #include diff --git a/include/SparkyStudios/Audio/Amplitude/Core/Common.h b/include/SparkyStudios/Audio/Amplitude/Core/Common.h index 68b86798..a653744e 100644 --- a/include/SparkyStudios/Audio/Amplitude/Core/Common.h +++ b/include/SparkyStudios/Audio/Amplitude/Core/Common.h @@ -253,6 +253,25 @@ namespace SparkyStudios::Audio::Amplitude AM_FADER_STATE_ACTIVE = 1, }; + /** + * @brief Enumerates the list of supported up axis. + * + * This should be synchronized with the game engine settings. + * It will affect how vectors and matrices calculation are performed. + */ + enum GameEngineUpAxis : AmUInt8 + { + /** + * @brief The up axis is Y. + */ + eUpAxis_Y = 0, + + /** + * @brief The up axis is Z. + */ + eUpAxis_Z = 1, + }; + /** * @brief Describe the format of an audio sample. * diff --git a/include/SparkyStudios/Audio/Amplitude/Math/PolarPosition.h b/include/SparkyStudios/Audio/Amplitude/Math/PolarPosition.h new file mode 100644 index 00000000..da0ef699 --- /dev/null +++ b/include/SparkyStudios/Audio/Amplitude/Math/PolarPosition.h @@ -0,0 +1,33 @@ +// Copyright (c) 2024-present Sparky Studios. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +namespace SparkyStudios::Audio::Amplitude +{ + struct PolarPosition + { + PolarPosition() = default; + PolarPosition(AmReal32 azimuth, AmReal32 elevation, AmReal32 radius); + PolarPosition(AmVec3 originPosition, AmVec3 originDirection, AmVec3 originUp, AmVec3 position); + + [[nodiscard]] AmVec3 ToCartesian(GameEngineUpAxis upAxis) const; + + AmReal32 m_Azimuth = 0; + AmReal32 m_Elevation = 0; + AmReal32 m_Radius = 0; + }; +} // namespace SparkyStudios::Audio::Amplitude diff --git a/src/Core/Engine.cpp b/src/Core/Engine.cpp index 2d371012..be9b5130 100644 --- a/src/Core/Engine.cpp +++ b/src/Core/Engine.cpp @@ -685,7 +685,7 @@ namespace SparkyStudios::Audio::Amplitude _state->samples_per_stream = config->output()->buffer_size() / config->output()->channels(); // Set the game engine up axis - _state->up_axis = config->game()->up_axis(); + _state->up_axis = static_cast(config->game()->up_axis()); // Save obstruction/occlusion configurations _state->obstruction_config.Init(config->game()->obstruction()); @@ -1043,10 +1043,10 @@ namespace SparkyStudios::Audio::Amplitude switch (amEngine->GetState()->up_axis) { default: - case eGameEngineUpAxis_Y: + case eUpAxis_Y: return AM_V2(AM_Dot(AM_V3(1, 0, 0), direction), AM_Dot(AM_V3(0, 0, 1), direction)); - case eGameEngineUpAxis_Z: + case eUpAxis_Z: return AM_V2(AM_Dot(AM_V3(1, 0, 0), direction), AM_Dot(AM_V3(0, 1, 0), direction)); } } diff --git a/src/Core/EngineInternalState.h b/src/Core/EngineInternalState.h index 32a00792..083caf2d 100644 --- a/src/Core/EngineInternalState.h +++ b/src/Core/EngineInternalState.h @@ -141,7 +141,7 @@ namespace SparkyStudios::Audio::Amplitude , listener_fetch_mode(eListenerFetchMode_None) , sound_speed(333.0) , doppler_factor(1.0) - , up_axis(eGameEngineUpAxis_Y) + , up_axis(eUpAxis_Y) , obstruction_config() , occlusion_config() , track_environments(false) @@ -266,7 +266,7 @@ namespace SparkyStudios::Audio::Amplitude AmReal32 doppler_factor; // The up axis of the game engine. - eGameEngineUpAxis up_axis; + GameEngineUpAxis up_axis; ObstructionOcclusionState obstruction_config; diff --git a/src/Math/PolarPosition.cpp b/src/Math/PolarPosition.cpp new file mode 100644 index 00000000..4921301c --- /dev/null +++ b/src/Math/PolarPosition.cpp @@ -0,0 +1,93 @@ +// Copyright (c) 2024-present Sparky Studios. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +namespace SparkyStudios::Audio::Amplitude +{ + PolarPosition::PolarPosition(AmReal32 azimuth, AmReal32 elevation, AmReal32 radius) + : m_Azimuth(azimuth) + , m_Elevation(elevation) + , m_Radius(radius) + {} + + PolarPosition::PolarPosition(AmVec3 originPosition, AmVec3 originDirection, AmVec3 originUp, AmVec3 position) + { + if (originPosition == position) + { + m_Azimuth = 0; + m_Elevation = 0; + m_Radius = 0; + return; + } + + AmVec3 fwd = AM_Norm(originDirection); + AmVec3 right = AM_Norm(AM_Cross(fwd, originUp)); + AmVec3 up = AM_Cross(right, fwd); + AmVec3 pos = position - originPosition; + + m_Radius = AM_Len(pos); + + pos = AM_V3(AM_Dot(pos, right), AM_Dot(pos, up), AM_Dot(pos, fwd)); + + if (AM_EqV3(up, AM_V3(0, 1, 0))) + { + m_Azimuth = std::atan2(pos.X, pos.Z); + + AmVec3 proj = AM_V3(pos.X, 0, pos.Z); + m_Elevation = std::acos(AM_Dot(AM_Norm(pos), AM_Norm(proj))); + + if (pos.Y < 0) + m_Elevation = -m_Elevation; + } + else if (AM_EqV3(up, AM_V3(0, 0, 1))) + { + m_Azimuth = std::atan2(pos.X, pos.Y); + + AmVec3 proj = AM_V3(pos.X, pos.Y, 0); + m_Elevation = std::acos(AM_Dot(AM_Norm(pos), AM_Norm(proj))); + + if (pos.Z < 0) + m_Elevation = -m_Elevation; + } + } + + AmVec3 PolarPosition::ToCartesian(GameEngineUpAxis upAxis) const + { + switch (upAxis) + { + case eUpAxis_Y: + { + // Translates spherical to cartesian, where Y - up, Z - forward, X - right + const AmReal32 x = +m_Radius * std::cos(m_Elevation) * std::cos(m_Azimuth); + const AmReal32 y = +m_Radius * std::sin(m_Elevation); + const AmReal32 z = -m_Radius * std::cos(m_Elevation) * std::sin(m_Azimuth); + + return AM_V3(x, y, z); + } + case eUpAxis_Z: + { + // Translates spherical to cartesian, where Z - up, Y - forward, X - right + const AmReal32 x = +m_Radius * std::cos(m_Elevation) * std::cos(m_Azimuth); + const AmReal32 y = -m_Radius * std::cos(m_Elevation) * std::sin(m_Azimuth); + const AmReal32 z = +m_Radius * std::sin(m_Elevation); + + return AM_V3(x, y, z); + } + } + + AMPLITUDE_ASSERT(false); + return AM_V3(0, 0, 0); + } +} // namespace SparkyStudios::Audio::Amplitude diff --git a/src/Math/Shape.cpp b/src/Math/Shape.cpp index 07a7d39d..5f315ffa 100644 --- a/src/Math/Shape.cpp +++ b/src/Math/Shape.cpp @@ -237,7 +237,7 @@ namespace SparkyStudios::Audio::Amplitude { default: [[fallthrough]]; - case eGameEngineUpAxis_Y: + case eUpAxis_Y: { const AmReal32 dP1 = AM_Dot(location - _p1, AM_Norm(_p2 - _p1)); const AmReal32 dP2 = AM_Dot(location - _p2, AM_Norm(_p1 - _p2)); @@ -249,7 +249,7 @@ namespace SparkyStudios::Audio::Amplitude return AM_MIN(dP1, AM_MIN(dP2, AM_MIN(dP3, AM_MIN(dP4, AM_MIN(dP5, dP6))))); } - case eGameEngineUpAxis_Z: + case eUpAxis_Z: { const AmReal32 dP1 = AM_Dot(location - _p1, AM_Norm(_p2 - _p1)); const AmReal32 dP2 = AM_Dot(location - _p2, AM_Norm(_p1 - _p2)); @@ -283,14 +283,14 @@ namespace SparkyStudios::Audio::Amplitude switch (amEngine->GetState()->up_axis) { default: - case eGameEngineUpAxis_Y: + case eUpAxis_Y: _p1 = AM_Mul(m_lookAtMatrix, AM_V4(-_halfWidth, -_halfHeight, -_halfDepth, 1.0f)).XYZ; _p2 = AM_Mul(m_lookAtMatrix, AM_V4(-_halfWidth, -_halfHeight, _halfDepth, 1.0f)).XYZ; _p3 = AM_Mul(m_lookAtMatrix, AM_V4(_halfWidth, -_halfHeight, -_halfDepth, 1.0f)).XYZ; _p4 = AM_Mul(m_lookAtMatrix, AM_V4(-_halfWidth, _halfHeight, -_halfDepth, 1.0f)).XYZ; break; - case eGameEngineUpAxis_Z: + case eUpAxis_Z: _p1 = AM_Mul(m_lookAtMatrix, AM_V4(-_halfWidth, -_halfDepth, -_halfHeight, 1.0f)).XYZ; _p2 = AM_Mul(m_lookAtMatrix, AM_V4(-_halfWidth, _halfDepth, -_halfHeight, 1.0f)).XYZ; _p3 = AM_Mul(m_lookAtMatrix, AM_V4(_halfWidth, -_halfDepth, -_halfHeight, 1.0f)).XYZ; @@ -416,12 +416,12 @@ namespace SparkyStudios::Audio::Amplitude switch (amEngine->GetState()->up_axis) { default: - case eGameEngineUpAxis_Y: + case eUpAxis_Y: _a = AM_Mul(m_lookAtMatrix, AM_V4(0.0f, halfHeight, 0.0f, 1.0f)).XYZ; _b = AM_Mul(m_lookAtMatrix, AM_V4(0.0f, -halfHeight, 0.0f, 1.0f)).XYZ; break; - case eGameEngineUpAxis_Z: + case eUpAxis_Z: _a = AM_Mul(m_lookAtMatrix, AM_V4(0.0f, 0.0f, halfHeight, 1.0f)).XYZ; _b = AM_Mul(m_lookAtMatrix, AM_V4(0.0f, 0.0f, -halfHeight, 1.0f)).XYZ; break; @@ -577,7 +577,7 @@ namespace SparkyStudios::Audio::Amplitude if (outer->m_needUpdate) outer->_update(); - const eGameEngineUpAxis upAxis = amEngine->GetState()->up_axis; + const GameEngineUpAxis upAxis = amEngine->GetState()->up_axis; const AmVec3& x = position; @@ -600,7 +600,7 @@ namespace SparkyStudios::Audio::Amplitude switch (upAxis) { default: - case eGameEngineUpAxis_Y: + case eUpAxis_Y: { const AmReal32 dP1 = AM_ABS(AM_Dot(x - outer->_p1, AM_Norm(outer->_p2 - outer->_p1))) / (outer->GetHalfDepth() - inner->GetHalfDepth()); @@ -620,7 +620,7 @@ namespace SparkyStudios::Audio::Amplitude return AM_CLAMP(shortestPath, 0.0f, 1.0f); } - case eGameEngineUpAxis_Z: + case eUpAxis_Z: { const AmReal32 dP1 = AM_ABS(AM_Dot(x - outer->_p1, AM_Norm(outer->_p2 - outer->_p1))) / (outer->GetHalfHeight() - inner->GetHalfHeight()); @@ -659,7 +659,7 @@ namespace SparkyStudios::Audio::Amplitude if (outer->m_needUpdate) outer->Update(); - const eGameEngineUpAxis upAxis = amEngine->GetState()->up_axis; + const GameEngineUpAxis upAxis = amEngine->GetState()->up_axis; const AmVec3& x = position; @@ -715,7 +715,7 @@ namespace SparkyStudios::Audio::Amplitude if (outer->m_needUpdate) outer->Update(); - const eGameEngineUpAxis upAxis = amEngine->GetState()->up_axis; + const GameEngineUpAxis upAxis = amEngine->GetState()->up_axis; const AmVec3& soundToListener = position - inner->GetLocation(); const AmReal32 distance = AM_Len(soundToListener); diff --git a/src/Sound/AttenuationShapes.cpp b/src/Sound/AttenuationShapes.cpp index 49d753ad..d8c46846 100644 --- a/src/Sound/AttenuationShapes.cpp +++ b/src/Sound/AttenuationShapes.cpp @@ -318,7 +318,7 @@ namespace SparkyStudios::Audio::Amplitude switch (amEngine->GetState()->up_axis) { default: - case eGameEngineUpAxis_Y: + case eUpAxis_Y: iA = AM_Mul(lookAt, AM_V4(0.0f, innerHalfHeight, 0.0f, 1.0f)).XYZ; iB = AM_Mul(lookAt, AM_V4(0.0f, -innerHalfHeight, 0.0f, 1.0f)).XYZ; @@ -326,7 +326,7 @@ namespace SparkyStudios::Audio::Amplitude oB = AM_Mul(lookAt, AM_V4(0.0f, -outerHalfHeight, 0.0f, 1.0f)).XYZ; break; - case eGameEngineUpAxis_Z: + case eUpAxis_Z: iA = AM_Mul(lookAt, AM_V4(0.0f, 0.0f, innerHalfHeight, 1.0f)).XYZ; iB = AM_Mul(lookAt, AM_V4(0.0f, 0.0f, -innerHalfHeight, 1.0f)).XYZ; diff --git a/src/Utils/Utils.h b/src/Utils/Utils.h index aa05508a..9cc4c951 100644 --- a/src/Utils/Utils.h +++ b/src/Utils/Utils.h @@ -155,16 +155,6 @@ namespace SparkyStudios::Audio::Amplitude std::memcpy(dest.GetBuffer(), src, srcSize * sizeof(AmReal32)); std::memset(dest.GetBuffer() + srcSize, 0, (dest.GetSize() - srcSize) * sizeof(AmReal32)); } - - AM_INLINE(AmVec3) SphericalToCartesian(AmReal32 azimuth, AmReal32 elevation, AmReal32 radius) - { - // Translates spherical to cartesian, where Y - up, Z - forward, X - right - const AmReal32 x = +radius * std::cos(elevation) * std::cos(azimuth); - const AmReal32 y = +radius * std::sin(elevation); - const AmReal32 z = -radius * std::cos(elevation) * std::sin(azimuth); - - return AM_V3(x, y, z); - } } // namespace SparkyStudios::Audio::Amplitude #endif // SS_AMPLITUDE_AUDIO_UTILS_H diff --git a/tools/amir/main.cpp b/tools/amir/main.cpp index 89a8fccf..2b0e86ad 100644 --- a/tools/amir/main.cpp +++ b/tools/amir/main.cpp @@ -72,29 +72,30 @@ void triangulate(const std::vector& vertices, std::vector(ch_vertices.size()), outIndices, faceCount, false, "debug_hrir_sphere"); + static char debugFileName[] = "debug_hrir_sphere"; + + convhull_3d_export_obj(ch_vertices.data(), static_cast(ch_vertices.size()), outIndices, faceCount, false, debugFileName); log(stdout, "debug_hrir_sphere.obj written\n"); } } -int parseFileName_IRCAM(const AmOsString& fileName, AmVec3& position) +int parseFileName_IRCAM(const AmOsString& fileName, PolarPosition& position) { - const auto azimuth_location = fileName.find("_T"); + const auto azimuth_location = fileName.find(AM_OS_STRING("_T")); if (azimuth_location == AmOsString::npos) { return EXIT_FAILURE; } - const auto elevation_location = fileName.find("_P"); + const auto elevation_location = fileName.find(AM_OS_STRING("_P")); if (elevation_location == AmOsString::npos) { return EXIT_FAILURE; @@ -111,12 +112,71 @@ int parseFileName_IRCAM(const AmOsString& fileName, AmVec3& position) // - from 015 to 090 for source above your head const auto elevation = static_cast(std::atof(fileName.substr(elevation_location + 2, 3).c_str())); - position = SphericalToCartesian(azimuth * AM_DegToRad, elevation * AM_DegToRad, 1.0); + position = PolarPosition(azimuth * AM_DegToRad, elevation * AM_DegToRad, 1.0); + return EXIT_SUCCESS; +} + +int parseFileName_MIT(const AmOsString& fileName, PolarPosition& position) +{ + const auto azimuth_location = fileName.find('e'); + if (azimuth_location == AmOsString::npos) + { + return EXIT_FAILURE; + } + + const auto elevation_location = fileName.find('H'); + if (elevation_location == AmOsString::npos) + { + return EXIT_FAILURE; + } + + AmOsString azimuthString; + for (AmSize az = azimuth_location + 1; fileName[az] != 'a'; ++az) + azimuthString += fileName[az]; + + AmOsString elevationString; + for (AmSize el = elevation_location + 1; el < azimuth_location; ++el) + elevationString += fileName[el]; + + char* end; + + // azimuth in degrees 3 digits, we need to negate so that the angle is relative to positive z-axis, + // - from 000 to 180 for source on your left, + // - from 180 to 359 for source on your right + const auto azimuth = -std::strtof(azimuthString.c_str(), nullptr); + + // azimuth in degrees 3 digits, we need 90 deg offset so that the angle is relative to z-axis, + // - from 000 to 180 for source on your left, + // - from 180 to 359 for source on your right + const auto elevation = std::strtof(elevationString.c_str(), nullptr); + + position = PolarPosition(azimuth * AM_DegToRad, elevation * AM_DegToRad, 1.0); return EXIT_SUCCESS; } -static int process_IRCAM(const std::filesystem::path& datasetPath, const std::filesystem::path& packagePath, const ProcessingState& state) +static int process(const AmOsString& inFileName, const AmOsString& outFileName, const ProcessingState& state) { + const std::filesystem::path datasetPath(inFileName); + const std::filesystem::path packagePath(outFileName); + + if (!exists(datasetPath)) + { + log(stderr, "The path " AM_OS_CHAR_FMT " does not exist.\n", datasetPath.native().c_str()); + return EXIT_FAILURE; + } + + if (!is_directory(datasetPath)) + { + log(stderr, "The path " AM_OS_CHAR_FMT " is not a directory.\n", datasetPath.native().c_str()); + return EXIT_FAILURE; + } + + if (state.datasetModel >= eHRIRSphereDatasetModel_Invalid) + { + log(stderr, "Unsupported dataset model.\n"); + return EXIT_FAILURE; + } + DiskFile packageFile(absolute(packagePath), eFOM_WRITE); AmUInt32 sampleRate = 0; @@ -127,11 +187,17 @@ static int process_IRCAM(const std::filesystem::path& datasetPath, const std::fi std::vector vertices; std::vector indices; - for (const auto& file : std::filesystem::directory_iterator(datasetPath)) + for (const auto& file : std::filesystem::recursive_directory_iterator(datasetPath)) { if (file.is_directory()) continue; + // Avoid known bad files + { + if (file.path().filename() == AM_OS_STRING(".DS_Store")) + continue; + } + sorted_by_name.insert(file); } @@ -144,60 +210,82 @@ static int process_IRCAM(const std::filesystem::path& datasetPath, const std::fi if (state.verbose) log(stdout, "Processing %s.\n", path.c_str()); - AmVec3 position; - if (parseFileName_IRCAM(path, position) == EXIT_FAILURE) + PolarPosition polar; + + if (state.datasetModel == eHRIRSphereDatasetModel_IRCAM && parseFileName_IRCAM(entry.filename().native(), polar) == EXIT_FAILURE) { log(stderr, "Invalid file name: %s.\n", path.c_str()); return EXIT_FAILURE; - }; - - auto* decoder = wavCodec->CreateDecoder(); - std::shared_ptr file = std::make_shared(absolute(entry)); + } - if (!decoder->Open(file)) + if (state.datasetModel == eHRIRSphereDatasetModel_MIT && parseFileName_MIT(entry.filename().native(), polar) == EXIT_FAILURE) { - log(stderr, "Failed to open file %s.\n", path.c_str()); + log(stderr, "Invalid file name: %s.\n", path.c_str()); return EXIT_FAILURE; } - if (decoder->GetFormat().GetNumChannels() != 2) + const AmUInt32 max = state.datasetModel == eHRIRSphereDatasetModel_IRCAM ? 1 : 2; + for (AmUInt32 i = 0; i < max; ++i) { - log(stderr, "Unsupported number of channels: %d. Only 2 channels is supported.\n", decoder->GetFormat().GetNumChannels()); - return EXIT_FAILURE; - } + if (i == 1 && (polar.m_Azimuth == -AM_PI32 || polar.m_Azimuth == AM_PI32)) + continue; // Do not duplicate borders - if (sampleRate == 0) - sampleRate = decoder->GetFormat().GetSampleRate(); + polar.m_Azimuth -= static_cast(i) * AM_PI32; + const AmVec3 position = polar.ToCartesian(eUpAxis_Y); - if (irLength == 0) - irLength = decoder->GetFormat().GetFramesCount(); + auto* decoder = wavCodec->CreateDecoder(); + std::shared_ptr file = std::make_shared(absolute(entry)); - AmAlignedReal32Buffer buffer; - buffer.Init(decoder->GetFormat().GetFramesCount() * decoder->GetFormat().GetFrameSize()); + if (!decoder->Open(file)) + { + log(stderr, "Failed to open file %s.\n", path.c_str()); + return EXIT_FAILURE; + } - decoder->Load(buffer.GetBuffer()); + if (decoder->GetFormat().GetNumChannels() != 2) + { + log(stderr, "Unsupported number of channels: %d. Only 2 channels is supported.\n", decoder->GetFormat().GetNumChannels()); + return EXIT_FAILURE; + } - HRIRSphereVertex vertex; - vertex.m_Position = position; - vertex.m_LeftIR.resize(decoder->GetFormat().GetFramesCount()); - vertex.m_RightIR.resize(decoder->GetFormat().GetFramesCount()); + const AmUInt64 totalFrames = decoder->GetFormat().GetFramesCount(); - for (AmUInt32 i = 0, l = decoder->GetFormat().GetFramesCount(); i < l; ++i) - { - vertex.m_LeftIR[i] = buffer[i * 2 + 0]; - vertex.m_RightIR[i] = buffer[i * 2 + 1]; - } + if (sampleRate == 0) + sampleRate = decoder->GetFormat().GetSampleRate(); + + if (irLength == 0) + irLength = totalFrames; + + AmAlignedReal32Buffer buffer; + buffer.Init(totalFrames * 2); + + decoder->Load(buffer.GetBuffer()); + + HRIRSphereVertex vertex; + vertex.m_Position = position; + vertex.m_LeftIR.resize(totalFrames); + vertex.m_RightIR.resize(totalFrames); + + for (AmUInt32 i = 0; i < totalFrames; ++i) + { + vertex.m_LeftIR[i] = buffer[i * 2 + 0]; + vertex.m_RightIR[i] = buffer[i * 2 + 1]; + } - vertices.push_back(vertex); + vertices.push_back(vertex); - buffer.Release(); - wavCodec->DestroyDecoder(decoder); + buffer.Release(); + wavCodec->DestroyDecoder(decoder); + } } triangulate(vertices, indices, state.debug); // Header - packageFile.Write(reinterpret_cast("AMIR"), 4); + packageFile.Write8('A'); + packageFile.Write8('M'); + packageFile.Write8('I'); + packageFile.Write8('R'); packageFile.Write16(kCurrentVersion); packageFile.Write32(sampleRate); packageFile.Write32(irLength); @@ -205,7 +293,7 @@ static int process_IRCAM(const std::filesystem::path& datasetPath, const std::fi packageFile.Write32(static_cast(indices.size())); // Indices - packageFile.Write(reinterpret_cast(indices.data()), static_cast(indices.size()) * sizeof(AmUInt32)); + packageFile.Write(reinterpret_cast(indices.data()), indices.size() * sizeof(AmUInt32)); // Vertices for (const auto& vertex : vertices) @@ -223,32 +311,6 @@ static int process_IRCAM(const std::filesystem::path& datasetPath, const std::fi return EXIT_SUCCESS; } -static int process(const AmOsString& inFileName, const AmOsString& outFileName, const ProcessingState& state) -{ - const std::filesystem::path datasetPath(inFileName); - const std::filesystem::path packagePath(outFileName); - - if (!exists(datasetPath)) - { - log(stderr, "The path " AM_OS_CHAR_FMT " does not exist.\n", datasetPath.native().c_str()); - return EXIT_FAILURE; - } - - if (!is_directory(datasetPath)) - { - log(stderr, "The path " AM_OS_CHAR_FMT " is not a directory.\n", datasetPath.native().c_str()); - return EXIT_FAILURE; - } - - if (state.datasetModel == eHRIRSphereDatasetModel_IRCAM) - { - return process_IRCAM(datasetPath, packagePath, state); - } - - log(stderr, "Unsupported dataset model.\n"); - return EXIT_FAILURE; -} - int main(int argc, char* argv[]) { MemoryManager::Initialize(MemoryManagerConfig()); @@ -363,12 +425,13 @@ int main(int argc, char* argv[]) log(stdout, " -[oO]: \tHide logo and copyright notice.\n"); log(stdout, " -[qQ]: \tQuiet mode. Shutdown all messages.\n"); log(stdout, " -[vV]: \tVerbose mode. Display all messages.\n"); + log(stdout, " -[dD]: \tDebug mode. Will create an obj file with a preview of the sphere shape.\n"); log(stdout, " -[mM]: \tThe dataset model to use.\n"); - log(stdout, " \tThe default value is 1. The available values are:\n"); + log(stdout, " \tThe default value is 0. The available values are:\n"); log(stdout, " 0: \tIRCAN (LISTEN) dataset (http://recherche.ircam.fr/equipes/salles/listen/download.html).\n"); log(stdout, " 1: \tMIT (KEMAR) dataset (http://sound.media.mit.edu/resources/KEMAR.html).\n"); log(stdout, "\n"); - log(stdout, "Example: amir -c 1 /path/to/project/ output_package.amir\n"); + log(stdout, "Example: amir -m 1 /path/to/mit/dataset/ output_package.amir\n"); log(stdout, "\n"); // clang-format on